2011-02-18 11 views
15

Estoy intentando pasar el alcance de la función a un método de devolución de llamada. El problema que estoy teniendo es que estoy obteniendo el alcance del objeto, que no me da acceso a los parámetros y las variables locales en la función original. Mi comprensión de "esto" significa el contexto actual (ya sea ventana o algún objeto) además de variables y parámetros declarados localmente. [cite el excelente trabajo de Richard Cornford en http://jibbering.com/faq/notes/closures/ en la sección "Contextos de ejecución"]. También entiendo que las variables en JavaScript tienen alcance de función (que si se declaran dentro de una función, solo son accesibles desde esa función). Con ese entendimiento, en un nuevo entorno, intento codificar un patrón que hice mucho para mi anterior empleador, llamar a un método asincrónico, especificar un manejador de devolución de llamada y pasar mi alcance actual, esperando que esté disponible en el método de devolución de llamada. No estoy encontrando que este sea el caso en mi entorno actual. (divulgación: estaba usando ExtJS en mi entorno anterior ... haciéndome sentir como si fuera un poco demasiado acogedor con el framework, haciendo suposiciones sobre lo que estaba pasando).Pasando el ámbito a la función de devolución de llamada/vinculación

Mi código de prueba simple demuestra lo que estoy tratando de hacer y lo que no funciona.

function myHandler(data, ctx) { 
    console.log('myHandler(): bar: ' + bar); // <- prob: bar undefined 
    console.log(JSON.stringify(data)); 
} 
MySvcWrap = { 
    doWork: function(p1, callback, scope) { 
     var result = {colors: ['red', 'green'], name:'Jones', what: p1}; 
     if (callback) { 
      callback.call(scope||this,result, scope); 
     } 
    } 
} 
function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this 
} 

lookup(); 

El problema aquí es que el 'esto' se pasa a MySvcWrap.doWork es el objeto global de la ventana en este caso. Mi intención es pasar el contexto de ejecución de la función a myHandler.

Lo que he intentado. Si, en lugar de 'esto', me pasa un objeto, que funciona, por ejemplo:

function myHandler(data, ctx) { 
    console.log('myHandler(): this.bar: ' + this.bar); // <- no prob: this.bar 
    console.log(JSON.stringify(data)); 
} 

function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', myHandler, {bar: bar}); // scope object is just object 
} 

Necesito a alguien que me palo sobre la cabeza aquí ... al pasar 'esto' en mi primer caso, por supuesto este es el alcance global (estoy en una función definida globalmente) ... Mi problema es que pensé al pasar el alcance que tenía acceso a las variables y parámetros definidos localmente con ese contexto ... ¿Estoy oscilando mi comprensión anterior? de JS? ¿Cómo lograr esta tarea?

Respuesta

16

por cierto, algunas palabras acerca de los alcances de su código:

function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', myHandler, this); // this here points to window object and not to the function scope 
} 

lo que es lo mismo que escribir:

function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', myHandler, window); 
} 

es así porque se define la función de búsqueda a nivel mundial. dentro de la función doWork, cuando escribe this apunta al objeto MySvcWrap (porque esa función se define dentro de ese objeto).

Si su función de devolución de llamada tiene que ver bar variable, se debe definirse en el mismo ámbito, al igual que

MySvcWrap = { 
    doWork: function(p1, callback, scope) { 
     var result = {colors: ['red', 'green'], name:'Jones', what: p1}; 
     if (callback) { 
      callback.call(scope||this,result, scope); 
     } 
    } 
} 
function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', 
     function (data, ctx) { 
      console.log('myHandler(): bar: ' + bar); 
      console.log(JSON.stringify(data)); 
     }, 
     this); // scope object is this 
} 

lookup(); 

en este caso se envía función anónima como llamada de retorno, se define dentro de la función de búsqueda por lo que tiene acceso a sus variables locales; mi consola me muestra en esta CAE:

myHandler(): bar: food 
{"colors":["red","green"],"name":"Jones","what":"thang"} 

para que sea más fácil para apoyar, puede definir myHandler dentro de la función de búsqueda:

function lookup() { 
    var bar = 'food'; // local var 
    var myHandler = function(data, ctx) { 
     console.log('myHandler(): bar: ' + bar); 
     console.log(JSON.stringify(data)); 
    }; 
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this 
} 

Por otra parte, ¿por qué una función debe tener acceso a locales variables de otra función? Tal vez se puede rediseñar ...

una forma más de hacer su trabajo de código es utilizar la función anónima, en lugar de las operaciones de búsqueda (trabajará si lo que declara y ejecuta esa función una vez):

(function() { 
    var bar = 'food'; 

    function myHandler(data, ctx) { 
     console.log('myHandler(): bar: ' + bar); 
     console.log(JSON.stringify(data)); 
    } 
    MySvcWrap = { 
     doWork: function(p1, callback, scope) { 
      var result = {colors: ['red', 'green'], name:'Jones', what: p1}; 
      if (callback) { 
       callback.call(scope||this,result, scope); 
      } 
     } 
    } 
    MySvcWrap.doWork('thang', myHandler, this); 
    } 
)(); 

resultado es el mismo, pero no de búsqueda función más ...

Y una idea más para hacerlo funcionar ... Realmente necesita definir el manejador de devolución de llamada en el mismo ámbito donde se define la variable de barra, por lo que se puede hacer un poco complicado, pero solo como alternativa :

