vue computed原理

352 阅读2分钟

前言

首先要明白的是,vue内发布订阅模式的机制是:Watcher对象去订阅Dep对象,每一个Dep对象都绑定了一个vue.data/computed/props属性,也就是说Watcher最终订阅到了某个具体属性。

当属性值发生改变时,Dep对象会去notify所有订阅他的Watcher对象,Watcher收到信号就去update

本文章属于vue源码的简化版,只是为了方便理解,比源码少了些东西

1

初始化时vue对象给所有computed属性加个Watcher实例,以便于订阅具体的vm.$data属性值

并将所有computed属性通过defineProperties绑定到vm上,以便可以通过vm[key]直接获取到值

vm._computedWatchers = []
for(const key in computed) {
    // 给所有computed属性加个Watcher实例
	const getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get
	vm._computedWatchers.push(new Watcher(vm, getter || noop, noop))

    // 将computed属性绑定到vm上
    Object.defineProperties(vm, key, {
		get: function() {
			var watcher = this._computedWatchers[key];
            if (watcher) {
              // dirty表示脏值
              // 如果此watcher已经'脏'了,就去重新获取值,否则直接返回watcher缓存的value值即可
              if (watcher.dirty) {
                watcher.get();
              }
              return watcher.value
            }
		}
    })
}

2

假如computed是这么写的:

computed: {
  b() {
    return this.a + 1
  }
}

当vue对象中首次获取b时,会触发第1步定义的get,因为dirty初始为true,所以会执行watcher.get()去计算值

function Watcher(vm, getter){
	this.getter = getter
    this.dirty = true
    this.value = undefined
}
Watcher.prototype.get = function(){
	Dep.target = this
	this.value = this.getter()
    this.dirty = false
}

请注意,watcher.get()把当前Watcher缓存到Dep.target

当调用到this.getter()时会去获取this.a,那么会触发a的getter

function defineReactive(obj, key, val, customSetter, shallow) {
	// 这里源码上并没有传参,这里加了个参数是为了更好的说明dep和属性的绑定关系,实际上是通过闭包绑定的
	const dep = new Dep(vm, key)
	Object.defineProperty(obj, key, {
		get: function reactiveGetter() {
			var value = val
            if (Dep.target) {
          	// 让Dep.target订阅dep
          	dep.depend()
            return value
        }
    },
}

getter内部,如果有Dep.target,会让这个对象去订阅本属性,从而每次this.a发生变化后,会去调用Watcherupdate

Watcher.prototype.update = function(){
	this.dirty = true
}

update内部更新此Watcherdirty的,已经脏了,那么下次再获取b的时候,就会如第1步所写,会再次调用Watchergetter去重新取值,而如果this.a不发生改变,如第1步所写,会直接返回Watcher缓存的value