Vue.js原理分析之observer模块详解


Posted in Javascript onFebruary 17, 2017

介绍

observer是Vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭observer的支持。

注意:本文是针对Vue@2.1.8进行分析

observer模块在Vue项目中的代码位置是src/core/observer,模块共分为这几个部分:

  1. Observer: 数据的观察者,让数据对象的读写操作都处于自己的监管之下
  2. Watcher: 数据的订阅者,数据的变化会通知到Watcher,然后由Watcher进行相应的操作,例如更新视图
  3. Dep: Observer与Watcher的纽带,当数据变化时,会被Observer观察到,然后由Dep通知到Watcher

示意图如下:

Vue.js原理分析之observer模块详解

Observer

Observer类定义在src/core/observer/index.js中,先来看一下Observer的构造函数

constructor (value: any) {
 this.value = value
 this.dep = new Dep()
 this.vmCount = 0
 def(value, '__ob__', this)
 if (Array.isArray(value)) {
 const augment = hasProto
 ? protoAugment
 : copyAugment
 augment(value, arrayMethods, arrayKeys)
 this.observeArray(value)
 } else {
 this.walk(value)
 }
}

value是需要被观察的数据对象,在构造函数中,会给value增加__ob__属性,作为数据已经被Observer观察的标志。如果value是数组,就使用observeArray遍历value,对value中每一个元素调用observe分别进行观察。如果value是对象,则使用walk遍历value上每个key,对每个key调用defineReactive来获得该key的set/get控制权。

解释下上面用到的几个函数的功能:

  • observeArray: 遍历数组,对数组的每个元素调用observe
  • observe: 检查对象上是否有__ob__属性,如果存在,则表明该对象已经处于Observer的观察中,如果不存在,则new Observer来观察对象(其实还有一些判断逻辑,为了便于理解就不赘述了)
  • walk: 遍历对象的每个key,对对象上每个key的数据调用defineReactive
  • defineReactive: 通过Object.defineProperty设置对象的key属性,使得能够捕获到该属性值的set/get动作。一般是由Watcher的实例对象进行get操作,此时Watcher的实例对象将被自动添加到Dep实例的依赖数组中,在外部操作触发了set时,将通过Dep实例的notify来通知所有依赖的watcher进行更新。

如果不太理解上面的文字描述可以看一下图:

Vue.js原理分析之observer模块详解

Dep

Dep是Observer与Watcher之间的纽带,也可以认为Dep是服务于Observer的订阅系统。Watcher订阅某个Observer的Dep,当Observer观察的数据发生变化时,通过Dep通知各个已经订阅的Watcher。

Dep提供了几个接口:

  • addSub: 接收的参数为Watcher实例,并把Watcher实例存入记录依赖的数组中
  • removeSub: 与addSub对应,作用是将Watcher实例从记录依赖的数组中移除
  • depend: Dep.target上存放这当前需要操作的Watcher实例,调用depend会调用该Watcher实例的addDep方法,addDep的功能可以看下面对Watcher的介绍
  • notify: 通知依赖数组中所有的watcher进行更新操作

Watcher

Watcher是用来订阅数据的变化的并执行相应操作(例如更新视图)的。Watcher的构造器函数定义如下:

constructor (vm, expOrFn, cb, options) {
 this.vm = vm
 vm._watchers.push(this)
 // options
 if (options) {
 this.deep = !!options.deep
 this.user = !!options.user
 this.lazy = !!options.lazy
 this.sync = !!options.sync
 } else {
 this.deep = this.user = this.lazy = this.sync = false
 }
 this.cb = cb
 this.id = ++uid // uid for batching
 this.active = true
 this.dirty = this.lazy // for lazy watchers
 this.deps = []
 this.newDeps = []
 this.depIds = new Set()
 this.newDepIds = new Set()
 this.expression = process.env.NODE_ENV !== 'production'
 ? expOrFn.toString()
 : ''
 if (typeof expOrFn === 'function') {
 this.getter = expOrFn
 } else {
 this.getter = parsePath(expOrFn)
 if (!this.getter) {
 this.getter = function () {}
 process.env.NODE_ENV !== 'production' && warn(
 `Failed watching path: "${expOrFn}" ` +
 'Watcher only accepts simple dot-delimited paths. ' +
 'For full control, use a function instead.',
 vm
 )
 }
 }
 this.value = this.lazy
 ? undefined
 : this.get()
}

参数中,vm表示组件实例,expOrFn表示要订阅的数据字段(字符串表示,例如a.b.c)或是一个要执行的函数,cb表示watcher运行后的回调函数,options是选项对象,包含deep、user、lazy等配置。

watcher实例上有这些方法:

  • get: 将Dep.target设置为当前watcher实例,在内部调用this.getter,如果此时某个被Observer观察的数据对象被取值了,那么当前watcher实例将会自动订阅数据对象的Dep实例
  • addDep: 接收参数dep(Dep实例),让当前watcher订阅dep
  • cleanupDeps: 清除newDepIds和newDep上记录的对dep的订阅信息
  • update: 立刻运行watcher或者将watcher加入队列中等待统一flush
  • run: 运行watcher,调用this.get()求值,然后触发回调
  • evaluate: 调用this.get()求值
  • depend: 遍历this.deps,让当前watcher实例订阅所有dep
  • teardown: 去除当前watcher实例所有的订阅

