Proxy 對象攔截器
“ ES6的對象攔截器,可以攔截哪些情況呢**”**
01 前言
自從ES6誕生以來,各種新特性也逐漸顯現出來,比如ES6中的Proxy對象,是一個重要的技術。之前Vue的數據雙向數據綁定是使用Object.defineProperty()來做的,而現在vue3.0使用的是代理Proxy來編寫。原因是前者有一些對象或者數組的變化是監聽不了的,但是Proxy可以監聽整個對象。
Proxy可以理解爲,當你試圖訪問一個對象的時候必須先經過一個攔截或者代理,你纔可以進行對對象的操作。這種機制的好處就是可以對外界的訪問進行過濾和改寫。
02 Proxy實例的方法
proxy實例的構造
ES6提供一個原生的Proxy構造函數,用於生成Proxy實例。
var proxy = new Proxy ( target, handler ) ;
參數解析:
- target:所要攔截(代理)的對象
- 處理函數,攔截對應的操作
利用Proxy對象可以攔截的方法有一下13種,下面一一來介紹一下。
1、get()
get方法是意思是讀取對象,那麼相應的攔截就是在讀取對象的時候進行攔截。
var person = {
name:'alanwu'
}
var proxy = new Proxy(person, {
get:function(target, property){
if(property in target){
return target[property]
}else{
throw new Error('對象屬性不存在')
}
}
})
proxy.name //'alanwu'
proxy.age //錯誤
假如上述代碼訪問不存在的屬性的時候,沒有拋出錯誤的話就會返回undefined。
2、set()
set攔截操作用於攔截對對象賦值的操作。
const obj = {
a: 101,
b: 46,
c: 200
}
var proxy = new Proxy(obj, {
set(target, key, value) {
if (value < 100) {
throw Error(`${value}值不能小於100`)
}
target[key] = value
}
})
3、apply()
apply方法用於攔截函數的調用、call、apply操作。
const sum = (num1, num2) => {
return num1 + num2
}
const proxy = new Proxy(sum, {
apply(target, context, args) {
// 我們可以通過 Reflect.apply()來調用目標函數
return Reflect.apply(...arguments) * 2
}
})
proxy(3,4) // 14
4、has()
has方法用於攔截hasProperty操作,即判斷對象是否具有某個屬性時,該方法有效。值得注意的是,對for…in循環是沒有效果的。has方法可以判斷該屬性來自繼承的屬性,不只是自身的屬性。下面是攔截訪問私有屬性“_prop”
var handler = {
has(target, key){
if(key[0]==='_'){
return false
}
return key in target
}
}
var target = {_prop:'foo', prop:'foo'};
var proxy = new Proxy(target, handler)
'_prop' in proxy //false
5、construct
該方法的本意是構造,所以會攔截 new 操作符命令。該方法接受兩個參數:
- target:目標對象
- args:構造函數的參數對象
var handler = {
construct (target, args, newTarget){
return new target(...args);
}
}
例子:
var p = new Proxy(function() {}, {
construct: function(target, argumentsList, newTarget) {
console.log('called: ' + argumentsList.join(', '));
return { value: argumentsList[0] * 10 };
}
});
console.log(new p(1).value); // "called: 1"
// 10
6、deleteProperty()
deleteProperty方法用於攔截delete操作,如果這個方法拋出錯誤或者返回false,當前屬性就無法被delete命令刪除。值得注意的是,目標對象自身的不可配置(configurable)的屬性不能被deleteProperty方法刪除,否則會報錯。
var handler = {
deleteProperty(target, key){
invariant(key, 'delete');
return true
}
}
function invariant(key, action){
if(key[0]==='_'){
throw new Error(`Invalid attemp to ${action} private "${key}" property`)
}
}
var target = {_prop:'foo'}
var proxy = new Proxy(target, handler);
delete proxy._prop //Error
7、defineProperty
該方法用於攔截Object.defineProperty操作。下面操作添加新的屬性會觸發defineProperty函數,所以會報錯。如果目標對象不可擴展(extensible)或不可寫(writable)或不可配置(configurable),也不可添加新屬性,否則也會報錯。
var handler = {
defineProperty(target, key, descriptor){
return false
}
}
var target {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'
8、getOwnPropertyDescriptor
該方法用於攔截Object.getOwnPropertyDescriptor(),返回一個屬性描述對象或者undefined。
var handler = {
getOwnPropertyDescriptor(target, key){
if(key[0]==='_'){
return
}
return Object.getOwnPropertyDescriptor(target, key)
}
}
var target = {_foo:'bar', baz:'tar'};
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat'); //undefined
Object.getOwnPropertyDescriptor(proxy, '_foo'); //undefined
Object.getOwnPropertyDescriptor(proxy, 'baz'); //{value:'tar', writable:true,enumerable:true,configurable:true}
9、getPrototypeOf()
該方法用於攔截獲取對象的原型。有如下幾種情況:
- Object.prototype. __ proto __
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
var proto = {}
var p = new Proxy({}, {
getPrototypeOf(target){
return proto
}
})
Object.getPrototypeOf(p) === proto //true
值得注意的是getPrototype方法返回值必須是對象或者null,否則會報錯。目標對象不可擴展(extensible),那麼該方法必須返回對象的原型對象。
10、isExtensible()
該方法用於攔截Object.isExtensible()操作。
var p = new Proxy({},{
isExtensible: function(target){
return true
}
})
該方法返回值必須是布爾值,否則會轉成布爾值。如果返回值與目標對象的IsExtensible(target)不一致,也會拋出錯誤。
11、ownKeys()
該方法用於攔截對象自身屬性的讀取操作。可以攔截以下操作:
- Object.getOwnPropertyNames ()
- Object.getOwnPropertySymbols ()
- Object.keys ()
let target = {
a:1,
b:2,
c:3
}
let handler = {
ownKeys(target){
return ['a']
}
}
let proxy = new Proxy(target,handler);
Object.keys(proxy) //['a']
12、preventExtensions()
該方法攔截 Object.preventExtensions () ,該方法必須返回個布爾值,否則會被自動轉爲布爾值。只有目標對象不可擴展時,即 Object.isExtensible(proxy)爲false, proxy.preventExtensions 才能返回 true ,否則會報錯。
var p =new Proxy({}, {
preventExtensions: function(target) {
return true;
}
})
Object.preventExtensions(p) //報錯
13、setPrototypeOf()
該方法主要用於攔截 Object.setPrototypeOf 方法。下面的代碼中,只要修改 target 的原型對象就會錯。
var handler = {
setPrototypeOf (target, proto) {
throw new Error ('Changing the prototype is forbidden');
}
}
var proto = {} ;
var target = function () {};
var proxy = new Proxy(target , handler) ;
Object.setPrototypeOf(proxy, proto ); //Error : Changing the prototype is forbidden
14、Proxy.revocable()
Proxy.revocable 方法返回 一個可取消 Proxy實例。
let target = {};
let handler = {};
let { proxy, revoke } = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo //123
revoke();
proxy.foo //TypeError: Revoked
Proxy.revocable 方法返回一個對象,其 proxy 屬性是 Proxy 實例, revoke 屬性是一個函數,可以取消 Proxy 實例。上面的代碼中,當執行 revoke 函數後再訪問 Proxy 實例,就會拋出一個錯誤。
Proxy.revocable 個使用場景是,目標對象不允許直接訪問,必須通過代理訪問,一旦訪問結束,就收回代理權,不允許再次訪問。
03 小結
上述的實例方法在很多地方都有很大的作用,特別是對對象進行攔截的場景的時候。ES6語法在近年來已經很多使用,特別是vue3.0會使用proxy作爲數據的響應式寫法。
一般面試還都會問你Object.defineProperty有什麼缺點,然後問你有沒有關注vue3.0,知道3.0中的響應式使用什麼寫法。還有Proxy有什麼方法之類的。
參考文章
- 阮一峯 ES6入門標準 ——Proxy