Vue响应式原理Observer、Dep、Watcher理解


Posted in Javascript onJune 06, 2019

开篇

最近在学习Vue的源码,看了网上一些大神的博客,看起来感觉还是蛮吃力的。自己记录一下学习的理解,希望能够达到简单易懂,不看源码也能理解的效果?

Object.defineProperty

相信很多同学或多或少都了解Vue的响应式原理是通过Object.defineProperty实现的。被Object.defineProperty绑定过的对象,会变成「响应式」化。也就是改变这个对象的时候会触发get和set事件。进而触发一些视图更新。举个栗子?

function defineReactive (obj, key, val) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      console.log('我被读了,我要不要做点什么好?');
      return val;
    },
    set: newVal => {
      if (val === newVal) {
        return;
      }
      val = newVal;
      console.log("数据被改变了,我要把新的值渲染到页面上去!");
    }
  })
}

let data = {
  text: 'hello world',
};

// 对data上的text属性进行绑定
defineReactive(data, 'text', data.text);

console.log(data.text); // 控制台输出 <我被读了,我要不要做点什么好?>
data.text = 'hello Vue'; // 控制台输出 <hello Vue && 数据被改变了,我要把新的值渲染到页面上去!>

Observer 「响应式」

Vue中用Observer类来管理上述响应式化Object.defineProperty的过程。我们可以用如下代码来描述,将this.data也就是我们在Vue代码中定义的data属性全部进行「响应式」绑定。

class Observer {
  constructor() {
    // 响应式绑定数据通过方法
   observe(this.data);
  }
}

export function observe (data) {
  const keys = Object.keys(data);
  for (let i = 0; i < keys.length; i++) {
    // 将data中我们定义的每个属性进行响应式绑定
    defineReactive(obj, keys[i]);
  }
}

Dep 「依赖管理」

什么是依赖?

相信没有看过源码或者刚接触Dep这个词的同学都会比较懵。那Dep究竟是用来做什么的呢? 我们通过defineReactive方法将data中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?

Dep就是帮我们收集【究竟要通知到哪里的】。比如下面的代码案例,我们发现,虽然data中有text和message属性,但是只有message被渲染到页面上,至于text无论怎么变化都影响不到视图的展示,因此我们仅仅对message进行收集即可,可以避免一些无用的工作。

那这个时候message的Dep就收集到了一个依赖,这个依赖就是用来管理data中message变化的。

<div>
  <p>{{message}}</p>
</div>
data: {
  text: 'hello world',
  message: 'hello vue',
}

当使用watch属性时,也就是开发者自定义的监听某个data中属性的变化。比如监听message的变化,message变化时我们就要通知到watch这个钩子,让它去执行回调函数。

这个时候message的Dep就收集到了两个依赖,第二个依赖就是用来管理watch中message变化的。

watch: {
  message: function (val, oldVal) {
    console.log('new: %s, old: %s', val, oldVal)
  },
}

当开发者自定义computed计算属性时,如下messageT属性,是依赖message的变化的。因此message变化时我们也要通知到computed,让它去执行回调函数。 这个时候message的Dep就收集到了三个依赖,这个依赖就是用来管理computed中message变化的。

computed: {
  messageT() {
    return this.message + '!';
  }
}

图示如下:一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。

Vue响应式原理Observer、Dep、Watcher理解

如何收集依赖

我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法。可以将代码进行如下改造:

function defineReactive (obj, key, val) {
  let Dep; // 依赖

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      console.log('我被读了,我要不要做点什么好?');
      // 被读取了,将这个依赖收集起来
      Dep.depend(); // 本次新增
      return val;
    },
    set: newVal => {
      if (val === newVal) {
        return;
      }
      val = newVal;
      // 被改变了,通知依赖去更新
      Dep.notify(); // 本次新增
      console.log("数据被改变了,我要把新的值渲染到页面上去!");
    }
  })
}

什么是依赖

那所谓的依赖究竟是什么呢?上面的图中已经暴露了答案,就是Watcher。

Watcher 「中介」

Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。

Watcher能够控制自己属于哪个,是data中的属性的还是watch,或者是computed,Watcher自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。

因此我们可以推测出,Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。

class Watcher {
  addDep() {
    // 我这个Watcher要被塞到Dep里去了~~
  },
  update() {
    // Dep通知我更新呢~~
  }, 
}

总结

回顾一下,Vue响应式原理的核心就是Observer、Dep、Watcher。

Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher。

在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watch和computed就执行开发者自定义的回调方法。

