Vue3 源碼解析(六):響應(yīng)式原理與 reactive

今天這篇文章是筆者會帶著大家一起深入剖析 Vue3 的響應(yīng)式原理實(shí)現(xiàn)柳譬,以及在響應(yīng)式基礎(chǔ) API 中的 reactive 是如何實(shí)現(xiàn)的干签。對于 Vue 框架來說碗旅,其非侵入的響應(yīng)式系統(tǒng)是最獨(dú)特的特性之一了贷揽,所以不論任何一個(gè)版本的 Vue棠笑,在熟悉其基礎(chǔ)用法后,響應(yīng)式原理都是筆者最想優(yōu)先了解的部分禽绪,也是閱讀源碼時(shí)必細(xì)細(xì)研究的部分蓖救。畢竟知己知彼百戰(zhàn)不殆,當(dāng)你使用 Vue 時(shí)印屁,掌握了響應(yīng)式原理一定會讓你的 coding 過程更加游刃有余的循捺。

Vue2 的響應(yīng)式原理

在開始介紹 Vue3 的響應(yīng)式原理前,我們先一起回顧一下 Vue2 的響應(yīng)式原理雄人。

當(dāng)我們把一個(gè)普通選項(xiàng)傳入 Vue 實(shí)例的 data 選項(xiàng)中从橘,Vue 將遍歷此對象所有的 property,并使用 Object.defineProperty 把這些 property 全部轉(zhuǎn)為 getter/setter础钠。而 Vue2 在處理數(shù)組時(shí)恰力,也會通過原型鏈劫持會改變數(shù)組內(nèi)元素的方法,并在原型鏈觀察新增的元素珍坊,以及派發(fā)更新通知牺勾。

vue2-observer

這里放上一張 Vue2 文檔中介紹響應(yīng)式的圖片。對于文檔中有的描述筆者就不再贅述阵漏,而從 Vue2 的源碼角度來對照圖片說一說驻民。在 Vue2 的源碼中的 src/core 路徑下有一個(gè) observer 模塊翻具,它就是 Vue2 中處理響應(yīng)式的地方了。在這個(gè)模塊下 observer 負(fù)責(zé)將對象回还、數(shù)組轉(zhuǎn)換成響應(yīng)式的裆泳,即圖中的紫色部分,處理 Data 的 getter 及 setter柠硕。當(dāng) data 中的選項(xiàng)被訪問時(shí)工禾,會觸發(fā) getter,此時(shí) observer 目錄下的 wather.js 模塊就會開始工作蝗柔,它的任務(wù)就是收集依賴闻葵,我們收集到的依賴是一個(gè)個(gè) Dep 類的實(shí)例化對象。而 data 中的選項(xiàng)變更時(shí)癣丧,會觸發(fā) setter 的調(diào)用槽畔,而在 setter 的過程中,觸發(fā) dep 的 notify 函數(shù)胁编,派發(fā)更新事件厢钧,由此實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)監(jiān)聽。

Vue3 的響應(yīng)式變化

在簡單回顧了 Vue2 的響應(yīng)式原理后嬉橙,我們會有一個(gè)疑惑早直,Vue3 的響應(yīng)式原理與 Vue2 相比有什么不同呢?

在 Vue3 中響應(yīng)式系統(tǒng)最大的區(qū)別就是市框,數(shù)據(jù)模型是被代理的 JavaScript 對象了霞扬。不論是我們在組件的 data 選項(xiàng)中返回一個(gè)普通的JavaScript 對象,還是使用 composition api 創(chuàng)建一個(gè) reactive 對象枫振,Vue3 都會將該對象包裹在一個(gè)帶有 get 和 set 處理程序的 Proxy 中祥得。

Proxy 對象用于創(chuàng)建一個(gè)對象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找蒋得、賦值等)。

其基礎(chǔ)語法類似于:

const p = new Proxy(target, handler)

Proxy 相比較于 Object.defineProperty 究竟有什么優(yōu)勢呢乒疏?這個(gè)問題讓我們先從 Object.defineProperty 的弊端說起额衙。

從 Object 的角度來說,由于 Object.defineProperty 是對指定的 key 生成 getter/setter 以進(jìn)行變化追蹤怕吴,那么如果這個(gè) key 一開始不存在我們定義的對象上窍侧,響應(yīng)式系統(tǒng)就無能為力了,所以在 Vue2 中無法檢測對象的 property 的添加或移除转绷。而對于這個(gè)缺陷伟件,Vue2 提供了 vm.$set 和全局的 Vue.set API 讓我們能夠向?qū)ο筇砑禹憫?yīng)式的 property。

