• Vue源码解读之Dep,Observer和Watcher


    在解读Dep,Observer和Watcher之前,首先我去了解了一下Vue的数据双向绑定,即MVVM,学习于:https://blog.csdn.net/u013321...
    以及关于Observer和watcher的学习来自于:https://www.jb51.net/article/...

    整体过程

    Vue实例化一个对象的具体过程如下:

    1. 新创建一个实例后,Vue调用compile将el转换成vnode。
    2. 调用initState, 创建props, data的钩子以及其对象成员的Observer(添加getter和setter)。
    3. 执行mount挂载操作,在挂载时建立一个直接对应render的Watcher,并且编译模板生成render函数,执行vm._update来更新DOM。
    4. 每当有数据改变,都将通知相应的Watcher执行回调函数,更新视图。

      • 当给这个对象的某个属性赋值时,就会触发set方法。
      • set函数调用,触发Dep的notify()向对应的Watcher通知变化。
      • Watcher调用update方法。

    在这里插入图片描述

    在这个过程中:

    1. Observer是用来给数据添加Dep依赖。
    2. Dep是data每个对象包括子对象都拥有一个该对象, 当所绑定的数据有变更时, 通过dep.notify()通知Watcher。
    3. Compile是HTML指令解析器,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
    4. Watcher是连接Observer和Compile的桥梁,Compile解析指令时会创建一个对应的Watcher并绑定update方法 , 添加到Dep对象上。

    在这里插入图片描述

    接下来我们来分析一下对象具体的代码实现。

    Observer

    
    var Observer = function Observer (value) {
      this.value = value;
      this.dep = new Dep();
      this.vmCount = 0;
      // 给value添加__ob__属性,值就是本Observer对象,value.__ob__ = this;
      // Vue.$data 中每个对象都 __ob__ 属性,包括 Vue.$data对象本身
      def(value, '__ob__', this);
      //判断是否为数组,不是的话调用walk()添加getter和setter
      //如果是数组,调用observeArray()遍历数组,为数组内每个对象添加getter和setter
      if (Array.isArray(value)) {
        var augment = hasProto
          ? protoAugment
          : copyAugment;
        augment(value, arrayMethods, arrayKeys);
        this.observeArray(value);
      } else {
        this.walk(value);
      }
    };
    

    walk和defineReactive

    
    // 遍历每个属性并将它们转换为getter/setter。只有当值类型为对象时才调用此方法。
    Observer.prototype.walk = function walk (obj) {
      var keys = Object.keys(obj);
      for (var i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i]);
      }
    };
    
    function defineReactive (
      obj,
      key,
      val,
      customSetter,
      shallow
    ) {
      var dep = new Dep();
      var property = Object.getOwnPropertyDescriptor(obj, key);
      if (property && property.configurable === false) {
        return
      }
      //获取已经实现的 getter /setter 方法
      var getter = property && property.get;
      var setter = property && property.set;
      if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
      }
    
      var childOb = !shallow && observe(val);
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          var value = getter ? getter.call(obj) : val;
          //Dep.target 全局变量指向的就是当前正在解析指令的Complie生成的 Watcher
          // 会执行到 dep.addSub(Dep.target), 将 Watcher 添加到 Dep 对象的 Watcher 列表中
          if (Dep.target) {
            dep.depend();
            if (childOb) {
              childOb.dep.depend();
              if (Array.isArray(value)) {
                dependArray(value);
              }
            }
          }
          return value
        },
        set: function reactiveSetter (newVal) {
          var value = getter ? getter.call(obj) : val;
          /* eslint-disable no-self-compare */
          if (newVal === value || (newVal !== newVal && value !== value)) {
            return
          }
          /* eslint-enable no-self-compare */
          if ("development" !== 'production' && customSetter) {
            customSetter();
          }
          if (setter) {
            setter.call(obj, newVal);
          } else {
            val = newVal;
          }
          childOb = !shallow && observe(newVal);
          dep.notify();//如果数据被重新赋值了, 调用 Dep 的 notify 方法, 通知所有的 Watcher
        }
      });
    }
    

    observeArray和observe

    
    Observer.prototype.observeArray = function observeArray (items) {
      for (var i = 0, l = items.length; i < l; i++) {
        // 如果是数组继续执行 observe 方法, 其中会继续新建 Observer 对象, 直到穷举完毕执行 walk 方法
        observe(items[i]);
      }
    };
    
    function observe (value, asRootData) {
      if (!isObject(value) || value instanceof VNode) {
        return
      }
      var ob;
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
      } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
      ) {
        ob = new Observer(value);
      }
      if (asRootData && ob) {
        ob.vmCount++;
      }
      return ob
    }
    

    Dep

    
    // Dep是订阅者Watcher对应的数据依赖
    var Dep = function Dep () {
      //每个Dep都有唯一的ID
      this.id = uid++;
      //subs用于存放依赖
      this.subs = [];
    };
    
    //向subs数组添加依赖
    Dep.prototype.addSub = function addSub (sub) {
      this.subs.push(sub);
    };
    //移除依赖
    Dep.prototype.removeSub = function removeSub (sub) {
      remove(this.subs, sub);
    };
    //设置某个Watcher的依赖
    //这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
    //也就是说判断他是Watcher的this.get调用的,而不是普通调用
    Dep.prototype.depend = function depend () {
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    };
    
    Dep.prototype.notify = function notify () {
      var subs = this.subs.slice();
      //通知所有绑定 Watcher。调用watcher的update()
      for (var i = 0, l = subs.length; i < l; i++) {
        subs[i].update();
      }
    };
    

    Watcher

    在initMixin()初始化完成Vue实例所有的配置之后,在最后根据el是否存在,调用$mount()实现挂载。

    
    if (vm.$options.el) {
          vm.$mount(vm.$options.el);
        }
    

    $mount

    
    //这是供外部使用的公共的方法
    Vue.prototype.$mount = function (
      el,
      hydrating
    ) {
      el = el && inBrowser ? query(el) : undefined;
      return mountComponent(this, el, hydrating)
    };
    

    mountComponent

    
    function mountComponent (
      vm,
      el,
      hydrating
    ) {
      vm.$el = el;
      //这个if判断目的在检测vm.$options.render
      if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode;
        {
          if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
            vm.$options.el || el) {
            warn(
              'You are using the runtime-only build of Vue where the template ' +
              'compiler is not available. Either pre-compile the templates into ' +
              'render functions, or use the compiler-included build.',
              vm
            );
          } else {
            warn(
              'Failed to mount component: template or render function not defined.',
              vm
            );
          }
        }
      }
      // 调用钩子函数
      callHook(vm, 'beforeMount');
    
      // 定义updateComponent,将作为Watcher对象的参数传入。
      var updateComponent;
      /* istanbul ignore if */
      if ("development" !== 'production' && config.performance && mark) {
        updateComponent = function () {
          var name = vm._name;
          var id = vm._uid;
          var startTag = "vue-perf-start:" + id;
          var endTag = "vue-perf-end:" + id;
    
          mark(startTag);
          var vnode = vm._render();
          mark(endTag);
          measure(("vue " + name + " render"), startTag, endTag);
    
          mark(startTag);
          vm._update(vnode, hydrating);
          mark(endTag);
          measure(("vue " + name + " patch"), startTag, endTag);
        };
      } else {
        updateComponent = function () {
          vm._update(vm._render(), hydrating);
        };
      }
    
    
      new Watcher(vm, updateComponent, noop, {
        before: function before () {
          if (vm._isMounted) {
            callHook(vm, 'beforeUpdate');
          }
        }
      }, true /* isRenderWatcher */);
      hydrating = false;
    
      // 调用钩子
      if (vm.$vnode == null) {
        vm._isMounted = true;
        callHook(vm, 'mounted');
      }
      return vm
    }
    

    watcher

    mountComponent在构造新的Watcher对象传了当前vue实例、updateComponent函数、空函数这三个参数。

    
    var Watcher = function Watcher (
      vm,
      expOrFn,
      cb,
      options,
      isRenderWatcher
    ) {
      this.vm = vm;
      if (isRenderWatcher) {
        vm._watcher = this;
      }
      // 当前Watcher添加到vue实例上
      vm._watchers.push(this);
      // 参数配置,options默认false
      if (options) {
        this.deep = !!options.deep;
        this.user = !!options.user;
        this.computed = !!options.computed;
        this.sync = !!options.sync;
        this.before = options.before;
      } else {
        this.deep = this.user = this.computed = this.sync = false;
      }
      this.cb = cb;
      this.id = ++uid$1; // uid for batching
      this.active = true;
      this.dirty = this.computed; //用于计算属性
      this.deps = [];
      this.newDeps = [];
      //内容不可重复的数组对象
      this.depIds = new _Set();
      this.newDepIds = new _Set();
      this.expression = expOrFn.toString();
      //将watcher对象的getter设为updateComponent方法
      if (typeof expOrFn === 'function') {
        this.getter = expOrFn;
      } else {
        this.getter = parsePath(expOrFn);
        if (!this.getter) {
          this.getter = function () {};
          "development" !== 'production' && warn(
            "Failed watching path: "" + expOrFn + "" " +
            'Watcher only accepts simple dot-delimited paths. ' +
            'For full control, use a function instead.',
            vm
          );
        }
      }
      //如果是计算属性,就创建Dep数据依赖,否则通过get获取value
      if (this.computed) {
        this.value = undefined;
        this.dep = new Dep();
      } else {
        this.value = this.get();
      }
    };
    };
    

    get

    
    Watcher.prototype.get = function get () {
      pushTarget(this);//将Dep的target添加到targetStack,同时Dep的target赋值为当前watcher对象
      var value;
      var vm = this.vm;
      try {
       // 调用updateComponent方法
        value = this.getter.call(vm, vm);
      } catch (e) {
        if (this.user) {
          handleError(e, vm, ("getter for watcher "" + (this.expression) + """));
        } else {
          throw e
        }
      } finally {
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {
          traverse(value);
        }
        popTarget();//update执行完成后,又将Dep.target从targetStack弹出。
        this.cleanupDeps();
      }
      return value
    };
    
    //这是全局唯一的,因为任何时候都可能只有一个watcher正在评估。
    Dep.target = null;
    var targetStack = [];
    
    function pushTarget (_target) {
      if (Dep.target) { targetStack.push(Dep.target); }
      Dep.target = _target;
    }
    
    function popTarget () {
      Dep.target = targetStack.pop();
    }
    

    Watcher的get方法实际上就是调用了updateComponent方法,updateComponent就是

    
        updateComponent = function() {
            vm._update(vm._render(), hydrating);
        };
    

    调用这个函数会接着调用_update函数更新dom,这个是挂载到vue原型的方法,而_render方法重新渲染了vnode。

    
    Vue.prototype._render = function () {
        var vm = this;
        var ref = vm.$options;
        var render = ref.render;
        var _parentVnode = ref._parentVnode;
        // reset _rendered flag on slots for duplicate slot check
        {
          for (var key in vm.$slots) {
            // $flow-disable-line
            vm.$slots[key]._rendered = false;
          }
        }
        if (_parentVnode) {
          vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
        }
        // set parent vnode. this allows render functions to have access
        // to the data on the placeholder node.
        vm.$vnode = _parentVnode;
        // render self
        var vnode;
        try {
          vnode = render.call(vm._renderProxy, vm.$createElement);
        } catch (e) {
          handleError(e, vm, "render");
          // return error render result,
          // or previous vnode to prevent render error causing blank component
          /* istanbul ignore else */
          {
            if (vm.$options.renderError) {
              try {
                vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
              } catch (e) {
                handleError(e, vm, "renderError");
                vnode = vm._vnode;
              }
            } else {
              vnode = vm._vnode;
            }
          }
        }
        // return empty vnode in case the render function errored out
        if (!(vnode instanceof VNode)) {
          if ("development" !== 'production' && Array.isArray(vnode)) {
            warn(
              'Multiple root nodes returned from render function. Render function ' +
              'should return a single root node.',
              vm
            );
          }
          vnode = createEmptyVNode();
        }
        // set parent
        vnode.parent = _parentVnode;
        return vnode
      };
    

    update

    
    //当依赖项改变时调用。前面有提到。
    Watcher.prototype.update = function update () {
        var this$1 = this;
    
      /* istanbul ignore else */
      //是否计算属性
      if (this.computed) {
        if (this.dep.subs.length === 0) {
          this.dirty = true;
        } else {
          this.getAndInvoke(function () {
            this$1.dep.notify();
          });
        }
        //是否缓存
      } else if (this.sync) {
       //调用run方法执行回调函数
        this.run();
      } else {
        queueWatcher(this);
      }
    };
    
    Watcher.prototype.run = function run () {
      if (this.active) {
        //这里的cb就是指watcher的回调函数
        this.getAndInvoke(this.cb);
      }
    };
    
    Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
      var value = this.get();
      if (value !== this.value ||isObject(value) ||this.deep) {
        //设置新的值
        var oldValue = this.value;
        this.value = value;
        this.dirty = false;
        if (this.user) {
          try {
            cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(e, this.vm, ("callback for watcher "" + (this.expression) + """));
          }
        } else {
          //执行回调函数
          cb.call(this.vm, value, oldValue);
        }
      }
    };
    

    后记

    关于Vue数据双向绑定的文章很多,查看越多资料越觉得自己只是浅薄,要更努力才行啊。
    学习到比较系统思路的来自于:https://segmentfault.com/a/11...
    十分感谢

    来源:https://segmentfault.com/a/1190000016208088

  • 相关阅读:
    org.apache.poi.ss.usermodel 类操作excel数据遗漏
    小强的HTML5移动开发之路(13)——HTML5中的全局属性
    小强的HTML5移动开发之路(12)——从一个多媒体标签说起
    我们是怎样将网站加载时间减少 24% 的?
    CSS书写位置
    彻底理解浏览器缓存机制
    css的repaint和reflow
    CSS Reset浏览器样式重置
    专业Web设计师应该避免的6个关键错误
    网站服务器的选择
  • 原文地址:https://www.cnblogs.com/datiangou/p/10144883.html
Copyright © 2020-2023  润新知