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

Когда я исполняю $("p.neat").addClass("ohmy").show("slow"); на jQuery.com (через Firebug), я получаю соответствующий стек вызовов, но я, кажется, сойду контекст где-то по пути, потому что ничего не происходит, и я получаю сообщение об ошибке e is undefined из JQuery (ясно, что крючок прикрутил что-то).

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 

.. вместо animation, show, merge и т.д. Прямо сейчас, все крюк делает это сказать called functionName, но в конце концов я хотел бы сделать трассировку стека и временные функции (с помощью апплета Java).

Этот вопрос оказался огромным, и я прошу прощения, но любая помощь приветствуется!

Примечание: вышеуказанный код может привести к краху вашего браузера, если вы не будете осторожны. Справедливое предупреждение: P

+0

Аналогичный вопрос: http://stackoverflow.com/questions/5033836/adding-console-log-to-every-function-automatically –

ответ

3

Я думаю, что вы на правильном пути. Значение this получает clobbered, когда вы используете apply. Функции, определенные в 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, этот контекст потерян. Firebug показывает:

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); 
}; 

На этот раз Firebug говорит:

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

Корень вашей проблемы IMHO заключается в том, что вы устанавливаете явное значение для this (то есть, parent, которое относится к родительскому объекту). Таким образом, ваша функция hook заканчивается переписыванием значения this, которое может быть явно задано с помощью кода, вызывающего исходную функцию. Конечно, этот код не знает, что вы завернули оригинальную функцию своей собственной функцией hook. Таким образом, ваша функция Крючок должен сохранить значение this, когда он называет первоначальную функцию:

return fn.apply(this, arguments); 

Так что, если вы используете this вместо parent в вашем применении, ваша проблема может быть решена.

Приносим извинения, если я не понял вашу проблему правильно. Пожалуйста, поправьте меня везде, где я ошибаюсь.

UPDATE

Есть два вида функций в JQuery. Которые привязаны к самому объекту jQuery (вроде как статические методы), а затем у вас есть те, которые работают с результатом jQuery(selector) (вроде подобных методов экземпляра). Это последнее, о чем вам нужно беспокоиться. Здесь имеет значение, потому что именно так вы реализуете цепочку.

Я смог получить следующий пример для работы. Обратите внимание, что я работаю над экземпляром объекта объекта, а не самого объекта. Таким образом, в вашем примере, я буду работать на 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 даст вам более глубокое понимание :). I думаю, что трудность для вас заключалась бы в том, чтобы различать функции, вызываемые через apply, и функции, вызываемые посредством цепочки (или непосредственно вызываемые).

+0

Вы прекрасно это поняли, и я пробовал это «раньше», но если я использую 'this', похоже, что цепочка функций прерывается (что-то вроде' $ ('something'). addClass ('asdfg'). show ('slow') 'перестает работать). В частности, я понимаю, что 'push()' не определено. –

+0

@ Давид, я обновил свой ответ.Я не знаю, отвечает ли он на ваш вопрос или нет, но я попытался: p Я упомянул об этом в ответе, но я думаю, что вы столкнетесь с трудностями, когда пытаетесь отличить функции, вызываемые с помощью 'apply 'против тех, которые вызываются цепочкой или напрямую. Мне также интересно знать решение этого вопроса, так что, надеюсь, кто-то другой, кто более осведомлен, чем я могу звонить. –

+0

Даже если это может не решить проблему в общем случае, моя интуиция подсказывает мне, что это должно быть возможно и я ценю ваше понимание. Я думаю, вы ударили ноготь по голове, когда вы сделали различие между цепочкой методов и статическими методами. Кроме того, проделав некоторые испытания, я узнал, что иногда вызывается применение аргументов wraps в массив своих ... что странно. –

Смежные вопросы