一、定義
Proxy用于修改某些操作的默認(rèn)行為,可以理解成在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問進(jìn)行過濾和改寫。換句話說就是Proxy對(duì)象就是允許對(duì)JS中的一切合法對(duì)象的基本操作進(jìn)行自定義,然后用自定義的操作去覆蓋其對(duì)象的基本操作,說白了就是重寫其所屬類或構(gòu)造函數(shù)中的基本操作
二、語法
let proxy = new Proxy(target, handler)
target:目標(biāo)對(duì)象(待操作對(duì)象),需要使用Proxy包裝的目標(biāo)對(duì)象(可以是任何類型的對(duì)象,包括原生數(shù)組、函數(shù),甚至另一個(gè)代理)
handler:自定義操作方法的一個(gè)對(duì)象,,最多可包含13個(gè)操作方法,也可以是空對(duì)象
proxy:一個(gè)被代理后的新對(duì)象,它擁有target的一切屬性和方法,只不過其行為和結(jié)果是在handler中自定義的。在一定程度上可以看成是對(duì)target的引用
三、作用
1、在目標(biāo)對(duì)象之前架設(shè)一層攔截,外界對(duì)該對(duì)象的訪問都必須先通過這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問進(jìn)行過濾和改寫
2、ES6原生提供Proxy構(gòu)造函數(shù),用來生成Proxy實(shí)例
3、通過handler對(duì)象中的攔截方法攔截目標(biāo)對(duì)象target的某些操作(如讀取、賦值、函數(shù)調(diào)用、new等),然后過濾或者改寫這些操作的默認(rèn)行為
4、生成的實(shí)例對(duì)象是針對(duì)target對(duì)象的攔截器,也可以叫做代理器
5、如果handler是個(gè)空對(duì)象({}),那么操作攔截器相當(dāng)于直接操作目標(biāo)對(duì)象target
四、Proxy攔截器的13種方法(只介紹其中的兩種)
1、get方法
get(target, propKey, receiver):攔截某個(gè)屬性的讀取操作,接收三個(gè)參數(shù),依次為目標(biāo)對(duì)象、屬性名、和Proxy實(shí)例本身,最后一個(gè)參數(shù)是可選的,一般情況下指向的是proxy對(duì)象,但是如果proxy作為其他對(duì)象的原型時(shí),則指向讀取該屬性的對(duì)象
var person = {
name: "張三"
};
var proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property];
}else{
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name // "張三"
proxy.age // 拋出一個(gè)錯(cuò)誤
2、set方法
set(target, propKey, value, receiver):攔截某個(gè)屬性的賦值操作,接收4個(gè)參數(shù),依次為目標(biāo)對(duì)象、屬性名、屬性值和Proxy實(shí)例本身,最后一個(gè)參數(shù)是可選的。返回一個(gè)布爾值
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 對(duì)于age以外的屬性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age) // 100
person.age = 'young' // 報(bào)錯(cuò)
person.age = 300 // 報(bào)錯(cuò)
五、實(shí)例
1、get方法應(yīng)用
let test = {
name: "Julian小龍蝦"
};
test = new Proxy(test, {
get(target, key) {
console.log('獲取getter屬性');
return target[key];
}
});
console.log(test.name);
解析:首先創(chuàng)建一個(gè)test對(duì)象,里面有name屬性,然后使用Proxy將其包裝起來,再返回給test,此時(shí)的test已經(jīng)成為一個(gè)Proxy實(shí)例,我們對(duì)其的操作都會(huì)被Proxy攔截。handler中定義了get函數(shù),當(dāng)獲取test屬性時(shí)會(huì)觸發(fā)此函數(shù)
2、set方法應(yīng)用
let julian = {
name: "Julian",
age: 18
};
julian = new Proxy(julian, {
get(target, key) {
let result = target[key];
//如果是獲取 年齡 屬性,則添加 歲字
if (key === "age"){
result += "歲";
}
return result;
},
set(target, key, value) {
if (key === "age" && typeof value !== "number") {
throw Error("age字段必須為Number類型");
}
return Reflect.set(target, key, value); //將target對(duì)象的name屬性設(shè)置為value。返回值為 boolean ,true 表示修改成功,false 表示失敗。當(dāng) target 為不存在的對(duì)象時(shí),會(huì)報(bào)錯(cuò)
}
});
console.log(`我叫${julian.name} 今年${julian.age}了`); // 獲取name和age的屬性值
julian.age = "not a number"; // 報(bào)錯(cuò)
//julian.age = 20;
//console.log(`我叫${julian.name} 我今年${julian.age}了`); // 正常輸出并修改了age的值
解析:定義了julian對(duì)象,其中有age和name兩個(gè)字段,我們在Proxy的get攔截函數(shù)中添加了一個(gè)判斷,如果是取age屬性的值,則在后面添加歲。在set攔截函數(shù)中判斷如果是更改age屬性時(shí),類型不是number則拋出錯(cuò)誤。
六、ES3、ES5、ES6實(shí)現(xiàn)同一個(gè)例子的對(duì)比
要求:有一組數(shù)據(jù),有name、age、sex三個(gè)屬性,其中name和age是可讀可寫屬性,但是sex是只讀屬性,用ES3、ES5、ES6分別實(shí)現(xiàn)
實(shí)現(xiàn)步驟:首先頂一個(gè)構(gòu)造函數(shù),內(nèi)部定義一個(gè)data數(shù)據(jù),包含name、age、sex三個(gè)屬性,一個(gè)get方法,一個(gè)set方法。get方法用來讀數(shù)據(jù),set方法用來寫數(shù)據(jù),寫數(shù)據(jù)的時(shí)候進(jìn)行判斷,如果設(shè)置的是sex屬性就給出錯(cuò)誤提示
1、ES3實(shí)現(xiàn)
var Person = function(){
var data = {
name:'Julian小龍蝦',
age:18,
sex:'男'
}
this.get = function(key){
console.log(key)
return data[key]
}
this.set = function(key,value){
if(key!=='sex'){
return data[key] = value
}else{
throw '該屬性為只讀屬性'
}
}
}
var person = new Person;
var name = person.get('name')
person.set('sex','女')
console.log(person.get('sex'))

2、ES5實(shí)現(xiàn):通過defineProperty方法設(shè)置sex戶型為不可寫屬性
var person = {
name:'Julian小龍蝦',
age:30
}
Object.defineProperty(person,'sex',{
writable:false,
value:'男'
})
person.sex = '女'
console.log(person.sex) // 男
3、ES6實(shí)現(xiàn)
var person = {
name:'Julian小龍蝦',
age:18,
sex:'男'
}
var p1 = new Proxy(person,{
get(target,key){
console.log(target)
console.log(key)
return target[key]
},
set(target,key,value){
if(key=='sex'){
throw '不允許修改sex'
}else{
target[key] = value
}
}
})
p1.name
p1.sex = '女'
