你能得到调用函数的 属性 名称吗?

Can you get the property name through which a function was called?

我进行了大量的搜索和一些尝试,我很确定这个问题的答案是,但我希望JavaScript 专家可能有办法做到这一点。

一个 JavaScript 函数可以被多个属性引用,即使是在完全不同的对象上,所以没有 对象或 属性 这样的东西持有的功能。但是任何时候你实际上 调用 一个函数,你必须通过单个对象(至少, window 用于全局函数调用的对象)和 属性 在那个对象上。

(一个函数也可以通过一个函数局部变量来调用,但是我们可以认为函数局部变量是作用域的激活对象的一个​​属性,这样case就不是一个此规则的例外。)

我的问题是,有没有办法从函数体的内部获取用于调用函数的属性名称?我不想将 属性 名称作为参数传入,也不想在封闭范围内围绕变量进行闭包,或者将名称作为单独的 属性 存储在包含函数引用的对象上,并且让函数访问 this 对象上的名称 属性。

这是我想做的一个例子:

var callName1 = function() { var callName = /* some magic */; alert(callName); };
var obj1 = {'callName2':callName1, 'callName3':callName1 };
var obj2 = {'callName4':callName1, 'callName5':callName1 };

callName1(); // should alert 'callName1'
obj1.callName2(); // should alert 'callName2'
obj1.callName3(); // should alert 'callName3'
obj2.callName4(); // should alert 'callName4'
obj2.callName5(); // should alert 'callName5'

根据我的搜索,看起来最接近上述内容的是 arguments.callee.name,但这行不通,因为只有 returns 函数的固定名称defined 时的对象,并且仅当它被定义为 named 函数时(我的示例中的函数不是)。

我还考虑过也许您可以遍历 this 对象的所有属性并测试与 arguments.callee 是否相等以找到 属性 其值是对函数的引用本身,但这也不起作用(在一般情况下),因为在对象自己的(或继承的)属性 集中可能有多个对该函数的引用,如我的示例所示。 (此外,这似乎是一种低效的解决方案。)

这可以做到吗?

简答:

  1. 不,您不能 "the property name" 用于调用您的函数。
  2. 可能根本没有名字,或者在不同的范围内有多个名字,所以"the property name"的定义很不明确。
  3. arguments.callee 已弃用,不应使用。
  4. 不存在不使用参数或闭包的解决方案。

长答案:

正如 thefourtheye 评论的那样,您应该重新考虑您正在尝试做什么,而不是在一个新问题中提出这个问题。但是有一些常见的误解,所以我会尝试解释为什么你不能得到 "simple property name".

原因不简单

在我们继续之前,让我们澄清一些事情。 激活对象根本不是对象。 ECMAScript 5.1 规范称它们为 Environment Records (10.2.1),但更常见的术语是 Scope chain。 在浏览器中,全局作用域是 (often) window 对象,但所有其他作用域都是 而不是 对象。 可能是一个你用来调用函数的对象,当你调用一个函数时,你必须在某个范围内.

除了少数例外,范围不是对象,对象也不是范围。

然后,有很多名字。

  • 当你调用一个函数时,你需要引用它,比如通过一个对象属性。此引用可能有一个名称。
  • 作用域链有声明总是有一个名字。
  • A Function(真正的函数,不是引用)可能也有一个函数名——你的arguments.callee.name——它在声明时是固定的。

它们不仅名称不同,而且(总是)不是您正在寻找的"the property name"。

var obj = { prop : function f(){} }, func = obj.prop;
// "obj" and "func" are declarations.
// Function name is "f" - use this name instead of arguments.callee
// Property name is "prop"
func();     // Reference name is "func"
obj.prop(); // Reference names are "obj" and "prop"
// But they are the same function!
// P.S. "this" in f is undefined (strict mode) or window (non-strict)

因此,函数引用可能来自 binding(例如函数声明)、Object(arguments.callee)或 variable。 都是References(8.7)。参考确实有一个名字(可以这么说)。

要注意的是,函数引用并不总是来自对象或作用域链,并且其名称并不总是被定义。 例如一个常见的闭包技巧:

(function(i){ /* what is my name? */ })(i)

即使引用确实有名称,函数调用 (11.2.3) 也不会以任何方式将引用或其名称传递给函数。 这使 JavaScript 引擎保持正常。考虑这个例子:

eval("(new Function('return function a(){}'))()")() // Calls function 'a'.

最后的函数调用引用了eval函数,它引用了一个新的全局范围(在strict mode,反正)的结果,它引用了一个函数调用语句,它引用了一个组,它引用了一个匿名函数对象,它包含表达 returns 函数的代码 'a'.

如果要从a中获取"property name",应该获取哪个? "eval"? "Function"? "anonymous"? "a"?他们都? 在您回答之前,请考虑复杂的情况,例如跨 iframe 的函数访问,它具有不同的全局变量以及跨源限制,或者与本机函数的交互(例如 Function.prototype.bind),您将看到它如何很快变得糟糕。