從數(shù)組的角度來說议经,當(dāng)我們直接利用索引設(shè)置一個(gè)數(shù)組項(xiàng)時(shí)斧账,或者當(dāng)我們修改數(shù)組長度時(shí)谴返,Vue2 的響應(yīng)式系統(tǒng)都不能監(jiān)聽到變化,解決的方法也如上咧织,使用上面提及的 2 個(gè) api嗓袱。

而這些問題在 ES6 的新特性 Proxy 面前通通都是不存在的,Proxy 對象能夠利用 handler 陷阱在 get习绢、set 時(shí)捕獲到任何變動渠抹,也能監(jiān)聽對數(shù)組索引的改動以及 數(shù)組 length 的改動。

而依賴收集和派發(fā)更新的方式在 Vue3 中也變得不同闪萄,在這里我先快速的整體描述一下:在 Vue3 中梧却,通過 track 的處理器函數(shù)來收集依賴,通過 trigger 的處理器函數(shù)來派發(fā)更新败去,每個(gè)依賴的使用都會被包裹到一個(gè)副作用(effect)函數(shù)中放航,而派發(fā)更新后就會執(zhí)行副作用函數(shù),這樣依賴處的值就被更新了为迈。

響應(yīng)式基礎(chǔ) reactive 的實(shí)現(xiàn)

既然這是一個(gè)源碼分析的文章三椿,咱們還是從源碼的角度來分析響應(yīng)式究竟是如何實(shí)現(xiàn)的。所以筆者會先分析響應(yīng)式基礎(chǔ)的 API —— reactive 葫辐,相信通過講解 reactive 的實(shí)現(xiàn)搜锰,大家會對 Proxy 有更深刻的認(rèn)識。

reactive

二話不說耿战,直接看源碼蛋叼。下面是 reactive API 的函數(shù),函數(shù)的參數(shù)接受一個(gè)對象剂陡,通過 createReactiveObject 函數(shù)處理后狈涮,直接返回一個(gè) proxy 對象。

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // 如果試圖去觀察一個(gè)只讀的代理對象鸭栖,會直接返回只讀版本
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 創(chuàng)建一個(gè)代理對象并返回
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

在第三行能看到通過判斷 target 中是否有 ReactiveFlags 中的 IS_READONLY key 確定對象是否為只讀對象歌馍。ReactiveFlags 枚舉會在源碼中不斷的與我們見面,所以有必要提前介紹一下 ReactiveFlags:

export const enum ReactiveFlags {
  SKIP = '__v_skip', // 是否跳過響應(yīng)式 返回原始對象
  IS_REACTIVE = '__v_isReactive', // 標(biāo)記一個(gè)響應(yīng)式對象
  IS_READONLY = '__v_isReadonly', // 標(biāo)記一個(gè)只讀對象
  RAW = '__v_raw' // 標(biāo)記獲取原始值
}

在 ReactiveFlags 枚舉中有 4 個(gè)枚舉值晕鹊,這四個(gè)枚舉值的含義都在注釋里松却。對于 ReactiveFlags 的使用是代理對象對 handler 中的 trap 陷阱非常好的應(yīng)用,對象中并不存在這些 key溅话,而通過 get 訪問這些 key 時(shí)晓锻,返回值都是通過 get 陷阱的函數(shù)內(nèi)處理的。介紹完 ReactiveFlags 后我們繼續(xù)往下看飞几。

createReactiveObject

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
)

先看 createReactiveObject 函數(shù)的簽名砚哆,該函數(shù)接受 5 個(gè)參數(shù):

  • target:目標(biāo)對象,想要生成響應(yīng)式的原始對象屑墨。
  • isReadonly:生成的代理對象是否只讀躁锁。
  • baseHandlers:生成代理對象的 handler 參數(shù)纷铣。當(dāng) target 類型是 Array 或 Object 時(shí)使用該 handler。
  • collectionHandlers:當(dāng) target 類型是 Map灿里、Set关炼、WeakMap、WeakSet 時(shí)使用該 handler匣吊。
  • proxyMap:存儲生成代理對象后的 Map 對象儒拂。

這里需要注意的是 baseHandlers 和 collectionHandlers 的區(qū)別,這兩個(gè)參數(shù)會根據(jù) target 的類型進(jìn)行判斷色鸳,最終選擇將哪個(gè)參數(shù)傳入 Proxy 的構(gòu)造函數(shù)社痛,當(dāng)做 handler 參數(shù)使用。

