浅析Vue响应式原理(三)

时间:2023-03-09 20:06:05
浅析Vue响应式原理(三)

Vue响应式原理之defineReactive

defineReactive

不论如何,最终响应式数据都要通过defineReactive来实现,实际要借助ES5新增的Object.defineProperty

defineReactive接受五个参数。obj是要添加响应式数据的对象;key是属性名,val是属性名对应的取值;customSetter是用户自定义的setter;会在响应式数据的setter中执行,只有开发环境可用;通过shallow指定是否浅比较,默认深比较。


export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
} const getter = property && property.get
if (!getter && arguments.length === 2) {
val = obj[key]
}
const setter = property && property.set let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = 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.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}

在函数内,首先实例化一个Dep实例depdep会在稍后添加为响应式数据自定义的get/set中发挥作用。接着获取属性描述符,如果属性不可配置,则无法调用Object.defineProperty来修改setter/getter,所以返回。

如果原来已设置过setter/getter,缓存起来。当未自定义getter且arguments长度为2(即只传入了objkey)时,可以直接用方括号求值,使用闭包变量val缓存初始值。

如果不是浅复制,执行observe(val),为val添加__ob__属性并返回__ob__指向的Observer实例。(只有数组和对象才可能是响应式,才能返回Observer实例)。

使用Object.definePropertyobj[key]设置getter和setter。

get内,如果原来已设置过getter,则用缓存的getter求值,否则使用闭包变量val作为返回值;同时添加依赖。此处为两个Dep实例添加依赖。dep是闭包变量,在getter/setter中会使用到。另一个Dep实例是childOb.dep,只用调用set/delete更新响应式数据时,才会触发;如果value是数组,还会遍历元素,为存在__ob__属性的元素收集依赖。

set内,先获取更新前的值(逻辑和get内第一步一样)。判断更新前后的值是否相等,相等时直接返回;不等时,如果有缓存的setter,调用缓存的setter更新,否则直接赋值。值得注意的是,NaN === NaN是不成立的,反而NaN !== NaN是成立的,后面的判断语句newVal !== newVal && value !== value就是为了避免newVal/val都是NaN。在更新后的值newVal上执行observe,更新闭包变量childOb,并调用notify。

参考链接

原文地址:https://segmentfault.com/a/1190000017216175