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 相关文章推荐
根据地区不同显示时间的javascript代码
Aug 13 Javascript
JQuery对checkbox操作 (循环获取)
May 20 Javascript
jQuery中关于ScrollableGridPlugin.js(固定表头)插件的使用逐步解析
Jul 17 Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
Mar 04 Javascript
javascript常用方法总结
May 14 Javascript
JavaScript动态添加style节点的方法
Jun 09 Javascript
使用JQuery选择HTML遍历函数的方法
Sep 17 Javascript
connection reset by peer问题总结及解决方案
Oct 21 Javascript
如何使用bootstrap框架 bootstrap入门必看!
Apr 13 Javascript
基于Vue、Vuex、Vue-router实现的购物商城(原生切换动画)效果
Jan 09 Javascript
解决vue项目中type=”file“ change事件只执行一次的问题
May 16 Javascript
教你一步步实现一个简易promise
Nov 02 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
php preg_replace替换实例讲解
2013/11/04 PHP
php实现粘贴截图并完成上传功能
2015/05/17 PHP
详解WordPress中用于更新和获取用户选项数据的PHP函数
2016/03/08 PHP
PHP数组去重的更快实现方式分析
2018/05/09 PHP
JS类中定义原型方法的两种实现的区别
2007/03/08 Javascript
早该知道的7个JavaScript技巧
2013/03/27 Javascript
JavaScript实现的图像模糊算法代码分享
2014/04/22 Javascript
javascript复制粘贴与clipboardData的使用
2014/10/16 Javascript
jQuery实现的超链接提示效果示例【附demo源码下载】
2016/09/09 Javascript
BootStrap下拉菜单和滚动监听插件实现代码
2016/09/26 Javascript
JavaScript中数组Array.sort()排序方法详解
2017/03/01 Javascript
Angular中的$watch、$watchGroup、$watchCollection
2017/06/25 Javascript
jQuery实现拖动效果的实例代码
2017/06/25 jQuery
vue中$set的使用(结合在实际应用中遇到的坑)
2018/07/10 Javascript
详解如何使用nvm管理Node.js多版本
2019/05/06 Javascript
Vue2.0实现组件之间数据交互和通信操作示例
2019/05/16 Javascript
layui当点击文本框时弹出选择框,显示选择内容的例子
2019/09/02 Javascript
对layer弹出框中icon数字参数的说明介绍
2019/09/04 Javascript
VUE 实现动态给对象增加属性,并触发视图更新操作示例
2019/11/29 Javascript
pygame播放音乐的方法
2015/05/19 Python
Python脚本获取操作系统版本信息
2016/12/17 Python
python实现猜数字小游戏
2020/03/24 Python
浅谈python 导入模块和解决文件句柄找不到问题
2018/12/15 Python
Python实现蒙特卡洛算法小实验过程详解
2019/07/12 Python
Python 如何优雅的将数字转化为时间格式的方法
2019/09/26 Python
python构建指数平滑预测模型示例
2019/11/21 Python
python图形用户接口实例详解
2019/12/16 Python
Python2和Python3中@abstractmethod使用方法
2020/02/04 Python
完美解决jupyter由于无法import新包的问题
2020/05/26 Python
在python3.9下如何安装scrapy的方法
2021/02/03 Python
html5响应式开发自动计算fontSize的方法
2020/01/13 HTML / CSS
管理科学大学生求职信
2013/11/13 职场文书
鼓励运动员的广播稿
2014/02/08 职场文书
母婴行业实体、电商模式全面解析
2019/08/01 职场文书
励志语录:只有自己足够强大,才能不被别人践踏
2020/01/09 职场文书
Python实现信息轰炸工具(再也不怕说不过别人了)
2021/06/11 Python