1. 程式人生 > 其它 >JS資料劫持 之 Proxy設定代理

JS資料劫持 之 Proxy設定代理

1、簡介

資料劫持,指的是在訪問或者修改物件的某個屬性時,通過一段程式碼攔截這個行為,進行額外的操作或者修改返回結果。

經典的應用是Vue雙向資料繫結。

常見的資料劫持實現方法有兩種:

  • Object.defineProperty
  • Proxy設定代理(ES6新增)

本篇文章介紹Proxy的使用。


2、Proxy

2.1、介紹

new Proxy(target, handler)

  • 引數:
    • target:要使用 Proxy 包裝的目標物件(可以是任何型別的物件,包括原生陣列,函式,甚至另一個代理)。
    • handler:一個通常以函式作為屬性的物件,各屬性中的函式分別定義了在執行各種操作時代理的行為。
  • handler中常⽤的物件⽅法:
    • get(target, propKey, receiver):攔截物件的讀取屬性操作。
    • set(target, propKey, value, receiver):設定屬性值操作的捕獲器。
    • has(target, propKey):對in操作符的代理方法。
    • construct(target, args):攔截new操作符,返回的是一個物件。
    • apply(target, object, args):攔截函式的呼叫。

2.2、程式碼示例

2.2.1、get(target, propKey, receiver)

攔截物件的讀取屬性操作。

  • target:目標物件。
  • propKey:要獲取的key值。
  • receiver:Proxy代理自身。
const target = {
    name: 'cxk'
};

const handler = {
    get(target, propKey, receiver) {
        console.log('---呼叫了get方法---');
        console.log(target, propKey, receiver);
        return target[propKey];
    }
};

const proxy = new Proxy(target, handler);

console.log(target.name); // cxk
console.log(proxy.name); 
// ---呼叫了get方法---
// {name: 'cxk'} 'name' Proxy {name: 'cxk'}
// cxk

2.2.2、set(target, propKey, value, receiver)

設定屬性值操作的捕獲器。

  • target:目標物件。
  • propKey:要新增或修改的key值。
  • value:要把key設定成什麼值。
  • receiver:Proxy代理自身。
const target = {
    name: 'cxk'
};

const handler = {
    set(target, propKey, value, receiver) {
        console.log('---呼叫了set方法---');
        console.log(target, propKey, value, receiver);
        target[propKey] = value;
    }
};

const proxy = new Proxy(target, handler);

// 通過proxy新增或修改屬性,觸發set方法,並修改成功
proxy.age = 18;
console.log(proxy); // Proxy {name: 'cxk', age: 18}
console.log(target); // {name: 'cxk', age: 18}

// 通過target新增或修改屬性,不觸發set方法,也修改成功
target.age = 20;
console.log(proxy); // Proxy {name: 'cxk', age: 20}
console.log(target); // {name: 'cxk', age: 20}

2.2.3、has(target, propKey)

對in操作符的代理方法。

  • target:目標物件。
  • propKey:in操作符左邊的引數。
const target = {
    name: 'cxk',
    _age: 18
};

const handler = {
    has(target, propKey) {
        console.log('---觸發了has方法---');
        console.log(target, propKey);
        // 物件內部的私有屬性以_開頭,外部不可以訪問。
        if (propKey.includes('_')) {
            return false;
        }
        return propKey in target;
    }
};

const proxy = new Proxy(target, handler);

console.log('_age' in target); // true
console.log('_age' in proxy); // false
// ---觸發了has方法---
// {name: 'cxk', _age: 18} '_age'
// false

2.2.4、construct(target, args)

攔截new操作符,返回的是一個物件。

  • target:目標物件,也就是建構函式。
  • args:被呼叫時的引數,是一個類陣列。
function target(name) {
    this.name = name;
}

const handler = {
    construct(target, args) {
        console.log('---觸發了construct方法---');
        console.log(target, args);
        return new target(...args);
    }
};

const proxy = new Proxy(target, handler);

new proxy('cxk', 18);
// ---觸發了construct方法---
// ƒ target(name) {
//     this.name = name;
// } (2) ['cxk', 18]

2.2.5、apply(target, object, args)

攔截函式的呼叫。

  • target:目標物件,也就是函式。
  • object:目標物件的上下文物件(this)。
  • args:被呼叫時的引數,是一個類陣列。
function sum(a, b) {
    return a + b;
}

const handler = {
    apply(target, object, args) {
        console.log('---觸發了apply方法---');
        console.log(target, object, args);
        return target(...args);
    }
};

const proxy = new Proxy(sum, handler);

console.log(proxy(1, 2));
// ---觸發了apply方法---
// ƒ sum(a, b) {
//  return a + b;
// }
// undefined
// [1, 2]
// 3

let obj = {
    a: 1,
    b: 2
}
console.log(proxy.call(obj, 3, 3));
// ---觸發了apply方法---
// ƒ sum(a, b) {
//  return a + b;
// } 
// {a: 1, b: 2}
// [3, 3]
// 6

  • Proxy代理的handler物件方法很多,上述只列舉了最常用的幾個,其他不怎麼常用的物件方法的需要讀者自行搜尋學習。