2011-05-27 13 views
6

Estoy empezando a trabajar en una herramienta de análisis dinámico para JS y me gustaría crear un perfil de todo un entorno discretamente. Básicamente atravieso varios contextos, profundizando en los objetos, y cada vez que toco una función, me engancho. Ahora bien, esto funciona relativamente bien, excepto por el hecho de que se rompe cuando se trata de las bibliotecas como jQuery/prototipo etc.Javascript perder contexto al enganchar recursivamente

Este es mi código hasta ahora (comentado a lo mejor de mi capacidad):

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

Ese es el perfil y así es como yo invoco:

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

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

de desplazamiento funciona bien y de enganche funciona bien siempre y cuando las funciones son simples y no anónimo (creo enganchar en funciones anónimas es imposible así que no estoy demasiado preocupado por eso).

Esto es una salida recortada de la fase de preprocesamiento.

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 

Cuando ejecuto $("p.neat").addClass("ohmy").show("slow"); en jQuery.com (a través de Firebug), me sale una pila de llamadas apropiada, pero parecen perder mi contexto en algún lugar a lo largo del camino porque no pasa nada y me da un error e is undefined de jQuery (claramente, el enganche arruinó algo).

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 

El problema es que creo que estoy perdiendo el contexto this al llamar

return fn.apply(parent, arguments); 

Aquí hay otro interesante peculiaridad. Si engancho antes transversal, es decir:

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

.. la aplicación se ejecuta absolutamente bien, pero se altera la pila de llamadas (y no parecen tener funciones específicas jQuery) por alguna razón:

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 

.. en lugar de animation, show, merge, etc. en este momento, todo el gancho no es decir called functionName pero al final me gustaría hacer seguimientos de pila y funciones de tiempo (a través de un applet de Java).

Esta pregunta terminó siendo enorme y me disculpo, pero cualquier ayuda es apreciada.

Nota: el código anterior puede bloquear su navegador si no tiene cuidado. Advertencia justa: P

+0

Pregunta similar: http://stackoverflow.com/questions/5033836/adding-console-log-to-every-function-automatically –

Respuesta

3

Creo que estás en el camino correcto. El valor de this está siendo destruido cuando usa apply. Las funciones definidas dentro de jQuery probablemente se llamen a través de apply internamente, y dependen del valor de this.

El primer argumento para apply es el valor que se utilizará para this. ¿Estás seguro de que deberías estar usando parent para eso?

que fue capaz de duplicar el problema de la siguiente manera:

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

Aquí, la función depende del valor de this para imprimir el texto Monkeys are funny!. Como puede ver, usando su algoritmo hook, este contexto se pierde. Firebug muestra:

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

hice un pequeño cambio y usados ​​this en la aplicación en lugar de obj (el padre):

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

Esta vez Firebug dice:

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

La raíz de su problema en mi humilde opinión es que está estableciendo un valor explícito para this (es decir, parent que se refiere al objeto principal). Por lo tanto, su función de enlace termina sobrescribiendo el valor de this, que podría haber sido establecido explícitamente por el código que está llamando a la función original. Por supuesto, ese código no sabe que usted envolvió la función original con su propia función de gancho. Por lo que su función de enlace debe preservar el valor de this cuando se llama a la función original:

return fn.apply(this, arguments); 

tanto, si utiliza this en lugar de parent en su solicitud, el problema podría ser fijo.

Disculpe si no he entendido bien su problema. Por favor corrígeme donde sea que esté mal.

ACTUALIZACIÓN

Hay dos tipos de funciones en jQuery. Los que están adjuntos al objeto jQuery en sí (algo así como métodos estáticos) y luego tiene los que operan en el resultado de jQuery(selector) (tipo de métodos de instancia similares). Es lo último por lo que debes preocuparte. Aquí, this importa mucho porque así es como se implementa el encadenamiento.

Pude obtener el siguiente ejemplo para trabajar. Tenga en cuenta que estoy trabajando en la instancia del objeto y no en el objeto en sí. Así que en su ejemplo, me gustaría estar trabajando en jQuery("#someId") y no en solo 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"); 

No sé si esto resuelve su problema o no. Quizás al buscar en la fuente de jQuery, obtendrá más información :). I piensa que la dificultad para usted sería distinguir entre las funciones que se llaman a través de apply y las funciones que se llaman mediante encadenamiento (o simplemente se llaman directamente).

+0

Lo entendiste perfectamente, y había intentado 'esto' anteriormente, pero si uso 'this', parece que la función de encadenamiento se rompe (algo así como' $ ('algo'). addClass ('asdfg'). show ('lento') 'deja de funcionar). Específicamente, entiendo que 'push()' no está definido. –

+0

@David Actualicé mi respuesta.No sé si responde a su pregunta o no, pero lo intenté: p He mencionado esto en la respuesta, pero creo que terminará enfrentando dificultades cuando intente distinguir entre las funciones que se llaman a través de 'aplicar 'contra aquellos que son llamados por encadenamiento, o directamente. También estoy interesado en conocer la solución a esto, así que espero que alguien más conocedor de lo que pueda sonar. –

+0

Aunque puede que no resuelva el problema en el caso general, mi intuición me dice que debería ser posible. y aprecio tu visión. Creo que dio en el clavo cuando hizo la distinción entre el método de encadenamiento y los métodos estáticos. Además, al hacer algunas pruebas descubrí que a veces se llama aplicar argumentos de ajuste en una matriz propia ... lo cual es extraño. –

Cuestiones relacionadas