Vue reactive函數(shù)實(shí)現(xiàn)流程詳解
1.Reflect
Proxy有著可以攔截對(duì)對(duì)象各種操作的能力,比如最基本的get和set操作,而Reflect也有與這些操作同名的方法,像Reflect.set()、Reflect.get(),這些方法和它們所對(duì)應(yīng)的對(duì)象基本操作完全一致。
const data = { value: '1', get fn() { console.log(this.value); return this.value; } }; data.value; // 1 Reflect.get(data,'value'); // 1
除此之外,Reflect除了和基本對(duì)象操作等價(jià)外,它還具有第三個(gè)參數(shù)receiver
,即指定該基礎(chǔ)操作的this對(duì)象。
Reflect.get(data,'value',{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->value: '2'}); // 會(huì)輸出2
對(duì)于Proxy,它只能夠攔截對(duì)象的基本操作,而對(duì)于data.fn(),這是一個(gè)復(fù)合操作,它由一個(gè)get操作和一個(gè)apply操作組成,即先通過(guò)get獲取fn的值,然后調(diào)用即apply對(duì)應(yīng)的函數(shù)。而現(xiàn)在,用我們之前創(chuàng)建的響應(yīng)式系統(tǒng)來(lái)執(zhí)行一次這個(gè)復(fù)合操作,我們期望的結(jié)果是,在對(duì)fn屬性綁定的同時(shí),對(duì)value的值也進(jìn)行綁定,因?yàn)樵趂n函數(shù)的執(zhí)行過(guò)程中,操作了value值??蓪?shí)際情況是,value的值并沒(méi)有進(jìn)行綁定。
effect(() => { obj.fn(); // 假設(shè)obj是一個(gè)已經(jīng)做了響應(yīng)式代理的Proxy對(duì)象 }) obj.value = '2'; // 改變obj.value的值,預(yù)想中的響應(yīng)式操作沒(méi)有執(zhí)行
這里就涉及到fn()函數(shù)中,this指向的問(wèn)題了。實(shí)際上,在fn函數(shù)中,this指向的是原來(lái)的data對(duì)象,即this.value實(shí)際上是data.value,因?yàn)椴僮鞯氖窃瓕?duì)象,因此并不會(huì)觸依賴(lài)收集。了解到問(wèn)題的原因之后,我們就可以用上之前所說(shuō)的Reflect的特性了,將get操作實(shí)際的this對(duì)象指定為obj,這樣就可以順利的實(shí)現(xiàn)我們我期望的功能了。
const obj = new Proxy(data, { get(target, key, receiver) { // get 接收第三個(gè)參數(shù),即操作的調(diào)用者,對(duì)應(yīng)obj.fn()就是obj了 track(target, key); return Reflect.get(target, key, receiver); // 將原來(lái)直接返回target[key]的操作改為Reflect.get } }
2.Proxy的工作原理
在js中一個(gè)對(duì)象必須部署包括[[GET]]、[[SET]]在內(nèi)的11個(gè)內(nèi)部方法,除此之外,函數(shù)擁有額外的[[Call]]和[[Construct]]兩個(gè)方法。而在創(chuàng)建Proxy對(duì)象時(shí),指定的攔截函數(shù),實(shí)際上就是用來(lái)自定義代理對(duì)象本身的內(nèi)部方法和行為,而不是指定。
3.代理Object
(1)代理讀取操作
對(duì)一個(gè)普通對(duì)象的所有可能的讀取操作:
- 訪問(wèn)屬性:obj.foo
- 判斷對(duì)象或原型上是否存在給定的key;key in obkj
- 使用for … in循環(huán)遍歷對(duì)象
首先對(duì)于基本的訪問(wèn)屬性,我們可以使用get方法攔截。
const obj = new Proxy(data, { get(target, key, receiver) { // get 接收第三個(gè)參數(shù),即操作的調(diào)用者,對(duì)應(yīng)obj.fn()就是obj了 track(target, key); return Reflect.get(target, key, receiver); // 將原來(lái)直接返回target[key]的操作改為Reflect.get } }
然后,對(duì)于in操作符,我們使用has方法進(jìn)行攔截。
has(target, key) { track(target, key); return Reflect.has(target,key); }
最后,對(duì)于for … in操作,我們使用ownKeys方法進(jìn)行攔截。這里使用和唯一標(biāo)識(shí)ITERATE_KEY和副作用函數(shù)綁定,因?yàn)閷?duì)于ownKeys操作來(lái)說(shuō),無(wú)論如何它都是對(duì)一個(gè)對(duì)象上所存在的所有屬性進(jìn)行遍歷,并不會(huì)產(chǎn)生實(shí)際的屬性讀取操作,因此我們需要用一個(gè)唯一的標(biāo)識(shí)來(lái)標(biāo)記ownKeys操作。
ownKeys(target, key) { // 這里將副作用函數(shù)和唯一標(biāo)識(shí)ITERATE_KEY綁定了 track(target, ITERATE_KEY); return Reflect.ownKeys(target); },
相應(yīng)的,在進(jìn)行賦值操作的時(shí)候,也需要相應(yīng)的對(duì)ITERATE_KEY這個(gè)標(biāo)識(shí)進(jìn)行處理
function trigger(target, key) { const depsMap = bucket.get(target); if (!depsMap) return; const effects = depsMap.get(key); const iterateEffects = depsMap.get(ITERATE_KEY); // 讀取ITERATE_KEY const effectToRun = new Set(); effects && effects.forEach((fn) => { if (fn !== activeEffect) { effectToRun.add(fn); } }); // 將與 ITERATE_KEY 相關(guān)聯(lián)的副作用函數(shù)也添加到 effectsToRun iterateEffects && iterateEffects.forEach((fn) => { if (fn !== activeEffect) { effectToRun.add(fn); } }); effectToRun.forEach((fn) => { if (fn.options.scheduler) { fn.options.scheduler(fn); } else { fn(); } }); }
雖然以上的代碼解決了添加屬性的問(wèn)題,但是隨之而來(lái)的是修改屬性的問(wèn)題。對(duì)于for … in循環(huán)來(lái)說(shuō),無(wú)論原對(duì)象的屬性如何修改,對(duì)它來(lái)說(shuō)只需要進(jìn)行一次遍歷就好了,因此我們需要區(qū)分添加和修改的操作。這里使用Object.prototype.hasOwnProperty檢查當(dāng)前操作的屬性是否已經(jīng)存在于目標(biāo)對(duì)象上,如果是,則說(shuō)明當(dāng)前的操作類(lèi)型是’SET‘,否則說(shuō)明是’ADD‘。然后將type作為第三個(gè)參數(shù),傳入trigger函數(shù)中。
set(target, key, newVal, receiver) { const type = Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; Reflect.set(target, key, newVal, receiver); trigger(target, key, type); },
(2)代理delete操作符
代理delete操作符使用的是deleteProperty方法,因?yàn)閐elete操作符刪除屬性會(huì)導(dǎo)致屬性的數(shù)量變少,因此當(dāng)操作類(lèi)型為DELETE時(shí)也要觸發(fā)一下for … in循環(huán)的操作。
deleteProperty(target, key) { // 檢查刪除的key是否為自身屬性 const hadKey = Object.prototype.hasOwnProperty.call(target, key); const res = Reflect.deleteProperty(target, key); if (res && hadKey) { trigger(target, key, "DELETE"); } return res; }, // 當(dāng)type為ADD或DELETE的時(shí)候,才執(zhí)行ITERATE_KEY相關(guān)的操作 if (type === "ADD" || type === "DELETE") { iterateEffects && iterateEffects.forEach((fn) => { if (fn !== activeEffect) { effectToRun.add(fn); } }); }
4.合理的觸發(fā)響應(yīng)
(1)完善響應(yīng)操作
觸發(fā)修改操作時(shí),若新值和舊值相等,則不需要觸發(fā)修改響應(yīng)操作。
set(target, key, newVal, receiver) { const oldVal = target[key]; // 獲取舊值 const type = Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; const res = Reflect.set(target, key, newVal, receiver); if (oldVal !== newVal) { // 比較新值和舊值 trigger(target, key, type); } return res; },
但是全等有一個(gè)特殊情況,就是NaN === NaN的值為false,因此我們需要對(duì)NaN進(jìn)行一個(gè)特殊判斷。
(2)封裝一個(gè)reactive函數(shù)
其實(shí)就是對(duì)new Proxy進(jìn)行了一個(gè)簡(jiǎn)單的封裝。
function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { track(target, key); return Reflect.get(target, key, receiver); }, set(target, key, newVal, receiver) { const oldVal = target[key]; // 獲取舊值 const type = Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; const res = Reflect.set(target, key, newVal, receiver); if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) { // 比較新值和舊值 trigger(target, key, type); } return res; }, has(target, key) { track(target, key); return Reflect.has(target, key); }, ownKeys(target, key) { // 這里將副作用函數(shù)和唯一標(biāo)識(shí)ITERATE_KEY綁定了 track(target, ITERATE_KEY); return Reflect.ownKeys(target); }, deleteProperty(target, key) { // 檢查刪除的key是否為自身屬性 const hadKey = Object.prototype.hasOwnProperty.call(target, key); const res = Reflect.deleteProperty(target, key); if (res && hadKey) { trigger(target, key, "DELETE"); } return res; }, }); }
現(xiàn)在,我們使用reactive創(chuàng)建兩個(gè)響應(yīng)式對(duì)象,child和parent,然后將child原型設(shè)置為parent。然后為child.bar函數(shù)綁定副作用函數(shù)。當(dāng)修改child.bar的值的時(shí)候,可以看到,副作用函數(shù)實(shí)際執(zhí)行了兩次。這是因?yàn)椋琧hild的原型是parent,child本身并沒(méi)有bar這個(gè)屬性,所以根據(jù)原型鏈的規(guī)則,最終會(huì)在parent身上拿到bar這個(gè)屬性。因?yàn)樵谶M(jìn)行原型鏈查找的過(guò)程中,訪問(wèn)到了parent上的屬性,因襲進(jìn)行了一次額外的綁定操作,所以最終副作用函數(shù)執(zhí)行了兩次。
const obj = {}; const proto = { bar: 1, }; const child = reactive(obj); const parent = reactive(proto); Object.setPrototypeOf(child, parent); effect(() => { console.log(child.bar); }); child.bar = 2; // 輸出 1 2 2
這里我們比較一下child和parent的攔截函數(shù),可以發(fā)現(xiàn)receiver的值都是相同的,發(fā)生變化的是target的值,因此我們可以通過(guò)比較taregt的值來(lái)取消parent觸發(fā)的那一次響應(yīng)操作。
// child 的攔截函數(shù) get(target, key, receiver) { // target是原始對(duì)象obj // receiver 是child } // parent 的攔截函數(shù) get(target, key, receiver) { // target是proto對(duì)象 // receiver 是child }
這里我們通過(guò)添加一個(gè)raw操作來(lái)實(shí)現(xiàn),當(dāng)訪問(wèn)raw屬性的時(shí)候,會(huì)返回該對(duì)象的target值。
function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { // 添加一個(gè)新值 raw return target; } track(target, key); return Reflect.get(target, key, receiver); }, set(target, key, newVal, receiver) { const oldVal = target[key]; // 獲取舊值 const type = Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; const res = Reflect.set(target, key, newVal, receiver); if (target === receiver.raw) { // 比較target值,如果receiver的target和當(dāng)前target相同,說(shuō)明就不是原型鏈操作。 if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) { // 比較新值和舊值 trigger(target, key, type); } } return res; } } }
5.深響應(yīng)和淺響應(yīng)
實(shí)際上,前面我們實(shí)現(xiàn)的reactive還只是淺層響應(yīng),也就是說(shuō)只有對(duì)象的第一層具有響應(yīng)式反應(yīng)。比如對(duì)于一個(gè)obj:{bar{val:1}}對(duì)象,當(dāng)對(duì)obj.bar.val進(jìn)行操作的時(shí)候,我們首先從obj中拿到bar,但是這時(shí)候的bar只是一個(gè)普通對(duì)象bar:{val:1},因此無(wú)法進(jìn)行響應(yīng)式操作。這里我們對(duì)Reflect.get獲取的值進(jìn)行一個(gè)判斷,如果拿到的值是一個(gè)對(duì)象,遞歸調(diào)用reactive函數(shù),最后拿到一個(gè)深層響應(yīng)的對(duì)象。
function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { return target; } track(target, key); const res = Reflect.get(target, key, receiver); if(typeof res === 'object') { return reactive(res); } return res; } } }
但是我們并非所有時(shí)候都期望深層響應(yīng),因此我們調(diào)整一下reactive函數(shù)。
function createReactive(obj, isShallow = false) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { return target; } track(target, key); const res = Reflect.get(target, key, receiver); if (isShallow) return res; // 如果淺層響應(yīng),直接返回 if (typeof res === "object") { return reactive(res); } return res; }, set(target, key, newVal, receiver) { const oldVal = target[key]; // 獲取舊值 const type = Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; const res = Reflect.set(target, key, newVal, receiver); if (target === receiver.raw) { if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) { // 比較新值和舊值 trigger(target, key, type); } } return res; }, has(target, key) { track(target, key); return Reflect.has(target, key); }, ownKeys(target, key) { // 這里將副作用函數(shù)和唯一標(biāo)識(shí)ITERATE_KEY綁定了 track(target, ITERATE_KEY); return Reflect.ownKeys(target); }, deleteProperty(target, key) { // 檢查刪除的key是否為自身屬性 const hadKey = Object.prototype.hasOwnProperty.call(target, key); const res = Reflect.deleteProperty(target, key); if (res && hadKey) { trigger(target, key, "DELETE"); } return res; }, }); } function reactive(obj) { return createReactive(obj, true); } function shallowReactive(obj) { return createReactive(obj, false); }
6.只讀和淺只讀
實(shí)現(xiàn)只讀其實(shí)只需要在createReactiv函數(shù)中添上第三個(gè)參數(shù)isReadOnly。
function createReactive(obj, isShallow = false, isReadOnly = false) { return new Proxy(obj, { set(target, key, newVal, receiver) { if (isReadOnly) { console.warn(`屬性${key}是只讀的`); return true; } const oldVal = target[key]; // 獲取舊值 const type = Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; const res = Reflect.set(target, key, newVal, receiver); if (target === receiver.raw) { if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) { // 比較新值和舊值 trigger(target, key, type); } } return res; }, deleteProperty(target, key) { if (isReadOnly) { console.warn(`屬性${key}是只讀的`); return true; } // 檢查刪除的key是否為自身屬性 const hadKey = Object.prototype.hasOwnProperty.call(target, key); const res = Reflect.deleteProperty(target, key); if (res && hadKey) { trigger(target, key, "DELETE"); } return res; }, } }
當(dāng)然,對(duì)于設(shè)置了只讀屬性的對(duì)象的屬性,很明顯就沒(méi)必要添加依賴(lài)了,所以對(duì)于get也要進(jìn)行相應(yīng)的修改.
function createReactive(obj, isShallow = false, isReadOnly = false) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { // 通過(guò)獲取raw屬性,拿到初始對(duì)象 return target; } if (!isReadOnly) { // 只讀情況下不需要建立聯(lián)系 track(target, key); } const res = Reflect.get(target, key, receiver); if (isShallow) return res; // 如果淺層響應(yīng),直接返回 if (typeof res === "object") { // 如果獲取的值是對(duì)象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象 return reactive(res); } return res; }, } }
但是,上述操作只能做到淺只讀,深只讀實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,判斷只讀標(biāo)記然后遞歸添加只讀屬性就行了.
function createReactive(obj, isShallow = false, isReadOnly = false) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { // 通過(guò)獲取raw屬性,拿到初始對(duì)象 return target; } if (!isReadOnly) { // 只讀情況下不需要建立聯(lián)系 track(target, key); } const res = Reflect.get(target, key, receiver); if (isShallow) return res; // 如果淺層響應(yīng),直接返回 if (typeof res === "object" && res !== null) { // 如果獲取的值是對(duì)象,且只讀標(biāo)記的值為true,遞歸調(diào)用readonly函數(shù),得到深層只讀響應(yīng)對(duì)象.否則,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象 return isReadOnly ? readonly(res) : reactive(res); } return res; },
然后和reactive函數(shù)一樣,封裝一下只讀readonly函數(shù).
function readonly(obj) { return createReactive(obj, true, true); } function shallowReadonly(obj) { return createReactive(obj, false, true); }
7.代理數(shù)組
(1)讀取和修改操作
數(shù)組的讀取操作:
- 通過(guò)索引訪問(wèn)元素,arr[0]
- 訪問(wèn)數(shù)組長(zhǎng)度,arr.length
- for in循環(huán)訪問(wèn)arr對(duì)象
- for of循環(huán)訪問(wèn)arr對(duì)象
- 數(shù)組的原型方法,find,concat等
數(shù)組的修改操作:
- 通過(guò)索引修改數(shù)組,arr[0] = 1
- 修改數(shù)組長(zhǎng)度,arr.length = 1
- 數(shù)組的棧、隊(duì)列方法,arr.push
- 修改數(shù)組的原型方法,arr.slice,arr.sort等
對(duì)于通過(guò)索引訪問(wèn)這一操作,它實(shí)際上和普通對(duì)象是一樣的,都可以通過(guò)get直接攔截。但是對(duì)于通過(guò)索引修改這一操作,就稍有不同了,因?yàn)槿绻?dāng)前設(shè)置的索引>數(shù)組長(zhǎng)度的話,相應(yīng)的也會(huì)對(duì)數(shù)組的長(zhǎng)度進(jìn)行修改,而且在修改數(shù)組長(zhǎng)度的過(guò)程中,還需要對(duì)數(shù)組長(zhǎng)度的修改做出響應(yīng)。同時(shí),直接修改數(shù)組的length屬性也會(huì)造成影響,如果小于當(dāng)前數(shù)組長(zhǎng)度,那么會(huì)對(duì)差值內(nèi)元素進(jìn)行清楚操作,否則則對(duì)之前的元素沒(méi)有影響。
首先我們對(duì)應(yīng)修改數(shù)組索引設(shè)置這一操作:
function createReactive(obj, isShallow = false, isReadOnly = false) { return new Proxy(obj, { set(target, key, newVal, receiver) { if (isReadOnly) { // 如果對(duì)象只讀,提示報(bào)錯(cuò)信息 console.warn(`屬性${key}是只讀的`); return true; } const oldVal = target[key]; // 獲取舊值 // 判斷操作類(lèi)型,如果是數(shù)組類(lèi)型,則根據(jù)索引大小來(lái)判斷 const type = Array.isArray(target) ? Number(key) < target.length ? "SET" : "ADD" : Object.prototype.hasOwnProperty.call(target, key) ? "SET" : "ADD"; // 獲取操作類(lèi)型 const res = Reflect.set(target, key, newVal, receiver); if (target === receiver.raw) { if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) { trigger(target, key, type, newVal); // 添加第四個(gè)參數(shù) } } return res; } } }
然后修改trigger函數(shù),判斷是否為數(shù)組和ADD操作,然后添加length屬性的相關(guān)操作
// trigger函數(shù)添加第四個(gè)參數(shù)newVal,即觸發(fā)響應(yīng)的值 function trigger(target, key, type) { const depsMap = bucket.get(target); // 首先從對(duì)象桶中取出當(dāng)前對(duì)象的依賴(lài)表 if (!depsMap) return; const effects = depsMap.get(key); // 從依賴(lài)表中拿到當(dāng)前鍵值的依賴(lài)集合 const iterateEffects = depsMap.get(ITERATE_KEY); // 嘗試獲取for in循環(huán)操作的依賴(lài)集合 const effectToRun = new Set(); // 創(chuàng)建依賴(lài)執(zhí)行隊(duì)列 if (type === "ADD" && Array.isArray(target)) { // 如果操作類(lèi)型是ADD且對(duì)象類(lèi)型是數(shù)組,將length相關(guān)依賴(lài)添加到待執(zhí)行隊(duì)列中 const lengthEffects = depsMap.get("length"); lengthEffects && lengthEffects.forEach((fn) => { if (fn !== activeEffect) { effectToRun.add(effectFn); } }); } if (Array.isArray(target) && key === "length") { // 對(duì)于索引大于等于新length值的元素,需要將所有相關(guān)聯(lián)的函數(shù)取出添加到effectToRun中待執(zhí)行 if (key >= newVal) { effects.forEach((fn) => { if (fn !== activeEffect) { effectToRun.add(fn); } }); } }
(2)數(shù)組的遍歷
首先是for in循環(huán),會(huì)影響for in循環(huán)的操作主要是根據(jù)索引設(shè)置數(shù)組值和修改數(shù)組的length屬性,而這兩種操作,實(shí)際上都是對(duì)數(shù)組length值的操作,因此我們只需要在onwKeys方法里判斷,當(dāng)前操作的是否是數(shù)組,如果是數(shù)組的話,就使用length屬性作為key并建立聯(lián)系。
ownKeys(target, key) { // 這里將副作用函數(shù)和唯一標(biāo)識(shí)ITERATE_KEY綁定了 track(target, Array.isArray(target) ? "length" : ITERATE_KEY); // 進(jìn)行依賴(lài)收集 return Reflect.ownKeys(target); },
然后是for of循環(huán),它主要是通過(guò)和索引和length進(jìn)行操作,所以不需要進(jìn)行額外的操作,就可以實(shí)現(xiàn)依賴(lài)。但是在使用for of循環(huán)的時(shí)候,會(huì)對(duì)數(shù)組的Symbol.iterator屬性進(jìn)行讀取,該屬性是一個(gè)symbol值,為了避免發(fā)生意外錯(cuò)誤,以及性能上的考慮,需要對(duì)類(lèi)型為了symbol的值進(jìn)行隔離。
function createReactive(obj, isShallow = false, isReadOnly = false) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { // 通過(guò)獲取raw屬性,拿到初始對(duì)象 return target; } if (!isReadOnly && typeof key !== "symbol") { // 只讀情況和key值為symbol的情況下不需要建立聯(lián)系 track(target, key); } const res = Reflect.get(target, key, receiver); if (isShallow) return res; // 如果淺層響應(yīng),直接返回 if (typeof res === "object" && res !== null) { // 如果獲取的值是對(duì)象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象 return isReadOnly ? readonly(res) : reactive(res); } return res; }, } }
(3)數(shù)組的查找方法
arr.includes方法在正常情況下是可以正常觸發(fā)綁定的,因?yàn)閍rr.include方法會(huì)在查找過(guò)程中訪問(wèn)數(shù)組對(duì)象的length屬性和索引。但是在一些特殊的情況下,比如說(shuō)數(shù)組元素是對(duì)象的情況下,在我們目前的響應(yīng)式系統(tǒng)下,就會(huì)出現(xiàn)一些特殊的情況。
const obj = {}; const arr = reactive([arr]); console.log(arr.includes(arr)); // false
運(yùn)行上述代碼,得到的結(jié)果為false,這是因?yàn)樵谖覀冎按a設(shè)計(jì)中,如果讀取操作取到的值是一個(gè)可代理對(duì)象,那么我們會(huì)繼續(xù)對(duì)這個(gè)對(duì)象進(jìn)行代理。而進(jìn)行繼續(xù)代理后,得到的對(duì)象就是一個(gè)全新的對(duì)象了。
if (typeof res === "object" && res !== null) { // 如果獲取的值是對(duì)象,遞歸調(diào)用reactive函數(shù),得到深層響應(yīng)對(duì)象 return isReadOnly ? readonly(res) : reactive(res); }
對(duì)此,我們創(chuàng)建一個(gè)緩存Map,避免重復(fù)創(chuàng)建的問(wèn)題。
const reactiveMap = new Map(); function reactive(obj) { // 獲取當(dāng)前對(duì)象的緩存值 const existionProxy = reactiveMap.get(obj); // 如果當(dāng)前對(duì)象存在緩存值,直接返回 if (existionProxy) return existionProxy; // 否則創(chuàng)建新的響應(yīng)對(duì)象 const proxy = createReactive(obj, true); // 緩存新對(duì)象 reactiveMap.set(obj, proxy); return proxy; }
但是這個(gè)時(shí)候我們又會(huì)碰到一個(gè)新問(wèn)題,就是如果傳入原始對(duì)象,也就是obj的話,也會(huì)返回false,這是因?yàn)槲覀儠?huì)從arr中拿到的是響應(yīng)式對(duì)象,所以我們需要修改arr.includes的默認(rèn)行為。
const originMethod = Array.prototype.includes; const arrayInstrumentations = { includes: function (...args) { // this是代理對(duì)象,先在代理對(duì)象中進(jìn)行查找 let res = originMethod.apply(this, args); if (res === false) { // 如果在代理對(duì)象上無(wú)法找到,再到原始對(duì)象上找 res = originMethod.apply(this.raw, args); } return res; }, }; function createReactive(obj, isShallow = false, isReadOnly = false) { return new Proxy(obj, { get(target, key, receiver) { if (key === "raw") { // 通過(guò)獲取raw屬性,拿到初始對(duì)象 return target; } // 如果操作目標(biāo)是數(shù)組,而且key處于arrayInstrumentations之上,那么返回自定義的行為 if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) { return Reflect.get(arrayInstrumentations, key, receiver); } } } }
除了includes外,需要做類(lèi)似處理的還有indexof和lastIndexOf
const arrayInstrumentations = {}; ["includes", "indexof", "lastIndexof"].forEach((method) => { const originMethod = Array.prototype[method]; arrayInstrumentations[method] = function (...args) { // this 是代理對(duì)象,先在代理對(duì)象中查找,將結(jié)果存儲(chǔ)到 res 中 let res = originMethod.apply(this, args); // res 為 false 說(shuō)明沒(méi)找到,通過(guò) this.raw 拿到原始數(shù)組,再去其中查找,并更新 res 值 if (res === false || res === -1) { res = originMethod.apply(this.raw, args); } return res; }; });
(4)隱式修改數(shù)組的方法
主要有push、pop、shift、unshift和splice,以push為例,push在添加元素的同時(shí),也會(huì)讀取length屬性,而這回導(dǎo)致兩個(gè)獨(dú)立的副作用函數(shù)相互影響。因此我們也需要重寫(xiě)push操作,來(lái)避免這種情況的產(chǎn)生。這里我們添加一個(gè)是否進(jìn)行追蹤的標(biāo)記,在push方法執(zhí)行之前,將標(biāo)記置為false
let shouldTrack = true; // 是否進(jìn)行追蹤標(biāo)記 ["push"].forEach((method) => { const originMethod = Array.prototype[method]; arrayInstrumentations[method] = function (...args) { // 在調(diào)用原始方法之前,禁止追蹤 shouldTrack = false; // 默認(rèn)行為 let res = originMethod.apply(this, args); // 在調(diào)用原始方法之后,恢復(fù)原來(lái)的行為,即允許追蹤 shouldTrack = true; return res; }; }); function track(target, key) { if (!activeEffect || !shouldTrack) { // 如果沒(méi)有當(dāng)前執(zhí)行的副作用函數(shù),不進(jìn)行處理 return; } }
? 最后,修改所以該類(lèi)行為。
["push", "pop", "shift", "unshift", "splice"].forEach((method) => { const originMethod = Array.prototype[method]; arrayInstrumentations[method] = function (...args) { // 在調(diào)用原始方法之前,禁止追蹤 shouldTrack = false; // 默認(rèn)行為 let res = originMethod.apply(this, args); // 在調(diào)用原始方法之后,恢復(fù)原來(lái)的行為,即允許追蹤 shouldTrack = true; return res; }; });
到此這篇關(guān)于Vue reactive函數(shù)實(shí)現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)Vue reactive函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue + Elementui實(shí)現(xiàn)多標(biāo)簽頁(yè)共存的方法
這篇文章主要介紹了Vue + Elementui實(shí)現(xiàn)多標(biāo)簽頁(yè)共存的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06Vue注冊(cè)組件命名時(shí)不能用大寫(xiě)的原因淺析
這段時(shí)間一直在弄vue,當(dāng)然也遇到很多問(wèn)題,這里就來(lái)跟大家分享一些注冊(cè)自定義模板組件的心得 ,需要的朋友可以參考下2019-04-04純前端使用Vue3上傳文件到minio文件服務(wù)器(粘貼可直接用)
vue是目前最流行的前端框架,下面這篇文章主要給大家介紹了關(guān)于純前端使用Vue3上傳文件到minio文件服務(wù)器的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04詳解mpvue小程序中怎么引入iconfont字體圖標(biāo)
這篇文章主要介紹了詳解mpvue小程序中怎么引入iconfont字體圖標(biāo),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10Vue指令之 v-cloak、v-text、v-html實(shí)例詳解
當(dāng)用戶頻繁刷新頁(yè)面或網(wǎng)速慢時(shí),頁(yè)面未完成 Vue.js 的加載時(shí),導(dǎo)致 Vue 來(lái)不及渲染,這就會(huì)導(dǎo)致在瀏覽器中直接暴露插值(表達(dá)式),這篇文章主要介紹了Vue指令 v-cloak、v-text、v-html,需要的朋友可以參考下2019-08-08Vue實(shí)現(xiàn)移除數(shù)組中特定元素的方法小結(jié)
這篇文章主要介紹了Vue如何優(yōu)雅地移除數(shù)組中的特定元素,文中介紹了單個(gè)去除和批量去除的操作方法,并通過(guò)代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-03-03使用vue腳手架(vue-cli)搭建一個(gè)項(xiàng)目詳解
這篇文章主要介紹了vue腳手架(vue-cli)搭建項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05