嵌套的 ES6 代理未按预期工作
Nested ES6 Proxies not working as expected
我正在开发一个 API 客户端,它允许在提供 foo
的 ID 时调用特定的 API 方法,如下所示:
apiClient.myApiMethod('myFooId', 'firstApiArg', 'nthApiArg');
为了方便开发者,我正在尝试实现自定义代理对象:
var myFoo = apiClient.registerFoo('myFoo', 'myFooId');
myFoo.myApiMethod('firstApiArg', 'nthApiArg');
搜索了一段时间后,我认为 ES6 代理可能最适合它,因为 fooId
需要作为方法调用的第一个参数插入以支持两种工作方式。
因此,我创建了以下代码。如果调用 Foo.myFoos
的对象 属性(例如 Foo.myFoos.example
),则在 _myFooItems
中搜索它,如果存在,则返回另一个 Proxy 对象。
现在,如果在 that 对象上调用了一个方法,则会在 Foo
的属性中搜索它,如果找到,则使用 [=] 调用 Foo
方法21=] 作为它的第一个参数。
这意味着,您应该能够 Foo.myFoos.example.parentMethodX('bar', 'baz')
.
var Foo = function() {
// parent instance
_self = this;
// custom elements dictionary
_myFooItems = {};
// to call parent methods directly on custom elements
this.myFoos = Object.create(new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, myFooName) {
// whether called property is a registered foo
if (_myFooItems.hasOwnProperty(myFooName)) {
// create another proxy to intercept method calls on previous one
return Object.create(new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, methodName) {
// whether parent method exists
if (_self.hasOwnProperty(methodName)) {
return function(/* arguments */) {
// insert custom element ID into args array
var args = Array.prototype.slice.call(arguments);
args.unshift(_myFooItems[ myFooName ]);
// apply parent method with modified args array
return _self[ methodName ].apply(_self, args);
};
} else {
// parent method does not exist
return function() {
throw new Error('The method ' + methodName + ' is not implemented.');
}
}
}
}
));
}
}
}
));
// register a custom foo and its ID
this.registerFoo = function(myFooName, id) {
// whether the foo has already been registered
if (_myFooItems.hasOwnProperty(myFooName)) {
throw new Error('The Foo ' + myFooName + ' is already registered in this instance.');
}
// register the foo
_myFooItems[ myFooName ] = id;
// return the created foo for further use
return this.myFoos[ myFooName ];
};
};
module.exports = Foo;
虽然如果您 运行 代码并尝试注册一个 foo
会发生什么(上面的代码在 Node>=6.2.0 中工作),但以下错误是抛出:
> var exampleFoo = Foo.registerFoo('exampleFoo', 123456)
Error: The method inspect is not implemented.
at null.<anonymous> (/path/to/module/nestedProxyTest.js:40:31)
at formatValue (util.js:297:21)
at Object.inspect (util.js:147:10)
at REPLServer.self.writer (repl.js:366:19)
at finish (repl.js:487:38)
at REPLServer.defaultEval (repl.js:293:5)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.<anonymous> (repl.js:441:10)
at emitOne (events.js:101:20)
在花了很多时间思考为什么第二个代理甚至尝试在给它 none 的情况下调用方法后,我最终放弃了。我希望 exampleFoo
是一个 Proxy 对象,如果被调用则接受 Foo
方法。
是什么导致了这里的实际行为?
首先,我不确定代理模式是否是处理您的问题的最有效和最干净的方式,但它当然应该是可行的。
我看到的第一个问题是您的实际测试试图在 Foo 原型 (class) 本身上调用 registerFoo
,而您只为 [= 的实例定义了它14=]。所以你必须首先创建一个实例,如下所示:
var foo = new Foo();
var exampleFoo = foo.registerFoo('exampleFoo', 123456);
然后要完成测试,您必须调用一个应该存在的方法。所以为了测试它,我会添加到 Foo
这样的东西:
// Define an example method on a Foo instance:
this.myMethod = function (barName /* [, arguments] */) {
var args = Array.prototype.slice.call(arguments);
return 'You called myMethod(' + args + ') on a Foo object';
}
虽然不是问题,但我认为没有必要在 new Proxy(...)
上应用 Object.create
,因为后者已经创建了一个对象,我看不到将其用作原型而不是直接将其用作您的对象。
因此,通过这些微小的调整,我得到了这段代码,它似乎在浏览器中产生了正确的结果(此处使用 FireFox):
var Foo = function() {
// parent instance
_self = this;
// example method
this.myMethod = function (barName /* [, arguments] */) {
var args = Array.prototype.slice.call(arguments);
return 'You called myMethod(' + args + ') on a Foo object';
}
// custom elements dictionary
_myFooItems = {};
// to call parent methods directly on custom elements
this.myFoos = new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, myFooName) {
// whether called property is a registered foo
if (_myFooItems.hasOwnProperty(myFooName)) {
// create another proxy to intercept method calls on previous one
return new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, methodName) {
// whether parent method exists
if (_self.hasOwnProperty(methodName)) {
return function(/* arguments */) {
// insert custom element ID into args array
var args = Array.prototype.slice.call(arguments);
args.unshift(_myFooItems[ myFooName ]);
// apply parent method with modified args array
return _self[ methodName ].apply(_self, args);
};
} else {
// parent method does not exist
return function() {
throw new Error('The method ' + methodName + ' is not implemented.');
}
}
}
});
}
}
});
// register a custom foo and its ID
this.registerFoo = function(myFooName, id) {
// whether the foo has already been registered
if (_myFooItems.hasOwnProperty(myFooName)) {
throw new Error('The Foo ' + myFooName + ' is already registered in this instance.');
}
// register the foo
_myFooItems[ myFooName ] = id;
// return the created foo for further use
return this.myFoos[ myFooName ];
};
};
// Test it:
var foo = new Foo();
var exampleFoo = foo.registerFoo('exampleFoo', 123456);
var result = exampleFoo.myMethod(13);
console.log(result);
我认为您根本不应该在这里使用代理。假设你 API 有一个可怕的
class Foo {
…
myApiMethod(id, …) { … }
… // and so on
}
那么实现您所寻找的最干净的方法是
const cache = new WeakMap();
Foo.prototype.register = function(id) {
if (!cache.has(this))
cache.set(this, new Map());
const thisCache = cache.get(this);
if (!thisCache.get(id))
thisCache.set(id, new IdentifiedFoo(this, id));
return thisCache.get(id);
};
class IdentifiedFoo {
constructor(foo, id) {
this.foo = foo;
this.id = id;
}
}
Object.getOwnPropertyNames(Foo.prototype).forEach(function(m) {
if (typeof Foo.prototype[m] != "function" || m == "register") // etc
return;
IdentifiedFoo.prototype[m] = function(...args) {
return this.foo[m](this.id, ...args);
};
});
这样你就可以做到
var foo = new Foo();
foo.myApiMethod(id, …);
foo.register(id).myApiMethod(…);
我正在开发一个 API 客户端,它允许在提供 foo
的 ID 时调用特定的 API 方法,如下所示:
apiClient.myApiMethod('myFooId', 'firstApiArg', 'nthApiArg');
为了方便开发者,我正在尝试实现自定义代理对象:
var myFoo = apiClient.registerFoo('myFoo', 'myFooId');
myFoo.myApiMethod('firstApiArg', 'nthApiArg');
搜索了一段时间后,我认为 ES6 代理可能最适合它,因为 fooId
需要作为方法调用的第一个参数插入以支持两种工作方式。
因此,我创建了以下代码。如果调用 Foo.myFoos
的对象 属性(例如 Foo.myFoos.example
),则在 _myFooItems
中搜索它,如果存在,则返回另一个 Proxy 对象。
现在,如果在 that 对象上调用了一个方法,则会在 Foo
的属性中搜索它,如果找到,则使用 [=] 调用 Foo
方法21=] 作为它的第一个参数。
这意味着,您应该能够 Foo.myFoos.example.parentMethodX('bar', 'baz')
.
var Foo = function() {
// parent instance
_self = this;
// custom elements dictionary
_myFooItems = {};
// to call parent methods directly on custom elements
this.myFoos = Object.create(new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, myFooName) {
// whether called property is a registered foo
if (_myFooItems.hasOwnProperty(myFooName)) {
// create another proxy to intercept method calls on previous one
return Object.create(new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, methodName) {
// whether parent method exists
if (_self.hasOwnProperty(methodName)) {
return function(/* arguments */) {
// insert custom element ID into args array
var args = Array.prototype.slice.call(arguments);
args.unshift(_myFooItems[ myFooName ]);
// apply parent method with modified args array
return _self[ methodName ].apply(_self, args);
};
} else {
// parent method does not exist
return function() {
throw new Error('The method ' + methodName + ' is not implemented.');
}
}
}
}
));
}
}
}
));
// register a custom foo and its ID
this.registerFoo = function(myFooName, id) {
// whether the foo has already been registered
if (_myFooItems.hasOwnProperty(myFooName)) {
throw new Error('The Foo ' + myFooName + ' is already registered in this instance.');
}
// register the foo
_myFooItems[ myFooName ] = id;
// return the created foo for further use
return this.myFoos[ myFooName ];
};
};
module.exports = Foo;
虽然如果您 运行 代码并尝试注册一个 foo
会发生什么(上面的代码在 Node>=6.2.0 中工作),但以下错误是抛出:
> var exampleFoo = Foo.registerFoo('exampleFoo', 123456)
Error: The method inspect is not implemented.
at null.<anonymous> (/path/to/module/nestedProxyTest.js:40:31)
at formatValue (util.js:297:21)
at Object.inspect (util.js:147:10)
at REPLServer.self.writer (repl.js:366:19)
at finish (repl.js:487:38)
at REPLServer.defaultEval (repl.js:293:5)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.<anonymous> (repl.js:441:10)
at emitOne (events.js:101:20)
在花了很多时间思考为什么第二个代理甚至尝试在给它 none 的情况下调用方法后,我最终放弃了。我希望 exampleFoo
是一个 Proxy 对象,如果被调用则接受 Foo
方法。
是什么导致了这里的实际行为?
首先,我不确定代理模式是否是处理您的问题的最有效和最干净的方式,但它当然应该是可行的。
我看到的第一个问题是您的实际测试试图在 Foo 原型 (class) 本身上调用 registerFoo
,而您只为 [= 的实例定义了它14=]。所以你必须首先创建一个实例,如下所示:
var foo = new Foo();
var exampleFoo = foo.registerFoo('exampleFoo', 123456);
然后要完成测试,您必须调用一个应该存在的方法。所以为了测试它,我会添加到 Foo
这样的东西:
// Define an example method on a Foo instance:
this.myMethod = function (barName /* [, arguments] */) {
var args = Array.prototype.slice.call(arguments);
return 'You called myMethod(' + args + ') on a Foo object';
}
虽然不是问题,但我认为没有必要在 new Proxy(...)
上应用 Object.create
,因为后者已经创建了一个对象,我看不到将其用作原型而不是直接将其用作您的对象。
因此,通过这些微小的调整,我得到了这段代码,它似乎在浏览器中产生了正确的结果(此处使用 FireFox):
var Foo = function() {
// parent instance
_self = this;
// example method
this.myMethod = function (barName /* [, arguments] */) {
var args = Array.prototype.slice.call(arguments);
return 'You called myMethod(' + args + ') on a Foo object';
}
// custom elements dictionary
_myFooItems = {};
// to call parent methods directly on custom elements
this.myFoos = new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, myFooName) {
// whether called property is a registered foo
if (_myFooItems.hasOwnProperty(myFooName)) {
// create another proxy to intercept method calls on previous one
return new Proxy({}, {
// property getter function (proxy target and called property name as params)
get: function(target, methodName) {
// whether parent method exists
if (_self.hasOwnProperty(methodName)) {
return function(/* arguments */) {
// insert custom element ID into args array
var args = Array.prototype.slice.call(arguments);
args.unshift(_myFooItems[ myFooName ]);
// apply parent method with modified args array
return _self[ methodName ].apply(_self, args);
};
} else {
// parent method does not exist
return function() {
throw new Error('The method ' + methodName + ' is not implemented.');
}
}
}
});
}
}
});
// register a custom foo and its ID
this.registerFoo = function(myFooName, id) {
// whether the foo has already been registered
if (_myFooItems.hasOwnProperty(myFooName)) {
throw new Error('The Foo ' + myFooName + ' is already registered in this instance.');
}
// register the foo
_myFooItems[ myFooName ] = id;
// return the created foo for further use
return this.myFoos[ myFooName ];
};
};
// Test it:
var foo = new Foo();
var exampleFoo = foo.registerFoo('exampleFoo', 123456);
var result = exampleFoo.myMethod(13);
console.log(result);
我认为您根本不应该在这里使用代理。假设你 API 有一个可怕的
class Foo {
…
myApiMethod(id, …) { … }
… // and so on
}
那么实现您所寻找的最干净的方法是
const cache = new WeakMap();
Foo.prototype.register = function(id) {
if (!cache.has(this))
cache.set(this, new Map());
const thisCache = cache.get(this);
if (!thisCache.get(id))
thisCache.set(id, new IdentifiedFoo(this, id));
return thisCache.get(id);
};
class IdentifiedFoo {
constructor(foo, id) {
this.foo = foo;
this.id = id;
}
}
Object.getOwnPropertyNames(Foo.prototype).forEach(function(m) {
if (typeof Foo.prototype[m] != "function" || m == "register") // etc
return;
IdentifiedFoo.prototype[m] = function(...args) {
return this.foo[m](this.id, ...args);
};
});
这样你就可以做到
var foo = new Foo();
foo.myApiMethod(id, …);
foo.register(id).myApiMethod(…);