国产精品美女久久久浪潮AV,国产精品三级一二三区,久久精品国产一区二区小说 ,依依成人影视国产精品,全部无卡免费的毛片在线看,日本一区二区三深夜不卡,国产精品女同一区二区久久,国产精品夜色一区二区三区

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

        2019-6-6    seo達人

        如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點這里

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

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

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

        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 對應(yīng)的 val 是 object, 則重新迭代該 val, 使之 reactive 
          if (isObject(val)) {
            const childObj = val;
            new Observer(childObj);
          }
          // 如果某個 key 對應(yīng)的 val 不是 Object, 而是基礎(chǔ)類型,我們則對這個 key 進行 defineProperty 定義 
          Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: () => {
              console.info(`get ${key}-${val}`)
              return val;
            },
            set: (newVal) => {
              // 如果 newVal 和 val 相等,則不做任何操作(不執(zhí)行渲染邏輯)
              if (newVal === val) {
                return;
              }
              // 如果 newVal 和 val 不相等,且因為 newVal 為 Object, 所以先用 Observer迭代 newVal, 使之 reactive, 再用 newVal 替換掉 val, 再執(zhí)行對應(yīng)操作(渲染邏輯)
              else if (isObject(newVal)) {
                console.info(`set ${key} - ${val} - ${newVal} - newVal is Object`);
                new Observer(newVal);
                val = newVal;
              }
              // 如果 newVal 和 val 不相等,且因為 newVal 為基礎(chǔ)類型, 所以用 newVal 替換掉 val, 再執(zhí)行對應(yīng)操作(渲染邏輯)
              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 轉(zhuǎn)化成 reactive object

        2. 如何實現(xiàn)一個 watcher
        前面講的是如何將 plain object 轉(zhuǎn)換成 reactive object. 接下來講一下,如何實現(xiàn)一個watcher.

        實現(xiàn)的偽代碼應(yīng)如下:

        偽代碼

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

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

        Vue.js

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

        export default class Vue {
          constructor(options) {
            // 在 this 上掛載一個公有變量 $options ,用來暫存所有參數(shù)
            this.$options = options
            // 聲明一個私有變量 _data ,用來暫存 data
            let data = this._data = this.$options.data
            // 在 this 上掛載所有 data 里的 key 值, 這些 key 值對應(yīng)的 get/set 都被代理到 this._data 上對應(yīng)的同名 key 值
            Object.keys(data).forEach(key => this._proxy(key));
            // 將 this._data 進行 reactive 化
            new Observer(data, this)
          }
          // 對外暴露 $watch 的公有方法,可以對某個 this._data 里的 key 值創(chuàng)建一個 watcher 實例
          $watch(expOrFn, cb) {
            // 注意,每一個 watcher 的實例化都依賴于 Vue 的實例化對象, 即 this
            new Watcher(this, expOrFn, cb)
          }
          //  將 this.keyName 的某個 key 值的 get/set 代理到  this._data.keyName 的具體實現(xiàn)
          _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] 對應(yīng)的 val
            this.value = this.get();
          }
          // 用于獲取當(dāng)前 vm._data 對應(yīng)的 key = expOrFn 對應(yīng)的 val 值
          get() {
            Dep.target = this;
            const value = this.vm._data[this.expOrFn];
            Dep.target = null;
            return value;
          }
          // 每次 vm._data 里對應(yīng)的 expOrFn, 即 key 的 setter 被觸發(fā),都會調(diào)用 watcher 里對應(yīng)的 update方法
          update() {
            this.run();
          }
          run() {
            // 這個 value 是 key 被 setter 調(diào)用之后的 newVal, 然后比較 this.value 和 newVal, 如果不相等,則替換 this.value 為 newVal, 并執(zhí)行傳入的cb.
            const value = this.get();
            if (value !== this.value) {
              this.value = value;
              this.cb.call(this.vm);
            }
          }
        }

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

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

        首先, 我們要來理解一下 watcher 實例的概念。實際上 Vue 的 v-model, v-bind , {{ mustache }}, computed, watcher 等等本質(zhì)上是分別對 data 里的某個 key 節(jié)點聲明了一個 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 實例來監(jiān)聽abc, 1個 watcher 實例來監(jiān)聽 cbd. 如果 abc 的值被更改,那么 4 個 abc - watcher 的實例會執(zhí)行自身對應(yīng)的特定回調(diào)(比如重新渲染dom,或者是打印信息等等)

        不過,Vue 是如何知道,某個 key 對應(yīng)了多少個 watcher, 而 key 對應(yīng)的 value 發(fā)生變化后,又是如何通知到這些 watcher 來執(zhí)行對應(yīng)的不同的回調(diào)的呢?

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

        在 Observer階段,會為每個 key 都創(chuàng)建一個 dep 實例。并且,如果該 key 被某個 watcher 實例 get, 把該 watcher 實例加入 dep 實例的隊列里。如果該 key 被 set, 則通知該 key 對應(yīng)的 dep 實例, 然后 dep 實例會將依次通知隊列里的 watcher 實例, 讓它們?nèi)?zhí)行自身的回調(diào)方法

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

        watcher 實例用來監(jiān)聽某個 key ,如果該 key 產(chǎn)生變化,便會執(zhí)行 watcher 實例自身的回調(diào) 


        相關(guān)代碼如下:

        Dep.js

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

        // Dep 類的的某個靜態(tài)屬性,用于指向某個特定的 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 對應(yīng)的 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 實例發(fā)送通知
            dep.notify()
            }
            else if (!isObject(newVal)) {
              val = newVal;
            // 通知 dep 實例, 該 key 被 set,讓 dep 實例向所有收集到的該 key 的 watcher 發(fā)送通知
            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類的靜態(tài)屬性 Dep.target 指向這個 watcher 實例
            Dep.target = this;
            // 在這一步 this.vm._data[this.expOrFn] 調(diào)用了 data 里某個 key 的 getter, 然后 getter 判斷類的靜態(tài)屬性 Dep.target 不為null, 而為 watcher 的實例, 從而把這個 watcher 實例添加到 這個 key 對應(yīng)的 dep 實例里。 巧妙!
            const value = this.vm._data[this.expOrFn];
            // 重置類屬性 Dep.target 
            Dep.target = null;
            return value;
          }

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

        總結(jié):
        至此, Watcher, Observer , Dep 的關(guān)系全都梳理完成。而這些也是 Vue 實現(xiàn)的核心邏輯之一。再來簡單總結(jié)一下三者的關(guān)系,其實是一個簡單的 觀察-訂閱 的設(shè)計模式, 簡單來說就是, 觀察者觀察數(shù)據(jù)狀態(tài)變化, 一旦數(shù)據(jù)發(fā)生變化,則會通知對應(yīng)的訂閱者,讓訂閱者執(zhí)行對應(yīng)的業(yè)務(wù)邏輯 。我們熟知的事件機制,就是一種典型的觀察-訂閱的模式

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

        日歷

        鏈接

        個人資料

        藍藍設(shè)計的小編 http://www.shtzxx.cn

        存檔

        主站蜘蛛池模板: 又湿又紧又大又爽a视频| 广东省| 国产精品毛片a∨一区二区三区| 亚洲一区二区三区成人网站| 99久久人妻无码精品系列蜜桃| 五月丁香啪啪| 被夫の上司に犯中文字幕| 97se亚洲综合自在线| 漂亮人妻被修理工侵犯| 亚洲av本道一区二区三区四区| 好姑娘高清影视在线观看| 果冻传媒2021精品入口| 体育| 国产亚洲一区二区手机在线观看| 三级 亚洲 欧美| 最近中文字幕高清| 在线观看肉片av网站免费 | 久久久亚洲欧洲日产国码aⅴ| 天下第一社区在线观看| 女性高爱潮视频| 无码aⅴ精品一区二区三区浪潮| 久久亚洲精品中文字幕无男同| 国产成人无码精品午夜福利a| 亚洲精品有码在线观看| gogogo电影免费看| 最新国产精品拍自在线观看| 国产无套乱子伦精彩是白视频| 务川| 国产偷国产偷亚洲高清人| 午夜福利资源片在线| 镇江市| 久久久精品人妻久久影视| 果冻传媒在线| 美女黄频视频大全免费的国内| 免费视频网站在线看视频| japanesehd无码专区| tobu8日本高清在线观看| 武山县| 久久综合激激的五月天| 国产成人精品亚洲日本语言| 遂溪县|