这也是为什么 arguments.caller__caller__ 和其他类似技术现在都被弃用的原因。 一个函数的 "property name" 定义比调用者还要糟糕,几乎不现实。 至少调用者总是一个执行上下文(不一定是函数)。

因此,不知道您真正的问题是什么,获得 "property name" 的最佳选择是使用闭包。

是的。

有点。

这取决于浏览器。 (Chrome=确定,Firefox=不)

您可以使用工厂来创建 function,以及可能会让我被捕的调用堆栈解析 hack。

此解决方案适用于我在 Windows 7 上的 Chrome 版本,但该方法可以适用于其他浏览器(如果它们支持 stack 并显示 属性 名称在调用堆栈中就像 Chrome 一样)。我不建议在生产代码中这样做,因为这是一个非常脆弱的 hack;而是改进程序的体系结构,这样您就不需要依赖于知道调用的名称 属性。您没有 post 关于您的问题域的详细信息,所以这只是一个有趣的小思想实验;即:

JSFiddle 演示:http://jsfiddle.net/tv9m36fr/

运行可用代码段:(向下滚动并单击 运行 代码段

function getCallerName(ex) {
    // parse the call stack to find name of caller; assumes called from object property
    // todo: replace with regex (left as exercise for the reader)
    // this works in chrome on win7. other browsers may format differently(?) but not tested.
    // easy enough to extend this concept to be browser-specific if rules are known.
    // this is only for educational purposes; I would not do this in production code.
    var stack = ex.stack.toString();
    var idx = stack.indexOf('\n');
    var lines = ex.stack.substring(idx + 1);
    var objectSentinel = 'Object.';
    idx = lines.indexOf(objectSentinel);
    var line = lines.substring(idx + objectSentinel.length);
    idx = line.indexOf(' ');
    var callerName = line.substring(0, idx);
    return callerName;
}

var Factory = {
    getFunction: function () {
        return function () {
            var callName = "";
            try {
                throw up; // you don't *have* to throw to get stack trace, but it's more fun!
            } catch (ex) {
                callName = getCallerName(ex);
            }
            alert(callName);
        };
    }
}

var obj1 = {
    'callName2': Factory.getFunction(),
        'callName3': Factory.getFunction()
};

var obj2 = {
    'callName4': Factory.getFunction(),
        'callName5': Factory.getFunction()
};

obj1.callName2(); // should alert 'callName2' 
obj1.callName3(); // should alert 'callName3'
obj2.callName4(); // should alert 'callName4'
obj2.callName5(); // should alert 'callName5'

没有反射,但您可以使用函数行为来相当轻松地添加您自己的反射,而无需求助于 try/catcharguments.calleeFunction.caller 或其他强烈反对的行为,只是浪费循环:

// returning a function from inside a function always creates a new, unique function we can self-identify later:
function callName() { 
   return function callMe(){ 
             for(var it in this) if(this[it]===callMe) return alert(it);
   }  
};

//the one ugly about this is the extra "()" at the end:
var obj1 = {'callName2':callName(), 'callName3':callName() };
var obj2 = {'callName4':callName(), 'callName5':callName() };

//test out the tattle-tale function:
obj1.callName2(); // alerts 'callName2'
obj2.callName5(); // alerts 'callName5'

如果你真的想让它看起来像一个赋值并避免每次在对象文字中执行括号,你可以执行这个 hacky 例程来创建一个调用别名:

function callName() { 
   return function callMe(){ 
             for(var it in this) if(this[it]===callMe) return alert(it);
   }  
};

//make an alias to execute a function each time it's used :
Object.defineProperty(window, 'callNamer', {get: function(){ return callName() }});

//use the alias to assign a tattle-tale function (look ma, no parens!):
var obj1 = {'callName2': callNamer, 'callName3': callNamer };
var obj2 = {'callName4': callNamer, 'callName5': callNamer };

//try it out:
obj1.callName2(); // alerts 'callName2'
obj2.callName5(); // alerts 'callName5'

除此之外,您或许可以完成您需要做的事情,而无需此方法所需的所有循环。

优点:

  • 适用于全局变量或对象属性
  • 不需要重复 key/name 通过
  • 不使用专有或弃用的功能
  • 不使用参数或闭包
  • 周围代码的执行速度(优化后)比 try/catch 版本
  • 不被重复​​使用混淆
  • 可以处理新的和删除(重命名)的属性

注意事项:

  • 不适用于没有 属性 名称的私有变量
  • 每次访问时部分循环所有者对象
  • 计算速度比记忆属性或代码时间重复
  • 活不下去了 call/bind/apply
  • 如果没有 bind() 或包装函数
  • ,将无法在 setTimeout 中存活
  • 不容易被克隆

老实说,为了礼貌起见,我认为完成此任务的所有方法都是 "less than ideal",我建议您咬紧牙关并传递额外的键名,或者使用一种方法使其自动化向空白对象添加属性,而不是将其全部编码在对象文字中。