vue2數(shù)據(jù)綁定

數(shù)據(jù)綁定可以分為兩部分

一、響應(yīng)式數(shù)據(jù)的準(zhǔn)備

注:下面的initState 調(diào)用的代碼位于Vue.prototype._init中,而Vue.prototype._initinitMixin中執(zhí)行時(shí)被定義,在vue.js文件中存在

image.png

這樣幾行代碼,在你引入vue.js文件以后會執(zhí)行initMixin等后續(xù)一系列方法,其中initMixin會為Vue這個(gè)構(gòu)造函數(shù)掛載上_init這個(gè)方法,_initnew Vue時(shí)會被執(zhí)行,之后會進(jìn)入到initState方法中

主要位于生命周期的beforeCreated和created之間,其中最重要涉及的是initState函數(shù)

  function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    if (opts.methods) { initMethods(vm, opts.methods); }
    if (opts.data) {
      initData(vm);
    } else {
      observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); }
    if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
    }
  }

這里涉及到了三個(gè)函數(shù)initData、initComputed、initWatch,其實(shí)這三個(gè)函數(shù)執(zhí)行完畢后數(shù)據(jù)綁定的數(shù)據(jù)部分已經(jīng)處理完畢了,下面展開看看

initData

關(guān)鍵代碼就在于observe
這里先入為主一下,observe的目的就是給傳進(jìn)來的參數(shù)設(shè)置響應(yīng)式,對于普通的對象和數(shù)組處理方式不同,普通對象因?yàn)榇嬖趯ο笄短椎那闆r,所以會對key 進(jìn)行循環(huán),并為對應(yīng)的value設(shè)置響應(yīng)式,而數(shù)組會對下標(biāo)循環(huán),對value也設(shè)置響應(yīng)式;其實(shí)整體和deepClone的邏輯十分類似

  function initData (vm) {
    var data = vm.$options.data;
    //這里省略了很多代碼

    // observe data
    observe(data, true /* asRootData */);
  }

進(jìn)入到observe,還是省去大量代碼

  function observe (value, asRootData) {
    
    // 這里也省去了很多代碼
    } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
    ) {
      ob = new Observer(value);
    }
  
  }

重點(diǎn)在于ob = new Observer(value);,那再看一下Observer的定義(省去了小部分代碼)

  var Observer = function Observer (value) {
    this.dep = new Dep();
    if (Array.isArray(value)) {
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  };

這里并不能省去很多代碼,但是精簡過后看到主要的邏輯在于傳進(jìn)來的參數(shù)是否為數(shù)組
如果是數(shù)組,那么用observeArray就對數(shù)組進(jìn)行特殊處理,如果是普通對象則使用walk進(jìn)行遞歸
再看一下這兩個(gè)函數(shù)

//處理普通對象
  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  };
//處理數(shù)組
  Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  };

那在處理數(shù)組中我們再一次看到了observe函數(shù),到目前為止邏輯是這樣的

image.png

那再看 defineReactive$$1,defineReactive$$1為普通對象的每一個(gè)鍵值對利用defineProperty設(shè)置get和set方法構(gòu)建響應(yīng)式。在這里還是先精簡一部分代碼,理清流程

  function defineReactive$$1 (
      obj,
      key,
      val,
      customSetter,
      shallow
  ) {

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val
        return value
      },
      set: function reactiveSetter (newVal) {
        childOb = !shallow && observe(newVal);
      }
    });
  }

這樣一來就清晰很多了。

強(qiáng)調(diào):這里只是定義了get set的方法,并沒有調(diào)用,當(dāng)組件掛載的時(shí)候才會調(diào)用,后面會細(xì)講,這里還是理清邏輯為主。

先看這一句

var childOb = !shallow && observe(val);

又出現(xiàn)了observe,這是用于解決對象嵌套的

{
  a: 1,
  b: {c: 2}
}

當(dāng)循環(huán)到a:1時(shí),observe(1)返回空,當(dāng)循環(huán)到b: {c: 2}時(shí),observe(b: {c: 2})返回observer實(shí)例,此時(shí)對于{c: 2}的響應(yīng)式也構(gòu)建完畢了。
總結(jié)下,上面的代碼還未運(yùn)行到Object.defineProperty時(shí),已經(jīng)對所有的子結(jié)構(gòu)構(gòu)建完響應(yīng)式了
那么到目前為止的流程是這樣的

image.png

其實(shí)到這里響應(yīng)式基本就結(jié)束,下面結(jié)合mount時(shí)的代碼說一下數(shù)據(jù)和視圖時(shí)如何綁定的。

