Proxy对象浅析
打開 Chrome 控制臺(tái),輸入 window.Proxy ,你會(huì)發(fā)現(xiàn) JavaScript 已經(jīng)內(nèi)置了一個(gè)全局的 Proxy 對(duì)象,請(qǐng)問這個(gè)對(duì)象是做什么用的?其實(shí)你用關(guān)鍵詞「Proxy MDN」搜索一下,就能得到一個(gè)詳細(xì)的教程。(在關(guān)鍵詞后面加 MDN 是一個(gè)前端必備的小技巧哦)我今天只做一個(gè)簡單的介紹。
假設(shè)我們有一個(gè)數(shù)據(jù)(對(duì)象)data,內(nèi)容為
var data = {
username: 'Frank',
age: 26
}
現(xiàn)在我們給 data 創(chuàng)建一個(gè)代理 proxy
var proxy = new Proxy(data, {set: function(){...}, get: function(){...} })
那么,「proxy 就全權(quán)代理 data了」,這話是什么意思呢?意思就是 data 放假去了,如果你有任何事情要找 data,直接找 proxy 就好了,proxy 現(xiàn)在是 data 的秘書、代理人。比如原本你如果要改 username,那么應(yīng)該寫 data.username = 'frank';那么現(xiàn)在你只需要寫 proxy.username = 'frank' 就好了。原本你如果想寫 console.log(data.username),現(xiàn)在也只需要 console.log(proxy.username) 就可以了。
那么我們思考一下這樣做有什么意義呢?
意義就是你能監(jiān)控每一次對(duì) data 的讀寫操作。
proxy.username = 'frank' 這句話實(shí)際上會(huì)運(yùn)行 set 方法。set 方法可以對(duì)傳入的值進(jìn)行監(jiān)控和過濾。假設(shè) PM 要求「username 前后不能含有空格」,用 Proxy 就很好實(shí)現(xiàn)這一需求,只需要把 set 寫成這樣:
set: function(obj, prop, value){
obj[prop] = value.trim()
}
再假設(shè) PM 要求「統(tǒng)計(jì) username 被讀取的次數(shù)」,那么我們只需要把 get 寫成這樣:
get: function(obj, prop){
if(prop === 'username'){
count += 1
}
return obj[prop]
}
一、proxy對(duì)象是什么
Proxy 對(duì)象到底是什么呢?Proxy 的意思是代理,proxy對(duì)象的作用是:通過Proxy 創(chuàng)建1個(gè)代理對(duì)象,然后通過操作代理對(duì)象允許你對(duì)指定的對(duì)象的一些行為進(jìn)行自定義處理。
Proxy(target,handler); Proxy構(gòu)造函數(shù)接收2個(gè)對(duì)象,第1個(gè)參數(shù)就是要處理的對(duì)象,第2個(gè)參數(shù)就是要自定義處理的方法的合集(也就是個(gè)對(duì)象)。
很抽象?其實(shí)就和js中的Object.defineProperty很像。Object.defineProperty 定義訪問器屬性,可以對(duì)某個(gè)屬性的讀寫行為進(jìn)行控制,在Proxy中也可以做到,而且Proxy更靈活和強(qiáng)大,它能做到很多訪問器屬性做不到的事情。比如,監(jiān)聽屬性刪除事件(delete obj.prop;)、in 事件('id' in obj;)、apply調(diào)用等。
先來看看,proxy對(duì)象有哪些內(nèi)容。
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小學(xué)'
}
var handler = {};
// 此處,我們先不對(duì)targetObj的行為進(jìn)行干預(yù),所以傳個(gè)空對(duì)象進(jìn)去即可。
var proxy = new Proxy(targetObj,handler);
console.log(proxy);
看看打印的proxy是什么。
可以看到,proxy對(duì)象中,包含了Handler屬性和Target屬性和IsRevoked,它們的值分別是我們傳入的handler以及 targetObj和false。
這個(gè)isRevoked表示是否可撤銷,生成可撤銷的proxy對(duì)象用Proxy.revocable()方法,具體可去MDN查看文檔。
二、通過Proxy 對(duì)象操作原對(duì)象
上面我們創(chuàng)建了1個(gè)proxy對(duì)象,現(xiàn)在我們嘗試通過操作proxy對(duì)象來操作原對(duì)象,操作proxy對(duì)象就和操作原生對(duì)象一樣即可。(其實(shí)是proxy對(duì)象內(nèi)部做了映射。)
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小學(xué)'
}
var handler = {};
// 此處,我們先不對(duì)targetObj的行為進(jìn)行干預(yù),所以傳個(gè)空對(duì)象進(jìn)去即可。
var proxy = new Proxy(targetObj,handler)
/**
* 1、讀取及修改屬性,可以看到原來的對(duì)象的屬性也被修改了
*/
console.log(proxy.age); // 20
console.log(targetObj.age); // 20
proxy.age = 22;
console.log(proxy.age); // 22
console.log(targetObj.age); // 22
/**
* 2、刪除proxy對(duì)象的屬性,影響原來的對(duì)象的屬性
*/
console.log(proxy.school); // 小學(xué)
console.log(targetObj.school); // 小學(xué)
delete proxy.age;
console.log(proxy.age); // undefined
console.log(targetObj.age); // undefined
三、set方法和get方法
現(xiàn)在我們可以開始干預(yù)原來對(duì)象的行為了,具體我們通過實(shí)現(xiàn)以下方法達(dá)到干預(yù)對(duì)象行為的目的。
Proxy支持?jǐn)r截的操作,一共有13種:
get(target, propKey, receiver):攔截對(duì)象屬性的讀取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver):攔截對(duì)象屬性的設(shè)置,比如proxy.foo = v或proxy['foo'] = v,返回一個(gè)布爾值。
has(target, propKey):攔截propKey in proxy的操作,返回一個(gè)布爾值。
deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個(gè)布爾值。
ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環(huán),返回一個(gè)數(shù)組。該方法返回目標(biāo)對(duì)象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對(duì)象自身的可遍歷屬性。
getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對(duì)象。
defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個(gè)布爾值。
preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個(gè)布爾值。
getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個(gè)對(duì)象。
isExtensible(target):攔截Object.isExtensible(proxy),返回一個(gè)布爾值。
setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個(gè)布爾值。如果目標(biāo)對(duì)象是函數(shù),那么還有兩種額外操作可以攔截。
apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(...args)。
先來干預(yù)get行為(屬性讀取行為)
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小學(xué)'
}
var handler = {
// 定義get方法,get方法可以接收2個(gè)參數(shù),分別是原來的對(duì)象及屬性
get : function(target,prop){
console.log(`${prop}屬性正在被查看`);
console.log(targetObj == target); // true
return target[prop];
}
};
var proxy = new Proxy(targetObj,handler);
console.log(proxy.id);
/**
* 可以看到,打印順序?yàn)?
* id屬性正在被查看
* true
* 1
*/
接下來把某些屬性變?yōu)?“私有” ,如不允許讀取id屬性。定義set方法,不允許修改id,name,age屬性
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小學(xué)'
}
var handler = {
// 定義get方法,get方法可以接收2個(gè)參數(shù),分別是原來的對(duì)象及屬性
get : function(target,prop){
if(prop == 'id'){
return undefined;
}
return target[prop];
},
// 定義set方法,set方法比get多1個(gè)參數(shù),那就是該屬性修改時(shí)的值
set : function(target,prop,value){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允許修改${prop}屬性`)
}else{
target[prop] = value;
}
}
};
var proxy = new Proxy(targetObj,handler);
/**
* 修改屬性,分別打印
* 不允許修改id屬性
* 不允許修改name屬性
* 不允許修改age屬性
*/
proxy.id = 2;
proxy.name = 'pxh222';
proxy.age = 23;
proxy.school = '中學(xué)'; // 這個(gè)無打印
/**
* 讀取屬性,可以看到分別打印
* undefined
* pxh
* 20
* 中學(xué) // 這個(gè)沒有攔截,因此可以修改
*/
console.log(proxy.id);
console.log(proxy.name);
console.log(proxy.age);
console.log(proxy.school);
同樣的,我們對(duì)刪除對(duì)象屬性的行為進(jìn)行干預(yù),不允許刪除id,name,age屬性。
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小學(xué)'
}
var handler = {
// 在handler中定義get方法,get方法可以接收2個(gè)參數(shù),分別是原來的對(duì)象及屬性
get : function(target,prop){
if(prop == 'id'){
return undefined;
}
return target[prop];
},
// set方法比get多1個(gè)參數(shù),那就是該屬性修改時(shí)的值
set : function(target,prop,value){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允許修改${prop}屬性`)
}else{
target[prop] = value;
}
},
/**
* 這個(gè)方法要求返回個(gè)boolean值,表示是否刪除成功
* 如果返回的值不是boolean值,則會(huì)進(jìn)行類型轉(zhuǎn)換成boolean值再返回
*/
deleteProperty : function(target,prop){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允許刪除${prop}屬性`);
return false;
}else{
delete target[prop];
return true;
}
}
};
var proxy = new Proxy(targetObj,handler);
/**
* 嘗試刪除id屬性,可以看到打印順序?yàn)?
* 不允許刪除id屬性
* false
*/
console.log(delete proxy.id);
/**
* 刪除school屬性,可以看到打印
* true
* undefined
*/
console.log(delete proxy.school);
console.log(proxy.school);
上面我們不允許獲取對(duì)象的id值,也不可以修改和刪除,現(xiàn)在我們把它隱藏掉
var targetObj = {
id : 1,
name : 'pxh',
age : 20,
school : '小學(xué)'
}
var handler = {
// 在handler中定義get方法,get方法可以接收2個(gè)參數(shù),分別是原來的對(duì)象及屬性
get : function(target,prop){
if(prop == 'id'){
return undefined;
}
return target[prop];
},
// set方法比get多1個(gè)參數(shù),那就是該屬性修改時(shí)的值
set : function(target,prop,value){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允許修改${prop}屬性`)
}else{
target[prop] = value;
}
},
/**
* 這個(gè)方法要求返回個(gè)boolean值,表示是否刪除成功
* 如果返回的值不是boolean值,則會(huì)進(jìn)行類型轉(zhuǎn)換成boolean值再返回
*/
deleteProperty : function(target,prop){
if(prop == 'id' || prop == 'name' || prop == 'age'){
console.log(`不允許刪除${prop}屬性`);
return false;
}else{
delete target[prop];
return true;
}
},
/**
* 通過has 方法來控制,返回值也是個(gè)boolean,表示對(duì)象是否擁有某個(gè)屬性
* 如果返回的值不是boolean值,則會(huì)進(jìn)行類型轉(zhuǎn)換成boolean值再返回
*/
has : function(target,prop){
if(prop == 'id'){
return false
}else{
return prop in target;
}
}
};
var proxy = new Proxy(targetObj,handler);
console.log('id' in proxy); // false
console.log('name' in proxy); // true
更多實(shí)例用法,可以參考這篇博客:es6 Proxy對(duì)象詳解
同樣的方法,proxy還能干預(yù)對(duì)象的行為還有很多,感興趣可以看MDN文檔:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
總結(jié)
- 上一篇: 数据库表操作、数据类型及完整性约束
- 下一篇: 算法第一次作业