Proxy
和Reflect
是ES6中新增的用來操作物件的API。
Proxy
- Proxy原意是代理的意思,這裡表示由它來“代理”某些操作,也稱為“代理器”。
- Proxy可以理解成在目標物件前假設一層“攔截層” ,外界對該物件的訪問都必須先通過這層攔截,因此也提供了一種機制,可以對外界的訪問就行過濾和改寫。也可以理解為“資料劫持”。
Proxy基礎語法
proxy一共可以可以代理(劫持或叫trap)13種物件的操作,如下:
- 常用:set、get、has、deleteProperty、apply
- 其他:getPrototypeOf、setPrototypeOf、isExtensible、preventExtensions、getOwnPropertyDescription、defineProperty、ownKeys、construct
Proxy基礎應用
let test = {
name: 'zyh',
boy: {
name: 'zy',
age:0
}
}
var obj = new Proxy(test,{
get: function(target, key, receiver){
console.log('getting')
return Reflect.get(target, key, receiver)
},
set: function(target, key, value, receiver){
console.log('setting')
return Reflect.set(target, key, value, receiver)
}
})
obj.count = 1
// setting
複製程式碼
上面的例子可以看作Proxy過載(overload)了點運算子。
函式也是一個物件,so,Proxy也可以代理一個函式。
var func = (a, b)=> {
return a + b
}
var fproxy = new Proxy(func, {
apply: function (target, ctx, args) {
console.log('do sth')
return target(...args)
}
})
複製程式碼
- Proxy 能夠讓我們以簡潔易懂的方式控制外部對物件的訪問。功能類似於設計模式中的代理模式。
- 某些時候我們可以把一些非核心邏輯交給Proxy處理,例如訪問日誌記錄、訪問控制、屬性驗證等,達到關注點分離的目的。
表單驗證demo
// 校驗工具函式
var isNotEmpty = value => value !== '';
var isNumber = value => /^[0-9]*$/.test(value);
var isBetween = (value, min, max) => {
if (max === undefined) {
max = Number.MAX_VALUE;
}
if (min === undefined) {
min = Number.MIN_VALUE;
}
return value > min && value < max;
}
// 校驗器
let validators = {
name: [{
validator: isNotEmpty,
errorMsg: '姓名不能為空'
}],
age: [
{
validator: isNumber,
errorMsg: '年齡必須為數字'
},
{
validator: isBetween,
errorMsg: '年齡必須為大於 0 並且小於 100',
params: [0, 100]
}
]
}
var validatorCreater = (target, validator) => new Proxy(target, {
// 儲存校驗器
_validator: validator,
set(target, key, value, receiver) {
// 如果賦值的屬性存在校驗器,則進行校驗
if (this._validator[key]) {
// 遍歷其多個子校驗器
for (validatorStrategy of this._validator[key]) {
let { validator, errorMsg = '', params = [] } = validatorStrategy;
if (!validator.call(null, value, ...params)) {
throw new Error(errorMsg);
return false;
}
}
}
// 賦值語句放最後,如果失敗不賦值,如果不存在校驗器則賦值
return Reflect.set(target, key, value, receiver);
}
})
複製程式碼
Proxy進階
Proxy如果劫持了陣列的set方法,那麼是可以檢測到陣列的變化的,這一點比它的前輩Object.defineProperty
算是進步的,但Proxy本身也是不支援複雜物件(物件的值也是物件)的代理,so如果你要劫持一下複雜物件的方法,那需要自己用遞迴處理一下。
const obj = {
info: {
name: 'eason',
blogs: ['webpack', 'babel', 'postcss']
}
}
let handler = {
get(target, key, receiver) {
console.log('get', key)
// 遞迴建立並返回
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
複製程式碼
上面例子中的obj
就是個複雜物件,舉個例子,當我們執行obj.info.name = 2
時,控制檯輸出如下:
我們可以把上面這行程式碼拆成兩步走:1)
obj.info
,這是因為我們劫持了obj的get方法,此時發現我們要取的這個值是個物件,這裡就會遞迴呼叫new Proxy()
來把這個物件也代理一下。2)obj.info.name=2
這時因為我們設定了info這個代理物件的name值,所有自然會被列印出set的日誌。這個例子還是很直觀的告訴我們proxy是如何代理複雜物件的。完整例子
Reflect
其實上面的例子中,我們已經用到了Reflect,Reflect物件某種意義上也可以說是為Proxy準備的,Proxy的handler中的各個trap都有對應的Reflect的同名方法。Reflect與Proxy搭配使用,可以讓Proxy物件方便地呼叫對應的Reflect方法完成預設行為,作為修改行為的基礎。
下面簡單介紹一下:
Reflect基礎
- Reflect是一個內建物件,和Proxy一樣也是ES6為了操作物件提供的新API。
- Reflect不是一個函式物件,沒有建構函式。不能與new運算子一起使用。
- Reflect的所有屬性和方法都是靜態的(像Math物件一樣)
小結
- Proxy讓開發者很方便地使用代理模式,業務邏輯清晰。
- Proxy 不但可以取代 Object.defineProperty 並且還擴增了非常多的功能。Proxy 技術支援監測陣列的 push 等方法,支援物件屬性的動態新增和刪除,極大的簡化了響應化的程式碼量。