我正在尝试代理一组对象,以便我可以将它们传递给第三方代码并暂时取消突变方法和设置器,然后撤销代理处理程序陷阱以恢复正常行为。我发现代理对依赖this
的代码本质上是敌对的。
我很好奇&为什么Javascript代理打破this
绑定为他们的代理目标。在下面的示例中,我有一个简单的类,它在构造时摄取值,将其存储为私有字段,并在属性访问时返回它。观察到:
- 尝试通过代理访问属性而没有处理程序会抛出错误
- 通过
Reflect.get
显式转发处理程序get
陷阱恢复正常行为
class Thing {
#value
constructor(value){
this.#value = value
}
get value(){
return this.#value
}
}
// Default behaviour
const thing1 = new Thing('foo')
attempt(() => thing1.value)
// No-op proxy breaks contextual access behaviour
const proxy1 = new Proxy(thing1, {})
attempt(() => proxy1.value)
// Reinstated by explicitly forwarding handler get call to Reflect
const proxy2 = new Proxy(thing1, {get: (target, key) =>
Reflect.get(target, key)
})
attempt(() => proxy2.value)
function attempt(fn){
try {
console.log(fn())
}
catch(e){
console.error(e)
}
}
这为getter访问提供了一个解决方案,但我不明白为什么会出现这个问题,也不明白为什么需要额外的代码来修复它。在未处理的代理查询中,同样的上下文冲突问题在涉及到方法时更加令人烦恼。在下面的代码中,value
属性变成了一个方法,而不是getter。在本例中:
- 默认行为仍然被打破
Reflect.get
不再工作- I可以显式地在
get
陷阱中绑定this
- 但这不是预期行为的恢复
class Thing {
#value
constructor(value){
this.#value = value
}
value(){
return this.#value
}
}
// Default behaviour
const thing1 = new Thing('foo')
attempt(() => thing1.value())
// No-op proxy breaks contextual access behaviour
const proxy1 = new Proxy(thing1, {})
attempt(() => proxy1.value())
// Forwarding handler get trap to Reflect doesn't work
const proxy2 = new Proxy(thing1, {get: (target, key) =>
Reflect.get(target, key)
})
attempt(() => proxy2.value())
// Explicitly binding the returned method *does* work
const proxy3 = new Proxy(thing1, {get: (target, key) =>
target[key].bind(target)
})
attempt(() => proxy3.value())
// But this goes beyond reinstating normal behaviour
var {value} = thing1
attempt(() => value())
var {value} = proxy3
attempt(() => value())
function attempt(fn){
try {
console.log(fn())
}
catch(e){
console.error(e)
}
}
;
- 私有访问要求将操作上下文设置为创建私有成员的对象(提供代理解决方案)
- 对于您提供的用例和人为代码,不需要代理,因为可以使用简单的继承来完成目标(最底部的解决方案)
第一个例子第一个示例中的no-op Proxy没有被破坏。get()
方法仍然是通过代理对象调用的(甚至是无操作),而不是通过thing
对象。所以私有成员不能通过代理访问proxy1.value
。在第一个示例中使用反射的修复方法是在几乎所有具有访问限制(有些需要屈折)的语言中访问这些成员的常用方法。历史上,在Reflect.get()
可用之前,这是使用函数对象的.apply()
方法完成的。所以使用Reflect.get()
也是有意义的。
底线:
因此,必须采取一些操作将上下文设置为创建private成员的对象,否则将无法访问它。
第二个例子对Reflect.get()
的调用在第二个例子中不起作用,因为将getter语法从get value()
移到了value()
。现在调用一个函数来检索value
,它必须绑定到正确的对象。简单的反思是不够的。要使Reflect.get()
在这里工作,你必须将getter函数绑定到目标。
使用函数的.bind()
方法是控制操作上下文的另一种传统方法。来自文档:
bind()方法创建了一个新函数,当调用该函数时,该函数具有其
this
关键字设置为提供的值…
Reflect.get(target, key).bind(target)
这和你在下面使用的.bind()
完全一样:
target[key].bind(target)
静态Reflect.get()方法的工作原理类似于从对象中获取属性object (target[propertyKey])作为一个函数
在这两种情况下(Reflect.get()
,.bind()
),上下文都被转移到创建private成员的对象。这在许多用例中是必要的,并且与Proxy无关。
使用代理的工作解决方案
class Thing {
#value
constructor(value) { this.#value = value }
value() { return this.#value }
get value() { return this.#value; }
set value(v) { this.#value = v; }
someMethod() { return 'Cannot get here when proxied.'}
}
const thing = new Thing('foo')
const revokeMe = Proxy.revocable(thing, {
get: (target, key) => {
if (key === 'value') {
return () => 'value is undefined (blocked by proxy)'
}
if(key === 'someMethod') {
return () => `cannot invoke ${key}. (blocked by proxy)`;
}
return Reflect.get(target, key).bind(target);
},
set: (target, key, value) => {
if (key === 'value') {
console.log(`cannot set ${key} property. (blocked by proxy)`);
}
return Reflect.set(target, key, value);
}
});
const proxy = revokeMe.proxy;
console.log(proxy.value());
proxy.value = 'test';
console.log(proxy.value());
console.log(proxy.someMethod());
revokeMe.revoke();
try {
proxy.value();
} catch (err) {
console.log('proxy has been revoked');
}
thing.value = 'new value';
console.log(thing.value);
console.log(thing.someMethod());
使用简单继承的工作方案
关注这个问题语句:">暂时使突变方法和setter无效,然后[…]恢复正常行为">
根据您提供的代码,解决方案根本不需要Proxy。只需设置对象的原型,并根据需要重写属性/方法。
class Thing {
#value
constructor(value){ this.#value = value + ' cannot get here'}
value(){ return this.#value + ' not gonna happen'}
get value(){ return this.#value + ' not gonna happen'}
set value(v) { this.#value = value;};
toBeDisabled() { return 'will not show';}
}
class Overrides {
constructor(value) {
this.value = value + ' (set in Overrides)';
}
get value() {return 'value is undefined (set in Overrides)';}
set value(v) {}
toBeDisabled(){
return 'NoOp (in Overrides)';
}
}
let thing = new Thing('foo');
thing.__proto__ = new Overrides(thing.value);
thing.value = 'new value';
console.log(thing.value);
console.log(thing.toBeDisabled())
thing.__proto__ = {}
thing.value = 'now value will set; proxy is disabled;';
console.log(thing.value);