# ES6之proxy
## 是什么
Proxy是一個(gè)構(gòu)造器覆醇。通過(guò)new Proxy(原對(duì)象,{代理列表})的方式去創(chuàng)建對(duì)象,創(chuàng)建的這個(gè)對(duì)象我們稱之為代理對(duì)象刁笙。
即:
> 代理對(duì)象 = new Proxy(原對(duì)象,{代理列表})
之所以要額外產(chǎn)生這么一個(gè)代理對(duì)象贸典,好處在于可以保持原對(duì)象不變,在代理對(duì)象中添加新的功能眨层,或者是改造某些功能。而這個(gè)原對(duì)象則可以在適當(dāng)?shù)臅r(shí)機(jī)回滾回去失受。可以與設(shè)計(jì)模式中的代理模式對(duì)比理解咏瑟。
## 使用格式
```
var obj;
var proxyObj = new Proxy(obj, {
? ? 對(duì)obj的操作1: 函數(shù)1拂到,
? ? 對(duì)obj的操作2: 函數(shù)2,
? ? ...
? ?
})
```
## 入門示例
### Proxy的基本示范
```
var obj = {name:'fan',age:34}
console.info(obj.name)
var proxyObj = new Proxy(obj,{
? ? get:function(target,key,receiver){console.info(target,key,receiver); return 'no'}
})
console.info(proxyObj.name)
console.info(proxyObj.abc)
```
解釋如下:
- proxxy對(duì)象是在obj對(duì)象的基礎(chǔ)之上創(chuàng)建的一個(gè)新對(duì)象码泞。
- proxyObj.name是要去獲取proxy對(duì)象的name屬性兄旬。`.操作符會(huì)自動(dòng)去調(diào)用get()方法`。這一點(diǎn)非常重要余寥,`在js中领铐,對(duì)象是屬性的無(wú)序集合。對(duì)象只有屬性宋舷,其他什么都沒(méi)有`. 而我們經(jīng)常說(shuō)的調(diào)用對(duì)象的某個(gè)方法:例如數(shù)組對(duì)象arr的sort方法:arr.sort()绪撵,這里的sort也是arr對(duì)象的屬性(更嚴(yán)謹(jǐn)一點(diǎn),sort是arr.__proto__這個(gè)對(duì)象的屬性)肥缔,與length屬性相比,sort屬性的屬性值是一個(gè)函數(shù)汹来,所以在它的后面加()來(lái)執(zhí)行這個(gè)函數(shù)续膳,而length屬性的值是一個(gè)數(shù)值,所以不需要加()就可以直接使用收班。再次強(qiáng)調(diào)一下:`對(duì)象的.操作坟岔,會(huì)自動(dòng)去調(diào)用get`。當(dāng)然摔桦,我們平時(shí)使用.操作時(shí)社付,是沒(méi)有感知到這一點(diǎn)的。
- 在new Proxy的第二個(gè)參數(shù)中邻耕,明確設(shè)置了get的方法:當(dāng)訪問(wèn)proxyObj的任意屬性時(shí)鸥咖,輸出target,key,receiver的值,并統(tǒng)一返回no兄世。所以proxyObj.name和proxyObj.abc都會(huì)得到no啼辣。
寫到這里你會(huì)覺得原對(duì)象與代理對(duì)象之間有什么關(guān)系呢?為什么叫`代理`呢御滩?
### 理解代理的作用
代理對(duì)象可以理解為明星的經(jīng)紀(jì)人鸥拧。
```
外界 <----> 原對(duì)象党远;
外界 <----> 代理對(duì)象 <------> 原對(duì)象;
```
還以上面的代碼為例富弦,改進(jìn)一下需求:如果有人問(wèn)obj的名字沟娱,就直接告訴對(duì)方; 如果有人問(wèn)obj的年齡腕柜,就返回小5歲的年齡济似。
```
var obj = {name:'fan',age:34}
console.info(obj.age)? ? ? ? ? // 34
var proxyObj = new Proxy(obj,{
? ? get:function(target,key,receiver){
? ? ? ? console.info(target === obj);? ? ? ? ? //true
? ? ? ? console.info(receiver === proxyObj);? ? //true
? ? ? ? if('age' === key){
? ? ? ? ? ? return target[key] - 5;
? ? ? ? }
? ? ? ? else{
? ? ? ? ? ? return target[key]
? ? ? ? }
? ? }
})
console.info(proxyObj.age)? // 34- 5 = 29
```
解釋如下:
- get函數(shù)中的三個(gè)參數(shù):target,key,receiver。 target就是原對(duì)象j,keys是當(dāng)前的屬性名媳握;receiver是代理對(duì)象碱屁。你可以在get方法中做任意的自定義的處理。
## 代理對(duì)象與原對(duì)象的關(guān)系
```
var arr = [2,1]
var proxyArr = new Proxy(arr,{} )
proxyArr.push(3);
console.info(arr) // [2,1,3]
console.info(arr === proxyArr) // false
arr.sort();
console.info(proxyArr[0]) // 1
```
以上代碼中蛾找,這個(gè)代理對(duì)象并沒(méi)有做任何的特殊操作娩脾。理解為明星的經(jīng)理人消極怠工:原封不動(dòng)地轉(zhuǎn)告外界的信息給明星本身。所以在proxyArr上做到操作會(huì)直接影響到arr上打毛。
同理柿赊,在arr上的操作,也會(huì)影響proxyArr幻枉。
但是要注意:proxyArr與arr是兩個(gè)不同的對(duì)象:arr !== proxyArr碰声。
你可能會(huì)想一想:為什么proxyArr能夠直接使用push這個(gè)方法呢?原因是:
```
proxyArr.__proto__ === arr.__proto__ === Array.prototype
```
前一個(gè)等式成立的原因是由new Proxy的基因決定的:原對(duì)象被代理了嘛熬甫。
## 代理列表
在new Proxy的第二個(gè)參數(shù)中胰挑,可以設(shè)置的代理屬性如下:
```
var proxyObj = new Proxy(obj, {
? ? get: function(tagert,key,receiver){},
? ? set: function(tagert,key,receiver){},
? ? has: function(tagert,key){},
? ? deleteProperty: function(tagert,key){},
? ? ownKeys: function(tagert){},
? ? getOwnPropertyDescriptor: function(tagert,key){},
? ? defineProperty: function(tagert,key,desc){},
? ? preventExtensions: function(tagert){},
? ? getPrototypeOf: function(tagert){},
? ? isExtensible: function(tagert){},
? ? setPrototypeof: function(tagert,proto){},
? ? apply: function(tagert,obj,args){},
? ? construct: function(tagert,args){},
? ?
})
```
## get() 代理的應(yīng)用
### 訪問(wèn)不存在的屬性名時(shí)給出更加優(yōu)雅的提示
在我們的系統(tǒng)中,通常會(huì)把所有的用到的標(biāo)志字符串寫在成常量的格式.一般的做法是單獨(dú)寫在一個(gè)文件中椿肩,然后在其他需要用到的地方引入這個(gè)文件瞻颂。用es6的模塊化的寫法:
const.js
```
/*const.js 系統(tǒng)中所有的常量設(shè)置*/
const con = {
? COMPANYNAME:"jd",
?
}
export defalut con;
```
在其他文件中使用,如下:
file.js
```
import CONST from "./const"
console.info(CONST.COMPANYNAME)
console.info(CONST.COMPANYNAME1) // 不小心把常量名寫錯(cuò),并不會(huì)有什么特殊的提示效果郑象。
```
改進(jìn)如下:
```
const con = {
? COMPANYNAME:"jd",
?
}
let proxyConst = new Proxy(con, {
? ? get: function (target, key, receiver) {
? ? ? ? if(key in target)
? ? ? ? ? ? return target[key];
? ? ? ? else{
? ? ? ? ? ? throw new Error("error:常量名"+key+"不存在贡这!")
? ? ? ? }
? ? }
});
export defalut proxyConst;
```
### 允許數(shù)組下標(biāo)是負(fù)值
在js中,數(shù)組的有效下表是從0開始的厂榛。
```
var arr = [1,2,3];
console.info(arr[0])? // 1
console.info(arr[-1]) // undefined
console.info(arr[100]) // undefined
```
值得注意的是盖矫,`下標(biāo)越界或者是負(fù)值的情況下,得到的結(jié)果是undefined击奶,而不是報(bào)錯(cuò)`辈双。
如果我們希望數(shù)組可以取負(fù)值下表,且規(guī)則如下:
- -n表示倒數(shù)第n個(gè)元素柜砾。例如:-1表示倒數(shù)第一個(gè)元素辐马。
可以使用Proxy解決如下:
```
var arr = [1,2,3];
var proxyArr = new Proxy(arr,{
? ? get: (target,prop)=>{
? ? ? ? let index = Number(prop);
? ? ? ? if(index < 0){
? ? ? ? ? ? prop = target.length + index;
? ? ? ? }
? ? ? ? return target[prop];
? ? ? ?
? ? }
})
console.info(arr[-1]);? ? ? // undefined
console.info(proxyArr[-1]); // 3
```
注意:
- Number()可以把傳入的值轉(zhuǎn)成數(shù)值型。非數(shù)值 --> NaN;
- 如果是proxyArr.push(3),由于此時(shí)的prop是'push',所以不會(huì)進(jìn)入if分支喜爷。
- 如果是proxyArr[-1],此時(shí)的prop是'-1',所以會(huì)進(jìn)入到if分支:把prop從-1改成 2 ,從而實(shí)現(xiàn)了被代理的效果冗疮。
- 此時(shí),完全可以把proxyArr當(dāng)作一個(gè)數(shù)組來(lái)使用檩帐,sort,push等方法均可以調(diào)用术幔。Array.isArray(proxyArr) === true
當(dāng)然,你也可以進(jìn)一步封裝成工廠函數(shù)湃密。
```
function myArr(...args){
? ? var arr = new Array(...args);
? ? var proxyArr = new Proxy(arr,{
? ? ? ? get: (target,key)=>{
? ? ? ? ? ? let index = Number(key);
? ? ? ? ? ? if(index < 0){
? ? ? ? ? ? ? ? key = target.length + index;
? ? ? ? ? ? }
? ? ? ? ? ? return target[key];
? ? ? ? }
? ? })
? ? return proxyArr;
}
var obj = myArr([1,2,3]);
console.info(obj[0],obj[-1])
```
### 鏈?zhǔn)竭\(yùn)算
```
var double = n => n*2;
var pow2 = n => n*n;
var half = n => n/ 2;
var add1 = n => n+1;
function pipe (num){
? ? let funs = []
? ? let obj = new Proxy({},{
? ? ? ? get:function(target,prop){
? ? ? ? ? ? if(prop === 'end'){
? ? ? ? ? ? ? ? return funs.reduce((val,currentfn)=>currentfn(val),num);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? funs.push(window[prop])
? ? ? ? ? ? }
? ? ? ? ? ? return obj;
? ? ? ? }
? ? })
? ? return obj;
};
console.info( pipe(4).double.pow2.end);
console.info( pipe(4).pow.double.pow2.add1.end);
```
這種寫法在很多的工具庫(kù)中都有诅挑,例如[math.js](http://mathjs.org/docs/core/chaining.html)的chain.
說(shuō)明:
- [reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)