嫩草影院久久99_老司机午夜网站国内精品久久久久久久久_久久夜色精品国产_国产一级做a爰片久久毛片

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

2019-6-6    seo達(dá)人

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

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

我們知道,用 Object.defineProperty 攔截?cái)?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 進(jìn)行 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 不相等,且因?yàn)?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 不相等,且因?yàn)?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. 如何實(shí)現(xiàn)一個 watcher
前面講的是如何將 plain object 轉(zhuǎn)換成 reactive object. 接下來講一下,如何實(shí)現(xiàn)一個watcher.

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

偽代碼

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

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

Vue.js

// 引入將上面中實(shí)現(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 進(jìn)行 reactive 化
    new Observer(data, this)
  }
  // 對外暴露 $watch 的公有方法,可以對某個 this._data 里的 key 值創(chuàng)建一個 watcher 實(shí)例
  $watch(expOrFn, cb) {
    // 注意,每一個 watcher 的實(shí)例化都依賴于 Vue 的實(shí)例化對象, 即 this
    new Watcher(this, expOrFn, cb)
  }
  //  將 this.keyName 的某個 key 值的 get/set 代理到  this._data.keyName 的具體實(shí)現(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 時(shí), 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() 方法到底是在哪個時(shí)候被誰調(diào)用的,后面會說

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

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

<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 實(shí)例來監(jiān)聽abc, 1個 watcher 實(shí)例來監(jiān)聽 cbd. 如果 abc 的值被更改,那么 4 個 abc - watcher 的實(shí)例會執(zhí)行自身對應(yīng)的特定回調(diào)(比如重新渲染dom,或者是打印信息等等)

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

實(shí)際上更深層次的邏輯是:

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

dep 實(shí)例是收集該 key 所有 watcher 實(shí)例的地方.

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


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

Dep.js

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

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

  // 如果 data 里的某個 key 的 setter 被調(diào)用,則 key 會通知到 該 key 對應(yīng)的 dep 實(shí)例, 該Dep實(shí)例, 該 dep 實(shí)例會調(diào)用所有 依賴于該 key 的 watcher 實(shí)例的 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 實(shí)現(xiàn)的核心邏輯之一。再來簡單總結(jié)一下三者的關(guān)系,其實(shí)是一個簡單的 觀察-訂閱 的設(shè)計(jì)模式, 簡單來說就是, 觀察者觀察數(shù)據(jù)狀態(tài)變化, 一旦數(shù)據(jù)發(fā)生變化,則會通知對應(yīng)的訂閱者,讓訂閱者執(zhí)行對應(yīng)的業(yè)務(wù)邏輯 。我們熟知的事件機(jī)制,就是一種典型的觀察-訂閱的模式

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

日歷

鏈接

個人資料

存檔

