js 中好用的代理 Proxy

const p = new Proxy(target, handler)

参数

target
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
其中 handler的写法要注意


handler 对象的方法

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。


  • handler.getPrototypeOf()
    Object.getPrototypeOf方法的捕捉器。在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy)时。

const monster1 = {
  eyeCount: 4
};

const monsterPrototype = {
  eyeCount: 2
};

const handler = {
  getPrototypeOf(target) {
    return monsterPrototype;
  }
};

const proxy1 = new Proxy(monster1, handler);

console.log(Object.getPrototypeOf(proxy1) === monsterPrototype);
// 输出: true

console.log(Object.getPrototypeOf(proxy1).eyeCount);
// 输出: 2

  • handler.setPrototypeOf()
    Object.setPrototypeOf方法的捕捉器。在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null)时。

/* 
target
被拦截目标对象.
prototype
对象新原型或为null
如果成功修改了[[Prototype]], setPrototypeOf 方法返回 true,否则返回 false.
*/
// 1. ------------------------------------------------------------------------------
var handlerReturnsFalse = {
    setPrototypeOf(target, newProto) {
        return false;
    }
};
var newProto = {}, target = {};
var p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // return false

// 2. ------------------------------------------------------------------------------
var handlerThrows = {
    setPrototypeOf(target, newProto) {
        throw new Error('custom error');
    }
}; 
var newProto = {}, target = {};
var p2 = new Proxy(target, handlerThrows);
Object.setPrototypeOf(p2, newProto); // throws new Error("custom error")
Reflect.setPrototypeOf(p2, newProto); // throws new Error("custom error")

  • handler.isExtensible()
    Object.isExtensible方法的捕捉器。在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy)时。

const monster1 = {
  canEvolve: true
};

