Proxy介紹

紫雲夢發表於2019-02-16

Proxy 讓我們可以對任何物件的絕大部分行為進行監聽和干涉,實現更多的自定義程式行為。
用法:new Proxy(target, handler)。
   Proxy 通過設定行為監聽方法來捕獲程式對對應物件的行為。

    const obj = {};
    const proxy = new Proxy(obj, {
        // ...
    })

Proxy 的構造器接受兩個引數,第一個引數為需要進行包裝的目標物件,第二個引數則為用於監聽目標物件行為的監聽器,其中監聽器可以接受一些引數以監聽相對應的程式行為。
監聽屬性、引數及監聽內容

屬性值 監聽器引數 監聽內容
has (target, prop) 監聽 in 語句的使用
get (target, prop, reciver) 監聽目標物件的屬性讀取
set (target, prop, value, reciver) 監聽目標物件的屬性賦值
deleteProperty (target, prop) 監聽 delete 語句對目標物件的刪除屬性行為
ownKeys (target) 監聽 Object.getOwnPropertyName() 的讀取
apply (target, thisArg, arguments) 監聽目標函式(作為目標物件)的呼叫行為
construct (target, arguments, newTarget) 監聽目標建構函式(作為目標物件)利用 new 而生成例項的行為
getPrototypeOf (target) 監聽 Objext.getPrototypeOf() 的讀取
setPrototypeOf (target, prototype) 監聽 Objext.setPrototypeOf() 的呼叫
isExtensible (target) 監聽 Objext.isExtensible() 的讀取
preventExtensions (target) 監聽 Objext.preventExtensions() 的讀取
getOwnPropertyDescriptor (target, prop) 監聽 Objext.getOwnPropertyDescriptor() 的呼叫
defineProperty (target, property, descriptor) 監聽 Object.defineProperty() 的呼叫

has

可以通過為 Proxy 的 handler 定義 has 監聽方法,來監聽程式通過 in 語句來檢查一個字串或數字是否為該 Proxy 的目標物件中某個屬性的屬性鍵的過程。

const p = new Proxy({}, {
    has(target, prop){
        console.log(`Checking "${prop}" is in the target or not`);
        return true;
    }
})

console.log(`foo` in p);
// Checking "foo" is in the target or not
// true

該監聽方法有兩個需要注意的地方,如果遇到這兩種情況,便會丟擲 TypeError 錯誤。

1.當目標物件被其他程式通過 Object.preventExtensions() 禁用了屬性擴充 (該物件無法再增加新的屬性,只能對當前已有的屬性進行操作,包括讀取、操作和刪除,但是一旦刪除就無法再定義) 功能,且被檢查的屬性鍵確實存在與目標物件中,該監聽方法便不能返回 false。

const obj = {foo: 1};

Object.preventExtensions(obj);

const p = new Proxy(obj, {
    has(target, prop){
        console.log(`Checking "${prop}" is in the target or not`);
        return false; 
    }
})

console.log(`foo` in p);   
//丟擲Uncaught TypeError: 

2.當被檢查的屬性鍵存在與目標物件中,且該屬性的 configurable 配置是 false 時,該監聽方法不能返回 false。

const obj = {};

Object.defineProperty(obj, `foo`, {
    configurable: false,
    value: 10
})

const p = new Proxy(obj, {
    has(target, prop){
        console.log(`Checking "${prop}" is in the target or not`);
        return false;
    }
})

console.log(`foo` in p);
//丟擲Uncaught TypeError: 

get

Getter只能對已知的屬性鍵進行監聽,而無法對所有屬性讀取行為進行攔截,而 Proxy 可以通過設定 get 監聽方法,攔截和干涉目標物件的所有屬性讀取行為。

const obj = {foo: 1};
const p = new Proxy(obj, {
    get(target, prop){
        console.log(`Program is trying to fetch the property "${prop}".`);
        return target[prop];
    }
})

alert(p.foo);  // Program is trying to fetch the property "foo".
alert(p.something);    // Program is trying to fetch the property "something".

  這個監聽方法也存在需要注意的地方——當目標物件被讀取屬性的 configurable 和 writable 屬性都為 false 時,監聽方法最後返回的值必須與目標物件的原屬性值一致。

const obj = {};

Object.defineProperty(obj, `foo`, {
    configurable: false,
    value: 10,
    writable: false
})

const p = new Proxy(obj, {
    get(target, prop){
        return 20;
    }
})

console.log(p.foo);

set

  handler.set 用於監聽目標物件的所有屬性賦值行為。注意,如果目標物件自身的某個屬性是不可寫也不可配置的,那麼 set 不得改變這個屬性的值,只能返回同樣的值,否則報錯。

const obj = {};
const p = new Proxy(obj, {
    set(target, prop, value){
        console.log(`Setting value "${value}" on the key "${prop}" in the target object`);
        target[prop] = value;
        return true;
    }
})

p.foo = 1;  
// Setting value "1" on the key "foo" in the target object

apply

handler.apply , Proxy 也為作為目標物件的函式提供了監聽其呼叫行為的屬性。

const sum = function(...args) {
  return args
    .map(Number)
    .filter(Boolean)
    .reduce((a, b) => a + b);

}

const p = new Proxy(sum, {
  apply(target, thisArg, args) {
    console.log(`Function is being called with arguments [${args.join()}] and context ${thisArg}`);
    return target.call(thisArg, ...args);
  }
})

console.log(p(1, 2, 3));
// Function is being called with arguments [1,2,3] and context undefined
// 6

construct

  handler.construct, Proxy 也可以將類作為目標監聽物件,並監聽其通過 new 語句來生產新例項的行為,這同樣可以使用再作為構造器的建構函式上。

class Foo{};

const p = new Proxy(Foo, {
    construct(target, args, newTarget){
        return {arguments: args}    // 這裡返回的結果會是 new 所得到的例項
    }
});

const obj = new p(1, 2, 3);
console.log(obj.arguments);  // [1, 2, 3]

建立可解除 Proxy 物件

用法:Proxy.revocable(target, handler) : (proxy, revoke)。

const obj = {foo: 10};
const revocable = Proxy.revocable(obj, {
    get(target, prop){
        return 20;
    }
})
const proxy = revocable.proxy;
console.log(proxy.foo); // 20
revocable.revoke();
console.log(proxy.foo); 
// TypeError: Cannot perform `get` on a proxy that has been revoked

  Proxy.revocable(target, handler) 會返回一個帶有兩個屬性的物件,其中一個 proxy 便是該函式所生成的可解除 Proxy 物件,而另一個 revoke 則是將剛才的 Proxy 物件解除的解除方法。

相關文章