自從 Vue 發(fā)布以來,就受到了廣大開發(fā)人員的青睞,提到 Vue,我們首先想到的就是 Vue 的響應(yīng)式系統(tǒng),那響應(yīng)式系統(tǒng)到底是怎么回事呢?接下來我就給大家簡單介紹一下 Vue 中的響應(yīng)式原理。
vue2 的響應(yīng)式原理
盡管 Vue2 將于 2023 年 12 月 31 日停止維護(hù),但是我們依然有很多項(xiàng)目是基于 Vue2.X 進(jìn)行開發(fā)的,那么我們先簡單看一看 Vue2.X 是基于什么實(shí)現(xiàn)的吧~
Object.defineProperty
Vue2 的響應(yīng)式原理是基于對象的 defineProperty () 方法進(jìn)行開發(fā)的,那么這個(gè)方法有什么作用呢?MDN 是這樣介紹的:
object.defineProperty () 方法會直接在一個(gè)對象上定義一個(gè)新屬性,或者修改一個(gè)對象的現(xiàn)有屬性,并返回此對象。
也就是說,我們可以通過對象的這個(gè)方法精確的添加或者修改對象的屬性。每個(gè)對象都具有 get/set 屬性,當(dāng)訪問 get 屬性時(shí),會調(diào)用 getter 方法,當(dāng)對象的屬性值被修改時(shí),會調(diào)用 setter 方法,正式基于 getter 和 setter 方法,Vue 才可以利用 Object.defineProperty 來實(shí)現(xiàn)響應(yīng)式系統(tǒng)。
Object.defineProperty 在 Vue 中的使用
在 vue 中,當(dāng)把一個(gè)普通的 JavaScript 對象傳入 Vue 實(shí)例作為 data 選項(xiàng),Vue 會遍歷此對象的所有屬性,并使用 object.defineProperty 將這些屬性轉(zhuǎn)為 getter/setter, getter/setter 可以追蹤依賴,在屬性被訪問的時(shí)候通知視圖變更。
Object.defineProperty(obj, 'targetObj', { get() { // 完成依賴收集 }, set() { // 發(fā)生變更,同時(shí)通知相關(guān)依賴 } })
vue3 的響應(yīng)式原理
vue2.0 很好的實(shí)現(xiàn)了數(shù)據(jù)的雙向綁定,但是也遺留了一個(gè)很重要的問題:由于 Vue 會在初始化實(shí)例時(shí)將 property 轉(zhuǎn)化為 getter/setter,所以,property 必須在 data 對象上先存在才能讓 Vue 將其轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)。
那么對于新增加的對象、或者某些需要特殊操作的數(shù)組想要轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)就需要使用 Vue.set 等方法。 Vue3 就很好的解決了這個(gè)問題。那么,Vue3 是如何解決的呢?讓我們就一起看看吧~
Proxy
提到 Vue3 的數(shù)據(jù)攔截,我們首先要了解什么是 proxy?
Proxy 可以理解成,在目標(biāo)對象之前架設(shè)一層 “攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來 “代理” 某些操作,可以譯為 “代理器”。
原來,Vue3 用了 Proxy 代理代替了 Object.defineProperty 方法。同樣的,在 proxy 中也有 get/set 方法,舉個(gè)例子~
var obj = new Proxy({}, { get: function (target, name) { return name; }, set: function (target, key, val) { target[key] = val return target; } });我們通過給每一個(gè)目標(biāo)對象都建立一個(gè)對應(yīng)的 Proxy 對象對其代理就可以彌補(bǔ) Object.defineProperty 對于新增對象無法監(jiān)聽的缺陷。
簡單設(shè)計(jì)一個(gè) Vue3 的響應(yīng)系統(tǒng)
實(shí)現(xiàn)一個(gè)簡單的響應(yīng)系統(tǒng)的思路: ?讀取(get)時(shí),將副作用函數(shù)入棧; ?設(shè)置(set)時(shí),將副作用函數(shù)出棧,執(zhí)行副作用函數(shù)。
// 存儲副作用函數(shù)的棧 const bucket = new Set() // 存儲被注冊的副作用函數(shù) let activeEffect // 注冊副作用函數(shù) functioneffect (fn) { // 存儲副作用函數(shù) activeEffect = fn fn() } // 副作用函數(shù)fn effect ( () => { document.body.innerText = obj.text } )執(zhí)行匿名函數(shù) fn 方法時(shí),會觸發(fā)響應(yīng)式數(shù)據(jù) obj.text 的讀取操作,進(jìn)而觸發(fā)代理對象 Proxy 的 get 攔截函數(shù):
const Proxy = new Proxy(data, { get (target, key) { if (activeEffect) { bucket.add(activeEffect) } return target[key] }, set (target, key, newVal) { target[key] = newVal bucket.forEach(fn => fn()) return true } })到此,我們會發(fā)現(xiàn),有一個(gè)疑問,我們怎樣能保證修改一個(gè)屬性之后觸發(fā)的副作用函數(shù)是我預(yù)期想要觸發(fā)的副作用函數(shù)呢?為了解決這個(gè)問題,我們還需要建立副作用函數(shù)與目標(biāo)對象的聯(lián)系: 我們僅需要用 WeakMap 代替 Set 數(shù)據(jù)結(jié)構(gòu):
const bucket = new WeakMap()
修改 Proxy 對象:
const Proxy = new Proxy(data, { get (target, key) { if (!activeEffect) return target[key] // 先從棧中取出depsMap,depsMap中保存目標(biāo)對象和其相關(guān)副作用函數(shù)的一對多的關(guān)系 let depsMap = bucket.get(target) if (!depsMap) { bucket.set(target, (depsMap = new Map()) } // 再根據(jù)key從depsMap中取得deps,deps保存所有與key相關(guān)聯(lián)的副作用函數(shù) let deps = depsMap.get(key) if (!deps) { depsMap.set(key, (deps = new Set()) } deps.add(activeEffect) return target[key] }, set (target, key, newVal) { target[key] = newVal const depsMap = bucket.get(target) if (!depsMap) return const effects = depsMap.get(key) effects && effects.forEach(fn => fn()) } })這樣,我們就實(shí)現(xiàn)了一個(gè)簡易的響應(yīng)系統(tǒng)。那么為什么要用 weakMap 而不是使用 Map 呢?就交給大家一起思考啦~
審核編輯:劉清
-
vue
+關(guān)注
關(guān)注
0文章
58瀏覽量
7851
原文標(biāo)題:初識 VUE 響應(yīng)式原理
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論