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 相关文章推荐
textarea的value是html文件源代码,存成html文件的代码
Apr 20 Javascript
JQuery中extend使用介绍
Mar 13 Javascript
JS函数this的用法实例分析
Feb 05 Javascript
jQuery实现表单步骤流程导航代码分享
Aug 28 Javascript
jQueryUI 拖放排序遇到滚动条时有可能无法执行排序的小bug及解决方案
Dec 19 Javascript
详解用vue.js和laravel实现微信支付
Jun 23 Javascript
javascript 中select框触发事件过程的分析
Aug 01 Javascript
js求数组中全部数字可拼接出的最大整数示例代码
Aug 25 Javascript
浅谈在vue项目中如何定义全局变量和全局函数
Oct 24 Javascript
详解webpack中的hash、chunkhash、contenthash区别
Jan 05 Javascript
VUE + UEditor 单图片跨域上传功能的实现方法
Feb 08 Javascript
Angular6中使用Swiper的方法示例
Jul 09 Javascript
原生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
ThinkPHP与PHPExcel冲突解决方法
2011/08/08 PHP
PHP实现检测客户端是否使用代理服务器及其匿名级别
2015/01/07 PHP
WordPress分页伪静态加html后缀
2016/06/08 PHP
php指定长度分割字符串str_split函数用法示例
2017/01/30 PHP
Laravel基础_关于view共享数据的示例讲解
2019/10/14 PHP
关于使用 jBox 对话框的提交不能弹出问题解决方法
2012/11/07 Javascript
利用javascript判断文件是否存在
2013/12/31 Javascript
使用jQuery重置(reset)表单的方法
2014/05/05 Javascript
jQuery函数map()和each()介绍及异同点分析
2014/11/08 Javascript
jquery表单对象属性过滤选择器实例分析
2015/05/18 Javascript
JS正则表达式学习之贪婪和非贪婪模式实例总结
2016/12/26 Javascript
JavaScript实现图片瀑布流和底部刷新
2017/01/02 Javascript
微信小程序 video详解及简单实例
2017/01/16 Javascript
jquery插件ContextMenu设置右键菜单
2017/03/13 Javascript
详解开源的JavaScript插件化框架MinimaJS
2017/10/26 Javascript
angular4 获取wifi列表中文显示乱码问题的解决
2018/10/20 Javascript
使用js在layui中实现上传图片压缩
2019/06/18 Javascript
JS+html5实现异步上传图片显示上传文件进度条功能示例
2019/11/09 Javascript
编写v-for循环的技巧汇总
2020/12/01 Javascript
[47:18]完美世界DOTA2联赛循环赛 IO vs FTD BO2第一场 11.05
2020/11/06 DOTA
在Python的Django框架中生成CSV文件的方法
2015/07/22 Python
浅谈numpy库的常用基本操作方法
2018/01/09 Python
python实现图像检索的三种(直方图/OpenCV/哈希法)
2019/08/08 Python
Python如何实现的二分查找算法
2020/05/27 Python
Python Map 函数的使用
2020/08/28 Python
python实现三种随机请求头方式
2021/01/05 Python
台湾最大银发乐活百货:乐龄网
2018/05/21 全球购物
中科创达面试题
2016/12/28 面试题
国贸专业自荐信范文
2014/03/02 职场文书
天猫活动策划方案
2014/08/21 职场文书
工程催款通知书
2015/04/17 职场文书
入党积极分子培养人意见
2015/06/02 职场文书
《攀登者》:“海拔8000米以上,你不能指望任何人”
2019/11/25 职场文书
教你修复 Win11应用商店加载空白问题
2021/12/06 数码科技
「女孩的钓鱼慢活」全新版权绘公布
2022/03/21 日漫
Python matplotlib 利用随机函数生成变化图形
2022/04/26 Python