成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

源碼學(xué)習(xí)VUE之Watcher

sutaking / 1771人閱讀

摘要:這里面還有一些問題對應(yīng)的回調(diào)函數(shù)這就是執(zhí)行上下文收集依賴同步異步更新所謂的同步更新是指當(dāng)觀察的主體改變時(shí)立刻觸發(fā)更新。

我們在前面推導(dǎo)過程中實(shí)現(xiàn)了一個(gè)簡單版的watcher。這里面還有一些問題

class Watcher {
    constructors(component, getter, cb){
        this.cb = cb // 對應(yīng)的回調(diào)函數(shù),callback
        this.getter = getter;
        this.component = component; //這就是執(zhí)行上下文
    }
    
    //收集依賴
    get(){
        Dep.target = this;        
        this.getter.call(this.component)   
        if (this.deep) {
            traverse(value)
        }
        Dep.target = null;
    }
    
    update(){
        this.cb()
    }
}
同步異步更新

所謂的同步更新是指當(dāng)觀察的主體改變時(shí)立刻觸發(fā)更新。而實(shí)際開發(fā)中這種需求并不多,同一事件循環(huán)中可能需要改變好幾次state狀態(tài),但視圖view只需要根據(jù)最后一次計(jì)算結(jié)果同步渲染就行(react中的setState就是典型)。如果一直做同步更新無疑是個(gè)很大的性能損耗。
這就要求watcher在接收到更新通知時(shí)不能全都立刻執(zhí)行callback。我們對代碼做出相應(yīng)調(diào)整

constructors(component, getter, cb, options){
        this.cb = cb // 對應(yīng)的回調(diào)函數(shù),callback
        this.getter = getter;
        this.id = UUID() // 生成一個(gè)唯一id
        this.sync = options.sync; //默認(rèn)一般為false
        this.vm = component; //這就是執(zhí)行上下文
        this.value = this.getter() // 這邊既收集了依賴,又保存了舊的值
    }
        
    update(){
        if(this.sync){ //如果是同步那就立刻執(zhí)行回調(diào)
            this.run();
        }else{
            // 否則把這次更新緩存起來
            //但是就像上面說的,異步更新往往是同一事件循環(huán)中多次修改同一個(gè)值,
            // 那么一個(gè)wather就會(huì)被緩存多次。因?yàn)樾枰粋€(gè)id來判斷一下,
            queueWatcher(this)
        }
    }
    
    run: function(){
        //獲取新的值
        var newValue = this.getter();
        this.cb.call(this.vm, newValue, this.value)
    }

這里的一個(gè)要注意的地方是,考慮到極限情況,如果正在更新隊(duì)列中wather時(shí),又塞入進(jìn)來該怎么處理。因此,加入一個(gè)flushing來表示隊(duì)列的更新狀態(tài)。
如果加入的時(shí)候隊(duì)列正在更新狀態(tài),這時(shí)候分兩種情況:

這個(gè)watcher已經(jīng)更新過, 就把這個(gè)watcher再放到當(dāng)前執(zhí)行的下一位,當(dāng)前watcher處理完,立即處理這個(gè)最新的。

這個(gè)watcher還沒有處理,就找到這個(gè)wather在隊(duì)列中現(xiàn)有的位置,并再把新的放在后面。

let flushing = false;
let has = {}; // 簡單用個(gè)對象保存一下wather是否已存在
function queueWatcher (watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true // 如果之前沒有,那么就塞進(jìn)去吧,如果有了就不用管了
    if (!flushing) {
      queue.push(watcher)
    } else {
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
   // ... 等同一事件循環(huán)結(jié)束后再依次處理隊(duì)列里的watcher。具體代碼放到后面nexttick部分再說
    }
  }
}

這么設(shè)計(jì)不無道理。我們之所以為了將wather放入隊(duì)列中,就是為了較少不必要的操作??紤]如下代碼

data: {
    a: 1
},
computed: {
    b: function(){
        this.a + 1
    }
}

methods: {
    act: function(){
        this.a = 2;
        // do someting
        this.a = 1
    }
}

在act操作中,我們先改變a,再把它變回來。我們理想狀況下是a沒變,b也不重新計(jì)算。這就要求,b的wather執(zhí)行update的時(shí)候要拿到a最新的值來計(jì)算。這里就是1。如果隊(duì)列中a的watehr已經(jīng)更新過,那么就應(yīng)該把后面的a的wather放到當(dāng)前更新的wather后面,立即更新。這樣可以保證后面的wather用到a是可以拿到最新的值。
同理,如果a的wather還沒有更新,那么把新的a的wather放的之前的a的wather的下一位,也是為了保證后面的wather用到a是可以拿到最新的值。

computed

之所以把計(jì)算屬性拿出愛多帶帶講,是因?yàn)?/p>

計(jì)算屬性存在按需加載的情況

與render和$watcher相比,計(jì)算屬性a可能依賴另一個(gè)計(jì)算屬性b。

按需加載

