Proxy 的一些認識

1.概述

Proxy 用於修改某些操作的默認行爲,等同於在語言層面做出修改,所以屬於一種“元編程”(meta programming),即對編程語言進行編程。

Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來“代理”某些操作,可以譯爲“代理器”。

var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});

上面代碼對一個空對象架設了一層攔截,重定義了屬性的讀取(get)和設置(set)行爲。這裏暫時先不解釋具體的語法,只看運行結果。對設置了攔截行爲的對象obj,去讀寫它的屬性,就會得到下面的結果。

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2

上面代碼說明,Proxy 實際上重載(overload)了點運算符,即用自己的定義覆蓋了語言的原始定義。

ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
var proxy = new Proxy(target, handler);
Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。
Proxy 支持的攔截操作一覽,一共 13 種。

		get(target, propKey, receiver):攔截對象屬性的讀取,比如proxy.foo和proxy['foo']set(target, propKey, value, receiver):攔截對象屬性的設置,比如proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
		
		has(target, propKey):攔截propKey in proxy的操作,返回一個布爾值。
		
		deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布爾值。
		
		ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)for...in循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。
		
		getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
		
		defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
		
		preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。
		
		getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象。
		
		isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。
		
		setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標對象是函數,那麼還有兩種額外操作可以攔截。
		
		apply(target, object, args):攔截 Proxy 實例作爲函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)construct(target, args):攔截 Proxy 實例作爲構造函數調用的操作,比如new proxy(...args)

2.Proxy 實例的方法

get()
get方法用於攔截某個屬性的讀取操作,可以接受三個參數,依次爲目標對象、屬性名和 proxy 實例本身(嚴格地說,是操作行爲所針對的對象),其中最後一個參數可選。

var person = {
  name: "張三"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
    }
  }
});

proxy.name // "張三"
proxy.age // 拋出一個錯誤

set()
set方法用來攔截某個屬性的賦值操作,可以接受四個參數,依次爲目標對象、屬性名、屬性值和 Proxy 實例本身,其中最後一個參數可選。

假定Person對象有一個age屬性,該屬性應該是一個不大於 200 的整數,那麼可以使用Proxy保證age的屬性值符合要求。

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;

person.age // 100
person.age = 'young' // 報錯
person.age = 300 // 報錯

apply()
apply方法攔截函數的調用、call和apply操作。

apply方法可以接受三個參數,分別是目標對象、目標對象的上下文對象(this)和目標對象的參數數組。

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p()
// "I am the proxy"

剩下的方法可參考https://es6.ruanyifeng.com/?search=proxy&x=0&y=0#docs/proxy

3.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

4.this問題

雖然 Proxy 可以代理針對目標對象的訪問,但它不是目標對象的透明代理,即不做任何攔截的情況下,也無法保證與目標對象的行爲一致。主要原因就是在 Proxy 代理的情況下,目標對象內部的this關鍵字會指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

一旦proxy代理target.m,後者內部的this就是指向proxy,而不是target。

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