接著我們開始看 createReactiveObject 的邏輯部分:

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 如果目標(biāo)不是對象命雀,直接返回原始值
  if (!isObject(target)) {
    return target
  }
  // 如果目標(biāo)已經(jīng)是一個(gè)代理蒜哀,直接返回
  // 除非對一個(gè)響應(yīng)式對象執(zhí)行 readonly
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // 目標(biāo)已經(jīng)存在對應(yīng)的代理對象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 只有白名單里的類型才能被創(chuàng)建響應(yīng)式對象
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

在該函數(shù)的邏輯部分,可以看到基礎(chǔ)數(shù)據(jù)類型并不會被轉(zhuǎn)換成代理對象吏砂,而是直接返回原始值撵儿。

并且會將已經(jīng)生成的代理對象緩存進(jìn)傳入的 proxyMap,當(dāng)這個(gè)代理對象已存在時(shí)不會重復(fù)生成狐血,會直接返回已有對象淀歇。

也會通過 TargetType 來判斷 target 目標(biāo)對象的類型,Vue3 僅會對 Array匈织、Object浪默、Map、Set缀匕、WeakMap纳决、WeakSet 生成代理,其他對象會被標(biāo)記為 INVALID乡小,并返回原始值阔加。

當(dāng)目標(biāo)對象通過類型校驗(yàn)后,會通過 new Proxy() 生成一個(gè)代理對象 proxy满钟,handler 參數(shù)的傳入也是與 targetType 相關(guān)掸哑,并最終返回已生成的 proxy 對象。

所以回顧 reactive api零远,我們可能會得到一個(gè)代理對象,也可能只是獲得傳入的 target 目標(biāo)對象的原始值厌蔽。

Handlers 的組成

在 @vue/reactive 庫中有 baseHandlers 和 collectionHandlers 兩個(gè)模塊牵辣,分別生成 Proxy 代理的 handlers 中的 trap 陷阱。

例如在上面生成 reactive 的 api 中 baseHandlers 的參數(shù)傳入了一個(gè) mutableHandlers 對象奴饮,這個(gè)對象是這樣的:

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

通過變量名我們能知道 mutableHandlers 中存在 5 個(gè) trap 陷阱纬向。而在 baseHandlers 中择浊,get 和 set 都是通過工廠函數(shù)生成的,以便于適配除 reactive 外的其他 api逾条,例如 readonly琢岩、shallowReactive、shallowReadonly 等师脂。

baseHandlers 是處理 Array担孔、Object 的數(shù)據(jù)類型的,這也是我們絕大部分時(shí)間使用 Vue3 時(shí)使用的類型吃警,所以筆者接下來著重的講一下baseHandlers 中的 get 和 set 陷阱糕篇。

get 陷阱

上一段提到 get 是由一個(gè)工廠函數(shù)生成的,先來看一下 get 陷阱的種類酌心。

const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)

get 陷阱有 4 個(gè)類型织阳,分別對應(yīng)不同的響應(yīng)式 API泡一,從名稱中就可以知道對應(yīng)的 API 名稱,非常一目了然。而所有的 get 都是由 createGetter 函數(shù)生成的谐丢。所以接下來我們著重看一下 createGetter 的邏輯。

還是老規(guī)矩焕盟,先從函數(shù)簽名看起夜畴。

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {}
}

createGetter 有 isReadonly 和 shallow 兩個(gè)參數(shù),讓使用 get 陷阱的 api 按需使用壳鹤。而函數(shù)的內(nèi)部返回了一個(gè) get 函數(shù)盛龄,使用高階函數(shù)的方式返回將會傳入 handlers 中 get 參數(shù)的函數(shù)。

接著看 createGetter 的邏輯:

// 如果 get 訪問的 key 是 '__v_isReactive'芳誓,返回 createGetter 的 isReadonly 參數(shù)取反結(jié)果
if (key === ReactiveFlags.IS_REACTIVE) {
  return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
  // 如果 get 訪問的 key 是 '__v_isReadonly'余舶,返回 createGetter 的 isReadonly 參數(shù)
  return isReadonly
} else if (
  // 如果 get 訪問的 key 是 '__v_raw',并且 receiver 與原始標(biāo)識相等锹淌,則返回原始值
  key === ReactiveFlags.RAW &&
  receiver ===
    (isReadonly
      ? shallow
        ? shallowReadonlyMap
        : readonlyMap
      : shallow
        ? shallowReactiveMap
        : reactiveMap
    ).get(target)
) {
  return target
}

