2011-05-27 47 views
6

我开始在JS的动态分析工具上工作,并且希望不显眼地分析整个环境。我基本上遍历各种上下文,深入挖掘对象,并且每次我点击一个函数时,都会将它钩住。现在,这个工作相对较好,除了它与像图书馆打交道时打破了事实上的jQuery /原型等当递归挂钩时,Javascript丢失上下文

这是我的代码迄今(评论尽我的能力):

var __PROFILER_global_props = new Array(); // visited properties 

/** 
* Hook into a function 
* @name the name of the function 
* @fn the reference to the function 
* @parent the parent object 
*/ 
function __PROFILER_hook(name, fn, parent) { 
    //console.log('hooking ' + name + ' ' + fn + ' ' + parent); 

    if (typeof parent == 'undefined') 
     parent = window; 

    for (var i in parent) { 
     // find the right function 
     if (parent[i] === fn) { 
      // hook into it 
      console.log('--> hooking ' + name); 
       parent[i] = function() { 
         console.log('called ' + name); 
         return fn.apply(parent, arguments); 
       } 

       //parent[i] = fn; // <-- this works (obviously) 
       break; 
     } 
    } 
} 

/** 
* Traverse object recursively, looking for functions or objects 
* @obj the object we're going into 
* @parent the parent (used for keeping a breadcrumb) 
*/ 
function __PROFILER_traverse(obj, parent) { 
    for (i in obj) { 
     // get the toString object type 
     var oo = Object.prototype.toString.call(obj[i]); 
     // if we're NOT an object Object or an object Function 
     if (oo != '[object Object]' && oo != '[object Function]') { 
      console.log("...skipping " + i); 
      // skip 
      // ... the reason we do this is because Functions can have sub-functions and sub-objects (just like Objects) 
      continue; 
     } 
     if (__PROFILER_global_props.indexOf(i) == -1 // first we want to make sure we haven't already visited this property 
      && (i != '__PROFILER_global_props'  // we want to make sure we're not descending infinitely 
      && i != '__PROFILER_traverse'   // or recusrively hooking into our own hooking functions 
      && i != '__PROFILER_hook'    // ... 
      && i != 'Event'    // Event tends to be called a lot, so just skip it 
      && i != 'log'    // using FireBug for debugging, so again, avoid hooking into the logging functions 
      && i != 'notifyFirebug')) {   // another firebug quirk, skip this as well 

      // log the element we're looking at 
      console.log(parent+'.'+i); 
      // push it.. it's going to end up looking like '__PROFILER_BASE_.something.somethingElse.foo' 
      __PROFILER_global_props.push(parent+'.'+i); 
      try { 
       // traverse the property recursively 
       __PROFILER_traverse(obj[i], parent+'.'+i); 
       // hook into it (this function does nothing if obj[i] is not a function) 
       __PROFILER_hook(i, obj[i], obj); 
      } catch (err) { 
       // most likely a security exception. we don't care about this. 
      } 
     } else { 
      // some debugging 
      console.log(i + ' already visited'); 
     } 
    } 
} 

这是配置文件,这是我如何调用它:

// traverse the window 
__PROFILER_traverse(window, '__PROFILER_BASE_'); 

// testing this on jQuery.com 
$("p.neat").addClass("ohmy").show("slow"); 

遍历工作正常,挂钩工作正常,只要功能是简单的和非匿名的(我想钩住匿名函数是不可能的,所以我不太担心了)。

这是预处理阶段的一些修剪输出。

notifyFirebug already visited 
...skipping firebug 
...skipping userObjects 
__PROFILER_BASE_.loadFirebugConsole 
--> hooking loadFirebugConsole 
...skipping location 
__PROFILER_BASE_.$ 
__PROFILER_BASE_.$.fn 
__PROFILER_BASE_.$.fn.init 
--> hooking init 
...skipping selector 
...skipping jquery 
...skipping length 
__PROFILER_BASE_.$.fn.size 
--> hooking size 
__PROFILER_BASE_.$.fn.toArray 
--> hooking toArray 
__PROFILER_BASE_.$.fn.get 
--> hooking get 
__PROFILER_BASE_.$.fn.pushStack 
--> hooking pushStack 
__PROFILER_BASE_.$.fn.each 
--> hooking each 
__PROFILER_BASE_.$.fn.ready 
--> hooking ready 
__PROFILER_BASE_.$.fn.eq 
--> hooking eq 
__PROFILER_BASE_.$.fn.first 
--> hooking first 
__PROFILER_BASE_.$.fn.last 
--> hooking last 
__PROFILER_BASE_.$.fn.slice 
--> hooking slice 
__PROFILER_BASE_.$.fn.map 
--> hooking map 
__PROFILER_BASE_.$.fn.end 
--> hooking end 
__PROFILER_BASE_.$.fn.push 
--> hooking push 
__PROFILER_BASE_.$.fn.sort 
--> hooking sort 
__PROFILER_BASE_.$.fn.splice 
--> hooking splice 
__PROFILER_BASE_.$.fn.extend 
--> hooking extend 
__PROFILER_BASE_.$.fn.data 
--> hooking data 
__PROFILER_BASE_.$.fn.removeData 
--> hooking removeData 
__PROFILER_BASE_.$.fn.queue 

当我jQuery.com(通过萤火虫)执行$("p.neat").addClass("ohmy").show("slow");,我得到一个适当的调用堆栈,但我似乎失去了地方我的上下文一路上,因为什么也没有发生,我得到一个jQuery的错误e is undefined(显然,挂钩拧了一些东西)。

