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 相关文章推荐
jquery 插件实现图片延迟加载效果代码
Feb 06 Javascript
IE本地存储userdata的一个bug说明
Jul 01 Javascript
使用ExtJS技术实现的拖动树结点
Aug 05 Javascript
一个基于jQuery的树型插件(OrangeTree)使用介绍
May 03 Javascript
CSS+JS实现点击文字弹出定时自动关闭DIV层菜单的方法
May 12 Javascript
js点击文本框后才加载验证码实例代码
Oct 20 Javascript
学习使用bootstrap3栅格系统
Apr 12 Javascript
jQuery使用经验小技巧(推荐)
May 31 Javascript
微信小程序 scroll-view组件实现列表页实例代码
Dec 14 Javascript
Vue数据驱动模拟实现2
Jan 11 Javascript
使用命令行工具npm新创建一个vue项目的方法
Dec 27 Javascript
JS实现关键词高亮显示正则匹配
Jun 22 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
使用 eAccelerator加速PHP代码的方法
2007/09/30 PHP
tp5(thinkPHP5)框架连接数据库的方法示例
2018/12/24 PHP
php高性能日志系统 seaslog 的安装与使用方法分析
2020/02/29 PHP
Extjs 几个方法的讨论
2010/01/28 Javascript
jquery 多行文本框(textarea)高度变化
2013/07/03 Javascript
Jquery实现侧边栏跟随滚动条固定(兼容IE6)
2014/04/02 Javascript
js读取json的两种常用方法示例介绍
2014/10/19 Javascript
Node.js中调用mysql存储过程示例
2014/12/20 Javascript
JS建造者模式基本用法实例分析
2015/06/30 Javascript
javascript断点调试心得分享
2016/04/23 Javascript
JavaScript基础教程——入门必看篇
2016/05/20 Javascript
Bootstrap组件系列之福利篇几款好用的组件(推荐二)
2016/07/12 Javascript
AngularJS 单元测试(一)详解
2016/09/21 Javascript
使用nodejs下载风景壁纸
2017/02/05 NodeJs
浅谈Emergence.js 检测元素可见性的 js 插件
2017/11/18 Javascript
vue中v-for循环给标签属性赋值的方法
2018/10/18 Javascript
微信小程序时间选择插件使用详解
2018/12/28 Javascript
[04:27]2014DOTA2国际邀请赛 NAVI战队官方纪录片
2014/07/21 DOTA
[47:55]Ti4第二日主赛事败者组 NaVi vs EG 1
2014/07/20 DOTA
[01:01]青春无憾,一战成名——DOTA2全国高校联赛开启
2018/02/25 DOTA
python中__call__内置函数用法实例
2015/06/04 Python
Python2.x版本中基本的中文编码问题解决
2015/10/12 Python
使用pytorch进行图像的顺序读取方法
2018/07/27 Python
keras中的History对象用法
2020/06/19 Python
Django用户认证系统如何实现自定义
2020/11/12 Python
利用CSS3制作简单的3d半透明立方体图片展示
2017/03/25 HTML / CSS
HTML5 虚拟键盘出现挡住输入框的解决办法
2017/02/14 HTML / CSS
AmazeUI 单选框和多选框的实现示例
2020/08/18 HTML / CSS
MONNIER Frères英国官网:源自巴黎女士奢侈品配饰电商平台
2018/12/06 全球购物
一些Unix笔试题和面试题
2013/01/22 面试题
大学自主招生自荐信
2013/12/16 职场文书
幼儿园教师请假制度
2014/01/16 职场文书
经理助理岗位职责
2014/03/05 职场文书
活动总结格式范文
2014/04/26 职场文书
交通违章检讨书
2014/09/21 职场文书
党员公开承诺书2015
2015/01/21 职场文书