從這段 createGetter 邏輯中匿值,筆者專門介紹過的 ReactiveFlags 枚舉在這就取得了妙用。其實(shí)目標(biāo)對象中并沒有這些 key赂摆,但是在 get 中Vue3 就對這些 key 做了特殊處理挟憔,當(dāng)我們在對象上訪問這幾個(gè)特殊的枚舉值時(shí),就會返回特定意義的結(jié)果烟号。而可以關(guān)注一下 ReactiveFlags.IS_REACTIVE 這個(gè) key 的判斷方式绊谭,為什么是只讀標(biāo)識的取反呢?因?yàn)楫?dāng)一個(gè)對象的訪問能觸發(fā)這個(gè) get 陷阱時(shí)汪拥,說明這個(gè)對象必然已經(jīng)是一個(gè) Proxy 對象了达传,所以只要不是只讀的,那么就可以認(rèn)為是響應(yīng)式對象了。

接著看 get 的后續(xù)邏輯宪赶。

繼續(xù)判斷 target 是否是一個(gè)數(shù)組宗弯,如果代理對象不是只讀的,并且 target 是一個(gè)數(shù)組搂妻,并且訪問的 key 在數(shù)組需要特殊處理的方法里蒙保,就會直接調(diào)用特殊處理的數(shù)組函數(shù)執(zhí)行結(jié)果,并返回欲主。

arrayInstrumentations 是一個(gè)對象邓厕,對象內(nèi)保存了若干個(gè)被特殊處理的數(shù)組方法,并以鍵值對的形式存儲岛蚤。

我們之前說過 Vue2 以原型鏈的方式劫持了數(shù)組邑狸,而在這里也有類似地作用,而數(shù)組的部分我們準(zhǔn)備放在后續(xù)的文章中再介紹涤妒,下面是需要特殊處理的數(shù)組单雾。

  • 對索引敏感的數(shù)組方法
    • includes、indexOf她紫、lastIndexOf
  • 會改變自身長度的數(shù)組方法硅堆,需要避免 length 被依賴收集,因?yàn)檫@樣可能會造成循環(huán)引用
    • push贿讹、pop渐逃、shift、unshift民褂、splice
// 判斷 taeget 是否是數(shù)組
const targetIsArray = isArray(target)
// 如果不是只讀對象茄菊,并且目標(biāo)對象是個(gè)數(shù)組,訪問的 key 又在數(shù)組需要劫持的方法里赊堪,直接調(diào)用修改后的數(shù)組方法執(zhí)行
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
  return Reflect.get(arrayInstrumentations, key, receiver)
}

// 獲取 Reflect 執(zhí)行的 get 默認(rèn)結(jié)果
const res = Reflect.get(target, key, receiver)

// 如果是 key 是 Symbol面殖,并且 key 是 Symbol 對象中的 Symbol 類型的 key
// 或者 key 是不需要追蹤的 key: __proto__,__v_isRef,__isVue
// 直接返回 get 結(jié)果
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  return res
}

// 不是只讀對象,執(zhí)行 track 收集依賴
if (!isReadonly) {
  track(target, TrackOpTypes.GET, key)
}

// 如果是 shallow 淺層響應(yīng)式哭廉,直接返回 get 結(jié)果
if (shallow) {
  return res
}

if (isRef(res)) {
  // 如果是 ref 脊僚,則返回解包后的值 - 當(dāng) target 是數(shù)組,key 是 int 類型時(shí)遵绰,不需要解包
  const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
  return shouldUnwrap ? res.value : res
}

if (isObject(res)) {
  // 將返回的值也轉(zhuǎn)換成代理辽幌,我們在這里做 isObject 的檢查以避免無效值警告。
  // 也需要在這里惰性訪問只讀和星影視對象椿访,以避免循環(huán)依賴乌企。
  return isReadonly ? readonly(res) : reactive(res)
}

// 不是 object 類型則直接返回 get 結(jié)果
return res

在處理完數(shù)組后,我們對 target 執(zhí)行 Reflect.get 方法成玫,獲得默認(rèn)行為的 get 返回值逛犹。

之后判斷 當(dāng)前 key 是否是 Symbol端辱,或者是否是不需要追蹤的 key,如果是的話直接返回 get 的結(jié)果 res虽画。

下面??幾個(gè) key 是不需要被依賴收集或者返回響應(yīng)式結(jié)果的。

  • __proto__
  • _v_isRef
  • __isVue

