Vue源碼學習之computed

首先,瞭解下Vue中,computed的作用,主要用於對一個變量的惰性更新,當這個屬性所依賴的變量發生改變時,它將會更新。用官網的話講 “計算屬性是基於它們的響應式依賴進行緩存的

 

1、創建 Vue 實例時會 initState 和 $mount,前者用於初始化 data,computed,watch初始化函數。後者用於做頁面渲染

Vue.prototype._init = function (options) {
    // vue中初始化  this.$options 表示的是vue中參數
    let vm = this;
    vm.$options = options;

    // MVVM原理 需要數據重新初始化
    // 攔截數組的方法 和 對象的屬性
    initState(vm); // data computed watch
    // ....

    // 初始化工作 vue1.0 =>

    if(vm.$options.el){
        vm.$mount();
    }
}
export function initState(vm){ 
    //做不同的初始化工作 
    let opts = vm.$options;
    if(opts.data){
        initData(vm); // 初始化數據
    }
    if(opts.computed){
        initComputed(vm,opts.computed); // 初始化計算屬性
    }
    if(opts.watch){ 
        initWatch(vm); // 初始化watch
    }
}

 

2、initComputed(),初始化計算屬性函數

function initComputed(vm,computed){
    // 將計算屬性的配置 放到vm上
    let watchers = vm._watchersComputed =  Object.create(null); // 創建存儲計算屬性的watcher的對象

    for(let key in computed){ // {fullName:()=>this.firstName+this.lastName}
        let userDef = computed[key];
        // new Watcher此時什麼都不會做 配置了lazy dirty = true
        watchers[key] = new Watcher(vm,userDef,()=>{},{lazy:true}); // 計算屬性watcher 默認剛開始這個方法不會執行
    // vm.fullName
        Object.defineProperty(vm,key,{
            get:createComputedGetter(vm,key)
        }) // 將這個屬性 定義到vm上
    }

}

將 Vue 實例上 computed 屬性進行遍歷,並且設置 vm._watchersComputed 的屬性爲每一個 computed 的Watcher,這個 Watcher 類和 渲染 Watcher 使用的是同一個類,第二個傳入參數是一個屬性計算函數,然後將 computed 中的屬性掛載到 vm 實例上,並且做一個屬性劫持。

 

 

 

3、createComputedGetter(),返回的是一個函數

function createComputedGetter(vm,key){
    let watcher = vm._watchersComputed[key]; // 這個watcher 就是我們定義的計算屬性watcher
    return function() { // 用戶取值是會執行此方法
        if(watcher){
            // 如果dirty 是false的話 不需要重新執行計算屬性中的方法
           if(watcher.dirty){ // 如果頁面取值 ,而且dirty是true 就會去調用watcher的get方法
            watcher.evaluate();
           } 
           if(Dep.target){ // watcher 就是計算屬性watcher dep = [firstName.dep,lastName.Dep]
               watcher.depend();
           }
           return watcher.value
        }
    }
}

4、computed如何new Watcher的

class Watcher{
  constructor(vm,exprOrFn,cb=()=>{},opts={}){
  this.vm = vm;
  this.exprOrFn = exprOrFn;
  if(typeof exprOrFn === 'function'){
    this.getter = exprOrFn; // getter就是new Watcher傳入的第二個函數
  }else {
    this.getter = function () { // 如果調用此方法 會將vm上對應的表達式取出來
      return util.getValue(vm,exprOrFn)
    }
  }
  this.lazy = opts.lazy; // 如果這個值爲true 說明他是計算屬性
  this.dirty = this.lazy;
  
  ....省略很多代碼
  ....
  
  this.value = this.lazy? undefined : this.get(); // 默認創建一個watcher 會調用自身的get方法;
  
  
}

  get(){
    // Dep.target = 用戶的watcher
    debugger;
    pushTarget(this); // 渲染watcher Dep.target = watcher  msg變化了 需要讓這個watcher重新執行
    // 默認創建watcher 會執行此方法
    // dep = [watcher]                    dep =[watcher]
    // fullName(){return this.firstName + this.lastName}
    // 這個函數調用時就會將當前計算屬性watcher 存起來
    let value = this.getter.call(this.vm); // 讓這個當前傳入的函數執行

    popTarget(); // Dep.target = undefined

    return value;
  }
  evaluate(){
    this.value = this.get();
    this.dirty = false; // 值求過了 下次渲染的時候不用求了
  }
  depend(){
    let i = this.deps.length;
    while(i--){
      this.deps[i].depend();
    }
  }
  update(){ // 如果立即調用get 會導致頁面刷新 異步來更新
    if(this.lazy){ // 如果是計算屬性 
      this.dirty = true; // 計算屬性依賴的值變化了 稍後取值時重新計算即可 
    }else {
      queueWatcher(this);
    }
  }
}

可以看到,傳入 lazy:true,在 Watcher中會在實例上掛載一個 dirty 屬性,watcher.dirty 爲true,則會調用 watcher.evaluate()進行求值,dirty:false則不會對computed中的屬性進行求值

 

evaluate() 可以看到該方法調用了 this.get() ,會將該屬性的計算函數執行,執行該屬性的計算函數是肯定會去訪問 該屬性所依賴的其他變量,所依賴的變量dep就會存放計算屬性watcher

 

watcher.depend() 則是讓上述所依賴變量的dep再添加一個渲染watcher,爲了後續 computed 計算屬性的更新。

 

 

我們設定如下示例,進行源碼分析:

<template>
  <div>{{fullName}}</div>
</template>

computed: {
  fullName: function () {
    return this.firstName + ' ' + this.lastName
  }
}

 

整個過程如下:

  1. $mount 會編譯模板,stack 中存放【渲染 watcher】
  2. 編譯模板時讀取 fullName 屬性,讀取時進行 fullName  屬性劫持 Object.defineProperty,createComputedGetter 函數中  watcher.evaluate();
  3. evaluate() 調用 this.get(),pushTarget(), Dep.target 目前爲計算Watcher, stack中爲 【渲染Watcher, 計算屬性Watcher】, this.getter() 可以看到是調用傳入的表達式函數,就會訪問  this.firstName, this.lastName,會導致 firstName.dep存入該 計算Watcher, lastName.dep存入該 計算Watcher,最後return 出計算出的結果,最後 計算屬性Watcher 從 stack 中出棧,Dep.target 爲 【渲染Watcher】
  4. 如果存在 Dep.target 則會 watcher.depend(), 該 watcher 還是計算屬性Watcher,watcher.depend()作用是:使得存在計算屬性的變量(firstName,lastName )的 計算屬性watcher 的 deps 中 dep 中(或者firstName,lastName每一個dep中)再存一個【渲染watcher】,用於在 fullName 所依賴的 firstName,lastName變化時,notify()遍歷自己的 watcher,執行update(),進而 fullName 在 watcher.dirty: true是重新計算求值,也再一次 進入步驟3

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章