反射性地查找通话中的关键
这可能是最可靠的方法。当obj.foo()
被调用时,然后foo
以obj
set 的值this
执行。这意味着我们可以从中查找密钥this
。我们可以很容易地检查对象,最难的是找到哪个键包含我们刚刚执行的函数。我们可以尝试进行字符串匹配,但它可能会失败:
const obj = {
foo: function() { /* magic */ },
bar: function() { /* magic */ },
}
因为函数的内容相同但键不同,所以很难通过字符串匹配来区分obj.foo()
和区分。obj.bar()
但是,有一个更好的选择 - 命名函数:
const obj = {
foo: function lookUpMyOwnKey() { /* magic */ }
}
通常,无论您是否为函数命名,都几乎没有影响。但是,我们可以利用的是该函数现在可以通过名称引用自身。这为我们提供了一个相当简单的解决方案,使用Object.entries
:
"use strict";
const fn = function lookUpMyOwnName() {
if (typeof this !== "object" || this === null) { //in case the context is removed
return "Sorry, I don't know";
}
const pair = Object.entries(this)
.find(([, value]) => value === lookUpMyOwnName);
if (!pair) {
return "I can't seem to find out";
}
return `My name is: ${pair[0]}`
}
const obj = {
foo: fn
}
console.log(obj.foo());
console.log(obj.foo.call(null));
console.log(obj.foo.call("some string"));
console.log(obj.foo.call({
other: "object"
}));
这非常接近完美的解决方案。正如我们所看到的,如果函数没有定义为对象的一部分而是稍后添加,它甚至可以工作。因此,它完全脱离了它所属的对象。问题是它仍然是一个函数,并且多次添加它不会得到正确的结果:
"use strict";
const fn = function lookUpMyOwnName() {
if (typeof this !== "object" || this === null) { //in case the context is removed
return "Sorry, I don't know";
}
const pair = Object.entries(this)
.find(([, value]) => value === lookUpMyOwnName);
if (!pair) {
return "I can't seem to find out";
}
return `My name is: ${pair[0]}`
}
const obj = {
foo: fn,
bar: fn
}
console.log(obj.foo()); // foo
console.log(obj.bar()); // foo...oops
幸运的是,这很容易通过高阶函数和动态创建lookUpMyOwnName
来解决。这样不同的实例就不会相互识别:
"use strict";
const makeFn = () => function lookUpMyOwnName() {
// ^^^^^^ ^^^^^
if (typeof this !== "object" || this === null) { //in case the context is removed
return "Sorry, I don't know";
}
const pair = Object.entries(this)
.find(([, value]) => value === lookUpMyOwnName);
if (!pair) {
return "I can't seem to find out";
}
return `My name is: ${pair[0]}`
}
const obj = {
foo: makeFn(),
bar: makeFn()
}
console.log(obj.foo()); // foo
console.log(obj.bar()); // bar
确保我们找到关键
仍然有可能失败的方法
例子:
"use strict";
const makeFn = () => function lookUpMyOwnName() {
// ^^^^^^ ^^^^^
if (typeof this !== "object" || this === null) { //in case the context is removed
return "Sorry, I don't know";
}
const pair = Object.entries(this)
.find(([, value]) => value === lookUpMyOwnName);
if (!pair) {
return "I can't seem to find out";
}
return `My name is: ${pair[0]}`
}
const obj = {
foo: makeFn()
}
const obj2 = Object.create(obj);
console.log(obj.foo()); // foo
console.log(obj2.foo()); // unknown
const obj3 = Object.defineProperties({}, {
foo: {
value: makeFn(),
enumerable: true
},
bar: {
value: makeFn(),
enumerable: false
}
})
console.log(obj3.foo()); // foo
console.log(obj3.bar()); // unknown
是否值得制作一个过度设计的解决方案来解决一个不存在的问题,只是为了在这里找到所有东西?
好吧,我不知道答案。无论如何我都会做到的 - 这是一个彻底检查其宿主对象及其原型链的函数,Object.getOwnPropertyDescriptors
以找到它被调用的确切位置:
"use strict";
const makeFn = () => function lookUpMyOwnName() {
if (typeof this !== "object" || this === null) {
return "Sorry, I don't know";
}
const pair = Object.entries(Object.getOwnPropertyDescriptors(this))
.find(([propName]) => this[propName] === lookUpMyOwnName);
if (!pair) {//we must go DEEPER!
return lookUpMyOwnName.call(Object.getPrototypeOf(this));
}
return `My name is: ${pair[0]}`;
}
const obj = {
foo: makeFn()
}
const obj2 = Object.create(obj);
console.log(obj.foo()); // foo
console.log(obj2.foo()); // foo
const obj3 = Object.defineProperties({}, {
foo: {
value: makeFn(),
enumerable: true
},
bar: {
value: makeFn(),
enumerable: false
},
baz: {
get: (value => () => value)(makeFn()) //make a getter from an IIFE
}
})
console.log(obj3.foo()); // foo
console.log(obj3.bar()); // bar
console.log(obj3.baz()); // baz
使用代理(轻微作弊)
这是一个替代方案。定义一个Proxy
拦截对对象的所有调用,这可以直接告诉你调用了什么。这有点作弊,因为该函数并没有真正查找自己,但从外部看它可能看起来像这样。
仍然可能值得上市,因为它具有非常强大且管理成本低的优势。无需递归遍历原型链和所有可能的属性来找到一个:
"use strict";
//make a symbol to avoid looking up the function by its name in the proxy
//and to serve as the placement for the name
const tellMe = Symbol("Hey, Proxy, tell me my key!");
const fn = function ItrustTheProxyWillTellMe() {
return `My name is: ${ItrustTheProxyWillTellMe[tellMe]}`;
}
fn[tellMe] = true;
const proxyHandler = {
get: function(target, prop) { ///intercept any `get` calls
const val = Reflect.get(...arguments);
//if the target is a function that wants to know its key
if (val && typeof val === "function" && tellMe in val) {
//attach the key as @@tellMe on the function
val[tellMe] = prop;
}
return val;
}
};
//all properties share the same function
const protoObj = Object.defineProperties({}, {
foo: {
value: fn,
enumerable: true
},
bar: {
value: fn,
enumerable: false
},
baz: {
get() { return fn; }
}
});
const derivedObj = Object.create(protoObj);
const obj = new Proxy(derivedObj, proxyHandler);
console.log(obj.foo()); // foo
console.log(obj.bar()); // bar
console.log(obj.baz()); // baz
查看调用堆栈
这是草率且不可靠的,但仍然是一种选择。它将非常依赖于此代码所在的环境,因此我将避免进行实现,因为它需要绑定到 StackSnippet 沙箱。
但是,整个事情的关键是检查函数调用位置的堆栈跟踪。这在不同的地方会有不同的格式。这种做法非常狡猾和脆弱,但它确实揭示了比你通常能得到的更多关于通话的背景信息。在特定情况下它可能非常有用。
David Walsh在这篇文章中展示了该技术,这里是它的简写——我们可以创建一个Error
自动收集堆栈跟踪的对象。大概是这样我们可以扔掉它并稍后检查它。相反,我们现在可以检查它并继续:
// The magic
console.log(new Error().stack);
/* SAMPLE:
Error
at Object.module.exports.request (/home/vagrant/src/kumascript/lib/kumascript/caching.js:366:17)
at attempt (/home/vagrant/src/kumascript/lib/kumascript/loaders.js:180:24)
at ks_utils.Class.get (/home/vagrant/src/kumascript/lib/kumascript/loaders.js:194:9)
at /home/vagrant/src/kumascript/lib/kumascript/macros.js:282:24
at /home/vagrant/src/kumascript/node_modules/async/lib/async.js:118:13
at Array.forEach (native)
at _each (/home/vagrant/src/kumascript/node_modules/async/lib/async.js:39:24)
at Object.async.each (/home/vagrant/src/kumascript/node_modules/async/lib/async.js:117:9)
at ks_utils.Class.reloadTemplates (/home/vagrant/src/kumascript/lib/kumascript/macros.js:281:19)
at ks_utils.Class.process (/home/vagrant/src/kumascript/lib/kumascript/macros.js:217:15)
*/