接著判斷當(dāng)前代理對象是否是只讀對象荣病,如果不是只讀的話码撰,則運(yùn)行筆者上文提及的 tarck 處理器函數(shù)收集依賴。

如果是 shallow 的淺層響應(yīng)式个盆,則不需要將內(nèi)部的屬性轉(zhuǎn)換成代理脖岛,直接返回 res。

如果 res 是一個(gè) Ref 類型的對象颊亮,就會自動解包返回柴梆,這里就能解釋官方文檔中提及的 ref 在 reactive 中會自動解包的特性了。而需要注意的是终惑,當(dāng) target 是一個(gè)數(shù)組類型绍在,并且 key 是 int 類型時(shí),即使用索引訪問數(shù)組元素時(shí)雹有,不會被自動解包偿渡。

如果 res 是一個(gè)對象,就會將該對象轉(zhuǎn)成響應(yīng)式的 Proxy 代理對象返回霸奕,再結(jié)合我們之前分析的緩存已生成的 proxy 對象溜宽,可以知道這里的邏輯并不會重復(fù)生成相同的 res,也可以理解文檔中提及的當(dāng)我們訪問 reactive 對象中的 key 是一個(gè)對象時(shí)质帅,它也會自動的轉(zhuǎn)換成響應(yīng)式對象适揉,而且由于在此處生成 reactive 或者 readonly 對象是一個(gè)延遲行為,不需要在第一時(shí)間就遍歷 reactive 傳入的對象中的所有 key煤惩,也對性能的提升是一個(gè)幫助嫉嘀。

當(dāng) res 都不滿足上述條件時(shí),直接返回 res 結(jié)果盟庞。例如基礎(chǔ)數(shù)據(jù)類型就會直接返回結(jié)果吃沪,而不做特殊處理。

至此什猖,get 陷阱的邏輯全部結(jié)束了票彪。

set 陷阱

與 createGetter 對應(yīng),set 也有一個(gè) createSetter 的工廠函數(shù)不狮,也是通過柯里化的方式返回一個(gè) set 函數(shù)降铸。

函數(shù)簽名都大同小異,那么接下來筆者直接帶大家盤邏輯摇零。

