JavaScript如何監(jiān)測(cè)數(shù)組的變化
前言
之前介紹defineProperty的時(shí)候說(shuō)到,其只能監(jiān)測(cè)對(duì)象的變化,并不能監(jiān)測(cè)數(shù)組的變化。
本文致力于說(shuō)清楚怎么實(shí)現(xiàn)監(jiān)測(cè)數(shù)組的變化。
核心思路:找到改變?cè)瓟?shù)組的方法,然后對(duì)這些方法進(jìn)行劫持處理。
上面這句話,是重中之重,務(wù)必讀三遍,記住了,再往下走。
改變?cè)瓟?shù)組,常用到的方法有push pop shift unshift reverse sort splice。
換言之,這些方法是改變數(shù)組的入口。
在數(shù)組實(shí)例和數(shù)組原型之間,加一個(gè)新的原型
直接修改Array.prototype,是極其危險(xiǎn)的。
換一個(gè)思路,復(fù)制已有的數(shù)組原型,然后修改其中的方法,但這里因?yàn)樵蜕系姆椒ú豢擅杜e,自然也就沒法復(fù)制。
于是再換一個(gè)思路,在數(shù)組和數(shù)組的原型之間,插入一個(gè)原型,形成原型鏈,數(shù)組 => 新原型 => 數(shù)組的原型,可以在新原型上增加同名的方法就可以了。
先借助偽代碼理解:
// 偽代碼 let arr = []; arr.__proto__ = newPrototype; newPrototype.__proto__ = Array.prototype; // 然后可以在新原型上添加同名的方法就可以了 newPrototype.push = xxx;
轉(zhuǎn)換成真實(shí)的代碼如下,核心使用了Object.create,
// Object.create返回一個(gè)新對(duì)象,而來(lái)新對(duì)象的__proto__就是傳進(jìn)去的參數(shù)。 let newPrototype = Object.create(Array.prototype); // 然后可以在新原型上添加同名的方法就可以了 newPrototype.push = xxx; // 需要監(jiān)測(cè)的數(shù)組,綁定新的原型就可以了 let arr = []; arr.__proto__ = newPrototype;
以 push 為例,劫持 push
就是在新原型上重新寫一個(gè) push 的方法,里面執(zhí)行老的 push,但除此之外還可以做點(diǎn)別的事。
let newPrototype = Object.create(Array.prototype); // 在新原型上添加同名push newPrototype.push = function(...args) { // 語(yǔ)義化this let curArr = this; console.log("使用了push"); // 最后還是會(huì)執(zhí)行原始的push return Array.prototype.push.call(curArr, ...args); }; // 需要監(jiān)測(cè)的數(shù)組,綁定新的原型就可以了 let arr = []; arr.__proto__ = newPrototype; // 執(zhí)行push的時(shí)候,就會(huì)打印了 arr.push(1);
然后其他的方法也是類似的,試著寫寫其他的方法
劫持其他的方法
將其他方法也一起寫了,因?yàn)檫壿嬕粯樱苯颖闅v即可。
let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); return Array.prototype[method].call(this, ...args); }; }); // 需要監(jiān)測(cè)的數(shù)組,綁定新的原型就可以了 let arr = []; arr.__proto__ = newPrototype; // 執(zhí)行的時(shí)候,就會(huì)打印了 arr.push(1); arr.pop();
數(shù)組里有數(shù)組項(xiàng)的話,也是需要監(jiān)測(cè)的
這里數(shù)組里可能也是有數(shù)組的,需要遍歷數(shù)組的每項(xiàng),如果是數(shù)組的話依然需要指向新的原型。
嗯,對(duì),用到遞歸了。
let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); return Array.prototype[method].call(this, ...args); }; }); function observeArr(arr) { // 既是條件限制,也是遞歸的終止條件 if (!Array.isArray(arr)) { return; } // 整個(gè)數(shù)組指向新的原型 arr.__proto__ = newPrototype; // 數(shù)組的每項(xiàng),如果是數(shù)組,也指向新的原型。 arr.forEach(observeArr); } // 需要監(jiān)測(cè)的數(shù)組,綁定新的原型就可以了 let arr = [[1, 2, 3]]; observeArr(arr); // 執(zhí)行的時(shí)候,就會(huì)打印了 arr[0].push(1); arr[1].pop();
數(shù)組新添加的項(xiàng),如果是數(shù)組也需要指向新的原型
能添加元素的方法:push unshift splice。
找到新加的元素,然后是數(shù)組的也同樣指向新的原型
let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; default: break; } inserted && observeArr(inserted); return Array.prototype[method].call(this, ...args); }; }); function observeArr(arr) { // 即是條件限制,也是遞歸的終止條件 if (!Array.isArray(arr)) { return; } // 整個(gè)數(shù)組指向新的原型 arr.__proto__ = newPrototype; // 數(shù)組的每項(xiàng),如果是數(shù)組,也指向新的原型。 arr.forEach(observeArr); } // 這里可以導(dǎo)出去,方便別的文件使用 export default observeArr; // 需要監(jiān)測(cè)的數(shù)組,綁定新的原型就可以了 let arr = []; observeArr(arr); let addItem = [1, 2, 3]; arr.push(addItem); // 執(zhí)行的時(shí)候,就會(huì)打印了 addItem.push(1); addItem.pop();
綜合使用 defineProperty 監(jiān)測(cè)對(duì)象和數(shù)組
現(xiàn)在已經(jīng)有了監(jiān)測(cè)對(duì)象的方法,也有了監(jiān)測(cè)數(shù)組的方法,將兩個(gè)綜合起來(lái),就能監(jiān)測(cè)數(shù)組里面的對(duì)象,對(duì)象里面的數(shù)組了。
將監(jiān)測(cè)數(shù)組和監(jiān)測(cè)對(duì)象的的可以單獨(dú)寫成一個(gè)文件,方便之后使用。
這里為了方便直接運(yùn)行代碼,直接放在一塊了。
/** * observeArr的部分 **/ // 生成新的原型 let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; // 在新原型上面添加以上方法,實(shí)現(xiàn)劫持 methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; default: break; } inserted && observeArr(inserted); return Array.prototype[method].call(this, ...args); }; }); function observeArr(arr) { // 新加?。。∈菍?duì)象的話,需要用對(duì)象 if (Object.prototype.toString.call(arr) === "[object Object]") { observeObj(arr); return; } if (Array.isArray(arr)) { // 整個(gè)數(shù)組指向新的原型 arr.__proto__ = newPrototype; // 數(shù)組的每項(xiàng),如果是數(shù)組,也指向新的原型。 arr.forEach(observeArr); } // 不是對(duì)象或者數(shù)組的,什么都不做 } /** * observeObj的部分 **/ function observeObj(obj) { // 加上參數(shù)限制,必須是對(duì)象才有劫持,也是遞歸的終止條件 if (typeof obj !== "object" || obj == null) { return; } // 新加!?。?shù)組交給數(shù)組處理 if (Array.isArray(obj)) { observeArr(obj); return; } // 是對(duì)象的話 才開始遞歸 for (let key in obj) { // 直接使用 obj.hasOwnProperty會(huì)提示不規(guī)范 if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(obj, key); // 這里劫持該屬性的屬性值,如果不是對(duì)象直接返回,不影響 observeObj(obj[key]); } } return obj; } function observeKey(obj, key) { let value = obj[key]; Object.defineProperty(obj, key, { get() { console.log("讀取屬性", value); return value; }, set(newValue) { console.log("設(shè)置屬性", newValue); value = newValue; } }); } /** * demo試試 **/ let data = { a: 1, b: [1, 2, { c: 2 }] }; observeObj(data); data.a = 2; data.b.push([2, 3]); let arr = [{ a: "數(shù)組里的對(duì)象" }, 3, 4]; observeArr(arr); arr[0].a = 3;
缺陷
當(dāng)然數(shù)組其實(shí)可以不通過(guò)方法改變,比如直接刪除數(shù)組可以直接使用 length 屬性,或者直接arr[0]=xxx改變數(shù)組。
但只有當(dāng)使用"push","pop", "shift","unshift","reverse","sort","splice"才能檢測(cè)到數(shù)組變化。
這也是 vue 的缺陷,當(dāng)然新版的 proxy 將干掉這個(gè)缺陷。
所以在使用 vue 的過(guò)程中,要盡量使用以上方法操作數(shù)組~~~
附注:查看數(shù)組的所有屬性和方法
在控制臺(tái)可以輸入dir([]),然后能看到數(shù)組所有的屬性和方法。
具體用法,可以直接到到mdn 上細(xì)看,點(diǎn)擊側(cè)邊欄看對(duì)應(yīng)的方法
總結(jié)
到此這篇關(guān)于JavaScript如何監(jiān)測(cè)數(shù)組變化的文章就介紹到這了,更多相關(guān)JS監(jiān)測(cè)數(shù)組變化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS性能優(yōu)化實(shí)現(xiàn)方法及優(yōu)點(diǎn)進(jìn)行
這篇文章主要介紹了JS性能優(yōu)化實(shí)現(xiàn)方法及優(yōu)點(diǎn)進(jìn)行,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08JS踩坑實(shí)戰(zhàn)之19位數(shù)Number型精度丟失問(wèn)題詳析
前幾天測(cè)試接口功能的時(shí)候,發(fā)現(xiàn)了一個(gè)奇怪的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于JS?Number型精度丟失問(wèn)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10利用TypeScript從字符串字面量類型提取參數(shù)類型
這篇文章主要介紹了利用TypeScript從字符串字面量類型提取參數(shù)類型,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09js/html光標(biāo)定位的實(shí)現(xiàn)代碼
光標(biāo)定位,想必大家有所了解吧,在本文將為大家介紹的是通過(guò)自定義函數(shù)來(lái)實(shí)現(xiàn)標(biāo)簽元素的定位,感興趣的朋友可以了解下2013-09-09關(guān)于ligerui子頁(yè)面關(guān)閉后,父頁(yè)面刷新,重新加載的方法
今天小編就為大家分享一篇關(guān)于ligerui子頁(yè)面關(guān)閉后,父頁(yè)面刷新,重新加載的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09