- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > web開(kāi)發(fā) > JavaScript >
- JavaScript defineProperty如何實(shí)現屬性劫持
defineProperty是vue實(shí)現數據劫持的核心,本文一點(diǎn)點(diǎn)的說(shuō)明defineProperty怎么實(shí)現屬性劫持的。
其實(shí)我們一般的操作對象屬性的方式,增加或者修改屬性,均可以使用Object.defineProperty。
let obj = {}; // 尋常操作:增加/修改 新屬性 obj.a = 1; // 等同于: Object.defineProperty(o, "a", { value: 1, writable: true, configurable: true, enumerable: true });
當然尋常的例子,我們是不會(huì )這么玩的,太啰嗦了。
但defineProperty可以更精確地添加或修改對象的屬性。
先說(shuō)個(gè)專(zhuān)有名詞:描述符。
其實(shí)就是defineProperty的第三個(gè)參數,是個(gè)對象。這個(gè)對象的有以下屬性:
注意?。?!
默念三遍,背誦。
寫(xiě)個(gè)get 和 set 的例子輔助理解。
這個(gè)例子必須掌握,弄懂之后基本就掌握了數據劫持的精髓了
let obj = {}; let value = 1; Object.defineProperty(obj, "b", { get() { console.log("讀取b屬性", value); return value; }, set(newValue) { console.log("設置b屬性", newValue); value = newValue; } }); // 觸發(fā)get函數,get的返回值就是屬性值 // 1 console.log(obj.b); // 觸發(fā)set函數,value的值變成了2,注意?。?!,此時(shí)內存里,屬性值并沒(méi)有改變 obj.b = 2; // 但是,想要讀取屬性值的時(shí)候,就必然會(huì )觸發(fā)get函數,屬性值也自然就改變了,這個(gè)思想真的很贊 console.log(obj.b);
這里有個(gè)坑:get里是不能有讀取的操作,不然一直死循環(huán),所以使用到get set的地方,總需要借助一個(gè)變量
所以,這里,變量value的值就是屬性的值,如果想要修改屬性,修改 value 的值即可。
這個(gè)例子弄懂了,get,set 的精髓,我覺(jué)得也就差不多了。
有了剛剛例子的基礎,試著(zhù)寫(xiě)寫(xiě)劫持對象的任意一個(gè)屬性。
function observeKey(obj, key) { let value = obj[key]; Object.defineProperty(obj, key, { get() { console.log("讀取屬性", value); return value; }, set(newValue) { console.log("設置屬性", newValue); value = newValue; } }); } let obj = { a: 1 }; observeKey(obj, "a"); // 讀取a,觸發(fā)get函數 console.log(obj.a); // 設置a,觸發(fā)set函數 obj.a = 1;
再試試劫持對象的所有屬性
其實(shí)就是遍歷:
function observeObj(obj) { for (let key in obj) { // 直接使用 obj.hasOwnProperty會(huì )提示不規范 if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(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("設置屬性", newValue); value = newValue; } }); } let obj = { a: 1, b: 2 }; observeObj(obj); console.log(obj); // 讀取a,觸發(fā)get函數 console.log(obj.a); // 設置a,觸發(fā)set函數 obj.a = 1;
上面的有個(gè)缺陷,就是當屬性值也是對象的時(shí)候,不能劫持屬性值,如{a:1,c:{b:1}}
簡(jiǎn)單,遞歸,補上就行。
function observeObj(obj) { // 加上參數限制,必須是對象才有劫持,也是遞歸的終止條件 if (typeof obj !== "object" || obj == null) { return; } for (let key in obj) { // 直接使用 obj.hasOwnProperty會(huì )提示不規范 if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(obj, key); // 這里劫持該屬性的屬性值,如果不是對象直接返回,不影響 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("設置屬性", newValue); value = newValue; } }); } let obj = { a: 1, b: 2, c: { name: "c" } }; observeObj(obj); console.log(obj); // 讀取a,觸發(fā)get函數 console.log(obj.a); // 設置a,觸發(fā)set函數 obj.a = 1; // 觸發(fā)set函數 obj.c.name = "d";
注意,observeObj這個(gè)函數,不能劫持對象的新增屬性,只能劫持對象已有的屬性。
當然數組的修改可以通過(guò)別的方式監測到的,。
以上缺陷,也是vue里面為啥有$set/$delete以及對數組只能使用特定方法才能檢測到。
let obj = { a: 1, b: [1, 2] }; observeObj(obj); // 新增屬性 obj.c = 3; // 不會(huì )觸發(fā)get函數 console.log(obj.c); // 不會(huì )觸發(fā)set函數 obj.b.push(3);
其實(shí)就是訪(fǎng)問(wèn)options.data.name 可以簡(jiǎn)寫(xiě)成 options.name,專(zhuān)業(yè)話(huà)術(shù),將data上的屬性?huà)燧d到options上
相當于,用defineProperty,在options上增加新屬性:
// 先掛載單個(gè)屬性 // options.data相當于source options相當于target function proxyKey(target, source, key) { Object.defineProperty(target, key, { // 這里的source[key]相當于變量value,所以說(shuō)最簡(jiǎn)單的那個(gè)例子是核心 get() { return source[key]; }, set(newValue) { if (newValue === source[key]) { return; } source[key] = newValue; } }); } // 遍歷屬性,掛載下 function proxyObj(target, source) { for (let key in source) { // 直接使用 obj.hasOwnProperty會(huì )提示不規范 if (Object.prototype.hasOwnProperty.call(source, key)) { proxyKey(target, source, key); } } } let options = { data: { name: 1 } }; proxyObj(options, options.data); // 1 console.log(options.name);
話(huà)說(shuō),vue的屬性劫持和掛載屬性,核心原理差不多就是上面這些。
比如 obj 有個(gè)屬性,此屬性值經(jīng)常變化,想要記錄其所有變化的值,以此可以形成日志。
let obj = { a: 1 }; let log = [obj.a]; let value = obj.a; Object.defineProperty(obj, "a", { get() { return value; }, set(newValue) { if (newValue === value) { return; } value = newValue; log.push(newValue); } }); obj.a = 2; obj.a = 3; obj.a = 4; // [1,2,3,4] console.log(log);
通用的可以抽離出一個(gè)類(lèi),專(zhuān)門(mén)記錄某個(gè)值的變化
class Archiver { constructor() { let value = null; this.archive = []; Object.defineProperty(this, "a", { get() { return value; }, set(newValue) { if (newValue === value) { return; } value = newValue; this.archive.push(newValue); } }); } } let archiver = new Archiver(); archiver.a = 1; archiver.a = 2; // [1,2] console.log(archiver.archive);
引用
到此這篇關(guān)于JavaScript defineProperty如何實(shí)現屬性劫持的文章就介紹到這了,更多相關(guān)defineProperty屬性劫持內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站