所謂的按需計(jì)算顧名思義就是用到了才會(huì)計(jì)算,即調(diào)用了某個(gè)計(jì)算屬性的get方法。在前面的方法中,我們在class Watcher的constructor中直接調(diào)用了getter方法收集依賴,這顯然是不符合按需加載的原則的。

依賴收集

實(shí)際開發(fā)中,我們發(fā)現(xiàn)一個(gè)計(jì)算屬性往往由另一個(gè)計(jì)算屬性得來。如,

computed: {
    a: function(){
        return this.name;
    },
    b: function(){
        return this.a + "123"; 
    }
}

對于a而言,它是b的依賴,因此有必要在a的wather執(zhí)行update操作時(shí)也更新b,也就意味著,a的watcher里需要收集著b的依賴。而收集的時(shí)機(jī)是執(zhí)行b的回調(diào)時(shí),this.a調(diào)用了a的get方法的時(shí)候
在computed部分,已經(jīng)對計(jì)算屬性的get方法進(jìn)行了改寫

function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      //調(diào)用一個(gè)計(jì)算屬性的get方法時(shí),會(huì)在watcher中收集依賴。
      watcher.depend() 
      return watcher.evaluate()
    }
  }

我們再修改一下wather代碼:

class Watcher {
    constructors(component, getter, cb, options){
         this.cb = cb 
        this.getter = getter;
        this.id = UUID() 
        this.sync = options.sync; 
        this.vm = component; 
        if(options){
            this.computed = options.computed //由于是對計(jì)算屬性特殊處理,那肯定要給個(gè)標(biāo)識(shí)符以便判斷
        }
        this.dirty = this.computed // for computed watchers
        this.value = this.lazy ? undefined : this.get();
    }
    
    update(){
        if (this.lazy) {
          this.dirty = true
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
    }
    
    run: function(){
         //拿到新值
        const value = this.get()
        if (value !== this.value || //基本類型的值直接比較
          // 對象沒辦法直接比較,因此都進(jìn)行計(jì)算
          isObject(value)) {
          // set new value
          const oldValue = this.value
          this.value = value
          this.dirty = false
          cb.call(this.vm, value, oldValue)
        }
    }
    
    // 新增depend方法,收集計(jì)算屬性的依賴
    depend () {
        if (this.dep && Dep.target) {
          this.dep.depend()
        }
      }
}
  
