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


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

  • 對屬性的添加孙咪、刪除動(dòng)作的監(jiān)測;
  • 對數(shù)組基于下標(biāo)的修改夺刑、對于 .length 修改的監(jiān)測缅疟;
  • 對 Map、Set遍愿、WeakMap 和 WeakSet 的支持存淫;

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

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

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

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

var proxy = new Proxy(target, handler);

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

baseHandler中定義攔截的get set方法寞冯,監(jiān)測和改寫數(shù)據(jù),為了方便晚伙,我們需要先將所有依賴收集起來吮龄,一旦數(shù)據(jù)發(fā)生變化,就統(tǒng)一通知更新咆疗。就是典型的“發(fā)布訂閱者”模式漓帚,數(shù)據(jù)變化為“發(fā)布者”,依賴對象為“訂閱者”民傻。

Proxy 與Reflect 組合使用胰默,Proxy攔截用戶對目標(biāo)對象的訪問场斑, 而實(shí)際對數(shù)據(jù)的操作由Reflect來完成

Reflect

Reflect對象與Proxy對象一樣,是ES6為了操作對象而提供的新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)式對象修改觸發(fā)的
   trigger(target, key, info);
 },
};

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

effect是副作用的意思,也就是說它是響應(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)) {
      // 新增依賴
      // 雙向存儲构蹬,方便查找優(yōu)化
      dep.add(effect);
      effect.deps.push(dep);
    }
  }
}

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

function trigger(target, key, info) {
  //1.找到依賴
  const depMap = targetMap.get(target);
  if (depMap === undefined) {
    // 沒有依賴直接return
    return;
  }
  // 區(qū)分普通的effect和computed有優(yōu)先級,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);
      }
    });
    // 拆開執(zhí)行
    effects.forEach((effect) => effect());
    computedRunners.forEach((computed) => computed());
  }
}

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

注:

proxy的兼容性不是很好怖亭,由于ES5的限制,ES6新增的Proxy無法被轉(zhuǎn)譯成ES5坤检,目前可以通過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)容合作請聯(lián)系作者
  • 序言:七十年代末依许,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缀蹄,更是在濱河造成了極大的恐慌峭跳,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缺前,死亡現(xiàn)場離奇詭異蛀醉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)衅码,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門拯刁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逝段,你說我怎么就攤上這事垛玻「钔保” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵帚桩,是天一觀的道長亿驾。 經(jīng)常有香客問我,道長账嚎,這世上最難降的妖魔是什么莫瞬? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮郭蕉,結(jié)果婚禮上疼邀,老公的妹妹穿的比我還像新娘。我一直安慰自己召锈,他們只是感情好旁振,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涨岁,像睡著了一般规求。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卵惦,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機(jī)與錄音瓦戚,去河邊找鬼沮尿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛较解,可吹牛的內(nèi)容都是我干的畜疾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼印衔,長吁一口氣:“原來是場噩夢啊……” “哼啡捶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奸焙,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瞎暑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后与帆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體了赌,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年玄糟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勿她。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阵翎,死狀恐怖逢并,靈堂內(nèi)的尸體忽然破棺而出之剧,到底是詐尸還是另有隱情,我是刑警寧澤砍聊,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布背稼,位于F島的核電站,受9級特大地震影響辩恼,放射性物質(zhì)發(fā)生泄漏雇庙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一灶伊、第九天 我趴在偏房一處隱蔽的房頂上張望疆前。 院中可真熱鬧,春花似錦聘萨、人聲如沸竹椒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胸完。三九已至,卻和暖如春翘贮,著一層夾襖步出監(jiān)牢的瞬間赊窥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工狸页, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锨能,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓芍耘,卻偏偏與公主長得像址遇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子斋竞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

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