目錄
Vue 是如何實現的資料回應式" >Vue 是如何實現的資料回應式
#什麼是Object.defineProperty ? " >#什麼是Object.defineProperty ?
響應式系統基礎實作" >響應式系統基礎實作
首頁 web前端 Vue.js 一文聊聊Vue響應式實作原理

一文聊聊Vue響應式實作原理

Sep 15, 2022 pm 07:36 PM
vue vue3

一文聊聊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);
登入後複製
接下來我們在瀏覽器中運行這段程式碼,會得到期望的效果

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

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

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

什麼是觀察者模式?

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

擁有一些值得關注狀態的對象通常被稱為

目標,由於它自身狀態改變時需要通知其他對象,我們也將其成為發布者(publisher) 。所有希望關注發布者狀態變化的其他物件稱為訂閱者(subscribers) 。此外,發布者與所有訂閱者直接僅透過介面交互,都必須具有相同的介面

一文聊聊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 <p>这里我们通过参数 value 的闭包,来保存最新的数据,避免新增其他变量</p><pre class="brush:php;toolbar:false">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响应式原理的核心就是ObserverDepWatcher,三者共同构成 MVVM 中的 VM

Observer中进行数据响应式处理以及最终的WatcherDep关系绑定,在数据被读的时候,触发get方法,将 Watcher收集到 Dep中作为依赖;在数据被修改的时候,触发set方法,Dep就遍历 subs 中的订阅者,通知Watcher更新。

本篇文章属于入门篇,并非源码实现,在源码的基础上简化了很多内容,能够便于理解ObserverDepWatcher三者的作用和关系。

本文的源码,以及作者学习 Vue 源码完整的逐行注释源码地址:github.com/yue1123/vue…

(学习视频分享:web前端开发编程基础视频

以上是一文聊聊Vue響應式實作原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

vue中怎麼用bootstrap vue中怎麼用bootstrap Apr 07, 2025 pm 11:33 PM

在 Vue.js 中使用 Bootstrap 分為五個步驟:安裝 Bootstrap。在 main.js 中導入 Bootstrap。直接在模板中使用 Bootstrap 組件。可選:自定義樣式。可選:使用插件。

vue怎麼給按鈕添加函數 vue怎麼給按鈕添加函數 Apr 08, 2025 am 08:51 AM

可以通過以下步驟為 Vue 按鈕添加函數:將 HTML 模板中的按鈕綁定到一個方法。在 Vue 實例中定義該方法並編寫函數邏輯。

vue中的watch怎麼用 vue中的watch怎麼用 Apr 07, 2025 pm 11:36 PM

Vue.js 中的 watch 選項允許開發者監聽特定數據的變化。當數據發生變化時,watch 會觸發一個回調函數,用於執行更新視圖或其他任務。其配置選項包括 immediate,用於指定是否立即執行回調,以及 deep,用於指定是否遞歸監聽對像或數組的更改。

vue多頁面開發是啥意思 vue多頁面開發是啥意思 Apr 07, 2025 pm 11:57 PM

Vue 多頁面開發是一種使用 Vue.js 框架構建應用程序的方法,其中應用程序被劃分為獨立的頁面:代碼維護性:將應用程序拆分為多個頁面可以使代碼更易於管理和維護。模塊化:每個頁面都可以作為獨立的模塊,便於重用和替換。路由簡單:頁面之間的導航可以通過簡單的路由配置來管理。 SEO 優化:每個頁面都有自己的 URL,這有助於搜索引擎優化。

vue返回上一頁的方法 vue返回上一頁的方法 Apr 07, 2025 pm 11:30 PM

Vue.js 返回上一頁有四種方法:$router.go(-1)$router.back()使用 &lt;router-link to=&quot;/&quot;&gt; 組件window.history.back(),方法選擇取決於場景。

vue.js怎麼引用js文件 vue.js怎麼引用js文件 Apr 07, 2025 pm 11:27 PM

在 Vue.js 中引用 JS 文件的方法有三種:直接使用 &lt;script&gt; 標籤指定路徑;利用 mounted() 生命週期鉤子動態導入;通過 Vuex 狀態管理庫進行導入。

vue遍歷怎麼用 vue遍歷怎麼用 Apr 07, 2025 pm 11:48 PM

Vue.js 遍歷數組和對像有三種常見方法:v-for 指令用於遍歷每個元素並渲染模板;v-bind 指令可與 v-for 一起使用,為每個元素動態設置屬性值;.map 方法可將數組元素轉換為新數組。

vue的div怎麼跳轉 vue的div怎麼跳轉 Apr 08, 2025 am 09:18 AM

Vue 中 div 元素跳轉的方法有兩種:使用 Vue Router,添加 router-link 組件。添加 @click 事件監聽器,調用 this.$router.push() 方法跳轉。

See all articles