二、數(shù)據(jù)與視圖之間的綁定

找到mountComponent函數(shù),該函數(shù)會在$mount調(diào)用時(shí)被執(zhí)行

  function mountComponent (
      vm,
      el,
      hydrating
  ) {
  //精簡很多代碼
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);

  }

核心代碼new Watcher,看下watcher構(gòu)造函數(shù)

  var Watcher = function Watcher (
      vm,
      expOrFn,
      cb,
      options,
      isRenderWatcher
  ) {
    this.vm = vm;
    if (options) {
      this.lazy = !!options.lazy;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.deps = [];
    this.value = this.lazy
        ? undefined
        : this.get();
  };

對比下形參和實(shí)參:
expOrFn=updateComponent
cb=noop
option= { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } }
得到執(zhí)行時(shí)的相關(guān)變量:lazy=false
this.value的值為this.get()的返回值
getwatcher實(shí)例上的方法

  Watcher.prototype.get = function get () {
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      if (this.user) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw e
      }
    } finally {
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };

其中value = this.getter.call(vm, vm);中的getterupdateComponent這里就不多做介紹了,但是需要提到就是在updateComponent中執(zhí)行的vm._render()會有對于data的訪問,也就是觸發(fā)defineProperty中的get方法。

重新回到defineReactive$$1中看看完整的代碼

function defineReactive$$1 (
      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;
    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();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },

出現(xiàn)了var dep = new Dep();

 var Dep = function Dep () {
    this.id = uid++;
    this.subs = [];
  };

其中subs 是用來保存依賴于當(dāng)前數(shù)據(jù)的watcher的

強(qiáng)調(diào):subs中存的是watcher實(shí)例

進(jìn)入到get方法中
Dep.target表示的是當(dāng)前處于執(zhí)行狀態(tài)的watcher,默認(rèn)情況下為空

  Dep.target = null;
  var targetStack = [];

那么Dep.target什么時(shí)候會有值呢?再看一下watcher實(shí)例上的get方法

Watcher.prototype.get = function get () {
    pushTarget(this);
    try {
     //
    } finally {
     //
      popTarget();
    }
    return value
  };

這個(gè)兩行很關(guān)鍵

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

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

其中pushTargetDep.target設(shè)置為自己,設(shè)置完之后,訪問了data中的數(shù)據(jù),此時(shí)再看defineReactive$$1中的get

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
      },

先執(zhí)行了dep.depend(),把當(dāng)前激活的watcher添加到dep中

  Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };

  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);
      }
    }
  };

  Dep.prototype.addSub = function addSub (sub) {
    this.subs.push(sub);
  };

注意下調(diào)用鏈:dep調(diào)用depend,depend中把調(diào)用當(dāng)前激活的watcher的addDep方法,參數(shù)為dep自身,addDep中dep把當(dāng)前的激活的watcher添加到subs數(shù)組中

訂閱發(fā)布模式:目前為止我們只做到了訂閱,這里簡單講一下,vue為什么能做到精準(zhǔn)的訂閱。
我們再從頭理一下。從watcher.get開始,watcher.get調(diào)用時(shí)會訪問到視圖中所需要的所有的data,同時(shí)watcher也會把自身設(shè)置為Dep.target,而訪問data的過程中,每個(gè)data的key-value會創(chuàng)建一個(gè)dep對象,收集依賴這一個(gè)個(gè)data的watcher,恰好當(dāng)前的watcher能訪問到這些data,也因此對這些data產(chǎn)生了依賴,所以通過設(shè)置Dep.target能夠準(zhǔn)確收集依賴。但是做法不一定需要時(shí)Dep.target,只需要一個(gè)全局變量即可

同樣的對于對象嵌套的情況,子對象的dep也應(yīng)該把當(dāng)前的watcher加入到數(shù)組中

if (childOb) 
     childOb.dep.depend();

再來看下set,相對而言簡單了很多

      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();
      }

主要就是這幾行代碼

set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }

對設(shè)置的新值進(jìn)行observe,保證新值改變的時(shí)候也能被觀測到
然后就是訂閱發(fā)布的發(fā)布dep.notify(),主要就是對dep拷貝一份后執(zhí)行subs中的watcher的回調(diào)函數(shù),在這里就不展開了,具體的是利用promise將更新任務(wù)放入微隊(duì)列中,然后取出dep中的watcher執(zhí)行watcher.run,watcher.run中會執(zhí)行wachter.get,也就是updatecomponent完成視圖的更新

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();
    }
  };
  Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

歡迎提問

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容