<thead id="kdfuf"><font id="kdfuf"></font></thead>
<samp id="kdfuf"></samp>
    <nav id="kdfuf"><strong id="kdfuf"></strong></nav>
      中文字幕无码一区二区三区在线,久久精品人人做人人爽,国产一级内射无挡观看,十八禁在线黄色网站,日韩欧美国产另类久久久精品 ,少妇人妻偷人精品一区二区,久久午夜视频,亚洲春色AⅤ无码专区

      Vue的響應式原理(MVVM)深入解析

      2019-6-6    seo達人

      如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

      1. 如何實現一個響應式對象
      最近在看 Vue 的源碼,其中最核心基礎的一塊就是 Observer/Watcher/Dep, 簡而言之就是,Vue 是如何攔截數據的讀寫, 如果實現對應的監聽,并且特定的監聽執行特定的回調或者渲染邏輯的。總的可以拆成三大塊來說。這一塊,主要說的是 Vue 是如何將一個 plain object 給處理成 reactive object 的,也就是,Vue 是如何攔截攔截對象的 get/set 的

      我們知道,用 Object.defineProperty 攔截數據的 get/set 是 vue 的核心邏輯之一。這里我們先考慮一個最簡單的情況 一個 plain obj 的數據,經過你的程序之后,使得這個 obj 變成 Reactive Obj (不考慮數組等因素,只考慮最簡單的基礎數據類型,和對象):

      如果這個 obj 的某個 key 被 get, 則打印出 get ${key} - ${val} 的信息 
      如果這個 obj 的某個 key 被 set, 如果監測到這個 key 對應的 value 發生了變化,則打印出 set ${key} - ${val} - ${newVal} 的信息。 
      對應的簡要代碼如下:

      Observer.js

      export class Observer {
        constructor(obj) {
          this.obj = obj;
          this.transform(obj);
        }
        // 將 obj 里的所有層級的 key 都用 defineProperty 重新定義一遍, 使之 reactive 
        transform(obj) {
          const _this = this;
          for (let key in obj) {
            const value = obj[key];
            makeItReactive(obj, key, value);
          }
        }
      }
      function makeItReactive(obj, key, val) {
        // 如果某個 key 對應的 val 是 object, 則重新迭代該 val, 使之 reactive 
        if (isObject(val)) {
          const childObj = val;
          new Observer(childObj);
        }
        // 如果某個 key 對應的 val 不是 Object, 而是基礎類型,我們則對這個 key 進行 defineProperty 定義 
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: () => {
            console.info(`get ${key}-${val}`)
            return val;
          },
          set: (newVal) => {
            // 如果 newVal 和 val 相等,則不做任何操作(不執行渲染邏輯)
            if (newVal === val) {
              return;
            }
            // 如果 newVal 和 val 不相等,且因為 newVal 為 Object, 所以先用 Observer迭代 newVal, 使之 reactive, 再用 newVal 替換掉 val, 再執行對應操作(渲染邏輯)
            else if (isObject(newVal)) {
              console.info(`set ${key} - ${val} - ${newVal} - newVal is Object`);
              new Observer(newVal);
              val = newVal;
            }
            // 如果 newVal 和 val 不相等,且因為 newVal 為基礎類型, 所以用 newVal 替換掉 val, 再執行對應操作(渲染邏輯)
            else if (!isObject(newVal)) {
              console.info(`set ${key} - ${val} - ${newVal} - newVal is Basic Value`);
              val = newVal;
            }
          }
        })
      }

      function isObject(data) {
        if (typeof data === 'object' && data != 'null') {
          return true;
        }
        return false;
      }

      index.js

      import { Observer } from './source/Observer.js';
      // 聲明一個 obj,為 plain Object
      const obj = {
        a: {
          aa: 1
        },
        b: 2,
      }
      // 將 obj 整體 reactive 化
      new Observer(obj);
      // 無輸出
      obj.b = 2;
      // set b - 2 - 3 - newVal is Basic Value
      obj.b = 3;
      // set b - 3 - [object Object] - newVal is Object
      obj.b = {
        bb: 4
      }
      // get b-[object Object]
      obj.b;
      // get a-[object Object]
      obj.a;
      // get aa-1
      obj.a.aa
      // set aa - 1 - 3 - newVal is Basic Value
      obj.a.aa = 3

      這樣,我們就完成了 Vue 的第一個核心邏輯, 成功把一個任意層級的 plain object 轉化成 reactive object

      2. 如何實現一個 watcher
      前面講的是如何將 plain object 轉換成 reactive object. 接下來講一下,如何實現一個watcher.

      實現的偽代碼應如下:

      偽代碼

      // 傳入 data 參數新建新建一個 vue 對象
      const v = new Vue({
          data: {
              a:1,
              b:2,
          }
      });
      // watch data 里面某個 a 節點的變動了,如果變動,則執行 cb
      v.$watch('a',function(){
          console.info('the value of a has been changed !');
      });

      //  watch data 里面某個 b 節點的變動了,如果變動,則執行 cb
      v.$watch('b',function(){
          console.info('the value of b has been changed !');
      })

      Vue.js

      // 引入將上面中實現的 Observer
      import { Observer } from './Observer.js';
      import { Watcher } from './Watcher.js';

      export default class Vue {
        constructor(options) {
          // 在 this 上掛載一個公有變量 $options ,用來暫存所有參數
          this.$options = options
          // 聲明一個私有變量 _data ,用來暫存 data
          let data = this._data = this.$options.data
          // 在 this 上掛載所有 data 里的 key 值, 這些 key 值對應的 get/set 都被代理到 this._data 上對應的同名 key 值
          Object.keys(data).forEach(key => this._proxy(key));
          // 將 this._data 進行 reactive 化
          new Observer(data, this)
        }
        // 對外暴露 $watch 的公有方法,可以對某個 this._data 里的 key 值創建一個 watcher 實例
        $watch(expOrFn, cb) {
          // 注意,每一個 watcher 的實例化都依賴于 Vue 的實例化對象, 即 this
          new Watcher(this, expOrFn, cb)
        }
        //  將 this.keyName 的某個 key 值的 get/set 代理到  this._data.keyName 的具體實現
        _proxy(key) {
          var self = this
          Object.defineProperty(self, key, {
            configurable: true,
            enumerable: true,
            get: function proxyGetter() {
              return self._data[key]
            },
            set: function proxySetter(val) {
              self._data[key] = val
            }
          })
        }
      }

      Watch.js

      // 引入Dep.js, 是什么我們待會再說
      import { Dep } from './Dep.js';

      export class Watcher {
        constructor(vm, expOrFn, cb) {
          this.cb = cb;
          this.vm = vm;
          this.expOrFn = expOrFn;
          // 初始化 watcher 時, vm._data[this.expOrFn] 對應的 val
          this.value = this.get();
        }
        // 用于獲取當前 vm._data 對應的 key = expOrFn 對應的 val 值
        get() {
          Dep.target = this;
          const value = this.vm._data[this.expOrFn];
          Dep.target = null;
          return value;
        }
        // 每次 vm._data 里對應的 expOrFn, 即 key 的 setter 被觸發,都會調用 watcher 里對應的 update方法
        update() {
          this.run();
        }
        run() {
          // 這個 value 是 key 被 setter 調用之后的 newVal, 然后比較 this.value 和 newVal, 如果不相等,則替換 this.value 為 newVal, 并執行傳入的cb.
          const value = this.get();
          if (value !== this.value) {
            this.value = value;
            this.cb.call(this.vm);
          }
        }
      }

      對于什么是 Dep, 和 Watcher 里的 update() 方法到底是在哪個時候被誰調用的,后面會說

      3. 如何收集 watcher 的依賴
      前面我們講了 watcher 的大致實現,以及 Vue 代理 data 到 this 上的原理。現在我們就來梳理一下,Observer/Watcher 之間的關系,來說明它們是如何調用的.

      首先, 我們要來理解一下 watcher 實例的概念。實際上 Vue 的 v-model, v-bind , {{ mustache }}, computed, watcher 等等本質上是分別對 data 里的某個 key 節點聲明了一個 watcher 實例.

      <input v-model="abc">
      <span>{{ abc }}</span>
      <p :data-key="abc"></p>
      ...

      const v = new Vue({
          data:{
              abc: 111,
          }
          computed:{
              cbd:function(){
                  return `${this.abc} after computed`;
              }
          watch:{
              abc:function(val){
                  console.info(`${val} after watch`)
              }
           }  
          }
      })

      這里,Vue 一共聲明了 4 個 watcher 實例來監聽abc, 1個 watcher 實例來監聽 cbd. 如果 abc 的值被更改,那么 4 個 abc - watcher 的實例會執行自身對應的特定回調(比如重新渲染dom,或者是打印信息等等)

      不過,Vue 是如何知道,某個 key 對應了多少個 watcher, 而 key 對應的 value 發生變化后,又是如何通知到這些 watcher 來執行對應的不同的回調的呢?

      實際上更深層次的邏輯是:

      在 Observer階段,會為每個 key 都創建一個 dep 實例。并且,如果該 key 被某個 watcher 實例 get, 把該 watcher 實例加入 dep 實例的隊列里。如果該 key 被 set, 則通知該 key 對應的 dep 實例, 然后 dep 實例會將依次通知隊列里的 watcher 實例, 讓它們去執行自身的回調方法

      dep 實例是收集該 key 所有 watcher 實例的地方.

      watcher 實例用來監聽某個 key ,如果該 key 產生變化,便會執行 watcher 實例自身的回調 


      相關代碼如下:

      Dep.js

      export class Dep {
        constructor() {
          this.subs = [];
        }
        // 將 watcher 實例置入隊列
        addSub(sub) {
          this.subs.push(sub);
        }
        // 通知隊列里的所有 watcher 實例,告知該 key 的 對應的 val 被改變
        notify() {
          this.subs.forEach((sub, index, arr) => sub.update());
        }
      }

      // Dep 類的的某個靜態屬性,用于指向某個特定的 watcher 實例.
      Dep.target = null
      observer.js

      import {Dep} from './dep'
      function makeItReactive(obj, key, val) {
       var dep = new Dep()
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
          // 收集依賴! 如果該 key 被某個 watcher 實例依賴,則將該 watcher 實例置入該 key 對應的 dep 實例里
          if(Dep.target){
            dep.addSub(Dep.target)
          }
          return val
        },
        set: (newVal) => {
          if (newVal === val) {
            return;
          }
          else if (isObject(newVal)) {
            new Observer(newVal);
            val = newVal;
          // 通知 dep 實例, 該 key 被 set,讓 dep 實例向所有收集到的該 key 的 watcher 實例發送通知
          dep.notify()
          }
          else if (!isObject(newVal)) {
            val = newVal;
          // 通知 dep 實例, 該 key 被 set,讓 dep 實例向所有收集到的該 key 的 watcher 發送通知
          dep.notify()
          }
        }
      })
           }    

      watcher.js

      import { Dep } from './Dep.js';

      export class Watcher {
        constructor(vm, expOrFn, cb) {
          this.cb = cb;
          this.vm = vm;
          this.expOrFn = expOrFn;
          this.value = this.get();
        }
        get() {
          // 在實例化某個 watcher 的時候,會將Dep類的靜態屬性 Dep.target 指向這個 watcher 實例
          Dep.target = this;
          // 在這一步 this.vm._data[this.expOrFn] 調用了 data 里某個 key 的 getter, 然后 getter 判斷類的靜態屬性 Dep.target 不為null, 而為 watcher 的實例, 從而把這個 watcher 實例添加到 這個 key 對應的 dep 實例里。 巧妙!
          const value = this.vm._data[this.expOrFn];
          // 重置類屬性 Dep.target 
          Dep.target = null;
          return value;
        }

        // 如果 data 里的某個 key 的 setter 被調用,則 key 會通知到 該 key 對應的 dep 實例, 該Dep實例, 該 dep 實例會調用所有 依賴于該 key 的 watcher 實例的 update 方法。
        update() {
          this.run();
        }
        run() {
          const value = this.get();
          if (value !== this.value) {
          this.value = value;
          // 執行 cb 回調
          this.cb.call(this.vm);
          }
        }
      }

      總結:
      至此, Watcher, Observer , Dep 的關系全都梳理完成。而這些也是 Vue 實現的核心邏輯之一。再來簡單總結一下三者的關系,其實是一個簡單的 觀察-訂閱 的設計模式, 簡單來說就是, 觀察者觀察數據狀態變化, 一旦數據發生變化,則會通知對應的訂閱者,讓訂閱者執行對應的業務邏輯 。我們熟知的事件機制,就是一種典型的觀察-訂閱的模式

      Observer, 觀察者,用來觀察數據源變化. 
      Dep, 觀察者和訂閱者是典型的 一對多 的關系,所以這里設計了一個依賴中心,來管理某個觀察者和所有這個觀察者對應的訂閱者的關系, 消息調度和依賴管理都靠它。 
      Watcher, 訂閱者,當某個觀察者觀察到數據發生變化的時候,這個變化經過消息調度中心,最終會傳遞到所有該觀察者對應的訂閱者身上,然后這些訂閱者分別執行自身的業務回調即可 
      參考 
      Vue源碼解讀-滴滴FED 
      代碼參考
      藍藍設計www.tuitetiyu.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務

      日歷

      鏈接

      個人資料

      藍藍設計的小編 http://www.tuitetiyu.cn

      存檔

      主站蜘蛛池模板: 人摸人人人澡人人超碰小说| 日韩毛片无码永久免费看| 一色屋精品无码免费视频| 快速了解国产AV无码专区| 日韩内射美女人妻一区二区三区 | 亚洲一区在线观看| 革吉县| 一级毛片久久久久久久女人18| 国产精品女视频一区二区| 国产精品色拉拉| 无人视频免费观看免费视频| 亚洲中文字幕不卡一区| 日本中文字幕乱码在线高清 | 久9视频这里只有精品试看| 青青久在线视频免费观看| 少女大人免费观看高清电视剧韩剧| 日韩精品一区二区三区中文| 午夜级限制电影在线观看| 天堂va欧美ⅴa亚洲va在线| 久久久人人爱AV高潮喷水| 亚洲中文字幕无线无码| 久久久精品视频中文字幕| 久久久久国色av免费观看性色| 麻豆果冻传媒av精品一区| 色猫咪av在线观看| 日韩视频在线播放| igao激情视频| 天天碰免费上传视频| 九九无码人妻一区二区三区| 久久夜色精品国产网站| 99精品欧美一区二区精品久久| 国产大神高清视频在线观看| 影院久久久久亚洲精品男人的天堂| 精品少妇人妻av无码中文字幕| 久久久久久精品免费观看| 日本在线一区二区三区欧美| 亚洲一级精品无码AV色欲| 亚洲另类丝袜综合网| 亚洲高清国产拍精品网络战| 欧美最猛性开放2ovideos| 最近免费中文字幕大全免费版视频|