• 从源码看vue(v2.7.10)的computed的实现原理


    从vue(v2.7.10)源码分析vue是如何收集依赖和触发依赖这篇文章中我们讲了vue是怎么收集依赖的。其中computed的实现原理和这个密切相关,接下来我们看看computed的实现原理。

    基础代码如下:

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    定义依赖

    直接上代码,在执行render函数实例化TestWebpackTest组件的时候会执行下面的方法:

    Vue.extend = function (extendOptions) {
        ...
        if (Sub.options.computed) {
            initComputed(Sub);
        }
        ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在该方法中会执行initComputed方法,该方法遍历computed并执行defineComputed方法:

    function initComputed(Comp) {
        var computed = Comp.options.computed;
        for (var key in computed) {
            defineComputed(Comp.prototype, key, computed[key]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    var sharedPropertyDefinition = {
          enumerable: true,
          configurable: true,
          get: noop,
          set: noop
      };
    // key: "getA"  target: Vue {constructor: ƒ} userDef:ƒ getA()
    function defineComputed(target, key, userDef) {
          var shouldCache = !isServerRendering(); // true
          if (isFunction(userDef)) {
              sharedPropertyDefinition.get = shouldCache
                  ? createComputedGetter(key)
                  : createGetterInvoker(userDef);
              sharedPropertyDefinition.set = noop;
          }
          else {
             ...
          }
         ...
          Object.defineProperty(target, key, sharedPropertyDefinition);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    主要执行了createComputedGetter方法设置computed函数getA的get方法:

    // key: "getA"
    function createComputedGetter(key) {
          return function computedGetter() {
              var watcher = this._computedWatchers && this._computedWatchers[key];
              if (watcher) {
                  if (watcher.dirty) {
                      watcher.evaluate();
                  }
                  if (Dep.target) {
                      if (Dep.target.onTrack) {
                          Dep.target.onTrack({
                              effect: Dep.target,
                              target: this,
                              type: "get" /* TrackOpTypes.GET */,
                              key: key
                          });
                      }
                      watcher.depend();
                  }
                  return watcher.value;
              }
          };
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    该方法执行过程在收集依赖部分分析。返回该函数后执行Object.defineProperty(target, key, sharedPropertyDefinition),给当前的vm设置getA响应式。至此设置响应完毕。

    收集依赖

    收集依赖发生在执行组件渲染过程,会通过_vm.getA触发computed的get方法。

    var render = function render() {
      var _vm = this,
        _c = _vm._self._c
      return _c("div", [
        _vm._v("\n  " + _vm._s(_vm.getA) + "\n  "),
        _c("button", { on: { click: _vm.addModule } }, [_vm._v("新增")]),
      ])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    会获取当前的computed函数生成的watcher,继续执行watcher.evaluate()方法:

    var watcher = this._computedWatchers && this._computedWatchers[key];
     if (watcher) {
         if (watcher.dirty) { // true
             watcher.evaluate();
         }
         if (Dep.target) {
             if (Dep.target.onTrack) {
                 Dep.target.onTrack({
                     effect: Dep.target,
                     target: this,
                     type: "get" /* TrackOpTypes.GET */,
                     key: key
                 });
             }
             watcher.depend();
         }
         return watcher.value;
     }
    Watcher.prototype.evaluate = function () {
         this.value = this.get();
         this.dirty = false;
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    调用当前watcher的get方法,首先设置当前的Dep.target为当前的watcher,然后执行this.getter方法,该方法为getA方法,相当于执行该函数。

    Watcher.prototype.get = function () {
         pushTarget(this);
         var value;
         var vm = this.vm;
         try {
             value = this.getter.call(vm, vm);
         }
         catch (e) {
            ...
         }
         finally {
             // "touch" every property so they are all tracked as
             // dependencies for deep watching
             if (this.deep) {
                 traverse(value);
             }
             popTarget();
             this.cleanupDeps();
         }
         return value;
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    由于依赖于a变量,所以会触发a变量的get方法,并执行dep.depend方法

    var value = getter ? getter.call(obj) : val;
    if (Dep.target) {
         {
             dep.depend({
                 target: obj,
                 type: "get" /* TrackOpTypes.GET */,
                 key: key
             });
         }
         ...
     }
     return isRef(value) && !shallow ? value.value : value;
     ...
    Dep.prototype.depend = function (info) {
       if (Dep.target) {
            Dep.target.addDep(this);
            if (info && Dep.target.onTrack) {
                Dep.target.onTrack(__assign({ effect: Dep.target }, info));
            }
        }
    };
    ...
    Watcher.prototype.addDep = function (dep) {
        var id = dep.id;
        if (!this.newDepIds.has(id)) {
            this.newDepIds.add(id);
            this.newDeps.push(dep);
            if (!this.depIds.has(id)) {
                dep.addSub(this);
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    当前的Dep.target是getA的watcher实例,a变量会找到订阅者(getA的watcher),并在该watcher的newDeps里添加该变量的dep(this.newDeps.push(dep)),然后该变量会把getA的watcher加入自己的依赖(dep.addSub(this))。从而建立了getA和num之间的联系。接下来执行popTarget()和this.cleanupDeps()将当前Dep.target置为组件的watcher,然后newDeps的值赋给deps。至此watcher.evaluate()执行完后this.getter.call(vm, vm)执行完毕,返回a的value。

    触发依赖

    当变量发生变化时会触发该变量的set方法:

    function reactiveSetter(newVal) {
        var value = getter ? getter.call(obj) : val;
         ...
         else if (!shallow && isRef(value) && !isRef(newVal)) {
             value.value = newVal;
             return;
         }
         else {
             val = newVal;
         }
         childOb = !shallow && observe(newVal, false, mock);
         {
             dep.notify({
                 type: "set" /* TriggerOpTypes.SET */,
                 target: obj,
                 key: key,
                 newValue: newVal,
                 oldValue: value
             });
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    主要执行dep.notify方法:

    Dep.prototype.notify = function (info) {
        // stabilize the subscriber list first
        var subs = this.subs.slice();
        ...
        for (var i = 0, l = subs.length; i < l; i++) {
            ...
            subs[i].update();
        }
    };
    
    Watcher.prototype.update = function () {
       /* istanbul ignore else */
        if (this.lazy) {
            this.dirty = true;
        }
        else if (this.sync) {
            this.run();
        }
        else {
            queueWatcher(this);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    主要执行subs[i].update()方法,当前的变量a有两个dep,computed的lazy为true不会继续执行。第二个dep为组件的watcher,执行该watcher的update方法重新渲染刷新页面。
    总结

    1. 在组件实例化的时候会遍历computed设置computed的get方法并设置Dep.target为当前computed的watcher。
    2. 在执行渲染模板方法的时候会触发该computed的get方法。
    3. 会执行computed函数,当该函数里获取变量时会触发变量的get方法。该变量会通过Dep.target获取当前的watcher并添加自己的dep(相当于记录了订阅了哪些变量),也就是说获取变量的时候会订阅该变量,该变量也会在自己的依赖dep添加watcher(记录订阅者,发生变化时会通知订阅者)。
    4. 当前变量发生改变时会循环触发该变量的dep的update方法刷新页面。其中computed
      方法由于已经获取最新值所以只需要执行组件的update方法。
  • 相关阅读:
    【数据结构】830+848真题易错题汇总(10-23)
    【Matplotlib绘制图像大全】(八):Matplotlib使用text()添加文字标注
    InnoDB数据页结构(4)之页目录
    数据预处理
    小干货~ NFS在Linux系统中的应用
    精度误差问题与eps
    Paillier算法简介
    快鲸SCRM如何助力企业高效运营私域流量?
    抖音研发效率负责人:抖音能做到每周迭代,离不开飞书项目
    第四章分类问题
  • 原文地址:https://blog.csdn.net/qq_35094120/article/details/127409520