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