<listing id="z3xzv"><menuitem id="z3xzv"><meter id="z3xzv"></meter></menuitem></listing>
<address id="z3xzv"></address>
<noframes id="z3xzv"><address id="z3xzv"><listing id="z3xzv"></listing></address>

    <address id="z3xzv"><address id="z3xzv"><listing id="z3xzv"></listing></address></address><form id="z3xzv"><listing id="z3xzv"><meter id="z3xzv"></meter></listing></form><form id="z3xzv"></form><form id="z3xzv"><listing id="z3xzv"><meter id="z3xzv"></meter></listing></form>
    <noframes id="z3xzv">

    <form id="z3xzv"></form>
    溫馨提示×

    Vue 是如何實現的數據響應式

    發布時間:2022-09-16 13:57:00 來源:億速云 閱讀:50 作者:栢白 欄目:編程語言

    這篇文章主要介紹了Vue 是如何實現的數據響應式,具有一定借鑒價值,需要的朋友可以參考下。下面就和我一起來看看吧。

    vue 是一個易上手的框架,許多便捷功能都在其內部做了集成,其中最有區別性的功能就是其潛藏于底層的響應式系統。組件狀態都是響應式的 JavaScript 對象。當更改它們時,視圖會隨即更新,這讓狀態管理更加簡單直觀。那么,Vue 響應性系統是如何實現的呢?本文也是在閱讀了 Vue 源碼后的理解以及模仿實現,所以跟隨作者的思路,我們一起由淺入深的探索一下vue吧!【相關推薦:vuejs視頻教程】

    本文 Vue 源碼版本:2.6.14,為了便于理解,代碼都最簡化。

    Vue 是如何實現的數據響應式

    當你把一個普通的 JavaScript 對象傳入 Vue 實例作為 data 選項,Vue 將遍歷此對象所有的 property,并使用 Object.defineProperty 把這些 property 全部轉為 getter/setter,然后圍繞 getter/setter來運行。

    一句話概括Vue 的響應式系統就是: 觀察者模式 + Object.defineProperty 攔截getter/setter

    MDN ObjdefineProperty

    觀察者模式

    什么是Object.defineProperty ?

    Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,并返回此對象。

    簡單的說,就是通過此方式定義的 property,執行 obj.xxx 時會觸發 get,執行 obj.xxx = xxx會觸發 set,這便是響應式的關鍵。

    Object.defineProperty 是 ES5 中一個無法 shim(無法通過polyfill實現) 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的原因。

    響應式系統基礎實現

    現在,我們來基于Object.defineProperty實現一個簡易的響應式更新系統作為“開胃菜”

    let data = {};
    // 使用一個中間變量保存 value
    let value = "hello";
    // 用一個集合保存數據的響應更新函數
    let fnSet = new Set();
    // 在 data 上定義 text 屬性
    Object.defineProperty(data, "text", {
      enumerable: true,
      configurable: true,
      set(newValue) {
        value = newValue;
        // 數據變化
        fnSet.forEach((fn) => fn());
      },
      get() {
        fnSet.add(fn);
        return value;
      },
    });
    
    // 將 data.text 渲染到頁面上
    function fn() {
      document.body.innerText = data.text;
    }
    // 執行函數,觸發讀取 get
    fn();
    
    // 一秒后改變數據,觸發 set 更新
    setTimeout(() => {
      data.text = "world";
    }, 1000);

    接下來我們在瀏覽器中運行這段代碼,會得到期望的效果

    通過上面的代碼,我想你對響應式系統的工作原理已經有了一定的理解。為了讓這個“開胃菜”易于消化,這個簡易的響應式系統還有很多缺點,例如:數據和響應更新函數是通過硬編碼強耦合在一起的、只實現了一對一的情況、不夠模塊化等等……所以接下來,我們來一一完善。

    設計一個完善的響應式系統

    要設計一個完善的響應式系統,我們需要先了解一個前置知識,什么是觀察者模式?

    什么是觀察者模式?

    它就是一種行為設計模式, 允許你定義一種訂閱機制, 可在對象事件發生時通知多個 “觀察” 該對象的其他對象。

    擁有一些值得關注狀態的對象通常被稱為目標,由于它自身狀態發生改變時需要通知其他對象,我們也將其成為發布者(pub-lish-er) 。所有希望關注發布者狀態變化的其他對象被稱為訂閱者(sub-scribers) 。此外,發布者與所有訂閱者直接僅通過接口交互,都必須具有同樣的接口。

    Vue 是如何實現的數據響應式

    舉個例子?:

    你(即應用中的訂閱者)對某個書店的周刊感興趣,你給老板(即應用中的發布者)留了電話,讓老板一有新周刊就給你打電話,其他對這本周刊感興趣的人,也給老板留了電話。新周刊到貨時,老板就挨個打電話,通知讀者來取。

    假如某個讀者一不小心留的是 qq 號,不是電話號碼,老版打電話時就會打不通,該讀者就收不到通知了。這就是我們上面說的,必須具有相同的接口。

    了解了觀察者模式后,我們就開始著手設計響應式系統。

    抽象觀察者(訂閱者)類Watcher

    在上面的例子中,數據和響應更新函數是通過硬編碼強耦合在一起的。而實際開發過程中,更新函數不一定叫fn,更有可能是一個匿名函數。所以我們需要抽像一個觀察者(訂閱者)類Watcher來保存并執行更新函數,同時向外提供一個update更新接口。

    // Watcher 觀察者可能有 n 個,我們為了區分它們,保證唯一性,增加一個 uid
    let watcherId = 0;
    // 當前活躍的 Watcher
    let activeWatcher = null;
    
    class Watcher {
      constructor(cb) {
        this.uid = watcherId++;
        // 更新函數
        this.cb = cb;
        // 保存 watcher 訂閱的所有數據
        this.deps = [];
        // 初始化時執行更新函數
        this.get();
      }
      // 求值函數
      get() {
        // 調用更新函數時,將 activeWatcher 指向當前 watcher
        activeWatcher = this;
        this.cb();
        // 調用完重置
        activeWatcher = null;
      }
      // 數據更新時,調用該函數重新求值
      update() {
        this.get();
      }
    }

    抽象被觀察者(發布者)類Dep

    我們再想一想,實際開發過程中,data 中肯定不止一個數據,而且每個數據,都有不同的訂閱者,所以說我們還需要抽象一個被觀察者(發布者)Dep類來保存數據對應的觀察者(Watcher),以及數據變化時通知觀察者更新。

    class Dep {
      constructor() {
        // 保存所有該依賴項的訂閱者
        this.subs = [];
      }
      addSubs() {
        // 將 activeWatcher 作為訂閱者,放到 subs 中
        // 防止重復訂閱
        if(this.subs.indexOf(activeWatcher) === -1){
          this.subs.push(activeWatcher);
        }
      }
      notify() {
        // 先保存舊的依賴,便于下面遍歷通知更新
        const deps = this.subs.slice()
        // 每次更新前,清除上一次收集的依賴,下次執行時,重新收集
        this.subs.length = 0;
        deps.forEach((watcher) => {
          watcher.update();
        });
      }
    }

    抽象 Observer

    現在,WatcherDep只是兩個獨立的模塊,我們怎么把它們關聯起來呢?

    答案就是Object.defineProperty,在數據被讀取,觸發get方法,Dep 將當前觸發 get 的 Watcher 當做訂閱者放到 subs中,Watcher 就與 Dep建立關系;在數據被修改,觸發set方法,Dep就遍歷 subs 中的訂閱者,通知Watcher更新。

    下面我們就來完善將數據轉換為getter/setter的處理。

    上面基礎的響應式系統實現中,我們只定義了一個響應式數據,當 data 中有其他property時我們就處理不了了。所以,我們需要抽象一個 Observer類來完成對 data數據的遍歷,并調用defineReactive轉換為 getter/setter,最終完成響應式綁定。

    為了簡化,我們只處理data中單層數據。

    class Observer {
      constructor(value) {
        this.value = value;
        this.walk(value);
      }
      // 遍歷 keys,轉換為 getter/setter
      walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
          const key = keys[i]
          defineReactive(obj, key, obj[key]);
        }
      }
    }

    這里我們通過參數 value 的閉包,來保存最新的數據,避免新增其他變量

    function defineReactive(target, key, value) {
      // 每一個數據都是一個被觀察者
      const dep = new Dep();
      Object.defineProperty(target, key, {
        enumerable: true,
        configurable: true,
        // 執行 data.xxx 時 get 觸發,進行依賴收集,watcher 訂閱 dep
        get() {
          if (activeWatcher) {
            // 訂閱
            dep.addSubs(activeWatcher);
          }
          return value;
        },
        // 執行 data.xxx = xxx 時 set 觸發,遍歷訂閱了該 dep 的 watchers,
        // 調用 watcher.updata 更新
        set(newValue) {
          // 如果前后值相等,沒必要跟新
          if (value === newVal) {
            return;
          }
          value = newValue;
          // 派發更新
          dep.notify();
        },
      });
    }

    至此,響應式系統就大功告成了??!

    測試

    我們通過下面代碼測試一下:

    let data = {
      name: "張三",
      age: 18,
      address: "成都",
    };
    // 模擬 render
    const render1 = () => {
      console.warn("-------------watcher1--------------");
      console.log("The name value is", data.name);
      console.log("The age value is", data.age);
      console.log("The address value is", data.address);
    };
    const render2 = () => {
      console.warn("-------------watcher2--------------");
      console.log("The name value is", data.name);
      console.log("The age value is", data.age);
    };
    // 先將 data 轉換成響應式
    new Observer(data);
    // 實例觀察者
    new Watcher(render1);
    new Watcher(render2);

    在瀏覽器中運行這段代碼,和我們期望的一樣,兩個render都執行了,并且在控制臺上打印了結果。

    Vue 是如何實現的數據響應式

    我們嘗試修改 data.name = '李四 23333333',測試兩個 render 都會重新執行:

    Vue 是如何實現的數據響應式

    我們只修改 data.address = '北京',測試一下是否只有render 1回調都會重新執行:

    Vue 是如何實現的數據響應式

    都完美通過測試??!?

    總結

    Vue 是如何實現的數據響應式

    Vue響應式原理的核心就是Observer、Dep、Watcher,三者共同構成 MVVM 中的 VM

    Observer中進行數據響應式處理以及最終的WatcherDep關系綁定,在數據被讀的時候,觸發get方法,將 Watcher收集到 Dep中作為依賴;在數據被修改的時候,觸發set方法,Dep就遍歷 subs 中的訂閱者,通知Watcher更新。

    本篇文章屬于入門篇,并非源碼實現,在源碼的基礎上簡化了很多內容,能夠便于理解Observer、Dep、Watcher三者的作用和關系。

    本文的源碼,以及作者學習 Vue 源碼完整的逐行注釋源碼地址:github.com/yue1123/vue…

    以上就是Vue 是如何實現的數據響應式的詳細內容了,看完之后是否有所收獲呢?如果想了解更多相關內容,歡迎來億速云行業資訊!

    推薦閱讀:Vue data的數據響應式到底是如何實現的

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    主題地圖

    日本亚洲一区二区
    <listing id="z3xzv"><menuitem id="z3xzv"><meter id="z3xzv"></meter></menuitem></listing>
    <address id="z3xzv"></address>
    <noframes id="z3xzv"><address id="z3xzv"><listing id="z3xzv"></listing></address>

      <address id="z3xzv"><address id="z3xzv"><listing id="z3xzv"></listing></address></address><form id="z3xzv"><listing id="z3xzv"><meter id="z3xzv"></meter></listing></form><form id="z3xzv"></form><form id="z3xzv"><listing id="z3xzv"><meter id="z3xzv"></meter></listing></form>
      <noframes id="z3xzv">

      <form id="z3xzv"></form>