2008-10-08 14 views
58

Tengo algunos problemas con el JavaScript antiguo (sin marcos) al hacer referencia a mi objeto en una función de devolución de llamada.JavaScript Callback Scope

function foo(id) { 
    this.dom = document.getElementById(id); 
    this.bar = 5; 
    var self = this; 
    this.dom.addEventListener("click", self.onclick, false); 
} 

foo.prototype = { 
    onclick : function() { 
     this.bar = 7; 
    } 
}; 

Ahora cuando se crea un nuevo objeto (después de que el DOM se haya cargado, con una prueba lapso #)

var x = new foo('test'); 

El 'presente' en el interior de los puntos de función onclick a la prueba lapso # y no el objeto foo.

¿Cómo obtengo una referencia a mi objeto foo dentro de la función onclick?

Respuesta

81

(extraído alguna explicación que estaba oculto en los comentarios en otra respuesta)

El problema radica en la siguiente línea:

this.dom.addEventListener("click", self.onclick, false); 

Aquí, se pasa un objeto de función que se utilizará como devolución de llamada . Cuando se activa el evento, se llama a la función, pero ahora no tiene asociación con ningún objeto (esto).

El problema puede ser resuelto envolviendo la función (con el mismo de la referencia de objeto) en un cierre de la siguiente manera:

this.dom.addEventListener(
    "click", 
    function(event) {self.onclick(event)}, 
    false); 

Puesto que la variable auto fue asignado este cuando se creó el cierre, la función de cierre recordará el valor de la variable auto cuando se llame en un momento posterior.

Una forma alternativa de resolver esto es para hacer una función de utilidad (y evitar el uso de variables para la unión este):

function bind(scope, fn) { 
    return function() { 
     fn.apply(scope, arguments); 
    }; 
} 

El código actualizado tendría el siguiente aspecto:

this.dom.addEventListener("click", bind(this, this.onclick), false); 

Function.prototype.bind es parte de ECMAScript 5 y ofrece la misma funcionalidad. Por lo que puede hacer:

this.dom.addEventListener("click", this.onclick.bind(this), false); 

Para los navegadores que no soportan ES5 embargo, MDN provides the following shim:

if (!Function.prototype.bind) { 
    Function.prototype.bind = function (oThis) { 
    if (typeof this !== "function") { 
     // closest thing possible to the ECMAScript 5 internal IsCallable function 
     throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 
    } 

    var aArgs = Array.prototype.slice.call(arguments, 1), 
     fToBind = this, 
     fNOP = function() {}, 
     fBound = function() { 
      return fToBind.apply(this instanceof fNOP 
           ? this 
           : oThis || window, 
           aArgs.concat(Array.prototype.slice.call(arguments))); 
     }; 

    fNOP.prototype = this.prototype; 
    fBound.prototype = new fNOP(); 

    return fBound; 
    }; 
} 
+1

¡La función de utilidad createCallback es útil! Gracias. –

+0

closure() es probablemente un nombre mejor. – hishadow

+2

O bind() o bindMethod(). – outis

15
this.dom.addEventListener("click", function(event) { 
    self.onclick(event) 
}, false); 
+1

Gracias, pero se puede explicar por qué tengo que crear una función anónima allí? ¿Por qué eso cambia la unión? –

+0

Cada vez que se dispara un evento, 'esto' se refiere al objeto que invocó el evento. – Tom

+2

Cuando usa el formulario en su pregunta, self.onclick es solo una función anónima que, cuando se maneja, funciona como si la hubiera adjuntado directamente al tramo. Cuando lo envuelve en un cierre, self.onclick (evento) realmente hace lo que quiere, p. usa 'yo' del contexto que define el cierre. –

-4

este es uno de los puntos más confusos de la JS: significa el 'esto' variable para el objeto más locales ... pero las funciones son también objetos, por lo que 'esto' puntos allí. Hay otros puntos sutiles, pero no los recuerdo a todos.

Normalmente evito usar 'esto', simplemente defina una variable 'me' local y utilícela en su lugar.

+2

Su afirmación es completamente inexacta. Los métodos apply() y call() de una función le permiten ejecutar una función con 'this' refiriéndose a una variable que pasa en la llamada. – Kenaniah

+1

@Kenaniah: .... aumentando así la confusión, ¡el significado exacto del código depende de cómo se llame! – Javier

+0

Las funciones son muy flexibles en JavaScript y no utilizar 'this' no es una solución al problema. 'this' es la única característica de alcance dinámico en JS y agrega mucha potencia al lenguaje. Y sí, el hecho de que las funciones sean objetos de primera clase en JS implica la naturaleza dinámica del 'puntero de contexto de ejecución'. Es una gran característica cuando se usa sabiamente. –

2

La explicación es que self.onclick no quiere decir lo que creo que significa en JavaScript. Significa en realidad la función onclick en el prototipo del objeto self (sin referirse en modo alguno al self).

JavaScript solo tiene funciones y no hay delegados como C#, por lo que no es posible pasar un método Y el objeto al que se debe aplicar como una devolución de llamada.

La única manera de llamar a un método en una devolución de llamada es llamarlo usted mismo dentro de una función de devolución de llamada. Dado que las funciones de JavaScript son cierres, que son capaces de acceder a las variables declaradas en el alcance que fueron creadas en.

var obj = ...; 
function callback(){ return obj.method() }; 
something.bind(callback); 
+0

Llamé a la función de prototipo 'onclick' para asociarla fácilmente. Sabía que no iba a ser llamado automáticamente por el evento DOM onclick real, que era la razón por la que estaba tratando de hacer que los oyentes del evento vinculen el onclick de mi objeto con la función onclick del DOM. –

+0

Ese no era mi punto, entiendo tu función onclick. Mi punto es que no hay diferencia en JavaScript entre self.onclick y foo.prototype.onclick. No hay forma en JavaScript de decir "este método vinculado a este objeto". –

+0

La única forma es usar un cierre. – dolmen

2

Una buena explicación del problema (tuve problemas para entender las soluciones descritas hasta ahora) es available here.

5

Para los jQuery usuarios que buscan una solución a este problema, se debe utilizar jQuery.proxy

+0

Esta es una buena solución ya que está integrada directamente en jQuery. – Chetan