Vue源码学习-监听对象的变化

Vue的源码直接看起来还是很费劲,还好这方面的资料也很多。这篇文章写写Vue最基本的核心 — 基于Object.definePropertygettersetter函数的劫持,实现对数据变化的监听。

思路其实是很容易理解的,通过自定义访问器属性中的gettersetter函数来实现对属性的监听。

提到gettersetter就要说一说对象的访问器属性和数据属性,数据属性只有基本的四个特性:

  1. [[configurable]] 可配置
  2. [[enumerable]] 可枚举
  3. [[writable]] 可写
  4. [[value]] 值

当访问这一属性是,可以直接获取到[[value]]的值,同理修改时也是修改的这一值。而不同于数据属性,访问器的四个属性则是:

  1. [[configurable]]
  2. [[enumerable]]
  3. [[getter]]
  4. [[setter]]

通过get和set方法来获取和修改值,在JS中我们可以直接通过Object.defineProperty这一接口来自定义这两个方法以达成监听的目的:

// 最简单的情况,针对值为简单类型的属性
var person = {
  name: 'jack'
}

person.listen = function(property) {
  var val = this[property]
  Object.defineProperty(this, 'name', {
    get: function () {
      console.log('oops someone is trying to get the value')
      return val
    },
    set: function (newVal) {
      console.log('ok the name now has been changed to ' + newVal)
      val = newVal
    }
  })
}

可以看下面的GIF查看效果:

gif

但是如果name的值是一个对象,修改这个对象中的值并不会改变 name所保存的对象引用,因此也就无法达成监听的目的,解决的方法就是利用递归算法:

// 将数据多包装一层
var Observer = function(data) {
  this.data = data
  this.iterate(data)
}

// 遍历每一个属性
Observer.prototype.iterate = function(obj) {
  var val
  for (var key in obj) {
    val = obj[key]

    // 如果属性对应的值为对象,继续递归遍历
    if (typeof val === 'object') {
      new Observer(val)
    }

    // 监听属性
    this.listen(key, val)
  }
}

// 监听属性函数
Observer.prototype.listen = function(key, value) {
  Object.defineProperty(this.data, key, {
    get: function () {
      console.log('oops someone is trying to get the value of ' + key)
      return value
    },
    set: function (newVal) {
      console.log('ok the ' + key + ' now has been changed to ' + newVal)
      // 如果值设置为了新的对象,重新遍历监听
      if (typeof newVal === 'object') {
        new Observer(newVal)
      }
      value = newVal
    }
  })
}

var person = new Observer({
  name: 'jack',
  age: 25,
  info: {
    prof: 'teacher',
    city: 'beijng'
  }
})

结果如图:

gif

下一篇我们说说如何监听一个数组的问题。