set 的函數(shù)比較簡短推掸,所以這次一次性把寫好注釋的代碼放上來,先看代碼再講邏輯。

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 當(dāng)不是 shallow 模式時(shí)谅畅,判斷舊值是否是 Ref登渣,如果是則直接更新舊值的 value
      // 因?yàn)?ref 有自己的 setter
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // shallow 模式不需要特殊處理,對象按原樣 set
    }
        
    // 判斷 target 中是否存在 key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // Reflect.set 獲取默認(rèn)行為的返回值
    const result = Reflect.set(target, key, value, receiver)
    // 如果目標(biāo)是原始對象原型鏈上的屬性毡泻,則不會觸發(fā) trigger 派發(fā)更新
    if (target === toRaw(receiver)) {
      // 使用 trigger 派發(fā)更新胜茧,根據(jù) hadKey 區(qū)別調(diào)用事件
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

在 set 的過程中會首先獲取新舊與舊值,當(dāng)目前的代理對象不是淺層比較時(shí)仇味,會判斷舊值是否是一個(gè) Ref呻顽,如果舊值不是數(shù)組且是一個(gè) ref類型的對象,并且新值不是 ref 對象時(shí)丹墨,會直接修改舊值的 value廊遍。

看到這里可能會有疑問,為什么要更新舊值的 value贩挣?如果你使用過 ref 這個(gè) api 就會知道喉前,每個(gè) ref 對象的值都是放在 value 里的,而 ref 與 reactive 的實(shí)現(xiàn)是有區(qū)別的揽惹,ref 其實(shí)是一個(gè) class 實(shí)例被饿,它的 value 有自己的 set ,所以就不會在這里繼續(xù)進(jìn)行 set 了搪搏。ref 的部分在后續(xù)的文章中會詳細(xì)講解狭握。

在處理完 ref 類型的值后,會聲明一個(gè)變量 hadKey疯溺,判斷當(dāng)前要 set 的 key 是否是對象中已有的屬性论颅。

接下來調(diào)用 Reflect.set 獲取默認(rèn)行為的 set 返回值 result。

然后會開始派發(fā)更新的過程囱嫩,在派發(fā)更新前恃疯,需要保證 target 和原始的 receiver 相等,target 不能是一個(gè)原型鏈上的屬性墨闲。

之后開始使用 trigger 處理器函數(shù)派發(fā)更新今妄,如果 hadKey 不存在,則是一個(gè)新增屬性鸳碧,通過 TriggerOpTypes.ADD 枚舉來標(biāo)記盾鳞。這里可以看到開篇分析 Proxy 強(qiáng)于 Object.defineProperty 的地方,會監(jiān)測到任何一個(gè)新增的 key瞻离,讓響應(yīng)式系統(tǒng)更強(qiáng)大腾仅。

如果 key 是當(dāng)前 target 上已經(jīng)存在的屬性,則比較一下新舊值套利,如果新舊值不一樣推励,則代表屬性被更新鹤耍,通過 TriggerOpTypes.SET 來標(biāo)記派發(fā)更新。

在更新派發(fā)完后验辞,返回 set 的結(jié)果 result稿黄,至此 set 結(jié)束。

總結(jié)

在今天的文章中跌造,筆者先帶大家回顧了 Vue2 的響應(yīng)式原理抛猖,又開始介紹 Vue3 的響應(yīng)式原理,通過比較 Vue2 和 Vue3 的響應(yīng)式系統(tǒng)的區(qū)別引出 Vue3 響應(yīng)式系統(tǒng)的提升之處鼻听,尤其是其中最主要的調(diào)整將 Object.defineProperty 替換為 Proxy 代理對象。

為了讓大家屬性 Proxy 對響應(yīng)式系統(tǒng)的影響联四,筆者著重介紹了響應(yīng)式基礎(chǔ) API:reactive撑碴。分析了 reactive 的實(shí)現(xiàn),以及 reactive api 返回的 proxy 代理對象使用的 handlers 陷阱朝墩。并且對陷阱中我們最常用的 get 和 set 的源碼進(jìn)行分析醉拓,相信大家在看完本篇文章以后,對 proxy 這個(gè) ES2015 的新特性的使用又有了新的理解收苏。

本文只是介紹 Vue3 響應(yīng)式系統(tǒng)的第一篇文章亿卤,所以 track 收集依賴,trigger 派發(fā)更新的過程沒有詳細(xì)展開鹿霸,在后續(xù)的文章中計(jì)劃詳細(xì)講解副作用函數(shù) effect排吴,以及 track 和 trigger 的過程,如果希望能詳細(xì)了解響應(yīng)式系統(tǒng)的源碼懦鼠,麻煩大家點(diǎn)個(gè)關(guān)注免得迷路钻哩。

最后,如果這篇文章能夠幫助到你了解 Vue3 中的響應(yīng)式原理和 reactive 的實(shí)現(xiàn)肛冶,希望能給本文點(diǎn)一個(gè)喜歡??街氢。如果想繼續(xù)追蹤后續(xù)文章,也可以關(guān)注我的賬號或 follow 我的 github睦袖,再次謝謝各位可愛的看官老爺珊肃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市馅笙,隨后出現(xiàn)的幾起案子伦乔,更是在濱河造成了極大的恐慌,老刑警劉巖延蟹,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件评矩,死亡現(xiàn)場離奇詭異,居然都是意外死亡阱飘,警方通過查閱死者的電腦和手機(jī)斥杜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門虱颗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蔗喂,你說我怎么就攤上這事忘渔。” “怎么了缰儿?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵畦粮,是天一觀的道長。 經(jīng)常有香客問我乖阵,道長宣赔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任瞪浸,我火速辦了婚禮儒将,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘对蒲。我一直安慰自己钩蚊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布蹈矮。 她就那樣靜靜地躺著砰逻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泛鸟。 梳的紋絲不亂的頭發(fā)上蝠咆,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機(jī)與錄音谈况,去河邊找鬼勺美。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碑韵,可吹牛的內(nèi)容都是我干的赡茸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼祝闻,長吁一口氣:“原來是場噩夢啊……” “哼占卧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起联喘,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤华蜒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后豁遭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叭喜,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年蓖谢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捂蕴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬涡。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖啥辨,靈堂內(nèi)的尸體忽然破棺而出涡匀,到底是詐尸還是另有隱情,我是刑警寧澤溉知,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布陨瘩,位于F島的核電站,受9級特大地震影響级乍,放射性物質(zhì)發(fā)生泄漏舌劳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一玫荣、第九天 我趴在偏房一處隱蔽的房頂上張望蒿囤。 院中可真熱鬧,春花似錦崇决、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至建邓,卻和暖如春盈厘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背官边。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工沸手, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人注簿。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓契吉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诡渴。 傳聞我的和親對象是個(gè)殘疾皇子捐晶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容