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

后端 未结 3 850
隐瞒了意图╮
隐瞒了意图╮ 2020-12-12 04:01

I\'ve done a lot of searching and some playing around, and I\'m pretty sure the answer to this question is no, but I\'m hoping a JavaScript expert might hav

相关标签:
3条回答
  • 2020-12-12 04:20

    there is no reflection, but you can use function behavior to make adding your own fairly painless, and without resorting to try/catch, arguments.callee, Function.caller, or other strongly frowned-upon behavior, just wasteful looping:

    // 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'
    

    if you REALLY want to make it look like an assignment and avoid the execution parens each time in the object literal, you can do this hacky routine to create an invoking alias:

    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'
    

    all that aside, you can probably accomplish what you need to do without all the looping required by this approach.

    Advantages:

    • works on globals or object properties
    • requires no repetitive key/name passing
    • uses no proprietary or deprecated features
    • does not use arguments or closure
    • surrounding code executes faster (optimized) than a try/catch version
    • is not confused by repeated uses
    • can handle new and deleted (renamed) properties

    Caveats:

    • doesn't work on private vars, which have no property name
    • partially loops owner object each access
    • slower computation than a memorized property or code-time repetition
    • won't survive call/bind/apply
    • wont survive a setTimeout without bind() or a wrapper function
    • cannot easily be cloned

    honestly, i think all the ways of accomplishing this task are "less than ideal", to be polite, and i would recommend you just bite the coding bullet and pass extra key names, or automate that by using a method to add properties to a blank object instead of coding it all in an object literal.

    0 讨论(0)
  • 2020-12-12 04:21

    Yes.

    Sort Of.

    It depends on the browser. (Chrome=OK, Firefox=Nope)

    You can use a factory to create the function, and a call stack parsing hack that will probably get me arrested.

    This solution works in my version of Chrome on Windows 7, but the approach could be adapted to other browsers (if they support stack and show the property name in the call stack like Chrome does). I would not recommend doing this in production code as it is a pretty brittle hack; instead improve the architecture of your program so that you do not need to rely on knowing the name of the calling property. You didn't post details about your problem domain so this is just a fun little thought experiment; to wit:

    JSFiddle demo: http://jsfiddle.net/tv9m36fr/

    Runnable snippet: (scroll down and click Run code snippet)

    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'

    0 讨论(0)
  • 2020-12-12 04:34

    Short answer:

    1. No, you cannot get "the property name" used to call your function.
    2. There may be no name at all, or multiple names across different scopes, so "the property name" is pretty ill defined.
    3. arguments.callee is deprecated and should not be used.
    4. There exists no solution that does not use arguments or closure.

    Long answer:

    As thefourtheye commented, you should rethink what you are trying to do and ask that instead in a new question. But there are some common misconceptions, so I will try to explain why you cannot get the "simple property name".

    The reason is because it is not simple.

    Before we go ahead, let us clarify something. Activation Objects are not objects at all. The ECMAScript 5.1 specification calls them Environment Records (10.2.1), but a more common term is Scope chain. In a browser the global scope is (often) the window object, but all other scopes are not objects. There may be an object that you use to call a function, and when you call a function you must be in some scope.

    With few exceptions, scopes are not objects, and objects are not scopes.

    Then, there are many names.

    • When you call a function, you need to reference it, such as through an object property. This reference may have a name.
    • Scope chain has declarations, which always have a name.
    • A Function (the real function, not reference) may also have a function name - your arguments.callee.name - which is fixed at declaration.

    Not only are they different names, they are not (always) the "the property name" you are seeking.

    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)
    

    So, a function reference may comes from a binding (e.g. function declaration), an Object (arguments.callee), or a variable. They are all References (8.7). And reference does have a name (so to speak).

    The catch is, a function reference does not always come from an object or the scope chain, and its name is not always defined. For example a common closure technique:

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

    Even if the reference does have a name, a function call (11.2.3) does not pass the reference or its name to the function in any way. Which keeps the JavaScript engine sane. Consider this example:

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

    The final function call refers the eval function, which refers the result of a new global scope (in strict mode, anyway), which refers a function call statement, which refers a group, which refers an anonymous Function object, and which contains code that expresses and returns a function called 'a'.

    If you want to get the "property name" from within a, which one should it get? "eval"? "Function"? "anonymous"? "a"? All of them? Before you answer, consider complications such as function access across iframes, which has different globals as well as cross origin restriction, or interaction with native functions (Function.prototype.bind for example), and you will see how it quickly becomes hell.

    This is also why arguments.caller, __caller__, and other similar techniques are now all deprecated. The "property name" of a function is even more ill defined than the caller, almost unrealistic. At least caller is always an execution context (not necessary a function).

    So, not knowing what your real problem is, the best bet of getting the "property name" is using closure.

    0 讨论(0)
提交回复
热议问题