遍历arguments.callee.caller会导致无限循环

时间:2013-03-23 01:22:49

标签: javascript node.js

假设我想获得某种堆栈跟踪,获取在当前函数之前调用的所有函数的名称。

我这样做:

        var callee;
        var caller;
        var _args = arguments;
        var check = 0;
        do {
                    check++;

                    callee = _args.callee;
                    caller = callee.caller;

                    var msg = 'Check ' + check + ' - ' + callee.name 
                            + ' has been called by: ' + caller.name;
                    console.log(msg);

                    if (caller) {
                        // Get this caller's arguments
                        _args = caller.arguments;
                    } else {
                        reached_end = true;
                    }

        } while (!reached_end);

大部分时间都可以正常使用。但有时它会陷入无限循环,我想知道:这怎么可能?我能做些什么呢?

这是我的无限循环的输出:

    Check 1 - __parent__ has been called by: add
    Check 2 - add has been called by: afterComponentStartup
    Check 3 - afterComponentStartup has been called by: _launchComponents [arg0:"startup"]
    Check 4 - _launchComponents has been called by: beforeActionNext
    Check 5 - beforeActionNext has been called by: beforeAction
    Check 6 - beforeAction has been called by: afterComponentInitialize
    Check 7 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"]
    Check 8 - _launchComponents has been called by: beforeActionNext
    Check 9 - beforeActionNext has been called by: beforeAction
    Check 10 - beforeAction has been called by: afterComponentInitialize
    Check 11 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"]
    Check 12 - _launchComponents has been called by: beforeActionNext
    Check 13 - beforeActionNext has been called by: beforeAction
    Check 14 - beforeAction has been called by: afterComponentInitialize
    Check 15 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"]
    Check 16 - _launchComponents has been called by: beforeActionNext
    Check 17 - beforeActionNext has been called by: beforeAction
    Check 18 - beforeAction has been called by: afterComponentInitialize
    Check 19 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"]
    Check 20 - _launchComponents has been called by: beforeActionNext
    Check 21 - beforeActionNext has been called by: beforeAction
    Check 22 - beforeAction has been called by: afterComponentInitialize
    Check 23 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"]
    Check 24 - _launchComponents has been called by: beforeActionNext
    Check 25 - beforeActionNext has been called by: beforeAction
    Check 26 - beforeAction has been called by: afterComponentInitialize

3 个答案:

答案 0 :(得分:4)

arguments.callee.caller指向函数引用,它在每个调用堆栈中只存在每个函数之一。

每次调用一个函数时,caller属性都会被设置,这意味着如果在调用堆栈中多次调用相同的函数,就像递归函数一样,前一个值将被重置,而caller现在指向自己。这就是导致无限循环的原因。

所以在你的算法中,如果你达到callee === callee.caller你需要打破的点,以免发生这种情况。

答案 1 :(得分:1)

您需要在数组中添加调用者并检查下一个调用者是否在数组中。如果是这样,那么你有一个循环或递归调用结构。试试这个:

var args = arguments;
var callee = args.callee;
var caller = callee.caller;

var stack = [callee];

while (caller) {
    if (stack.indexOf(caller) < 0) {
        stack.push(caller);
        args = caller.arguments;
        callee = args.callee;
        caller = callee.caller;
    } else break;
}

console.log(stack);

答案 2 :(得分:0)

我找到了一种方法将状态信息(在这种情况下为var .ua(Object))传递给递归函数的callstack;

而不是

var fwa.animatedJavascriptControlCenter = {
    ...
    ,
    scanElements : function (el, scanResult) { 
    // a function that walks the DOM tree to find elements that require molding by my js framework
    if (!scanResult) scanResult={};
    if (el.tagName.toLowerCase()!='svg') {
        if (conditions_for_this_element_met(el)) {
            scanResults[el.id] = el;
        };
        if (el.children.length>0) {
            for (var i=0; i < el.children.length; i++) {
                if (el.children[i].tagName.toUpperCase()!=='IFRAME') {
                    scanResult = fwa.animatedJavascriptControlCenter.scanElements(el.children[i], scanResult);
                }
            }
        }
    }
    return scanResult;
    },
    ...
}

做的:

var fwa.animatedJavascriptControlCenter = {
    ...
    ,
    scanElements : function (el, scanResult) { 
        if (!scanResult) scanResult={};
        if (el.tagName.toLowerCase()!='svg') {
            if (conditions_for_this_element_met(el)) {
                scanResults[el.id] = el;
            }
            if (el.children.length>0) {
                for (var i=0; i < el.children.length; i++) {
                    if (el.children[i].tagName.toUpperCase()!=='IFRAME') {
                        var args = [el.children[i], scanResult];
                        args.ua= tracer.findUA(arguments);
                        var passUAfunc = function(scanResult) {
                            return fwa.animatedJavascriptControlCenter.scanElements(el.children[i], scanResult);
                        }
                        passUAfunc.ua = args.ua;
                        scanResult = passUAfunc(scanResult);
                    }
                }
            }
        }
        return scanResult;
    },
    ...
}

要完成这项工作,您还需要:

// Bonus : ;-)
// Enable the passage of the 'this' object through the JavaScript timers
// thanks to https://developer.mozilla.org/en/docs/DOM/window.setTimeout#Callback_arguments
var __nativeST__ = window.setTimeout, __nativeSI__ = window.setInterval;
window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
    var 
    oThis = this, 
    ua = tracer.findUA(arguments),
    aArgs = Array.prototype.slice.call(arguments, 2);
    if (ua) aArgs.ua = ua;
    //if (!ua) debugger;
    return __nativeST__(vCallback instanceof Function ? function () {
        vCallback.apply(oThis, aArgs);
    } : vCallback, nDelay);
};
window.setInterval = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
    var 
    oThis = this, 
    aArgs = Array.prototype.slice.call(arguments, 2),
    ua = tracer.findUA(arguments);
    if (ua) aArgs.ua = ua;
    return __nativeSI__(vCallback instanceof Function ? function () {
        vCallback.apply(oThis, aArgs);
    } : vCallback, nDelay);
};

var tracer = {
/*  object last modified : 14 May 2013, 04:46 CEST

    original code by http://javascriptweblog.wordpress.com/2010/06/01/a-tracer-utility-in-2kb/

    modified by rene7705@gmail.com with:
    (1) http://stackoverflow.com/a/15582432/2379929

    augmented by rene7705@gmail with (among other things):
    http://stacktracejs.com/

    this code is now used in and offered as part of the web-framework at http://fancywebapps.com
    (component name : hipLog, to be released later)
*/  


    nativeCodeEx: /\[native code\]/,
    tracing: [],
    traced : [],
    userActions : [],

    findUA : function (arg) {
        var p = arg;
        if (p.ua) return p.ua;

        var callee = arg.callee;
        var caller = callee.caller;

        var stack = [callee];

        while (caller) {
            if (stack.indexOf(caller) < 0) {
                stack.push(caller);
                args = caller.arguments;
                callee = args.callee;
                caller = callee.caller;
            } else break;
        }


        while (p = stack.shift()) {
            if (p.ua) return p.ua;
            if (p.arguments && p.arguments.ua) return p.arguments.ua;

        };
        return false;
    },

    traceMe: function(func, methodName, path) {
        var traceOn = function() {
            var 
            ua = tracer.findUA(arguments),
            startTime = +new Date;

            if (!ua){
                //debugger;
                //ua = tracer.findUA(arguments);
            };
            //if (path=='fwa.animatedJavascriptControlCenter.scanElements') debugger;

            if (!ua) {
                //debugger;
                var
                uaIdx = tracer.userActions.length,
                ua = tracer.userActions[uaIdx] = {
                    uaIdx : uaIdx,
                    startTime : startTime,
                    path : path,
                    stackLevel : 0
                };
                tracer.traced[uaIdx] = [];
            } else {
                var uaIdx = ua.uaIdx;
                ua.stackLevel++;
            }

            arguments.ua = ua;

            var idx = tracer.traced[uaIdx].length;
            tracer.traced[uaIdx][idx] = {
                path : path,
                arguments : arguments
            }; 

            var result = func.apply(this, arguments);

            tracer.traced[uaIdx][idx].stacktrace = printStackTrace() // see http://stacktracejs.com, free, quite useful
            tracer.traced[uaIdx][idx].result = result;
            tracer.traced[uaIdx][idx].timed = new Date - startTime;  
            tracer.traced[uaIdx][idx].stackLevel = ua.stackLevel;
            ua.stackLevel--;
            return result;
        };
        traceOn.traceOff = func;
        for (var prop in func) {
            traceOn[prop] = func[prop];
        }
        console.log("tracing " + path);
        return traceOn;
    },

    traceAll: function(root, path, recurse) {
        if ((root == window) || !((typeof root == 'object') || (typeof root == 'function'))) {return;}
        for (var key in root) {
            if ((root.hasOwnProperty(key)) && (root[key] != root)) {
                var thisObj = root[key];
                if (typeof thisObj == 'function') {
                    if ((this != root) && !thisObj.traceOff && !this.nativeCodeEx.test(thisObj)) {
                        root[key] = this.traceMe(root[key], key, path+'.'+key);
                        this.tracing.push({obj:root,methodName:key, path:path+'.'+key});
                    }
                }
                recurse && this.traceAll(thisObj, path+'.'+key, true);
             }
        }
    },

    untraceAll: function() {
        for (var i=0; i<this.tracing.length; ++i) {
            var thisTracing = this.tracing[i];
            thisTracing.obj[thisTracing.methodName] =
                thisTracing.obj[thisTracing.methodName].traceOff;
        }
        //console.log("tracing disabled");
        tracer.tracing = [];
    }
}

用作:

// call this just after _completely_ defining yourFrameworkRootObject and yourSiteCodeRootObject
tracer.traceAll (yourFrameworkRootObject, true);
tracer.traceAll (yourSiteCodeRootObject, true);

仅供参考:此代码来自我的js框架http://fancywebapps.com,并且将成为它的hipLog组件的一部分,旨在向您显示完整的执行路径(有或没有到服务器的往返)和/或setTimeout()s)任何userAction,如点击,悬停或页面加载。