- var obj = new Proxy({}, {
- get: function (target, key, receiver) {
- console.log(`getting ${key}!`);
- return Reflect.get(target, key, receiver);
- },
- set: function (target, key, value, receiver) {
- console.log(`setting ${key}!`);
- return Reflect.set(target, key, value, receiver);
- }
- });
Proxy 实例也可以作为其他对象的原型对象。
- var proxy = new Proxy({}, {
- get: function(target, property) {
- return 35;
- }
- });
-
- let obj = Object.create(proxy);
- obj.time // 35
上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
- function createArray(...elements) {
- let handler = {
- get(target, propKey, receiver) {
- let index = Number(propKey);
- if (index < 0) {
- propKey = String(target.length + index);
- }
- return Reflect.get(target, propKey, receiver);
- }
- };
-
- let target = [];
- target.push(...elements);
- return new Proxy(target, handler);
- }
-
- let arr = createArray('a', 'b', 'c');
- arr[-1] // c
- var handler = {
- get (target, key) {
- invariant(key, 'get');
- return target[key];
- },
- set (target, key, value) {
- invariant(key, 'set');
- target[key] = value;
- return true;
- }
- };
- function invariant (key, action) {
- if (key[0] === '_') {
- throw new Error(`Invalid attempt to ${action} private "${key}" property`);
- }
- }
- var target = {};
- var proxy = new Proxy(target, handler);
- proxy._prop
- // Error: Invalid attempt to get private "_prop" property
- proxy._prop = 'c'
- // Error: Invalid attempt to set private "_prop" property
- var twice = {
- apply (target, ctx, args) {
- return Reflect.apply(...arguments) * 2;
- }
- };
- function sum (left, right) {
- return left + right;
- };
- var proxy = new Proxy(sum, twice);
- proxy(1, 2) // 6
- proxy.call(null, 5, 6) // 22
- proxy.apply(null, [7, 8]) // 30
has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。下面的例子使用has方法隐藏某些属性,不被in运算符发现。
- 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
上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has就会返回false,从而不会被in运算符发现。
如果原对象不可配置或者禁止扩展,这时has拦截会报错。
- var obj = { a: 10 };
- Object.preventExtensions(obj);
- var p = new Proxy(obj, {
- has: function(target, prop) {
- return false;
- }
- });
-
- 'a' in p // TypeError is thrown
上面代码中,obj对象禁止扩展,结果使用has拦截就会报错。
值得注意的是,has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性。
另外,虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效。
- let stu1 = {name: '张三', score: 59};
- let stu2 = {name: '李四', score: 99};
-
- let handler = {
- has(target, prop) {
- if (prop === 'score' && target[prop] < 60) {
- console.log(`${target.name} 不及格`);
- return false;
- }
- return prop in target;
- }
- }
-
- let oproxy1 = new Proxy(stu1, handler);
- let oproxy2 = new Proxy(stu2, handler);
-
- 'score' in oproxy1
- // 张三 不及格
- // false
-
- 'score' in oproxy2
- // true
-
- for (let a in oproxy1) {
- console.log(oproxy1[a]);
- }
- // 张三
- // 59
-
- for (let b in oproxy2) {
- console.log(oproxy2[b]);
- }
- // 李四
- // 99
上面代码中,has拦截只对in循环生效,对for...in循环不生效,导致不符合要求的属性没有被排除在for...in循环之外。
- var handler = {
- construct (target, args, newTarget) {
- return new target(...args);
- }
- };
construct方法可以接受两个参数。
target: 目标对象
args:构建函数的参数对象。下面是一个例子。
- var p = new Proxy(function() {}, {
- construct: function(target, args) {
- console.log('called: ' + args.join(', '));
- return { value: args[0] * 10 };
- }
- });
-
- new p(1).value
- // "called: 1"
- // 10
- var p = new Proxy(function() {}, {
- construct: function(target, argumentsList) {
- return 1;
- }
- });
-
- new p() // 报错
- var handler = {
- deleteProperty (target, key) {
- invariant(key, 'delete');
- return true;
- }
- };
- function invariant (key, action) {
- if (key[0] === '_') {
- throw new Error(`Invalid attempt to ${action} private "${key}" property`);
- }
- }
-
- var target = { _prop: 'foo' };
- var proxy = new Proxy(target, handler);
- delete proxy._prop
- // Error: Invalid attempt to delete private "_prop" property
- var handler = {
- defineProperty (target, key, descriptor) {
- return false;
- }
- };
- var target = {};
- var proxy = new Proxy(target, handler);
- proxy.foo = 'bar'
- // TypeError: proxy defineProperty handler returned false for property '"foo"'
- 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 }
- var proto = {};
- var p = new Proxy({}, {
- getPrototypeOf(target) {
- return proto;
- }
- });
- Object.getPrototypeOf(p) === proto // true
- var p = new Proxy({}, {
- isExtensible: function(target) {
- console.log("called");
- return true;
- }
- });
-
- Object.isExtensible(p)
- // "called"
- // true
- let target = {};
-
- let handler = {
- ownKeys(target) {
- return ['hello', 'world'];
- }
- };
-
- let proxy = new Proxy(target, handler);
-
- Object.keys(proxy)
- // [ 'hello', 'world' ]
- var p = new Proxy({}, {
- preventExtensions: function(target) {
- return true;
- }
- });
-
- Object.preventExtensions(p) // 报错
- var handler = {
- setPrototypeOf (target, proto) {
- throw new Error('Changing the prototype is forbidden');
- }
- };
- var proto = {};
- var target = function () {};
- var proxy = new Proxy(target, handler);
- proxy.setPrototypeOf(proxy, proto);
- // Error: Changing the prototype is forbidden
- let target = {};
- let handler = {};
-
- let {proxy, revoke} = Proxy.revocable(target, handler);
-
- proxy.foo = 123;
- proxy.foo // 123
-
- revoke();
- proxy.foo // TypeError: Revoked
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。
将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。
修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
Reflect对象的方法
Reflect.get(target, name, receiver)
查找并返回target对象的name属性,如果没有该属性,则返回undefined。
如果name属性部署了读取函数,则读取函数的this绑定receiver。
- var obj = {
- name:'wang',
- get foo() { return this.bar(); },
- bar: function() {
- console.log(123)
- }
- };
-
- // 下面语句会让 this.bar()
- // 变成调用 wrapper.bar()
- console.log(Reflect.get(obj, "name")) //wang
- Reflect.get(obj, "foo"); //123
等同于Function.prototype.apply.call(fun,thisArg,args)。一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。
另外,需要注意的是,Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一个布尔值,表示操作是否成功。它们对应的Object方法,失败时都会抛出错误。
- // 失败时抛出错误
- Object.defineProperty(obj, name, desc);
- // 失败时返回false
- Reflect.defineProperty(obj, name, desc);