<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實現(xiàn) Vue 的響應式系統(tǒng)

          共 4545字,需瀏覽 10分鐘

           ·

          2020-11-29 01:07

          作者:小豪看世界

          來源:SegmentFault 思否




          前言


          Vue 最獨特的特性之一,是其非侵入性的響應式系統(tǒng)。比如我們修改了數(shù)據(jù),那么依賴這些數(shù)據(jù)的視圖都會進行更新,大大提高了我們的"搬磚"效率,回想一下初學 JS 的時候海量的 Dom操作~.~......,Vue 通過數(shù)據(jù)驅(qū)動視圖,極大的將我們從繁瑣的DOM操作中解放出來。


          如下圖,我們改變了 msg 的值,視圖也響應式的進行了更新






          Vue 響應式原理


          我們先看 vue 官網(wǎng)的圖,其實不太清晰,我初看的時候也是一臉懵逼的~.~:


          再看下面這張圖,響應式原理涵蓋在里面了(圖片來源于網(wǎng)絡):



          梳理一下流程:


          • Vue 初始化 => 劫持 data 設(shè)置 get、set (攔截數(shù)據(jù)讀寫)
          • Compile 解析模板 => 生成 watcher => 讀取 data,觸發(fā) get 方法 => Dep 收集依賴(watcher)
          • 數(shù)據(jù)變化 => 觸發(fā) set方法 => 通知 Dep 中的所有 watcher => 視圖更新


          對于 Observer、Dep 和 Watcher 這三大金剛 ,我初學的時候也是傻傻的分不清楚很懵,我的理解是:


          Dep(dependence)?即依賴收集器,收集 Watcher 即觀察者。


          Watcher 即觀察者,觀察數(shù)據(jù),數(shù)據(jù)變化時更新對應的視圖(dom)。


          Observer 即劫持者,通過 Object.defineProperty()?給數(shù)據(jù)設(shè)置 get 和 set 方法:


          • get: 當某個地方用到數(shù)據(jù)時,如下 h1、h2 標簽都用到了 msg 數(shù)據(jù),即觀察 msg 數(shù)據(jù) 的兩個 watcher 將被放入 msg 數(shù)據(jù)的依賴收集器 Dep 中。


          data() {  return {    msg: 'hello vue',  }},

          {{msg}}

          {{msg}}


          • set:當 msg 數(shù)據(jù)改變的時候,遍歷 Dep 依賴收集器,通知所有 Watcher 更新視圖,即更新 h1、h2 標簽內(nèi)的文本內(nèi)容




          實現(xiàn) Vue 的響應式系統(tǒng)


          通過上面分析,可知每一個數(shù)據(jù)有一個依賴收集器 Dep,Dep 里面存放用到該數(shù)據(jù)的 Watcher,如下圖所示(圖片來源于網(wǎng)絡):


          1. Dep


          我們先實現(xiàn) Dep,Dep 我們可以用數(shù)組來模擬,它應該有兩個方法:

          • add,收集 Watcher
          • notify,數(shù)據(jù)變化的時候通知 Watcher 更新視圖


          # 依賴收集器class Dep {  constructor() {    this.subs = [];  }  addSub(watcher) {    # 添加觀察者    this.subs.push(watcher);  }  notify() {    # 通知每一個觀察者更新視圖    this.subs.forEach(watcher => watcher.update());  }}


          2. Watcher


          Watcher 實現(xiàn)如下,其中 cb 是更新視圖的方法,關(guān)鍵點在于 oldVal,它有兩個用處:


          • Dep 觸發(fā) update 方法時,比對新舊值,若有變化才更新,避免不必要的視圖更新
          • 初始化的時候,會獲取舊值,會觸發(fā)數(shù)據(jù)的 get 方法,在此時可以把依賴注入到 Dep 中(即依賴收集)


          # 觀察者,用于更新視圖class Watcher {  constructor(vm, expr, cb) {    this.vm = vm;    this.expr = expr;    # 視圖更新函數(shù)    this.cb = cb;    # 舊值    this.oldVal = this.getOldVal();  }  getOldVal() {    # 傳遞watch自己    Dep.target = this;    # 獲取值的時候會觸發(fā) get 方法,把自己 push 進 deps[] 里    const oldVal = compileUtils.getVal(this.expr, this.vm);    Dep.target = null;    return oldVal;  }  update() {    # 獲取新值    const newVal = compileUtils.getVal(this.expr, this.vm);    if (newVal !== this.oldVal) {      this.cb(newVal);    }  }}


          Dep.target = this?的用處是相當于設(shè)置了一個全局變量讓 Dep 能收集到 watcher 自己,后面?Dep.target = null?用處是銷毀全局變量


          3. Observer


          Observer 實現(xiàn)如下,通過 Object.defineProperty 攔截數(shù)據(jù)的讀寫操作:


          • get 收集依賴,注意判斷 Dep.target 是否有值,因為模板解析的時候也會讀取數(shù)據(jù)觸發(fā) get 方法
          • set 通知依賴收集器,更新視圖


          // 數(shù)據(jù)劫持class Observer {  constructor(data) {    this.observer(data, key, data[key]);  }  observer(obj, key, value) {    const dep = new Dep();    Object.defineProperty(obj, key, {      enumerable: true,      configurable: false,      get() {        # 防止視圖初始化的時候也被收集到Dep中        Dep.target && dep.addSub(Dep.target);        return value;      },      set: newVal => {        this.observer(newVal);        if (newVal !== value) {          value = newVal;          # 通知依賴收集器,有變化          dep.notify();        }      },    });  }}


          4. Compile


          到這里我們已經(jīng)實現(xiàn)了 Observer、Dep 和 Watcher,實現(xiàn)了數(shù)據(jù)的響應式追蹤,可是還有一個點沒打通,那就是依賴收集,那么依賴什么時候收集呢?換言之我們怎么知道哪些數(shù)據(jù)依賴了哪些視圖呢?


          在 Vue 解析模板的時候,實際上我們已經(jīng)知道了哪些 Dom 依賴了哪些數(shù)據(jù),所以是在 compile 的時候完成了模板解析并完成了依賴收集。


          Compile 實現(xiàn)如下,省略大部分 dom 操作相關(guān)代碼,可以用 DocumentFragment 文檔碎片提升性能,邏輯比較簡單,我們在 dom 解析數(shù)據(jù)的時候生成了對應的 watcher,并完成了依賴收集:


          # 編譯類,輸出真實Domclass Compile {  constructor(el, vm) {    this.el = this.isElementNode(el) ? el : document.querySelector(el);    this.vm = vm;    # 獲取文檔對象    const fragment = this.nodeFragment(this.el);    # 編譯    this.compile(fragment);    # 掛載回app    this.el.appendChild(fragment);  }  # 是否元素節(jié)點  isElementNode(node) {    return node.nodeType === 1;  }  # 獲取文檔碎片  nodeFragment(el) {    # do something  }  compile(fragment) {    const childNodes = fragment.childNodes;    [...childNodes].forEach(node => {      if (this.isElementNode(node)) {        # 元素節(jié)點        # do something      } else {        # 文本節(jié)點        # do something      }    })  }}
          # 根據(jù)不同指令 執(zhí)行不同的編譯操作const compileUtils = { # v-text text(node, expr, vm) { const value = vm.$data[expr]; # 創(chuàng)建觀察者 完成依賴收集 new Watcher(vm, expr, newVal => { node.textContent = value; }); node.textContent = value; },};


          至此一個響應式的系統(tǒng)就已經(jīng)完了




          雙向數(shù)據(jù)綁定


          什么是雙向數(shù)據(jù)綁定


          上面我們實現(xiàn)了響應式的系統(tǒng),但只是單向的,即數(shù)據(jù)驅(qū)動視圖,什么是雙向數(shù)據(jù)綁定呢?如下圖:



          我們常見的 v-model, 就是雙向數(shù)據(jù)綁定,其實它是一個語法糖:



          等價于 =>




          實現(xiàn)


          雙向數(shù)據(jù)綁定即:


          • 數(shù)據(jù)改變 => 視圖更新
          • 視圖改變 => 數(shù)據(jù)改變 => 視圖更新

          比如最簡單的 input,我們只需要監(jiān)聽 input 事件,文本發(fā)生變化時更新數(shù)據(jù),觸發(fā)數(shù)據(jù)的 set 方法,通知所有的 watcher 更新視圖


          我們在模板編譯的時候,給 dom 元素綁定相應的事件,如 input 標簽綁定 input 事件并指定更新數(shù)據(jù)的回調(diào)函數(shù):


          const compileUtils = {  # v-model  model(node, expr, vm) {    const value = vm.$data[expr];    # 創(chuàng)建觀察者 完成依賴收集    new Watcher(vm, expr, newVal => {      node.value = value;    });    node.addEventListener('input', (e) => {      # 更新數(shù)據(jù),觸發(fā)數(shù)據(jù)的 set 方法      vm.$data[expr] = newVal;    });    node.value = value;  },};


          至此大功告成




          點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。
          -?END -

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品成人免费视频 | 中文字幕第三十页 | 乱系列视频在线观看 | 人人做人人爱青青草视频 | 五月丁香永久网站 |