Vue 源码分析之 Observer实现过程

网友投稿 265 2023-02-10


Vue 源码分析之 Observer实现过程

导语:

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

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

这篇文章讲解上篇文章没有覆盖到的 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'&&

!(thisinstahttp://nceofVue)

) {

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 实例并返回,如果daOhYoUtyta 有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++) {

defineReactivOhYoUtye(obj, keys[i], obj[keys[i]])

}

}

observeArray (items: Array) {

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实现过程,希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:vue的diff算法知识点总结
下一篇:vue 实现全选全不选的示例代码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~