Array methods

在src/core/observer/array.js中,Vue框架对数组的push、pop、shift、unshift、sort、splice、reverse方法进行了改造,在调用数组的这些方法时,自动触发dep.notify(),解决了调用这些函数改变数组后无法触发更新的问题。

在Vue的官方文档中对这个也有说明:http://cn.vuejs.org/v2/guide/list.html#变异方法

总结

以上就是这篇文中的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
解决使用attachEvent函数时,this指向被绑定的元素的问题的方法
Aug 13 Javascript
jquery乱码与contentType属性设置问题解决方案
Jan 07 Javascript
比较新旧两个数组值得增加和删除的JS代码
Oct 30 Javascript
JS实现字体选色板实例代码
Nov 20 Javascript
JQuery异步加载无限下拉框级联功能实现示例
Feb 19 Javascript
jQuery使用Selectator插件实现多选下拉列表过滤框(附源码下载)
Apr 08 Javascript
require简单实现单页应用程序(SPA)
Jul 12 Javascript
JavaScript实现翻页功能(附效果图)
Feb 16 Javascript
微信小程序实现的涂鸦功能示例【附源码下载】
Jan 12 Javascript
javaScript中"=="和"==="的区别详解
Mar 16 Javascript
微信小程序如何连接Java后台
Aug 08 Javascript
js找出5个数中最大的一个数和倒数第二大的数实现方法示例小结
Mar 04 Javascript
BootStrap的select2既可以查询又可以输入的实现代码
Feb 17 #Javascript
Bootstrap表单使用方法详解
Feb 17 #Javascript
Angularjs单选改为多选的开发过程及问题解析
Feb 17 #Javascript
javascript基础知识之html5轮播图实例讲解(44)
Feb 17 #Javascript
javascript ES6中箭头函数注意细节小结
Feb 17 #Javascript
数组Array的排序sort方法
Feb 17 #Javascript
Bootstrap BootstrapDialog使用详解
Feb 17 #Javascript
You might like
file_get_contents("php://input", "r")实例介绍
2013/07/01 PHP
PHP中通过fopen()函数访问远程文件示例
2014/11/18 PHP
全面解析PHP操作Memcache基本函数
2016/07/14 PHP
PHP常用排序算法实例小结【基本排序,冒泡排序,快速排序,插入排序】
2017/02/07 PHP
laravel 输出最后执行sql 附:whereIn的使用方法
2019/10/10 PHP
gearman中任务的优先级和返回状态实例分析
2020/02/27 PHP
Prototype使用指南之string.js
2007/01/10 Javascript
向左滚动文字 js代码效果
2013/08/17 Javascript
js判断选择时间不能小于当前时间的示例代码
2013/09/24 Javascript
详解Javascript动态操作CSS
2014/12/08 Javascript
JavaScript中遍历对象的property的3种方法介绍
2014/12/30 Javascript
jQuery遍历页面所有CheckBox查看是否被选中的方法
2015/04/14 Javascript
JS根据浏览器窗口大小实时动态改变网页文字大小的方法
2016/02/25 Javascript
JS如何设置元素样式的方法示例
2017/08/28 Javascript
vue-cli项目中使用公用的提示弹层tips或加载loading组件实例详解
2018/05/28 Javascript
JS引用传递与值传递的区别与用法分析
2018/06/01 Javascript
vue拖拽组件使用方法详解
2018/12/01 Javascript
如何让Nodejs支持H5 History模式(connect-history-api-fallback源码分析)
2019/05/30 NodeJs
Node.js爬虫如何获取天气和每日问候详解
2019/08/26 Javascript
vue element实现表格合并行数据
2020/11/30 Vue.js
Python设计模式编程中解释器模式的简单程序示例分享
2016/03/02 Python
python 循环while和for in简单实例
2016/08/16 Python
django启动uwsgi报错的解决方法
2018/04/08 Python
Python爬虫基础之XPath语法与lxml库的用法详解
2018/09/13 Python
Django单元测试中Fixtures的使用方法
2020/02/26 Python
将 Ubuntu 16 和 18 上的 python 升级到最新 python3.8 的方法教程
2020/03/11 Python
python报错TypeError: ‘NoneType‘ object is not subscriptable的解决方法
2020/11/05 Python
CSS3解析抖音LOGO制作的方法步骤
2019/04/11 HTML / CSS
英国时尚泳装品牌:Maru Swimwear
2019/10/06 全球购物
M.M.LaFleur官网:美国职业女装品牌
2020/10/27 全球购物
中国电子产品批发商/跨境电商/外贸网:Sunsky-online
2020/04/20 全球购物
标准化管理实施方案
2014/02/25 职场文书
承诺函格式模板
2015/01/21 职场文书
2015年安全生产月活动总结
2015/03/26 职场文书
Spring-cloud Config Server的3种配置方式
2021/09/25 Java/Android
一次Mysql update sql不当引起的生产故障记录
2022/04/01 MySQL