众所周知,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中













网友评论