Vue 源码分析之 Observer实现过程


Posted in Javascript onMarch 29, 2018

导语:

本文是对 Vue 官方文档深入响应式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通过源码还原实现过程。

响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程。依赖收集的过程,有三个很重要的类,分别是 Watcher、Dep、Observer。本文主要解读 Observer 。

这篇文章讲解上篇文章没有覆盖到的 Observer 部分的内容,还是先看官网这张图:

Vue 源码分析之 Observer实现过程

Observer 最主要的作用就是实现了上图中touch -Data(getter) - Collect as Dependency这段过程,也就是依赖收集的过程。

还是以下面的代码为例子进行梳理:

(注:左右滑动即可查看完整代码,下同)

varvm = newVue({
el: '#demo',
data: {
firstName: 'Hello',
fullName: ''
},
watch: {
firstName(val) {
this.fullName = val + 'TalkingData';
},
}
})

在源码中,通过还原Vue 进行实例化的过程,从开始一步一步到Observer 类的源码依次为(省略了很多不在本篇文章讨论的代码):

// src/core/instance/index.js
functionVue(options) {
if(process.env.NODE_ENV !== 'production'&&
!(thisinstanceofVue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
constvm: Component = this
// ...
initState(vm)
// ...
}
// src/core/instance/state.js
exportfunctioninitState(vm: Component) {
// ...
constopts = vm.$options
if(opts.data) {
initData(vm)
}
// ...
}
functioninitData(vm: Component) {
letdata = vm.$options.data
data = vm._data = typeofdata === 'function'
? getData(data, vm)
: data || {}
// ...
// observe data
observe(data, true/* asRootData */)
}

在initData 方法中,开始了对data 项中的数据进行“观察”,会将所有数据的变成observable 的。接下来看observe 方法的代码:

// src/core/observer/index.js
functionobserve(value: any, asRootData: ?boolean): Observer| void{
// 如果不是对象,直接返回
if(!isObject(value) || value instanceofVNode) {
return
}
letob: Observer | void
if(hasOwn(value, '__ob__') && value.__ob__ instanceofObserver) {
// 如果有实例则返回实例
ob = value.__ob__
} elseif(
// 确保value是单纯的对象,而不是函数或者是Regexp等情况
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 实例化一个 Observer
ob = newObserver(value)
}
if(asRootData && ob) {
ob.vmCount++
}
returnob
}

observe 方法的作用是给data 创建一个Observer 实例并返回,如果data 有ob属性了,说明已经有Observer 实例了,则返回现有的实例。Vue 的响应式数据都会有一个ob的属性,里面存放了该属性的Observer 实例,防止重复绑定。再来看new Observer(value) 过程中发生了什么:

exportclassObserver{
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor(value: any) {
this.value = value
this.dep = newDep()
this.vmCount = 0
def(value, '__ob__', this)
if(Array.isArray(value)) {
// ...
this.observeArray(value)
} else{
this.walk(value)
}
}
walk (obj: Object) {
constkeys = Object.keys(obj)
for(leti = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray (items: Array<any>) {
for(leti = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}

通过源码可以看到,实例化Observer 过程中主要是做了两个判断。如果是数组,则对数组里面的每一项再次调用oberser 方法进行观察;如果是非数组的对象,遍历对象的每一个属性,对其调用defineReactive 方法。这里的defineReactive 方法就是核心!通过使用Object.defineProperty 方法对每一个需要被观察的属性添加get/set,完成依赖收集。依赖收集过后,每个属性都会有一个Dep 来保存所有Watcher 对象。按照文章最开始的例子来讲,就是对firstName和fullName分别添加了get/set,并且它们各自有一个Dep 实例来保存各自观察它们的所有Watcher 对象。下面是defineReactive 的源码:

exportfunctiondefineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
constdep = newDep()
// 获取属性的自身描述符
constproperty = Object.getOwnPropertyDeor(obj, key)
if(property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 检查属性之前是否设置了 getter/setter
// 如果设置了,则在之后的 get/set 方法中执行设置了的 getter/setter
constgetter = property && property.get
constsetter = property && property.set
// 通过对属性再次调用 observe 方法来判断是否有子对象
// 如果有子对象,对子对象也进行依赖搜集
letchildOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: functionreactiveGetter() {
// 如果属性原本拥有getter方法则执行
constvalue = getter ? getter.call(obj) : val
if(Dep.target) {
// 进行依赖收集
dep.depend()
if(childOb) {
// 如果有子对象,对子对象也进行依赖搜集
childOb.dep.depend()
// 如果属性是数组,则对每一个项都进行依赖收集
// 如果某一项还是数组,则递归
if(Array.isArray(value)) {
dependArray(value)
}
}
}
returnvalue
},
set: functionreactiveSetter(newVal) {
// 如果属性原本拥有getter方法则执行
// 通过getter方法获取当前值,与新值进行比较
// 如果新旧值一样则不需要执行下面的操作
constvalue = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if(newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV !== 'production'&& customSetter) {
customSetter()
}
if(setter) {
// 如果属性原本拥有setter方法则执行
setter.call(obj, newVal)
} else{
// 如果原本没有setter则直接赋新值
val = newVal
}
// 判断新的值是否有子对象,有的话继续观察子对象
childOb = !shallow && observe(newVal)
// 通知所有的观察者,更新状态
dep.notify()
}
})
}

按照源码中的中文注释,应该可以明白defineReactive 执行的过程中做了哪些工作。其实整个过程就是递归,为每个属性添加getter/setter。对于getter/setter,同样也需要对每一个属性进行递归(判断子对象)的完成观察者模式。对于getter,用来完成依赖收集,即源码中的dep.depend()。对于setter,一旦一个数据触发其set方法,便会发布更新消息,通知这个数据的所有观察者也要发生改变。即源码中的dep.notify()。

总结

以上所述是小编给大家介绍的 Vue 源码分析之 Observer实现过程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
自己写了一个展开和收起的多更能型的js效果
Mar 05 Javascript
用javascript添加控件自定义属性解析
Nov 25 Javascript
Node.js中的模块机制学习笔记
Nov 04 Javascript
jQuery EasyUI Tab 选项卡问题小结
Aug 16 Javascript
JavaScript实现前端实时搜索功能
Mar 26 Javascript
vue elementUI tree树形控件获取父节点ID的实例
Sep 12 Javascript
Vue.js下拉菜单组件使用方法详解
Oct 19 Javascript
微信公众号服务器验证Token步骤图解
Dec 30 Javascript
卸载vue2.0并升级vue_cli3.0的实例讲解
Feb 16 Javascript
jQuery 选择器用法实例分析【prev + next】
May 22 jQuery
vue keep-alive实现多组件嵌套中个别组件存活不销毁的操作
Oct 30 Javascript
JS不要再到处使用绝对等于运算符了
Apr 30 Javascript
vue 实现全选全不选的示例代码
Mar 29 #Javascript
Angular开发实践之服务端渲染
Mar 29 #Javascript
使用vue2实现购物车和地址选配功能
Mar 29 #Javascript
vue axios请求拦截实例代码
Mar 29 #Javascript
JavaScript面试出现频繁的一些易错点整理
Mar 29 #Javascript
jQuery实现的上传图片本地预览效果简单示例
Mar 29 #jQuery
详解Javascript中new()到底做了些什么?
Mar 29 #Javascript
You might like
第二节--PHP5 的对象模型
2006/11/16 PHP
php pcntl_fork和pcntl_fork 的用法
2009/04/13 PHP
PHP查询MySQL大量数据的时候内存占用分析
2011/07/22 PHP
PHP人民币金额数字转中文大写的函数代码
2013/02/27 PHP
php几个预定义变量$_SERVER用法小结
2014/11/07 PHP
PHP环境搭建的详细步骤
2016/06/30 PHP
php桥接模式应用案例分析
2019/10/23 PHP
小议Function.apply() 之一------(函数的劫持与对象的复制)
2006/11/30 Javascript
javascript编程起步(第二课)
2007/01/10 Javascript
爱恋千雪-US-AscII加密解密工具(网页加密)下载
2007/06/06 Javascript
javascript YUI 读码日记之 YAHOO.util.Dom - Part.4
2008/03/22 Javascript
关于document.cookie的使用javascript
2008/04/11 Javascript
js png图片(有含有透明)在IE6中为什么不透明了
2010/02/07 Javascript
jQuery中实现动画效果的基本操作介绍
2013/04/16 Javascript
使用Grunt.js管理你项目的应用说明
2013/04/24 Javascript
浅析JavaScript中的对象类型Object
2016/05/26 Javascript
详谈ES6中的迭代器(Iterator)和生成器(Generator)
2017/07/31 Javascript
理解Koa2中的async&amp;await的用法
2018/02/05 Javascript
jQuery实现输入框的放大和缩小功能示例
2018/07/21 jQuery
JavaScript对象拷贝与赋值操作实例分析
2018/12/10 Javascript
详解基于electron制作一个node压缩图片的桌面应用
2019/01/29 Javascript
jQuery 添加元素和删除元素的方法
2020/07/15 jQuery
Python3实现简单可学习的手写体识别(实例讲解)
2017/10/21 Python
Flask实现图片的上传、下载及展示示例代码
2018/08/03 Python
python中open函数的基本用法示例
2019/09/07 Python
css3实例教程 一款纯css3实现的发光屏幕旋转特效
2014/12/07 HTML / CSS
日本最大的药妆连锁店:Matsukiyo松本清药妆店
2017/11/23 全球购物
日本最大的彩色隐形眼镜销售网站:CharmColor
2020/09/09 全球购物
电台实习生求职信
2014/02/25 职场文书
4s店市场专员岗位职责
2014/04/09 职场文书
副乡长民主生活会个人对照检查材料思想汇报
2014/10/01 职场文书
2015年基层党建工作汇报材料
2015/06/25 职场文书
小学数学教学随笔
2015/08/14 职场文书
表扬稿表扬信的格式及范文
2019/06/24 职场文书
详细谈谈MYSQL中的COLLATE是什么
2021/06/11 MySQL
Java实现添加条码或二维码到Word文档
2022/06/01 Java/Android