vue 快速入門 系列 —— 偵測數據的變化 - [vue 源碼分析]

其餘章節請看:javascript

vue 快速入門 系列html

偵測數據的變化 - [vue 源碼分析]

本文將 vue 中與數據偵測相關的源碼摘了出來,配合上文(偵測數據的變化 - [基本實現]) 一塊兒來分析一下 vue 是如何實現數據偵測的。vue

Tip: 如下代碼出自 vue.esm.js,版本爲 v2.5.20。無關代碼有一些刪減。中文註釋都是筆者添加。java

/**
   * Define a property.
   * 定義屬性的方法
   */
  function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
  }
  
  /**
   * Parse simple path.
   * 解析簡單路徑。好比 vm.$watch('a.b.c', function(){})
   */
  var bailRE = /[^\w.$]/;
  function parsePath (path) {
    if (bailRE.test(path)) {
      return
    }
    var segments = path.split('.');
    return function (obj) {
      // 例如 a.b.c
      for (var i = 0; i < segments.length; i++) {
        if (!obj) { return }
        // 最後讀取到 c
        obj = obj[segments[i]];
      }
      return obj
    }
  }
  /**
   * A dep is an observable that can have multiple
   * directives subscribing to it.
   * 依賴。對咱們的依賴列表 dependList 進行了封裝,這裏提取出來了一個類,用於存儲依賴(Watcher)。
   */
  var Dep = function Dep () {
    this.id = uid++;
    // subs 也就是咱們的依賴列表 dependList
    this.subs = [];
  };

  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 () {
    // Dep.target 也就是咱們的全局變量(globalData),指向 Watcher。
    if (Dep.target) {
      // 收集依賴 Watcher
      Dep.target.addDep(this);
    }
  };
  // 通知依賴
  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.
  // 相似咱們的全局變量(globalData ),用於存儲 Watcher
  Dep.target = null;
  var targetStack = [];

  function pushTarget (target) {
    targetStack.push(target);
    Dep.target = target;
  }

  function popTarget () {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1];
  }

  /*
   * not type checking this file because flow doesn't play well with
   * dynamically accessing methods on Array prototype
   */
  // 接下來是偵測數組的變化
  // 也就是經過攔截器來實現數組的偵測
  var arrayProto = Array.prototype;
  // arrayMethods就是攔截器
  var arrayMethods = Object.create(arrayProto);
  // 能改變數組的7個方法
  var methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
  ];

  /**
   * Intercept mutating methods and emit events
   * 給攔截器(arrayMethods)定義以上7個方法
   */
  methodsToPatch.forEach(function (method) {
    // cache original method
    // 數組的原始方法
    var original = arrayProto[method];
    def(arrayMethods, method, function mutator () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];
      // 調用攔截器中的方法,攔截器接着會去調用數組中對應的方法
      var result = original.apply(this, args);
      // 數據變成響應式後,數據上就會掛載 __ob__(Observer 的實例) 屬性,裏面有數據的依賴
      var ob = this.__ob__;
      // 只有 push、unshift、splice 這三個方法能增長數據,而增長的數據也須要轉爲響應式
      var inserted;
      switch (method) {
        case 'push':
        case 'unshift':
          inserted = args;
          break
        case 'splice':
          inserted = args.slice(2);
          break
      }
      // 數組增長的數據也須要轉爲響應式
      if (inserted) { ob.observeArray(inserted); }
      // notify change
      // 通知依賴
      ob.dep.notify();
      return result
    });
  });

  /**
   * Observer class that is attached to each observed
   * object. Once attached, the observer converts the target
   * object's property keys into getter/setters that
   * collect dependencies and dispatch updates.
   * 1. 將數據轉爲響應式的主入口。
   * 2. 在咱們的實現中是經過 defineReactive() 將數據轉爲響應式,沒有遞歸偵測全部的 key。好比 
   * data = {a: 1, b: {c:1}},咱們只偵測了數據的第一層(data.a、data.b),孩子節點若是是對象,
   * 也須要偵測 data.b.c。
   * 3. 遞歸偵測調用順序:Observer -> walk -> defineReactive$$1 -> observe -> Observer
   * 4. 將對象和數組分別處理。
   */
  var Observer = function Observer (value) {            
    this.value = value;
    // 定義依賴,用於存儲於數據有關的依賴
    // 好比數據 let data = {a: [11,22]},某處使用了 data.a。當執行 data.a.push(33) 時,
    // data.a 就應該通知其依賴
    this.dep = new Dep();
    this.vmCount = 0;
    // 將 this 掛載到數據的 __ob__ 屬性上。Array 的攔截器就能夠經過數據取得 Observer 的 dep,從而通知依賴
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      // 若是有原型,就經過更改原型的方式將攔截器掛載到數組上,不然就將攔截器中的方法依次拷貝到數組上
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      // 數組中的每一項也須要轉爲響應式
      this.observeArray(value);
    } else {
      // 依次遍歷對象中每一個 key,將其轉爲響應式
      this.walk(value);
    }
  };

  /**
   * Walk through all properties 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++) {
      // 經過 Object.defineProperty() 偵測對象
      defineReactive$$1(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]);
    }
  };

  /**
   * Augment a target Object or Array by intercepting
   * the prototype chain using __proto__
   * 經過更改原型來掛載攔截器,實現數組的偵測。
   */
  function protoAugment (target, src) {
    // 做用與 setPrototype 相同
    target.__proto__ = src;
  }

  // 將攔截器中的方法拷貝到數組中,實現數組的偵測。
  function copyAugment (target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i];
      def(target, key, src[key]);
    }
  }

  /**
   * 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.
   * 觀察數據。若是數據不是對象,直接返回;若是已是響應式,則返回 Observer 的實例;不然將值轉爲響應式
   */
  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) &&
      // 不能是 vue 實例
      !value._isVue
    ) {
      ob = new Observer(value);
    }
    if (asRootData && ob) {
      ob.vmCount++;
    }
    return ob
  }

  /**
   * Define a reactive property on an Object.
   * 偵測數據變化。功能與咱們的 defineReactive() 方法相似。
   */
  function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    // 每一個 key 都有一個 Dep 用於存儲依賴
    // dep 就是咱們的依賴列表
    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;
    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;
        // 若是有值
        if (Dep.target) {
          // 收集依賴
          dep.depend();
          // 若是值(childOb)是對象,childOb也須要收集依賴
          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 (customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        // 新的值也須要轉爲響應式
        childOb = !shallow && observe(newVal);
        // 通知依賴
        dep.notify();
      }
    });
  }

  /**
   * 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.
   * Watcher 相對比較複雜,稍微分析一下
   */
  var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    // vue 實例
    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;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb;
    this.id = ++uid$1; // 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
    // expOrFn 能夠是函數,也能夠是表達式,例如 a.b.c,統一爲 this.getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    // 經過 get() 方法讀取數據
    this.value = this.lazy
      ? undefined
      : this.get();
  };

  /**
   * Evaluate the getter, and re-collect dependencies.
   * 
   */
  Watcher.prototype.get = function get () {
    // 會將本身賦值給 Dep.target
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      // 調用 Watcher 構造函數中分裝的 getter() 方法
      // 觸發數據的 getter,從而收集依賴(Watcher)
      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);
      }
    }
  };

  /**
   * 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 {
      queueWatcher(this);
    }
  };

其餘章節請看:react

vue 快速入門 系列express

相關文章
相關標籤/搜索