众所周知,vue响应式原理就是用的Object.defineProperty方法去定义setter和getter的。多数人一上来就觉得很简单,然后写出如下代码:
let person = {age: 18}
Object.defineProperty(person, 'age', {
get: function(){
console.log('getter call')
return person.age
},
set: function(val){
console.log('setter call')
person.age = val
}
})
然后一调用,报错了:
VM560:4 Uncaught RangeError: Maximum call stack size exceeded
at Object.get [as age] (<anonymous>:4:18)
at Object.get [as age] (<anonymous>:6:23)
at Object.get [as age] (<anonymous>:6:23)
at Object.get [as age] (<anonymous>:6:23)
问题就在于,上面的代码会递归调用get和set方法。 那么如何才能避免这个问题呢,答案就是用闭包
使用闭包之前,先来学习下人家尤大大是怎么写的,下面是简化修改后留下关键部分的代码:
export function defineReactive (
obj: Object,
key: string
) {
// ... 省略源码中收集依赖通知变更的代码
let val = obj[key] // 源码这里是传入的参数
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
const setter = property && property.set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 收集依赖
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 通知变更
}
})
}
从上面代码中可以看出,执行defineReactive会产生一个闭包,闭包中的变量val, property, getter, setter
都是被新定义的属性的getter, setter
持有引用的。这样,当属性没有定义getter,setter
函数时,经过defineReactive
函数修饰后,每次修改属性值,reactiveSetter
函数就会把新的值赋值给val
变量, 这样再调用reactiveGetter
时就会返回这个val
值.
以上就是这篇文章所讲的全部了。关于依赖收集和通知变更相关逻辑,有兴趣可以看登录GitHub查看vue源码,源码在src/core/observer/index.js
中