Vue生命週期詳解+對應程式碼解析

So ?發表於2018-03-17

-使用GitHub閱覽

對於Vue的例項,比如

const app = new Vue({...})
複製程式碼

瀏覽器解析到這段程式碼的時候,自動執行beforeCreate => created => beforeMount => mounted方法,每當data的某個屬性值更改了,比如==app.mes = "hi"==,自動執行beforeUpdate => updated方法。

beforeCreate:

el     : undefined
data   : undefined
message: undefined
複製程式碼

created:

el     : undefined
data   : [object Object]
message: hi
複製程式碼

beforeMount:

el     : [object HTMLDivElement]
<div id="app"><p>{{ message }}</p></div>
data   : [object Object]
message: hi
複製程式碼

mounted:

el     : [object HTMLDivElement]
<div id="app"><p>hi</p></div>
data   : [object Object]
message: hi
複製程式碼

當需要銷燬這個例項的時候,需要手動執行app.$destroy()。然後Vue此時會自動呼叫beforeDestroy、destroyed方法。

一旦元件被銷燬,它將不再響應式,即更改data的屬性值,比如app.mes = "hello",頁面不會同時被更新,頁面上的資料任然顯示的是 hi

此時需要手動呼叫app.$mount(),這個方法在這個時候被手動呼叫的時候,Vue會自動按照下面的順序執行以下方法: beforeMount => beforeUpdate => updated => mounted。

此時,我們會發現頁面變成了hello 我的理解是,雖然之前app被銷燬了,但是對於mes屬性的dep裡面,通過監控它的watcher,仍然是變成了“hello”,但是不會呼叫底層的compile(this.cb)來渲染檢視。而當手動呼叫app.$mount()的時候,它的compile方法被啟用,又變成響應式的了。 當我們呼叫destroy方法的時候,其實呼叫了app._watcher的teardown方法,下面程式碼所示(在執行這個destroy之前,我們呼叫了callHook這個鉤子函式):

 Vue.prototype.$destroy = function () {
    var vm = this;
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy');
    if (vm._watcher) {
      vm._watcher.teardown();
    }
    var i = vm._watchers.length;
    while (i--) {
      vm._watchers[i].teardown();
    }
}
複製程式碼

_watcher的active值被置為false,然後呼叫removeSub將該watcher所依賴的訂閱器全都退訂(從每個dep的subs陣列中移除)。

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

複製程式碼

看到這裡,我們知道,儘管被destroy,_watcher還是那個_watcher(此時,如果呼叫$mount,就不是了)。什麼意思呢?就是說每個屬性值還是被放在它們各自的watcher中被監控著的。只不過,由於這些watcher都從deps裡面被退訂了,那麼一旦它們的值被更改,將不會呼叫watcher的update(因為每個watcher的update方法是在dep的notify裡面被呼叫的)。

我們看到update被呼叫的時候,實際是呼叫了run方法,也就是說真正的update(this.cb)實在run裡面被呼叫。

在run裡面,先判斷active,如果是false就不更新檢視(不再響應式了)

Watcher.prototype.update = function update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

//
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;
      /..../
      {this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};
複製程式碼

好了,當我們再次呼叫app.$mount(),其實我們此時是重新編譯了一遍節點。此時經歷了這麼個過程: beforeMount => beforeUpdate => updated => mounted

檢視原始碼,我們發現$mount方法裡面呼叫了compileToFunctions方法,而compileToFunctions方法裡面呼叫了compile方法,也就是說,此時我們為app.mes屬性,重新new了一個Watcher。

var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el,
  hydrating){
  //.......
  var ref = compileToFunctions(template, {
        shouldDecodeNewlines: shouldDecodeNewlines,
        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
   }, this);
   //....
    
}
//...
function compileToFunctions(){
  //....
  var compiled = compile(template, options);
  //...
}
複製程式碼

如果讀者對以上分析感到疑惑,我們可以測試一下:

在呼叫destroy之前,我們先宣告一個變數p來保持對此時的watcher物件的引用(由於jvm是根據對物件的持有,來決定是否從記憶體中刪除此物件。所以這個舊的watcher不會被刪除)

// teardown之前
p=app._watcher.deps[0].subs[0];
// teardown之後,又呼叫$mount()
pp = app._watcher.deps[0].subs[0];

// 比較
p === pp //輸出false,說明這個watcher是新new出來的
複製程式碼

-輕鬆掌握-vue-例項的生命週期/

-Vue2.0 探索之路——生命週期和鉤子函式的一些理解

相關文章