function myHandler(bar) { // actually in this case better to call it createHandler 
    return function(data, ctx) { 
     console.log('myHandler(): bar: ' + bar); 
     console.log(JSON.stringify(data)); 
    } 
} 
MySvcWrap = { 
    doWork: function(p1, callback, scope) { 
     var result = {colors: ['red', 'green'], name:'Jones', what: p1}; 
     if (callback) { 
      callback.call(scope||this,result, scope); 
     } 
    } 
} 
function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', myHandler(bar), this); // scope object is this 
} 

y pocos recursos para leer acerca de alcance JavaScript y el cierre:

  1. Explaining JavaScript scope and closures
  2. Picking up Javascript - Closures and lexical scoping
  3. JavaScript: Advanced Scoping & Other Puzzles - muy buena presentación sobre el tema
+0

me gusta el parametrizado enfoque del manejador. –

2

El código funcionaría si se estructuró como esta.

MySvcWrap = { 
    doWork: function(p1, callback, scope) { 
     var result = {colors: ['red', 'green'], name:'Jones', what: p1}; 
     if (callback) { 
      callback.call(scope||this,result, scope); 
     } 
    } 
} 
function lookup() { 
    var bar = 'food'; // local var 

    function myHandler(data, ctx) { 
     console.log('myHandler(): bar: ' + bar); // <- prob: bar undefined 
     console.log(JSON.stringify(data)); 
    } 

    MySvcWrap.doWork('thang', myHandler, this); // scope object is this 
} 

lookup(); 

La función myHandler puede acceder a las variables locales de las operaciones de búsqueda, ya que está encerrado por ella, por lo tanto un cierre.

Probablemente intentaré lograr el mismo resultado estructurando el código de esta manera.

function myHandler(data, bar, ctx) { 
    console.log('myHandler(): bar: ' + bar); // <- prob: bar undefined 
    console.log(JSON.stringify(data)); 
} 
MySvcWrap = { 
    doWork: function(p1, callback) { 
     var result = {colors: ['red', 'green'], name:'Jones', what: p1}; 
     if (callback) { 
      callback(result); 
     } 
    } 
} 
function lookup() { 
    var bar = 'food'; // local var 
    MySvcWrap.doWork('thang', myHandler.bind(this, bar)); // callback function is bound to the scope 
} 

lookup(); 

En lugar de pasar el alcance, utilizo bind dentro del método de búsqueda. Usando bind también puedo agregar las variables locales que deseo pasar de ese alcance.

Por supuesto, como el enlace no está disponible en los navegadores más antiguos, es necesario agregarlo a través de un marco, o usar uno de los muchos pequeños fragmentos de código que agregan el método si no existe.

0

Fui un poco rápido en mi primera respuesta y dejé algunos espacios vacíos según Maxym en los comentarios a continuación, gracias por informarme :) Eso es lo que obtengo por intentar publicar rápidamente antes de volver al trabajo: P

lo que estaba tratando de llegar en mi primera respuesta es si desea utilizar el enlace para acceder a variables en función de lookup de la función myHandler (que está fuera del alcance de lookup) que tendría que no utilice var pero this en lugar.

Usando var haría sólo se puede acceder a lookup's ámbito privado y las funciones anidadas en ella, que es como las otras respuestas han demostrado (y son probablemente la mejor ruta)

A continuación se muestra un ejemplo actualizada de cómo pase el alcance de búsqueda a myHandler dejando myHandler completamente fuera del alcance de la búsqueda. Esperemos que esto ayude a arrojar algo de luz sobre: ​​

"El problema que estoy teniendo es que estoy obteniendo alcance del objeto, que no me da acceso a los parámetros y variables locales en la función original."

como se indica en los comentarios acerca de mi respuesta anterior this puede ser complicado y puede terminar añadiendo cosas al alcance global si no tiene cuidado como lo hice en mi primer ejemplo. :(Así que hemos añadido una poco de un cheque hack'ish para ver qué alcance this es asegurarse de que no es window con fines de demostración ...

Live Example

function myHandler(data, ctx) { 
    console.log('myHandler(): bar: ' + this.bar); // <- must use `this` 
    console.log('myHandler(): privateBar: ' + this.privateBar); // <- undefined because it's privately scoped 
    console.log(JSON.stringify(data)); 
} 
MySvcWrap = { 
    doWork: function(p1, callback, scope) { 
     var result = { 
      colors: ['red', 'green'], 
      name: 'Jones', 
      what: p1 
     }; 
     if (callback) { 
      callback.call(scope || this, result, scope); 
     } 
    } 
} 

function lookup() { 
    if(this == window) return lookup.call(lookup); // <- my hack'ish check 

    var privateBar = 'private food'; // private local var 
    this.bar = 'food'; // public local var 
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this 
} 

lookup(); 

console.log('global space - bar: ' + this.bar); 
+1

'var bar = this.bar = 'food';' - en este caso, usted crea la variable 'bar' en el objeto ventana, por lo que es lo mismo que declarar esta variable fuera de la función de búsqueda y simplemente definirla aquí, o definir sin' bar = 'food'', quiero decir w/o 'var' ... ahora este código es bastante complicado, y oculta el hecho de que la variable' bar' ahora también es global – Maxym

Cuestiones relacionadas