通过函数调用JavaScript获取对象调用者名称

时间:2014-11-12 11:25:42

标签: javascript

我正在编写一段代码,以便在对象中轻松保存错误日志以进行调试。

我想要实现的是从调用它的函数中获取Object名称,如下所示:

var MainObject = {
    test : function() {
        return MainObject.test.caller;
        // When called from MainObject.testcaller,
        // it should return MainObject.testcaller.
    },

    testcaller : function() {
       return MainObject.test(); // Should return MainObject.testcaller, Returns own function code.
    },

    anothercaller : function() {
       return MainObject.test(); // Should return MainObject.anothercaller, Returns own function code.
    }
}

但是,当我运行此代码时,它返回MainObject.testcaller中的函数代码。

JSFiddle example

这有可能吗?

更新

在查看了Rhumborl的答案后,我发现通过另一个函数分配值会导致它在没有对象本身的情况下指向函数名称。

代码:

(function (name, func) {
    MainObject[name] = func;
})('invalid', function() {
    return MainObject.test("blah"); 
}); 

// This now points at invalid() rather than MainObject.invalid()

Updated fiddle

2 个答案:

答案 0 :(得分:2)

有一个非标准的caller property函数返回调用函数,但是这是一个指向函数对象的指针,并没有告诉你它被称为方法的对象,或者对象的名称。您可以通过arguments.callee获取对该功能的引用。

还有过时的arguments.caller,但不要使用它。它还提供对调用函数的引用(如果支持)。

一旦你有一个对调用函数的引用(如果有的话),你就会遇到解析它的名字的问题。假定函数是对象,并且对象可以由多个属性和变量引用,则具有特定名称的函数的概念是冲积的。

但是,如果您知道该函数是某个对象的属性,则可以迭代该对象自己的可枚举属性,以找出它是哪一个。

但这似乎是一件相当奇怪的事情。你究竟想做什么?您可能正在尝试以更加强大和简单的方式解决可以解决的问题。

修改

你可以使用上面针对OP中的情况描述的方法以非常有限的方式做你想要的事情,但是它不健壮或者是一般的解决方案:

var mainObject = {
    test : function() {
      var obj = this;
      var caller = arguments.callee.caller;
      var global = (function(){return this}());
      var fnName, objName;

      for (var p in global) {
        if (global[p] === obj) {
          objName = p;
        }
      }

      for (var f in obj) {
        if (obj[f] === caller) {
          fnName = f;
        }
      }

      return objName + '.' + fnName;
    },

    testcaller : function() {
       return mainObject.test();
    },

    anothercaller : function() {
       return mainObject.test();
    }
}


console.log(mainObject.testcaller());    // mainObject.testcaller
console.log(mainObject.anothercaller()); // mainObject.anothercaller

但它很脆弱:

var a = mainObject.anothercaller;
console.log(a()); // mainObject.anothercaller    

var b = {
  foo : mainObject.anothercaller
}

console.log(b.foo()); // mainObject.anothercaller

糟糕。

答案 1 :(得分:1)

您可以在http://www.eriwen.com/javascript/js-stack-trace/使用此技巧抛出错误,然后解析堆栈跟踪。

我已经为最新版本的Firefox,Chrome和IE更新了它。不幸的是,它在我的IE9上运行不正常(我还没有在Opera上测试过它)。

function getStackTrace() {
    var callstack = [];
    var isCallstackPopulated = false;
    try {
        i.dont.exist += 0; //doesn't exist- that's the point
    } catch (e) {
        if (e.stack) { //Firefox/Chrome/IE11
            var lines = e.stack.split('\n');
            for (var i = 0, len = lines.length; i < len; i++) {
                var line = lines[i].trim();
                if (line.match(/^at [A-Za-z0-9\.\-_\$]+\s*\(/)) {
                    // Chrome/IE: "    at Object.MainObject.testcaller (url:line:char)"
                    var entry = line.substring(3, line.indexOf('(') - 1);
                    // Chrome appends "Object." to the front of the object functions, so strip it off
                    if (entry.indexOf("Object.") == 0) {
                        entry = entry.substr(7, entry.length);
                    }
                    callstack.push(entry);
                } else if (line.match(/^[A-Za-z0-9\.\-_\$]+\s*@/)) {
                    // Firefox: "MainObject.testcaller@url:line:char"
                    callstack.push(line.substring(0, lines[i].indexOf('@')));
                }
            }
            //Remove call to getStackTrace()
            callstack.shift();
            isCallstackPopulated = true;
        } else if (window.opera && e.message) { //Opera
            var lines = e.message.split('\n');
            for (var i = 0, len = lines.length; i < len; i++) {
                if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
                    var entry = lines[i];
                    //Append next line also since it has the file info
                    if (lines[i + 1]) {
                        entry += lines[i + 1];
                        i++;
                    }
                    callstack.push(entry);
                }
            }
            //Remove call to getStackTrace()
            callstack.shift();
            isCallstackPopulated = true;
        }
    }
    if (!isCallstackPopulated) { //IE9 and Safari
        var currentFunction = arguments.callee.caller;
        while (currentFunction) {
            var fn = currentFunction.toString();
            var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
            callstack.push(fname);
            currentFunction = currentFunction.caller;
        }
    }
    return callstack;
}

var MainObject = {
    test: function (x) {
        // first entry is the current function (test), second entry is the caller
        var stackTrace = getStackTrace();
        var caller = stackTrace[1];
        return caller + "()";
    },

    testcaller: function () {
        return MainObject.test(1, null);
    }
}

function SomeFunction() {
  return MainObject.test("blah");
}

document.body.innerHTML += '<b style="color: red">' + MainObject.testcaller() + '</b>';

document.body.innerHTML += '<div>Calling SomeFunction() returns: <b style="color: red">' + SomeFunction() + '</b></div>';
MainObject.test() should return: <b style="color: blue">MainObject.testcaller()</b>
<hr />
MainObject.test() returns:

Updated fiddle here