zoukankan      html  css  js  c++  java
  • vue computed的依赖收集机制源码分析

    Dep类:data的所有属性都创建了一个dep实例收集被观察属性的watcher实例

    var uid = 0;
    
    /**
     * A dep is an observable that can have multiple
     * directives subscribing to it.
     */
    // 依赖收集
    var Dep = function Dep () {
      this.id = uid++;  // 唯一id
      this.subs = [];
    };
    // 注入依赖的watch实例
    Dep.prototype.addSub = function addSub (sub) {
      this.subs.push(sub);
    };
    // 解除依赖关系
    Dep.prototype.removeSub = function removeSub (sub) {
      remove(this.subs, sub);
    };
    // 注入依赖
    Dep.prototype.depend = function depend () {
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    };
    
    // 通知(执行watcher.update)所有watch属性更新
    Dep.prototype.notify = function notify () {
      // stabilize the subscriber list first
      var subs = this.subs.slice();
      for (var i = 0, l = subs.length; i < l; i++) {
        subs[i].update();
      }
    };
    
    // the current target watcher being evaluated.
    // this is globally unique because there could be only one
    // watcher being evaluated at any time.
    Dep.target = null;
    var targetStack = [];
    // 切换当前watch中的实例与入栈
    function pushTarget (_target) {
      if (Dep.target) { targetStack.push(Dep.target); }
      Dep.target = _target;
    }
    // 切换当前watch中的实例与出栈
    function popTarget () {
      Dep.target = targetStack.pop();
    }

     Watcher类:观察组件实例的某个属性,值发生变化执行传入的回调方法(cb),computed也借助了Watcher实现依赖项的收集

    /**
     * A watcher parses an expression, collects dependencies,
     * and fires callback when the expression value changes.
     * This is used for both the $watch() api and directives.
     */
    // watch实例
    var Watcher = function Watcher (
      vm,  // 组件本身
      expOrFn, // 读取观察属性
      cb,
      options,
      isRenderWatcher
    ) {
        // 当前组件实例
      this.vm = vm;
      if (isRenderWatcher) {
        vm._watcher = this;
      }
      vm._watchers.push(this);
      // options
      if (options) {
        this.deep = !!options.deep;  // 是否深度观察
        this.user = !!options.user;
        this.lazy = !!options.lazy;
        this.sync = !!options.sync; // 是否同步
      } else {
        this.deep = this.user = this.lazy = this.sync = false;
      }
      // 回调方法
      this.cb = cb;
      this.id = ++uid$2; // uid for batching
      this.active = true;
      this.dirty = this.lazy; // for lazy watchers
      this.deps = [];
      this.newDeps = [];
      this.depIds = new _Set();
      this.newDepIds = new _Set();
      this.expression = expOrFn.toString();
      // parse expression for getter
      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
          );
        }
      }
      // 获取被观察属性的值
    this.value = this.lazy ? undefined : this.get(); }; /** * Evaluate the getter, and re-collect dependencies. */ Watcher.prototype.get = function get () { // 调整正在watcher中的实例 pushTarget(this); var value; var vm = this.vm; try { // 该方法的执行过程中所有使用get操作的属性会被收集为依赖项 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(); // 清除旧的依赖列表,更新依赖列表 this.cleanupDeps(); } return value }; /** * Add a dependency to this directive. */ // 添加依赖项 Watcher.prototype.addDep = function addDep (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); } } }; /** * Clean up for dependency collection. */ // 更新依赖列表 Watcher.prototype.cleanupDeps = function cleanupDeps () { var this$1 = this; var i = this.deps.length; while (i--) { var dep = this$1.deps[i]; if (!this$1.newDepIds.has(dep.id)) { dep.removeSub(this$1); } } var tmp = this.depIds; this.depIds = this.newDepIds; this.newDepIds = tmp; this.newDepIds.clear(); tmp = this.deps; this.deps = this.newDeps; this.newDeps = tmp; this.newDeps.length = 0; }; /** * Subscriber interface. * Will be called when a dependency changes. */ // 更新试图 Watcher.prototype.update = function update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { // 同步执行 this.run(); } else { // 异步执行 队列阻塞实现nextTick queueWatcher(this); } }; /** * Scheduler job interface. * Will be called by the scheduler. */ // 执行watcher回调 Watcher.prototype.run = function run () { if (this.active) { var value = this.get(); if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value var oldValue = this.value; this.value = value; if (this.user) { try { this.cb.call(this.vm, value, oldValue); } catch (e) { handleError(e, this.vm, ("callback for watcher "" + (this.expression) + """)); } } else { this.cb.call(this.vm, value, oldValue); } } } }; /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false; }; /** * Depend on all deps collected by this watcher. */ // 注入依赖 Watcher.prototype.depend = function depend () { var this$1 = this; var i = this.deps.length; while (i--) { this$1.deps[i].depend(); } }; /** * Remove self from all dependencies' subscriber list. */ Watcher.prototype.teardown = function teardown () { var this$1 = this; if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this); } var i = this.deps.length; while (i--) { this$1.deps[i].removeSub(this$1); } this.active = false; } }; /* */

     Observer类:对data的所有属性进行递归,监听每一个属性的get、set操作。get操作用于收集观察实例的依赖项(即依赖属性),set操作用于更新数据与相应的视图

    var observerState = {
      shouldConvert: true
    };
    
    /**
     * Observer class that are attached to each observed
     * object. Once attached, the observer converts target
     * object's property keys into getter/setters that
     * collect dependencies and dispatches updates.
     */
    var Observer = function Observer (value) {
      this.value = value;
      this.dep = new Dep();
      this.vmCount = 0;
      // 设置ob原型
      def(value, '__ob__', this);
      if (Array.isArray(value)) {
        var augment = hasProto
          ? protoAugment
          : copyAugment;
        augment(value, arrayMethods, arrayKeys);  // 设置原型
        // 遍历数组观察每一个属性
        this.observeArray(value);
      } else {
        // 观察单个属性
        this.walk(value);
      }
    };
    
    /**
     * Walk through each property and convert them into
     * getter/setters. This method should only be called when
     * value type is Object.
     */
    // 遍历对象观察每一个属性
    Observer.prototype.walk = function walk (obj) {
      var keys = Object.keys(obj);
      for (var i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i], obj[keys[i]]);
      }
    };
    
    /**
     * Observe a list of Array items.
     */
    // 观察数组的每一项值
    Observer.prototype.observeArray = function observeArray (items) {
      for (var i = 0, l = items.length; i < l; i++) {
        observe(items[i]);
      }
    };
    /**
     * Attempt to create an observer instance for a value,
     * returns the new observer if successfully observed,
     * or the existing observer if the value already has one.
     */
    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 (
        observerState.shouldConvert &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
      ) {
        ob = new Observer(value);
      }
      if (asRootData && ob) {
        ob.vmCount++;
      }
      return ob
    }
    
    /**
     * Define a reactive property on an Object.
     */
    // 观察数据每个属性的变化(数据修改触发页面渲染等)
    function defineReactive (
      obj,
      key,
      val,
      customSetter,
      shallow
    ) {
      // 实例化依赖收集实例
      var dep = new Dep();
    
      var property = Object.getOwnPropertyDescriptor(obj, key);
      if (property && property.configurable === false) {
        return
      }
    
      // cater for pre-defined getter/setters
      var getter = property && property.get;
      var setter = property && property.set;
    
      var childOb = !shallow && observe(val);
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          var value = getter ? getter.call(obj) : val;
          // 如果有正在观察的实例
          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);
          // 执行依赖的watcher实例队列
          dep.notify();
        }
      });
    }
    
    /**
     * Set a property on an object. Adds the new property and
     * triggers change notification if the property doesn't
     * already exist.
     */
    // 设置属性时触发页面渲染
    function set (target, key, val) {
      if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val
      }
      if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val
      }
      var ob = (target).__ob__;
      if (target._isVue || (ob && ob.vmCount)) {
        "development" !== 'production' && warn(
          'Avoid adding reactive properties to a Vue instance or its root $data ' +
          'at runtime - declare it upfront in the data option.'
        );
        return val
      }
      if (!ob) {
        target[key] = val;
        return val
      }
      defineReactive(ob.value, key, val);
      ob.dep.notify();
      return val
    }
    
    /**
     * Delete a property and trigger change if necessary.
     */
    // 删除属性时触发页面渲染
    function del (target, key) {
      if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1);
        return
      }
      var ob = (target).__ob__;
      if (target._isVue || (ob && ob.vmCount)) {
        "development" !== 'production' && warn(
          'Avoid deleting properties on a Vue instance or its root $data ' +
          '- just set it to null.'
        );
        return
      }
      if (!hasOwn(target, key)) {
        return
      }
      delete target[key];
      if (!ob) {
        return
      }
      ob.dep.notify();
    }
    
    /**
     * Collect dependencies on array elements when the array is touched, since
     * we cannot intercept array element access like property getters.
     */
    // 递归数组监听属性
    function dependArray (value) {
      for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
        e = value[i];
        e && e.__ob__ && e.__ob__.dep.depend();
        if (Array.isArray(e)) {
          dependArray(e);
        }
      }

    computed计算属性:初始化计算属性,观察计算属性,收集计算属性的依赖并把计算属性写入组件实例本身

    /**
     * Perform no operation.
     * Stubbing args to make Flow happy without leaving useless transpiled code
     * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
     */
    function noop (a, b, c) {}
    /*  */
    
    var sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    };
    
    function proxy (target, sourceKey, key) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      };
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val;
      };
      Object.defineProperty(target, key, sharedPropertyDefinition);
    }
    
    var computedWatcherOptions = { lazy: true };
    // 初始化计算属性
    function initComputed (vm, computed) {
      // $flow-disable-line
      var watchers = vm._computedWatchers = Object.create(null);
      // computed properties are just getters during SSR
      var isSSR = isServerRendering(); // 是不是服务端渲染
    
      for (var key in computed) {
        var userDef = computed[key];  // 获取计算属性的回调方法,赋值给getter
        var getter = typeof userDef === 'function' ? userDef : userDef.get;
        if ("development" !== 'production' && getter == null) {
          warn(
            ("Getter is missing for computed property "" + key + ""."),
            vm
          );
        }
    
        if (!isSSR) {
        // 监听组件的computed属性
          // create internal watcher for the computed property.
          watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
          );
        }
    
        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined
        // at instantiation here.
        // 如果key并不属于组件实例本身,定义计算属性
        if (!(key in vm)) {
          defineComputed(vm, key, userDef);
        } else {
          if (key in vm.$data) {
            warn(("The computed property "" + key + "" is already defined in data."), vm);
          } else if (vm.$options.props && key in vm.$options.props) {
            warn(("The computed property "" + key + "" is already defined as a prop."), vm);
          }
        }
      }
    }
    // 创建计算属性至组件本身
    function defineComputed (
      target,
      key,
      userDef
    ) {
      // 不是服务端渲染
      var shouldCache = !isServerRendering();  
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key)
          : userDef;
        sharedPropertyDefinition.set = noop;
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop;
        sharedPropertyDefinition.set = userDef.set
          ? userDef.set
          : noop;
      }
      if ("development" !== 'production' &&
          sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function () {
          warn(
            ("Computed property "" + key + "" was assigned to but it has no setter."),
            this
          );
        };
      }
      // 监听组件本身的计算属性
      Object.defineProperty(target, key, sharedPropertyDefinition);
    }
    // 生成计算属性的getter方法
    function createComputedGetter (key) {
      return function computedGetter () {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate();
          }
        // 注入依赖
    if (Dep.target) { watcher.depend(); } return watcher.value } } }

     挂载组件:观察整个组件实例,收集依赖,当依赖发生变化重新调用render函数生成新的vnode,再通过diff算法将newVnode与oldVnode对比,更新变化的部分

    // 挂载组件
    function mountComponent (
      vm,
      el,
      hydrating
    ) {
      vm.$el = el;
      if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode;
        {
          /* istanbul ignore if */
          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');
    
      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);
        };
      }
    
      // we set this to vm._watcher inside the watcher's constructor
      // since the watcher's initial patch may call $forceUpdate (e.g. inside child
      // component's mounted hook), which relies on vm._watcher being already defined
      // 观察组件实例,收集依赖项,当依赖项发生变化就重新使用render函数生成vnode  
      new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
      hydrating = false;
    
      // manually mounted instance, call mounted on self
      // mounted is called for render-created child components in its inserted hook
      if (vm.$vnode == null) {
        vm._isMounted = true;
        callHook(vm, 'mounted');
      }
      return vm
    }
  • 相关阅读:
    IOS中常见的Operation —— NSOperation
    动态语言,别再说不
    CoreImage的使用及常见滤镜工具(一)
    【iOS】用Layer创建一个三维模型以及拖动
    前端基础-html、css
    mysql数据库—索引
    mysql数据库—用户管理、pymysql模块
    mysql数据库—函数、数据备份、流程控制
    mysql数据库基本操作2
    mysql数据库—事务、存储过程
  • 原文地址:https://www.cnblogs.com/dudeyouth/p/10726113.html
Copyright © 2011-2022 走看看