called init 
called init 
called find 
called find 
called pushStack 
called pushStack 
called init 
called init 
called isArray 
called isArray 
called merge 
called merge 
called addClass 
called addClass 
called isFunction 
called isFunction 
called show 
called show 
called each 
called each 
called isFunction 
called isFunction 
called animate 
called animate 
called speed 
called speed 
called isFunction 
called isFunction 
called isEmptyObject 
called isEmptyObject 
called queue 
called queue 
called each 
called each 
called each 
called each 
called isFunction 
called isFunction 

的问题是,我想打电话

return fn.apply(parent, arguments); 

这里还有一个有趣的怪癖,当我失去了this上下文。如果我前面的勾我遍历,即:

 // hook into it (this function does nothing if obj[i] is not a function) 
     __PROFILER_hook(i, obj[i], obj); 
     // traverse the property recursively 
     __PROFILER_traverse(obj[i], parent+'.'+i); 

..应用程序运行完全正常,但调用堆栈被改变(我似乎并没有得到jQuery的特定功能)由于某种原因:

called $ 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called setInterval 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called getComputedStyle 
called clearInterval 

..而不是animationshowmerge等眼下,所有的挂钩并是说called functionName但最终我想要做的堆栈跟踪和时间功能(通过Java小程序)。

这个问题最终是巨大的,我很抱歉,但任何帮助表示赞赏!

注意:如果您不小心,上述代码可能会导致浏览器崩溃。公平的警告:P

+0

相似的问题:http://stackoverflow.com/questions/5033836/adding-console-log-to-every-function-automatically – 2011-05-31 20:02:39

回答

3

我认为你是在正确的轨道上。当您使用apply时,this的值变得越来越少。在jQuery中定义的函数可能在内部通过apply进行调用,并且取决于this的值。

apply的第一个参数是将用于this的值。你确定你应该使用parent吗?

我能够复制以下方式问题:

var obj = { 
    fn : function() { 
     if(this == "monkeys") { 
     console.log("Monkeys are funny!"); 
     } 

     else { 
     console.log("There are no monkeys :("); 
     } 
    } 
}; 

obj.fn.apply("monkeys"); 

var ref = obj.fn; 

//assuming parent here is obj 
obj.fn = function() { 
    console.log("hooking to obj.fn"); 
    return ref.apply(obj); 
}; 

obj.fn.apply("monkeys"); 

这里,函数依赖于this值来打印文本Monkeys are funny!。正如你所看到的,使用你的hook算法,这个上下文丢失了。萤火显示:

Monkeys are funny! 
hooking to obj.fn 
There are no monkeys :(

我做了细微的变化,并在使用this的应用,而不是obj(父):

obj.fn = function() { 
    console.log("hooking to obj.fn"); 
    return ref.apply(this); 
}; 

这次萤火虫说:

Monkeys are funny! 
hooking to obj.fn 
Monkeys are funny!

根你的问题恕我直言是你设置一个明确的值为this(即,parent它指的是父对象)。所以你的钩子函数最终会覆盖this的值,这可能是由调用原始函数的任何代码明确设置的。当然,该代码并不知道你用自己的钩子函数包装了原始函数。如果在您的应用,您的问题可能是固定使用this代替parent所以

return fn.apply(this, arguments); 

:所以你的钩子函数应该当它被调用原有功能保留的this值。

如果我没有正确理解您的问题,我表示歉意。请纠正我,无论我错了。

UPDATE

有jQuery的两种功能。与jQuery对象本身相关的东西(有点像静态方法),然后你有一个对jQuery(selector)(有点像实例方法)的结果进行操作。这是你需要关注的后者。在这里,this很重要,因为这是你如何实现链接。

我能够得到下面的例子工作。请注意,我正在研究对象的实例,而不是对象本身。因此,在你的榜样,我会努力jQuery("#someId"),而不是仅仅jQuery

var obj = function(element) { 
    this.element = element; 
    this.fn0 = function(arg) { 
     console.log(arg, element); 
     return this; 
    } 

    this.fn1 = function(arg) { 
     console.log(arg, arg, element); 
     return this; 
    } 

    if(this instanceof obj) { 
     return this.obj; 
    } 

    else { 
     return new obj(element); 
    } 
}; 

var objInst = obj("monkeys"); 

var ref0 = objInst.fn0; 

objInst.fn0 = function(arg) { 
    console.log("calling f0"); 
    return ref0.apply(this, [arg]); 
}; 

var ref1 = objInst.fn1; 

objInst.fn1 = function(arg) { 
    console.log("calling f1"); 
    return ref1.apply(this, [arg]); 
}; 

objInst.fn0("hello").fn1("bye"); 

我不知道这样能否解决您的问题或没有。也许看着jQuery的源代码会给你更多的洞察力:)。我想认为您的难点在于区分通过apply调用的函数和通过链接调用的函数(或者直接调用)。

+0

你完全理解,我以前曾尝试过'this',但如果我使用'this',看起来函数链断了(所以像'$('something')。addClass('asdfg')。show('slow')'停止工作)。具体来说,我得到'push()'是未定义的。 – 2011-05-27 22:27:20

+0

@大卫我更新了我的答案。我不知道它是否回答你的问题,但我试过:p我已经在答案中提到了这一点,但我认为当你试图区分通过'apply调用的函数时,你会遇到困难'与通过链接或直接调用的方法相比。我很想知道这个解决方案,所以希望有人比我更懂事。 – 2011-05-27 23:34:24

+0

尽管它在一般情况下可能无法解决问题,但我的直觉告诉我应该有可能我感谢你的洞察力。我认为当你在方法链接和静态方法之间进行区分时,你碰到了头。另外,做一些试验后,我发现有时会把自己的数组应用到自变量中,这很奇怪。 – 2011-05-28 00:09:04