嫩草影院久久99_老司机午夜网站国内精品久久久久久久久_久久夜色精品国产_国产一级做a爰片久久毛片
<em id="09ttv"></em>
    <sup id="09ttv"><pre id="09ttv"></pre></sup>
    <dd id="09ttv"></dd>

        • 伊人久久大香线蕉综合热线 | 欧美激情一区二区三区四区| 亚洲啪啪91| 亚洲一区免费网站| 欧美日本三区| 亚洲精品小视频| 欧美激情亚洲综合一区| 久久久久国产精品午夜一区| 国产热re99久久6国产精品| 亚洲午夜激情网站| 日韩视频在线免费观看| 欧美国产先锋| 99视频在线观看一区三区| 91久久精品国产91久久性色| 欧美黄色视屏| 一区电影在线观看| 亚洲精品系列| 欧美另类在线播放| 日韩午夜av电影| 亚洲区一区二区三区| 欧美全黄视频| 亚洲欧美日韩精品久久奇米色影视 | 亚洲视频在线免费观看| 欧美三级电影大全| 先锋资源久久| 欧美在线免费播放| 亚洲黄色性网站| 亚洲日韩视频| 国产精品区一区二区三| 欧美一区二区三区喷汁尤物| 久久精品国产77777蜜臀| 136国产福利精品导航网址应用 | 日韩视频免费| 国产精品自拍网站| 蜜臀a∨国产成人精品| 欧美mv日韩mv国产网站| 中文亚洲视频在线| 亚洲欧美日韩在线高清直播| 激情久久综艺| 亚洲精品中文字幕在线| 国产欧美日韩在线视频| 欧美韩日一区二区三区| 国产精品乱子久久久久| 欧美成人激情视频免费观看| 欧美视频中文字幕在线| 另类人畜视频在线| 欧美午夜视频| 欧美成人免费va影院高清| 欧美人与禽猛交乱配| 久久高清国产| 欧美精品v日韩精品v韩国精品v| 亚洲一区二区免费在线| 久久久www成人免费无遮挡大片| 亚洲乱码国产乱码精品精天堂| 亚洲性色视频| 亚洲日本电影| 欧美亚洲综合另类| 99一区二区| 亚洲国语精品自产拍在线观看| 欧美性猛交视频| 欧美高清视频一区二区三区在线观看| 欧美午夜一区二区福利视频| 欧美成人福利视频| 国产亚洲一区二区三区在线播放 | 一区二区三区免费网站| 狠狠色狠狠色综合人人| 一区二区91| 亚洲伦伦在线| 久久免费午夜影院| 欧美一区二区三区四区高清| 欧美高清不卡在线| 美女视频一区免费观看| 国产日韩在线看片| 亚洲午夜一区| 亚洲一区二区黄| 欧美精品一卡| 亚洲激情不卡| 亚洲欧洲一区二区三区在线观看 | 国产精品主播| 一区二区三区.www| 亚洲最快最全在线视频| 欧美1区免费| 欧美大色视频| 亚洲福利专区| 玖玖玖国产精品| 欧美www视频| 亚洲二区免费| 蜜桃久久av一区| 欧美大色视频| 亚洲三级观看| 欧美精品三级日韩久久| 亚洲国产精品一区制服丝袜 | 亚洲视频电影图片偷拍一区| 一本久道久久综合中文字幕| 欧美激情视频网站| 亚洲精选在线观看| 亚洲视频中文| 国产精品亚洲аv天堂网| 亚洲尤物在线视频观看| 欧美在线三级| 激情综合久久| 久久一区免费| 亚洲国产精品一区在线观看不卡| 日韩视频精品| 国产精品a久久久久| 亚洲欧美在线一区| 久久夜精品va视频免费观看| 尤物精品在线| 亚洲黄色成人| 99视频在线精品国自产拍免费观看| 欧美成人午夜激情| 亚洲精品系列| 欧美一站二站| 在线欧美一区| 欧美日韩中文字幕精品| 亚洲性图久久| 免费一级欧美片在线播放| 最新亚洲激情| 国产精品久久久久久久久| 欧美一区二区网站| 免费国产一区二区| 一区二区三区 在线观看视频| 国产精品你懂的在线| 久久www免费人成看片高清| 亚洲人成7777| 欧美天堂亚洲电影院在线观看 | 欧美婷婷久久| 久久国产精品网站| 亚洲国产日韩欧美综合久久 | 亚洲一区二区免费看| 久久综合九色欧美综合狠狠| 99热精品在线观看| 国产一区二区三区四区| 欧美成人自拍视频| 亚洲欧美乱综合| 亚洲成人在线视频播放| 亚洲欧美另类在线观看| 亚洲国产精品成人综合色在线婷婷| 欧美先锋影音| 女人天堂亚洲aⅴ在线观看| 亚洲图片欧美午夜| 亚洲福利av| 久久久精品五月天| 亚洲性图久久| 亚洲国产1区| 国产一区二区成人| 欧美色另类天堂2015| 久久综合九色九九| 西瓜成人精品人成网站| 亚洲精品久久久久久下一站| 久久久青草青青国产亚洲免观| 亚洲一区二区在线播放| 亚洲精品视频一区| 精品999网站| 国产一区99| 国产精品自拍视频| 艳妇臀荡乳欲伦亚洲一区| 另类天堂av| 久久久www免费人成黑人精品| 亚洲欧美国内爽妇网| 一区二区三区精品在线| 亚洲精品中文字幕有码专区| 在线看日韩欧美| 国产一区二区丝袜高跟鞋图片 | 欧美在线视频日韩| 亚洲欧美日韩第一区| 亚洲在线观看免费视频| 一区二区三区视频观看| 亚洲精品在线一区二区| 亚洲欧洲精品一区二区三区波多野1战4 | 亚洲欧洲一二三| 亚洲国产经典视频| 欧美高清视频一区二区| 欧美肥婆在线| 亚洲大胆av| 亚洲黄色一区| 亚洲伦理在线观看| 日韩视频在线观看免费| 亚洲精品美女久久久久| 亚洲精品小视频在线观看| 亚洲美女福利视频网站| 99亚洲精品| 亚洲影音一区| 香蕉成人久久| 久久精品亚洲一区| 久久天堂精品| 欧美韩日一区| 欧美视频中文字幕| 国产精品社区| 国语自产精品视频在线看一大j8| 狠狠色2019综合网| 欧美日韩色一区| 国产精品久久久久久久午夜片| 国产嫩草一区二区三区在线观看 | 欧美日韩国产精品一卡| 欧美三级乱人伦电影| 国产精品久久77777| 国产亚洲视频在线观看| 在线观看国产精品淫| 久久久久国产精品麻豆ai换脸|