vue3.0的proxy響應(yīng)式原理簡(jiǎn)單實(shí)現(xiàn)


vue3.0監(jiān)測(cè)機(jī)制有了很大的改善,彌補(bǔ)了vue2.0
的一些局限:

  • 對(duì)屬性的添加、刪除動(dòng)作的監(jiān)測(cè);
  • 對(duì)數(shù)組基于下標(biāo)的修改、對(duì)于 .length 修改的監(jiān)測(cè);
  • 對(duì) Map、Set、WeakMap 和 WeakSet 的支持;

這里為了更好的理解原理手動(dòng)實(shí)現(xiàn)vue3.0的監(jiān)測(cè)對(duì)象屬性變化的Demo(實(shí)際源碼中還需考慮map, set等數(shù)據(jù)類型,這里僅用普通對(duì)象為例)

vue3.0 使用proxy代替了vue2.0版本中的defineProperty,首先利用compositionAPI中的 reactive() 函數(shù)返回一個(gè)proxy對(duì)象,使得數(shù)據(jù)可監(jiān)測(cè)

// reactive() 函數(shù)接受一個(gè)普通對(duì)象 返回一個(gè)響應(yīng)式數(shù)據(jù)對(duì)象
function reactive(target) {
  // 通過(guò)proxy將對(duì)象變?yōu)轫憫?yīng)式
  const observed = new Proxy(target, baseHandler);
  //  返回proxy代理后的對(duì)象
  return observed;
}

Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫(xiě)

var proxy = new Proxy(target, handler);

target參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler參數(shù)也是一個(gè)對(duì)象,用來(lái)定制攔截行為。

baseHandler中定義攔截的get set方法,監(jiān)測(cè)和改寫(xiě)數(shù)據(jù),為了方便,我們需要先將所有依賴收集起來(lái),一旦數(shù)據(jù)發(fā)生變化,就統(tǒng)一通知更新。就是典型的“發(fā)布訂閱者”模式,數(shù)據(jù)變化為“發(fā)布者”,依賴對(duì)象為“訂閱者”。

Proxy 與Reflect 組合使用,Proxy攔截用戶對(duì)目標(biāo)對(duì)象的訪問(wèn), 而實(shí)際對(duì)數(shù)據(jù)的操作由Reflect來(lái)完成

Reflect

Reflect對(duì)象與Proxy對(duì)象一樣,是ES6為了操作對(duì)象而提供的新API ,Reflect不能執(zhí)行new指令。

Reflect作用:優(yōu)化Object的一些操作方法以及合理的返回Object操作返回的結(jié)果。

const baseHandler = {
 get(target, key) {
   // Reflect.get
   const res = Reflect.get(target, key);
   // @todo 依賴收集
   // 嘗試獲取值obj.age,觸發(fā)getter
   track(target, key);
   return typeof res === "object" ? reactive(res) : res;
 },
 set(target, key, val) {
   const info = { oldValue: target[key], newValue: val };
   // Reflect.set
   // target[key] = val;
   const res = Reflect.set(target, key, val);
   // @todo 響應(yīng)式去通知變化 觸發(fā)執(zhí)行,effect函數(shù)是響應(yīng)式對(duì)象修改觸發(fā)的
   trigger(target, key, info);
 },
};

track() 函數(shù)用來(lái)收集依賴,將所有 get 的 target 跟 key 以及 effect 建立起對(duì)應(yīng)關(guān)系,使用一個(gè)全局的 WeakMap 類型變量 targetMap 來(lái)存儲(chǔ) target,還需要一個(gè)全局的數(shù)組來(lái)存儲(chǔ) effect

effect是副作用的意思,也就是說(shuō)它是響應(yīng)式副產(chǎn)品,每次觸發(fā)了 get 時(shí)收集effect,每次set時(shí)在觸發(fā)這些effects,這樣就可以做一些響應(yīng)式數(shù)據(jù)之外的一些事情了,比如計(jì)算屬性computed


function track(target, key) {
  const effect = effectStack[effectStack.length - 1];
  if (effect) {
    let depMap = targetMap.get(target);
    if (depMap === undefined) {
      depMap = new Map();
      targetMap.set(target, depMap);
    }
    let dep = depMap.get(key);
    if (dep === undefined) {
      dep = new Set(); // key去重
      depMap.set(key, dep);
    }
    // 以上為容錯(cuò) target key
    if (!dep.has(effect)) {
      // 新增依賴
      // 雙向存儲(chǔ),方便查找優(yōu)化
      dep.add(effect);
      effect.deps.push(dep);
    }
  }
}

trigger() 函數(shù)用來(lái)通知訂閱者,更新數(shù)據(jù),執(zhí)行effect,普通的effect和computed有優(yōu)先級(jí),effect先執(zhí)行,computed后執(zhí)行,因?yàn)?computed 可能會(huì)依賴普通的 effect

function trigger(target, key, info) {
  //1.找到依賴
  const depMap = targetMap.get(target);
  if (depMap === undefined) {
    // 沒(méi)有依賴直接return
    return;
  }
  // 區(qū)分普通的effect和computed有優(yōu)先級(jí),effect先執(zhí)行,computed后執(zhí)行
  // 因?yàn)?computed 可能會(huì)依賴普通的 effect
  const effects = new Set();
  const computedRunners = new Set();
  if (key) {
    let deps = depMap.get(key);
    deps.forEach((effect) => {
      if (effect.computed) {
        computedRunners.add(effect);
      } else {
        effects.add(effect);
      }
    });
    // 拆開(kāi)執(zhí)行
    effects.forEach((effect) => effect());
    computedRunners.forEach((computed) => computed());
  }
}

Demo的github地址:https://github.com/lihel/proxy-demo

注:

proxy的兼容性不是很好,由于ES5的限制,ES6新增的Proxy無(wú)法被轉(zhuǎn)譯成ES5,目前可以通過(guò)Polyfill提供部分兼容

https://www.npmjs.com/package/proxy-polyfill

https://www.npmjs.com/package/es6-proxy-polyfill

https://github.com/GoogleChrome/proxy-polyfill

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容