lyhper blog

解析Vue数据绑定实现原理

众所周知,vue实现数据绑定是通过遍历对象的属性,使用Object.defineProperty将每一个属性转为getter/setter。接下来,通过一个小的demo,我们来好好理解一下其中的奥秘。

demo

html代码:

<div id="test"></div>

javascript代码:

class Observer{
  constructor(obj, key, val){
    const dep = new Dep()
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function () {
        if (Dep.target) {
          dep.addSub(Dep.target)
        }
        return val
      },
      set: function (newVal) {
        val = newVal
        dep.notify()
      }
    })
  }
}

class Dep{
  constructor(){
    this.subs = new Set()
  }
  addSub (watcher) {
    if(!this.subs.has(watcher)){
      this.subs.add(watcher)
    }
  }
  notify () {
    this.subs.forEach(function (watcher) {
      watcher.update()
    })
  }
}

class Watcher{
  constructor(fn){
    this.fn = fn
    this.update()
  }
  update () {
    Dep.target = this
    this.fn()
    Dep.target = null
  }
}

const obj = {
  a: 1
}
new Observer(obj, 'a', obj.a)
new Watcher(function () {
  document.querySelector('#test').innerText = obj.a
})

将以上代码放到浏览器执行,当你修改a属性的值时,页面上的展示也会随之改变

解析

实际上,这个demo就是vue里数据绑定的精简版。数据绑定的相关代码位于src/core/observer目录。

  • Observer用来观察对象属性的改变,它递归地将所有属性转换为getter/setter,从而在数据发生变化时触发更新
  • Watcher用来存储数据变化时需要执行的回调函数
  • Dep用来建立起Observer和Watcher的联系。一个Observer通过Dep可以对应多个Watcher

Observer

首先new一个dep对象,用来存储与当前属性相关联的watcher。

当获取属性值时,往dep里添加watcher,然后返回属性值。这里Dep.target代表当前需要添加的watcher,这个值是全局唯一的,因为任何时候都只能有一个添加的watcher。

当修改属性值时,通过dep对象通知所有与此属性关联的watcher执行回调函数

Dep

dep里用一个Set来存储所有与之关联的watcher,这里包含一个addSub方法和notify方法。

addSub方法判断如果当前没有这个watcher,则往Set里添加。

notify方法遍历所有watcher,调用每个watcher的更新方法

Watcher

通过构造函数传入一个回调函数,并立即执行一次update方法。update方法里,首先将当前watcher赋值给Dep.target,从而在获取属性值时,将这个watcher添加到dep中。接着执行回调函数,然后将Dep.target清空

通过这样一番处理,当修改属性值时,便会通过setter方法去调用修改dom的函数,从而实现了页面与数据的绑定