  //不要忘了還要返回當(dāng)前computed的最新的值
  //由于可能不是立即更新的,因此根據(jù)dirty再判斷一下,如果數(shù)據(jù)臟了,調(diào)用get再獲取一下
  evaluate () {
    if (this.dirty) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

在綁定依賴之前(computed的get被觸發(fā)一次),computed用到的data數(shù)據(jù)改變是不會(huì)觸發(fā)computed的重新計(jì)算的。

路徑解析

對于render和computed想要收集依賴,我們只需要執(zhí)行一遍回調(diào)函數(shù)就行,但是對于$watch方法,我們并不關(guān)心他的回調(diào)是什么,而更關(guān)心我們需要監(jiān)聽哪個(gè)值。
這里的需求多種多樣,
比如單個(gè)值監(jiān)聽,監(jiān)聽對象的某個(gè)屬性(.),比如多個(gè)值混合監(jiān)聽(&&, ||)等。這就需要對監(jiān)聽的路徑進(jìn)行解析。

 constructors(component, expOrFn, cb, options){
         this.cb = cb 
        this.id = UUID() 
        this.sync = options.sync; 
        this.vm = component; 
        if(options){
            this.computed = options.computed
        }
        if(typeof expOrFn === "function"){
            // render or computed
            this.getter = expOrFn 
        }else{
            this.getter = this.parsePath();
        }
        if(this.computed){
            this.value = undefined
            this.dep = new Dep() 
        }else{
            this.value = this.get(); //非計(jì)算屬性是通過調(diào)用getter方法收集依賴。
        }
    }
    
    parsePath: function(){
        // 簡單的路徑解析,如果都是字符串則不需要解析
         if (/[^w.$]/.test(path)) {
            return
          }
        // 這邊只是簡單解析了子屬性的情況
          const segments = path.split(".")
          return function (obj) {
            for (let i = 0; i < segments.length; i++) {
              if (!obj) return
              obj = obj[segments[i]]
            }
            return obj
          }
    }
總結(jié)

我們在watcher乞丐版的基礎(chǔ)上,根據(jù)實(shí)際需求推導(dǎo)出了更健全的watcher版本。下面是完整代碼

class Watcher {
    constructors(component, getter, cb, options){
         this.cb = cb 
        this.getter = getter;
        this.id = UUID() 
        this.sync = options.sync; 
        this.vm = component; 
        if(options){
            this.computed = options.computed //由于是對計(jì)算屬性特殊處理,那肯定要給個(gè)標(biāo)識(shí)符以便判斷
        }
        if(typeof expOrFn === "function"){
            // render or computed
            this.getter = expOrFn 
        }else{
            this.getter = this.parsePath();
        }
        this.dirty = this.computed // for computed watchers
        if(this.computed){
            // 對于計(jì)算屬性computed而言,我們需要關(guān)心preValue嗎?   *********************
            this.value = undefined
            // 如果是計(jì)算屬性,就要收集依賴
            //同時(shí)根據(jù)按需加載的原則,這邊不會(huì)手機(jī)依賴,主動(dòng)執(zhí)行回調(diào)函數(shù)。
            this.dep = new Dep() 
        }else{
            this.value = this.get(); //非計(jì)算屬性是通過調(diào)用getter方法收集依賴。
        }
    }
    
    update(){
        if (this.lazy) {
          this.dirty = true
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
    }
    
    run: function(){
         //拿到新值
        const value = this.get()
        if (value !== this.value || //基本類型的值直接比較
          // 對象沒辦法直接比較,因此都進(jìn)行計(jì)算
          isObject(value)) {
          // set new value
          const oldValue = this.value
          this.value = value
          this.dirty = false
          cb.call(this.vm, value, oldValue)
        }
    }

    
    // 新增depend方法,收集計(jì)算屬性的依賴
    depend () {
        if (this.dep && Dep.target) {
          this.dep.depend()
        }
      }
}
  
  //不要忘了還要返回當(dāng)前computed的最新的值
  //由于可能不是立即更新的,因此根據(jù)dirty再判斷一下,如果數(shù)據(jù)臟了,調(diào)用get再獲取一下
  evaluate () {
    if (this.dirty) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

可以看到,基本vue的實(shí)現(xiàn)一樣了。VUE中有些代碼,比如teardown方法,清除自身的訂閱信息我并沒有加進(jìn)來,因?yàn)闆]有想到合適的應(yīng)用場景。
這種逆推的過程我覺得比直接讀源碼更有意思。直接讀源碼并不難,但很容易造成似是而非的情況。邏輯很容易理解,但是真正為什么這么寫,一些細(xì)節(jié)原因很容易漏掉。但是不管什么框架都是為了解決實(shí)際問題的,從需求出發(fā),才能更好的學(xué)習(xí)一個(gè)框架,并在自己的工作中加以借鑒。
借VUE的生命周期圖進(jìn)行展示

局部圖:

從局部圖里可以看出,vue收集依賴的入口只有兩個(gè),一個(gè)是在加載之前處理$wacth方法,一個(gè)是render生成虛擬dom。
而對于computed,只有在使用到時(shí)才會(huì)收集依賴。如果我們在watch和render中都沒有使用,而是在methods中使用,那么加載的過程中是不會(huì)計(jì)算這個(gè)computed的,只有在調(diào)用methods中方法時(shí)才會(huì)計(jì)算。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/97953.html

相關(guān)文章

  • Vue原理】NextTick - 源碼 服務(wù)Vue

    寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【Vue原理】NextTick - 源碼版 之 服務(wù)Vue 初次看的兄弟可以先看 【Vue原理】NextTick - 白話版 簡單了解下...

    Acceml 評論0 收藏0
  • vue源碼分析系列響應(yīng)式數(shù)據(jù)(四)

    摘要:執(zhí)行當(dāng)時(shí)傳入的回調(diào),并將新值與舊值一并傳入。文章鏈接源碼分析系列源碼分析系列之環(huán)境搭建源碼分析系列之入口文件分析源碼分析系列之響應(yīng)式數(shù)據(jù)一源碼分析系列之響應(yīng)式數(shù)據(jù)二源碼分析系列之響應(yīng)式數(shù)據(jù)三 前言 上一節(jié)著重講述了initComputed中的代碼,以及數(shù)據(jù)是如何從computed中到視圖層的,以及data修改后如何作用于computed。這一節(jié)主要記錄initWatcher中的內(nèi)容。 ...

    GHOST_349178 評論0 收藏0
  • Vue原理】依賴更新 - 源碼

    摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理依賴更新源碼版如果對依賴收集完 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...

    moven_j 評論0 收藏0
  • Vue原理】依賴收集 - 源碼基本數(shù)據(jù)類型

    摘要:當(dāng)東西發(fā)售時(shí),就會(huì)打你的電話通知你,讓你來領(lǐng)取完成更新。其中涉及的幾個(gè)步驟,按上面的例子來轉(zhuǎn)化一下你買東西,就是你要使用數(shù)據(jù)你把電話給老板,電話就是你的,用于通知老板記下電話在電話本,就是把保存在中。剩下的步驟屬于依賴更新 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【...

    VincentFF 評論0 收藏0
  • Vue源碼分析Observer

    摘要:中的觀察者模式觀察者模式一般包含發(fā)布者和訂閱者兩種角色顧名思義發(fā)布者負(fù)責(zé)發(fā)布消息,訂閱者通過訂閱消息響應(yīng)動(dòng)作了。中主要有兩種類型的,一種是另外一種是是通過或者中的屬性定義的。結(jié)束好了,基本結(jié)束,如有錯(cuò)漏,望指正。 碎碎念 四月份真是慵懶無比的一個(gè)月份,看著手頭上沒啥事干,只好翻翻代碼啥的,看了一會(huì)Vue的源碼,忽而有點(diǎn)感悟,于是便記錄一下。 Vue中的觀察者模式 觀察者模式一般包含發(fā)布...

    CoderBear 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<