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

时间:2015-02-01 07:11:24

标签: javascript properties

我已经做了很多搜索和一些游戏,我很确定这个问题的答案是没有,但我希望是一个JavaScript专家可能有一个可以做到这一点的技巧。

JavaScript函数可以由多个属性引用,即使在完全不同的对象上也是如此,因此不存在 对象或包含该函数的属性。但是每当你实际上调用一个函数时,你必须通过一个对象(至少是全局函数调用的window对象)和该对象的属性来完成。 / p>

(函数也可以通过函数局部变量调用,但我们可以将函数局部变量视为作用域的激活对象的属性,因此该情况​​不是此规则的例外。)

我的问题是,有没有办法获取用于调用函数的属性名称,从里面函数体?我不想将属性名称作为参数传递,或者在封闭范围内的变量周围传递,或者将该名称存储为包含函数引用的对象的单独属性,并使该函数访问该名称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,但这不会起作用,因为它只返回固定到函数对象的名称。 已定义,并且仅当它被定义为命名的函数时(我的示例中的函数不是)。

我还考虑过,也许您可​​以迭代this对象的所有属性并测试与arguments.callee的相等性,以找到其值是对函数本身的引用的属性,但是赢了&# 39;工作要么(在一般情况下),因为在对象的自己(或继承)属性集中可能有多个对函数的引用,如我的例子中所示。 (而且,这似乎是一种效率低下的解决方案。)

可以这样做吗?

3 个答案:

答案 0 :(得分:7)

简答:

  1. 不,您无法获得用于调用您的功能的“属性名称”。
  2. 可能根本没有名称,或者不同范围内的多个名称,因此“属性名称”的定义非常糟糕。
  3. arguments.callee已弃用,不应使用。
  4. 没有不使用参数或闭包的解决方案。
  5. 答案很长:

    正如thefourtheye评论的那样,你应该重新思考你想要做的事情,并在一个新问题中提出这个要求。但是有一些常见的误解,所以我会试着解释为什么你不能得到“简单的属性名称”。

    原因是因为它并不简单。

    在我们开始之前,让我们澄清一些事情。 激活对象根本不是对象。 ECMAScript 5.1规范称它们为Environment Records (10.2.1),但更常见的术语是Scope chain。 在浏览器中,全局范围是(oftenwindow对象,但所有其他范围都是不是对象。 可能是用于调用函数的对象,当您调用函数时,必须位于某个范围中。 他们是不同的。

    还有很多名字。

    • 调用函数时,需要引用,例如通过对象属性。此参考可能有一个名称。
    • 范围链具有声明始终具有名称。
    • Function(实际功能,不是参考)可能也有一个功能名称 - 您的arguments.callee.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中),它引用一个函数调用语句,它引用一个组,它引用一个匿名的Function对象,其中包含表达并返回名为“a”的函数的代码。

    如果你想从a中获取“属性名称”,它应该得到哪一个? “EVAL”? “功能”? “匿名”? “一个”?他们都是? 在回答之前,请考虑一些复杂问题,例如跨iframe的函数访问,它具有不同的全局变量以及交叉原点限制,或者与本机函数的交互(例如Function.prototype.bind),您将看到它如何迅速变成地狱。 / p>

    这也是为什么arguments.caller__caller__和其他类似技术现已全部弃用的原因。 函数的“属性名称”比调用者更加不明确,几乎不切实际。 至少调用者总是执行上下文(不一定是函数)。

    所以,不知道你的真正问题是什么,获得“属性名称”的最佳选择是使用闭包。

答案 1 :(得分:2)

没有反射,但你可以使用函数行为来添加你自己的相当无痛,而不依靠 try / catch arguments.callee Function.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'

如果您真的想让它看起来像一个赋值并且每次都避免在对象文字中执行parens,那么你可以执行这个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'

除此之外,如果没有这种方法所需的所有循环,你可以完成你需要做的事情。

<强>优点

  • 适用于全局或对象属性
  • 不需要重复的密钥/名称传递
  • 不使用专有或已弃用的功能
  • 不使用参数或闭包
  • 周围代码的执行速度比(优化)快 一个试用/捕获版本
  • 不会因重复使用而混淆
  • 可以处理新的和已删除(重命名)的属性

<强>注意事项:

  • 不对没有属性名称的私有变量工作
  • 部分循环所有者对象每次访问
  • 比记忆属性或代码时重复慢的计算
  • 未能通过电话/绑定/申请
  • 在没有 bind()或包装函数的情况下不会在setTimeout中存活
  • 无法轻易克隆
老实说,我认为完成这项任务的所有方法都不是理想的,而且要礼貌,我建议你只需咬一下编码子弹并传递额外的密钥名称,或者通过以下方式自动化使用方法向空白对象添加属性,而不是在对象文字中对其进行全部编码。

答案 2 :(得分:1)

排序。

这取决于浏览器。 (Chrome = OK,Firefox = Nope)

您可以使用工厂创建function,并使用调用堆栈解析黑客,这可能会让我被捕。

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

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

Runnable代码段:(向下滚动并点击运行代码段

&#13;
&#13;
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'
&#13;
&#13;
&#13;