和尤雨溪一起進階vue

夏天來嘍發表於2019-03-23

上圖,無圖無真相

vue

花了400大洋買了frontedmasters的一個月的會員,就是為了看男神的這份vue教程,沒有中文字幕,痛苦地堅持啃完了,建議有錢並且英語好的人直接買會員去官網看視訊,尤大神人長得帥,業務水平高,英語還超蘇,對不起,忍不住犯了會兒花痴,如果沒錢英語又渣的人只能看我的總結了,看我的文章的好處就是不用花錢,不用被英語折磨,壞處就是本人的文字水平和業務水平真的有限,不會一步步引導式地分析原理和程式碼的實現,只能貼程式碼和盡我所能來解釋了,能理解多少就看大家的水平了,這算不算無良商家,不包售後啊,哈哈。

這個系列文章將從下面幾個方面來介紹vue

  1. reactivity(響應式)
  2. plugin(外掛)
  3. render(渲染函式)
  4. routing(路由)
  5. state-management(狀態管理)
  6. international(多語言支援)

1 reactivity

相信瞭解過vue的同學都知道,vue是MVVM框架,最大的特點就是雙向資料繫結,資料Model修改了之後檢視View會自動更新, view也可以通過v-model的將ViewModel的變化同步到Model裡面

這篇文章要探討的問題:vue是如何監控資料的變化從而自動更新檢視,也就是vue響應式是如何實現的,我們一步步來看。

1.1. getter and setter

考慮下面這段程式碼,一個簡單的代數計算關係,b依賴a, b是a的10倍

    var a= 10;
    var b = a*10;
    a = 20
    //b = ? 如何監聽b的變化

複製程式碼

請認真思考一下再往下看:當每次改變a的值的時候,如何監聽a的變化並且更新b,保證他們的數學關係b是a的10倍?









給大家留了10行空白,不知道大家有沒有認真思考上面的問題?

不賣關子了,直接用兩個數值,我們目前無法監控,可以換種思路,直接監控一個數值變數行不通,我們可以監控一個物件屬性的變化,在屬性改變的時候收集它的依賴,就可以觸發依賴的更新

js有一個方法Object.defineProperty可以劫持屬性的讀取器getter和setter, 來看個例子

  let obj = {
        a:10
    };
    observer(obj, 'a');
    obj.a = 20;
    let b = obj.a;
    obj.a = 30;
    // 實現一個函式,可以監控到物件屬性的變化
    function observer(obj, key) {
        Object.defineProperty(obj, key, {
            configurable: true, // 可以被delete
            enumerable: true, // for in迭代
            set(val) {
                console.log(`物件的屬性${key}被賦值了${val}`);
            },
            get() {
                console.log(`物件的屬性${key}被讀取了`);
            }
        });
    }
    //物件的屬性a被賦值了20
    //物件的屬性a被讀取了
    //物件的屬性a被賦值了30
複製程式碼

從上面的例子可以看出, 我們只要用 Object.defineProperty重寫物件的屬性讀寫器getter和setter就可以監聽到物件屬性的變化,上面的程式碼只可以監控一個屬性的變化,對於物件的所有屬性,需要遍歷轉換,改造一下observer方法(這裡只考慮一層物件,深層次的需要遞迴遍歷,陣列的監控也需要另寫方法,有興趣的去看vue的原始碼)

 function observer(obj) {
        Object.keys(obj).forEach(key => {
            let internalValue = obj[key]
            Object.defineProperty(obj, key, {
                get() {
                    console.log(`getting key "${key}": ${internalValue}`)
                    return internalValue
                },
                set(newVal) {
                    console.log(`setting key "${key}" to: ${internalValue}`)
                    internalValue = newVal
                }
            })
        })
    }
複製程式碼

現在我們已經可以監聽到一個物件所有屬性的變化了,最開始提出的問題已經解決了一半了,接下來要解決的問題是當屬性變化的時候,如何收集它的依賴,觸發依賴的更新呢?也就是我們這篇文章的主題,如何實現一個簡易的響應式系統。

思路就是:在讀取屬性的時候收集依賴,在改變屬性值的時候觸發依賴的更新

  1. 實現一個observer,劫持物件屬性的getter和setter
  2. 實現一個全域性的訂閱器Dep,可以追加訂閱者,和通知依賴更新
  3. 在讀取屬性的時候追加當前依賴到Dep中,在設定屬性的時候迴圈觸發依賴的更新

按照這個思路,實習一個簡易的響應式系統。

   // 全域性的依賴收集器Dep
   window.Dep = class Dep {
       constructor() {
           this.subscribers = new Set(); // 保證依賴不重複新增
       }
       // 追加訂閱者
       depend() {
           if(activeUpdate) { // activeUpdate註冊為訂閱者
               this.subscribers.add(activeUpdate)
           }

       }
       // 執行所有的訂閱者更新方法
       notify() {
           this.subscribers.forEach(sub => {
              sub();
          })
       }
   }
   let activeUpdate
   // js單執行緒語言,任一時刻只能有一個函式執行,也就是任一時刻,只可能有一個依賴在更新 //用一個全域性變數activeUpdate來標誌,這裡有點不好理解,大家多想想就會明白autorun的巧妙之處了
   // autorun接受一個更新函式
   function autorun(update) {
       function wrapperUpdate() {
           activeUpdate = wrapperUpdate
           update() // wrapperUpdate, 閉包
           activeUpdate = null;
       }
       wrapperUpdate();
   }
   function observer(obj) {
       Object.keys(obj).forEach(key => {
           var dep = new Dep(); // 為每個key建立訂閱器Dep
           let internalValue = obj[key]
           Object.defineProperty(obj, key, {
               get() {
                   // console.log(`getting key "${key}": ${internalValue}`)
                   // 將當前正在執行的更新函式追加進訂閱者列表
                   if(activeUpdate) { 
                       dep.depend() //收集依賴
                   }
                   return internalValue
               },
               set(newVal) {
               	//console.log(`setting key "${key}" to: ${internalValue}`)
               	// 加個if判斷,資料發生變化再觸發更新
               	if(internalValue !== newVal) {
               	       internalValue = newVal
                       dep.notify() // 觸發依賴的更新
               	}
               }
           })
       })
   }
   let state = {
       count:0
   }
   observer(state);
   autorun(() => {
       console.log('state.count發生變化了', state.count)
   })
   state.count = state.count + 5;
   // state.count發生變化了 0
   // state.count發生變化了 5
複製程式碼

這篇文章先更到這裡,後續內容有時間繼續更新,雖然更新時間不定,但是計劃月底更新完,希望喜歡的小夥伴多多關注和支援,謝謝。

相關文章