本篇文章属于入门篇,能够先简单的理解Observer、Dep、Watcher三者的作用和关系。后面会逐渐详细和深入,循序渐进的理解和学习。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Document:getElementsByName()使用方法及示例
Oct 28 Javascript
JavaScript中的replace()方法使用详解
Jun 06 Javascript
AngularJS 依赖注入详解及示例代码
Aug 17 Javascript
JS一个简单的注册页面实例
Sep 05 Javascript
浅谈AngularJS中使用$resource(已更新)
Sep 14 Javascript
官方推荐react-navigation的具体使用详解
May 08 Javascript
Vue.js实现大屏数字滚动翻转效果
Nov 29 Javascript
基于js实现判断浏览器类型代码实例
Jul 17 Javascript
jQuery中getJSON跨域原理的深入讲解
Sep 02 jQuery
jQuery实现放大镜案例
Oct 19 jQuery
解决Mint-ui 框架Popup和Datetime Picker组件滚动穿透的问题
Nov 04 Javascript
vue 使用rules对表单字段进行校验的步骤
Dec 25 Vue.js
原生js通过一行代码实现简易轮播图
Jun 05 #Javascript
解决IOS端微信H5页面软键盘弹起后页面下方留白的问题
Jun 05 #Javascript
详解vue父子组件关于模态框状态的绑定方案
Jun 05 #Javascript
JS中实现浅拷贝和深拷贝的代码详解
Jun 05 #Javascript
优雅的处理vue项目异常实战记录
Jun 05 #Javascript
原生JS使用Canvas实现拖拽式绘图功能
Jun 05 #Javascript
Node.js 路由的实现方法
Jun 05 #Javascript
You might like
php中文验证码实现示例分享
2014/01/12 PHP
php清空(删除)指定目录下的文件,不删除目录文件夹的实现代码
2014/09/04 PHP
CodeIgniter针对lighttpd服务器URL重写的方法
2015/06/10 PHP
微信支付开发动态链接Native支付
2016/07/12 PHP
php实现数组纵向转横向并过滤重复值的方法分析
2017/05/29 PHP
详解提高使用Java反射的效率方法
2019/04/29 PHP
PHP实现倒计时功能
2020/11/16 PHP
js 解决“options为空或不是对象”
2008/12/22 Javascript
JavaScript学习笔记(一) js基本语法
2011/10/25 Javascript
分享一个我自己写的ToolTip提示插件(附源码)
2013/01/20 Javascript
JS实现点击下载的小例子
2013/07/10 Javascript
js如何获取object类型里的键值
2014/02/18 Javascript
Node调试工具JSHint的安装及配置教程
2014/05/27 Javascript
js实现浏览器窗口大小被改变时触发事件的方法
2015/02/02 Javascript
利用jQuery插件imgAreaSelect实现图片上传裁剪(同步显示图像位置信息)
2016/12/02 Javascript
详解如何在Vue2中实现组件props双向绑定
2017/03/29 Javascript
详解vuejs几种不同组件(页面)间传值的方式
2017/06/01 Javascript
JavaScript正则表达式简单实用实例
2017/06/23 Javascript
jQuery实现ajax的嵌套请求案例分析
2019/02/16 jQuery
详解vue-cli+element-ui树形表格(多级表格折腾小计)
2019/04/17 Javascript
python33 urllib2使用方法细节讲解
2013/12/03 Python
Python 中 Meta Classes详解
2016/02/13 Python
Python模拟三级菜单效果
2017/09/11 Python
Python常见字典内建函数用法示例
2018/05/14 Python
python调用HEG工具批量处理MODIS数据的方法及注意事项
2020/02/18 Python
Django添加bootstrap框架时无法加载静态文件的解决方式
2020/03/27 Python
用Python爬取LOL所有的英雄信息以及英雄皮肤的示例代码
2020/07/13 Python
Python3爬虫里关于识别微博宫格验证码的知识点详解
2020/07/30 Python
css3 border旋转时的动画应用
2016/01/22 HTML / CSS
Nike德国官网:Nike.com (DE)
2018/11/13 全球购物
BudgetAir印度:预订航班、酒店和汽车租赁
2019/07/07 全球购物
数据库面试要点基本概念
2013/10/31 面试题
国旗下讲话演讲稿
2014/05/08 职场文书
主持人开场白台词
2015/05/29 职场文书
母亲节主题班会
2015/08/14 职场文书
情况说明书格式及范文
2019/06/24 职场文书