JavaScript defineProperty如何實(shí)現(xiàn)屬性劫持
前言
defineProperty是vue實(shí)現(xiàn)數(shù)據(jù)劫持的核心,本文一點(diǎn)點(diǎn)的說明defineProperty怎么實(shí)現(xiàn)屬性劫持的。
其實(shí)我們一般的操作對象屬性的方式,增加或者修改屬性,均可以使用Object.defineProperty。
let obj = {};
// 尋常操作:增加/修改 新屬性
obj.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
當(dāng)然尋常的例子,我們是不會這么玩的,太啰嗦了。
但defineProperty可以更精確地添加或修改對象的屬性。
描述符
先說個專有名詞:描述符。
其實(shí)就是defineProperty的第三個參數(shù),是個對象。這個對象的有以下屬性:
- configurable 屬性:能不能修改描述符,就是能不能再次修改描述符的其他屬性
- enumerable 屬性:能不能枚舉該屬性,就是 a 屬性能不能被 for 到
- writable 屬性:能不能修改屬性值,就是能不能這樣修改obj.a = 1
- value 屬性:該屬性的值
- get 屬性:是個函數(shù),當(dāng)訪問該屬性的時候,函數(shù)自動調(diào)用,函數(shù)返回值就是該屬性的值
- set 屬性:是個函數(shù),當(dāng)修改該屬性的時候,函數(shù)自動調(diào)用,函數(shù)有且只有一個參數(shù),賦值的新值
注意!??!
- 描述符里的value屬性 writable屬性 與 get屬性 set屬性是互斥的關(guān)系,只能存在一個
- 另外的屬性默認(rèn)值都是false,不想false的話,記得配置哈,不細(xì)說(主要我也不怎么用)。
細(xì)說get 和 set
- get 屬性:是個函數(shù),當(dāng)訪問該屬性的時候,函數(shù)自動調(diào)用,函數(shù)返回值就是該屬性的值
- set 屬性:是個函數(shù),當(dāng)修改該屬性的時候,函數(shù)自動調(diào)用,函數(shù)有且只有一個參數(shù),賦值的新值
默念三遍,背誦。
寫個get 和 set 的例子輔助理解。
這個例子必須掌握,弄懂之后基本就掌握了數(shù)據(jù)劫持的精髓了
let obj = {};
let value = 1;
Object.defineProperty(obj, "b", {
get() {
console.log("讀取b屬性", value);
return value;
},
set(newValue) {
console.log("設(shè)置b屬性", newValue);
value = newValue;
}
});
// 觸發(fā)get函數(shù),get的返回值就是屬性值
// 1
console.log(obj.b);
// 觸發(fā)set函數(shù),value的值變成了2,注意?。?!,此時內(nèi)存里,屬性值并沒有改變
obj.b = 2;
// 但是,想要讀取屬性值的時候,就必然會觸發(fā)get函數(shù),屬性值也自然就改變了,這個思想真的很贊
console.log(obj.b);
這里有個坑:get里是不能有讀取的操作,不然一直死循環(huán),所以使用到get set的地方,總需要借助一個變量
所以,這里,變量value的值就是屬性的值,如果想要修改屬性,修改 value 的值即可。
這個例子弄懂了,get,set 的精髓,我覺得也就差不多了。
劫持對象的某個屬性
有了剛剛例子的基礎(chǔ),試著寫寫劫持對象的任意一個屬性。
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;
}
});
}
let obj = { a: 1 };
observeKey(obj, "a");
// 讀取a,觸發(fā)get函數(shù)
console.log(obj.a);
// 設(shè)置a,觸發(fā)set函數(shù)
obj.a = 1;
劫持對象的所有屬性
再試試劫持對象的所有屬性
其實(shí)就是遍歷:
function observeObj(obj) {
for (let key in obj) {
// 直接使用 obj.hasOwnProperty會提示不規(guī)范
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("設(shè)置屬性", newValue);
value = newValue;
}
});
}
let obj = { a: 1, b: 2 };
observeObj(obj);
console.log(obj);
// 讀取a,觸發(fā)get函數(shù)
console.log(obj.a);
// 設(shè)置a,觸發(fā)set函數(shù)
obj.a = 1;
劫持對象的所有屬性 - 包括對象類型的屬性值
上面的有個缺陷,就是當(dāng)屬性值也是對象的時候,不能劫持屬性值,如{a:1,c:{b:1}}
簡單,遞歸,補(bǔ)上就行。
function observeObj(obj) {
// 加上參數(shù)限制,必須是對象才有劫持,也是遞歸的終止條件
if (typeof obj !== "object" || obj == null) {
return;
}
for (let key in obj) {
// 直接使用 obj.hasOwnProperty會提示不規(guī)范
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("設(shè)置屬性", newValue);
value = newValue;
}
});
}
let obj = { a: 1, b: 2, c: { name: "c" } };
observeObj(obj);
console.log(obj);
// 讀取a,觸發(fā)get函數(shù)
console.log(obj.a);
// 設(shè)置a,觸發(fā)set函數(shù)
obj.a = 1;
// 觸發(fā)set函數(shù)
obj.c.name = "d";
注意,observeObj這個函數(shù),不能劫持對象的新增屬性,只能劫持對象已有的屬性。
defineProperty的缺陷
- 不能監(jiān)測對象增加屬性
- 不能監(jiān)測對象刪除屬性
- 不能劫持?jǐn)?shù)組的修改
當(dāng)然數(shù)組的修改可以通過別的方式監(jiān)測到的,其是通過劫持改變數(shù)組方法實(shí)現(xiàn)的。
以上缺陷,也是vue里面為啥有$set/$delete以及對數(shù)組只能使用特定方法才能檢測到。
let obj = { a: 1, b: [1, 2] };
observeObj(obj);
// 新增屬性
obj.c = 3;
// 不會觸發(fā)get函數(shù)
console.log(obj.c);
// 不會觸發(fā)set函數(shù)
obj.b.push(3);
defineProperty還可以掛載屬性
其實(shí)就是訪問options.data.name 可以簡寫成 options.name,專業(yè)話術(shù),將data上的屬性掛載到options上
相當(dāng)于,用defineProperty,在options上增加新屬性:
// 先掛載單個屬性
// options.data相當(dāng)于source options相當(dāng)于target
function proxyKey(target, source, key) {
Object.defineProperty(target, key, {
// 這里的source[key]相當(dāng)于變量value,所以說最簡單的那個例子是核心
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會提示不規(guī)范
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);
話說,vue的屬性劫持和掛載屬性,核心原理差不多就是上面這些。
defineProperty還能寫日志
比如 obj 有個屬性,此屬性值經(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);
通用的可以抽離出一個類,專門記錄某個值的變化
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);
引用
總結(jié)
到此這篇關(guān)于JavaScript defineProperty如何實(shí)現(xiàn)屬性劫持的文章就介紹到這了,更多相關(guān)defineProperty屬性劫持內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Canvas實(shí)現(xiàn)動態(tài)粒子文字效果的代碼示例
這篇文章主要介紹了如何用Canvas實(shí)現(xiàn)動態(tài)粒子文字效果,文中有完整的代碼示例,文章通過代碼介紹的非常清楚,感興趣的小伙伴跟著小編一起來看看吧2023-08-08
如何用CocosCreator實(shí)現(xiàn)射擊小游戲
這篇文章主要介紹了如何用CocosCreator實(shí)現(xiàn)射擊小游戲,此游戲難度不大,僅作為入門的練手小游戲,一小時就能完成,里面用到的知識很常用,喜歡游戲的同學(xué),可以參考下2021-04-04
關(guān)于layui flow loading占位圖的實(shí)現(xiàn)方法
今天小編就為大家分享一篇關(guān)于layui flow loading占位圖的實(shí)現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
JS實(shí)現(xiàn)選定指定HTML元素對象中指定文本內(nèi)容功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)選定指定HTML元素對象中指定文本內(nèi)容功能,涉及javascript針對HTML頁面元素的運(yùn)算與選定相關(guān)操作技巧,需要的朋友可以參考下2017-02-02
ion content 滾動到底部會遮住一部分視圖的快速解決方法
本文給大家?guī)砹薸on content 滾動到底部會遮住一部分視圖的快速解決方法,其實(shí)解決方法超簡單的,只要在你的controller里面預(yù)先注入$ionicScrollDelegate就可以了,感興趣的朋友通過本文一起學(xué)習(xí)吧2016-09-09

