欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue 源码分析之 Observer实现过程

程序员文章站 2022-03-27 15:23:32
导语: 本文是对 vue 官方文档深入响应式原理()的理解,并通过源码还原实现过程。 响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程。依赖收集的过程,有三个...

导语:

本文是对 vue 官方文档深入响应式原理()的理解,并通过源码还原实现过程。

响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程。依赖收集的过程,有三个很重要的类,分别是 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实现过程,希望对大家有所帮助