2011-05-27 45 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