本文主要是介绍《每天十分钟》-红宝书第4版-代理与反射(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
代理另一个代理
代理可以拦截反射 API 的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网:
foo: 'bar'
};
const firstProxy = new Proxy(target, { get() { console.log('first proxy'); return Reflect.get(...arguments); }
});
const secondProxy = new Proxy(firstProxy, { get() { console.log('second proxy'); return Reflect.get(...arguments); }
});
console.log(secondProxy.foo);
// second proxy
// first proxy
// ba
代理的问题与不足
- 代理中的 this
thisValEqualsProxy() { return this === proxy; }
}
const proxy = new Proxy(target, {});
console.log(target.thisValEqualsProxy()); // false
console.log(proxy.thisValEqualsProxy()); // true
以上代码符合预期
const wm = new WeakMap();
class User { constructor(userId) { wm.set(this, userId); } set id(userId) { wm.set(this, userId); } get id() { return wm.get(this); }
}
const user = new User(123);
console.log(user.id); // 123
const userInstanceProxy = new Proxy(user, {});
console.log(userInstanceProxy.id); // undefined
以上代码 ,User 实例一开始使用目标对象作为 WeakMap 的键,代理对象却尝试从自身取得这个实例。要解决这个问题,就需要重新配置代理,把代理 User 实例改为代理 User 类本身。
const UserClassProxy = new Proxy(User, {});
const proxyUser = new UserClassProxy(456);
console.log(proxyUser.id);
其实一个走的是构造函数,一个是触发的 set ,而不同的this 导致的
- 代理与内部槽位
一个典型的例子就是 Date 类型。根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的内部槽位[[NumberDate]]。代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()和 set()操作访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError:
const target = new Date();
const proxy = new Proxy(target, {});
console.log(proxy instanceof Date); // true
proxy.getDate(); // TypeError: 'this' is not a Date object
简单说,代理就是无法代理内部插槽
Map,Set,Date,Promise 等,都使用了所谓的“内部插槽”。
let map = new Map();let proxy = new Proxy(map, {});proxy.set('test', 1); // Error
解决办法
let map = new Map();let proxy = new Proxy(map, {get(target, prop, receiver) {let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;}
});proxy.set('test', 1);
alert(proxy.get('test')); // 1(工作了!)
代理捕获器与反射方法
- get()
const myTarget = {};
const proxy = new Proxy(myTarget, { get(target, property, receiver) { console.log('get()'); return Reflect.get(...arguments) }
});
proxy.foo;
// get()
- set()
const myTarget = {};
const proxy = new Proxy(myTarget, { set(target, property, value, receiver) { console.log('set()'); return Reflect.set(...arguments) }
});
proxy.foo = 'bar';
// set()
- has()
const myTarget = {};
const proxy = new Proxy(myTarget, { has(target, property) { console.log('has()'); return Reflect.has(...arguments) }
});
'foo' in proxy;
// has()
- defineProperty()
- getOwnPropertyDescriptor()
- deleteProperty()
- ownKeys()
- getPrototypeOf()
- setPrototypeOf()
- isExtensible()
- preventExtensions()
- apply()
- construct()
代理模式
举一些有用的编程模式
14. 跟踪属性访问
const user = { name: 'Jake'
};
const proxy = new Proxy(user, { get(target, property, receiver) { console.log(`Getting ${property}`); return Reflect.get(...arguments); }, set(target, property, value, receiver) { console.log(`Setting ${property}=${value}`); return Reflect.set(...arguments); }
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27
- 隐藏属性
const hiddenProperties = ['foo', 'bar'];
const targetObject = { foo: 1, bar: 2, baz: 3
};
const proxy = new Proxy(targetObject, { get(target, property) { if (hiddenProperties.includes(property)) { return undefined; } else { return Reflect.get(...arguments); } }, has(target, property) {if (hiddenProperties.includes(property)) { return false; } else { return Reflect.has(...arguments); } }
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true
- 属性验证
const target = { onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, { set(target, property, value) { if (typeof value !== 'number') { return false; } else { return Reflect.set(...arguments); } }
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1
- 函数与构造函数参数验证
function median(...nums) { return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, { apply(target, thisArg, argumentsList) { for (const arg of argumentsList) { if (typeof arg !== 'number') { throw 'Non-number argument provided'; } } return Reflect.apply(...arguments); }
});
console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided
class User { constructor(id) { this.id_ = id; }
}
const proxy = new Proxy(User, { construct(target, argumentsList, newTarget) { if (argumentsList[0] === undefined) { throw 'User cannot be instantiated without id'; } else { return Reflect.construct(...arguments); } }
});
new proxy(1);
new proxy();
// Error: User cannot be instantiated without id
- 数据绑定与可观察对象
const userList = [];
class User { constructor(name) { this.name_ = name; }
}
const proxy = new Proxy(User, { construct() { const newUser = Reflect.construct(...arguments); userList.push(newUser); return newUser; }
});
new proxy('John');
new proxy('Jacob');
new proxy('Jingleheimerschmidt');
console.log(userList); // [User {}, User {}, User{}]
代理是 ECMAScript 6 新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟出了一片前所未有的 JavaScript 元编程及抽象的新天地。
从宏观上看,代理是真实 JavaScript 对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分 JavaScript 的基本操作和方法。在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式。与代理如影随形的反射 API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射 API看作一套基本操作,这些操作是绝大部分 JavaScript 对象 API 的基础。
代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。
咏牡丹
宋·王溥
枣花至小能成实,桑叶虽柔解吐丝。
堪笑牡丹如斗大,不成一事又空枝。
这篇关于《每天十分钟》-红宝书第4版-代理与反射(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!