Proxy 對象攔截器

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

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章