const handler1 = {
  isExtensible(target) {
    return Reflect.isExtensible(target);
  },
  preventExtensions(target) {
    target.canEvolve = false;
    return Reflect.preventExtensions(target);
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(Object.isExtensible(proxy1));
// 输出: true

console.log(monster1.canEvolve);
// 输出: true

Object.preventExtensions(proxy1);

console.log(Object.isExtensible(proxy1));
// 输出: false

console.log(monster1.canEvolve);
// 输出: false

  • handler.preventExtensions()
    Object.preventExtensions 方法的捕捉器。在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy)时。

const monster1 = {
  canEvolve: true
};

const handler1 = {
  preventExtensions(target) {
    target.canEvolve = false;
    Object.preventExtensions(target);
    return true;
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(monster1.canEvolve);
// 输出: true

Object.preventExtensions(proxy1);

console.log(monster1.canEvolve);
// 输出: false


  • handler.getOwnPropertyDescriptor()
    Object.getOwnPropertyDescriptor方法的捕捉器。在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo")时。

/*
target
目标对象。
prop
返回属性名称的描述。
这个拦截器可以拦截这些操作:

*   Object.getOwnPropertyDescriptor()
*   Reflect.getOwnPropertyDescriptor()
*/
getOwnPropertyDescriptor 方法必须返回一个 object 或 undefined。

var p = new Proxy({ a: 20}, {
  getOwnPropertyDescriptor: function(target, prop) {
    console.log('called: ' + prop);
    return { configurable: true, enumerable: true, value: 10 };
  }
});

console.log(Object.getOwnPropertyDescriptor(p, 'a').value); 
// "called: a"
// 10
/*
如果下列不变量被违反,代理将抛出一个 TypeError:
*   getOwnPropertyDescriptor 必须返回一个 object 或 undefined。
*   如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
*   如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在。
*   如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在。
*   属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在。
*   Object.getOwnPropertyDescriptor(target)的结果可以使用 Object.defineProperty 应用于目标对象,也不会抛出异常。

*/

  • handler.defineProperty()
    Object.defineProperty方法的捕捉器。在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {})时。

/*
target
目标对象。
property
待检索其描述的属性名。
descriptor
待定义或修改的属性的描述符。
defineProperty 方法必须以一个 Boolean返回,表示定义该属性的操作成功与否。
*/
var p = new Proxy({}, {
  defineProperty: function(target, prop, descriptor) {
    console.log('called: ' + prop);
    return true;
  }
});

var desc = { configurable: true, enumerable: true, value: 10 };
Object.defineProperty(p, 'a', desc); 
// "called: a"

/*
当调用 Object.defineProperty()` 或者 Reflect.defineProperty(),传递给 `defineProperty` 的 `descriptor`   有一个限制 - 只有以下属性才有用,非标准的属性将会被无视 :

*   enumerable
*   configurable
*   writable
*   value
*   get
*   set
*/
var p = new Proxy({}, {
  defineProperty(target, prop, descriptor) {
    console.log(descriptor);
    return Reflect.defineProperty(target, prop, descriptor);
  }
});

Object.defineProperty(p, 'name', {
  value: 'proxy',
  type: 'custom'
});  
// { value: 'proxy' }

/*
如果违背了以下的不变量,proxy会抛出 TypeError

*   如果目标对象不可扩展, 将不能添加属性。
*   不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话。
*   如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的。
*   如果一个属性在目标对象中存在对应的属性,那么 `Object.defineProperty(target, prop, descriptor)` 将不会抛出异常。
*   在严格模式下, `false` 作为` handler.defineProperty` 方法的返回值的话将会抛出 [`TypeError`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError "TypeError(类型错误) 对象用来表示值的类型非预期类型时发生的错误。") 异常.

*/

  • handler.has()
    in 操作符的捕捉器。在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。

const handler1 = {
  has(target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};

const monster1 = {
  _secret: 'easily scared',
  eyeCount: 4
};

const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
// 输出: true

console.log('_secret' in proxy1);
// 输出: false

console.log('_secret' in monster1);
//输出: true

/*
target
目标对象.
prop
需要检查是否存在的属性.
has 方法返回一个 boolean 属性的值.
*/

  • handler.get()
    属性读取操作的捕捉器。在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。

/*
以下是传递给get方法的参数,this上下文绑定在handler对象上.

target
目标对象。
property
被获取的属性名。
receiver
Proxy或者继承Proxy的对象
返回值
get方法可以返回任何值。
*/
var p = new Proxy({}, {
  get: function(target, prop, receiver) {
    console.log("called: " + prop);
    return 10;
  }
});

console.log(p.a); 
// "called: a"
// 10

/*
该方法会拦截目标对象的以下操作:

*   访问属性: proxy[foo]和proxy.bar
*   访问原型链上的属性: Object.create(proxy)[foo]
*   `Reflect.get()
*/

  • handler.set()
    属性设置操作的捕捉器。在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。

function Monster() {
  this.eyeCount = 4;
}

const handler1 = {
  set(obj, prop, value) {
    if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
      console.log('Monsters must have an even number of eyes');
    } else {
      return Reflect.set(...arguments);
    }
  }
};

const monster1 = new Monster();
const proxy1 = new Proxy(monster1, handler1);
proxy1.eyeCount = 1;
// 输出: "Monsters must have an even number of eyes"

console.log(proxy1.eyeCount);
// 输出: 4

/*
target
*   目标对象。
property
*   将被设置的属性名或 Symbol.
value
*   新属性值.
receiver
*   最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)
set() 方法应当返回一个布尔值
*   返回 `true` 代表属性设置成功.
*   在严格模式下,如果 `set()` 方法返回 `false`,那么会抛出一个 TypeError异常.
*/
var p = new Proxy({}, {
  set: function(target, prop, value, receiver) {
    target[prop] = value;
    console.log('property set: ' + prop + ' = ' + value);
    return true;
  }
})

console.log('a' in p);  // false

p.a = 10;               // "property set: a = 10"
console.log('a' in p);  // true
console.log(p.a);       // 10

  • handler.deleteProperty()
    delete操作符的捕捉器。在删除代理对象的某个属性时触发该操作,即使用 delete运算符,比如在执行 delete proxy.foo 时。

var p = new Proxy(target, {
  deleteProperty: function(target, property) {
  }
});

/*
target
*  目标对象
property
*  待删除的属性名
deleteProperty 必须返回一个 Boolean 类型的值,表示了该属性是否被成功删除。
*/

var p = new Proxy({}, {
  deleteProperty: function(target, prop) {
    console.log('called: ' + prop);
    return true;
  }
});

delete p.a; // "called: a"

  • handler.ownKeys()
    Object.getOwnPropertyNames方法和 Object.getOwnPropertySymbols方法的捕捉器。

const monster1 = {
  _age: 111,
  [Symbol('secret')]: 'I am scared!',
  eyeCount: 4
};

const handler1 = {
  ownKeys(target) {
    return Reflect.ownKeys(target);
  }
};

const proxy1 = new Proxy(monster1, handler1);

for (const key of Object.keys(proxy1)) {
  console.log(key);
  // 输出: "_age"
  // 输出: "eyeCount"
}
/*
target
*  目标对象.
*  返回值
ownKeys 方法必须返回一个可枚举对象.
*/

var p = new Proxy({}, {
  ownKeys: function(target) {
    console.log('called');
    return ['a', 'b', 'c'];
  }
});

console.log(Object.getOwnPropertyNames(p)); 
// "called"
 // [ 'a', 'b', 'c' ]

  • handler.apply()
    函数调用操作的捕捉器。

function sum(a, b) {
  return a + b;
}

const handler = {
  apply: function(target, thisArg, argumentsList) {
    console.log(`Calculate sum: ${argumentsList}`);
    // expected output: "Calculate sum: 1,2"

    return target(argumentsList[0], argumentsList[1]) * 10;
  }
};

const proxy1 = new Proxy(sum, handler);

console.log(sum(1, 2));
// 输出: 3
console.log(proxy1(1, 2));
// 输出: 30

/*
target
*  目标对象(函数)。
thisArg
*  被调用时的上下文对象。
argumentsList
*  被调用时的参数数组。
apply方法可以返回任何值。
*/

var p = new Proxy(function() {}, {
  apply: function(target, thisArg, argumentsList) {
    console.log('called: ' + argumentsList.join(', '));
    return argumentsList[0] + argumentsList[1] + argumentsList[2];
  }
});

console.log(p(1, 2, 3)); 
// "called: 1, 2, 3"
// 6

  • handler.construct()
    new 操作符的捕捉器。

/*
handler.construct() 方法用于拦截new操作符. 为了使new操作符在生成的Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即 new target 必须是有效的)。
*/
function monster1(disposition) {
  this.disposition = disposition;
}

const handler1 = {
  construct(target, args) {
    console.log('monster1 constructor called');
    // expected output: "monster1 constructor called"

    return new target(...args);
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(new proxy1('fierce').disposition);
// 输出: "fierce"
/*
target
*  目标对象。
argumentsList
*  constructor的参数列表。
newTarget
*  最初被调用的构造函数,就上面的例子而言是p。
construct 方法必须返回一个对象。
*/

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

你可能感兴趣的:(js 中好用的代理 Proxy)