Cómo acceder al "esto" correcto dentro de una devolución de llamada

javascript callback this


Tengo una función de constructor que registra un manejador de eventos:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', function () {
        alert(this.data);
    });
}

// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};

// called as
var obj = new MyConstructor('foo', transport);

Sin embargo, no puedo acceder a la propiedad de data del objeto creado dentro de la devolución de llamada. Parece que this no se refiere al objeto que se creó, sino a otro.

También intenté usar un método de objetos en lugar de una función anónima:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', this.alert);
}

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

pero muestra los mismos problemas.

¿Cómo puedo acceder al objeto correcto?





Answer 1 Felix Kling


Lo que debes saber sobre this

this (también conocido como "el contexto") es una palabra clave especial dentro de cada función y su valor solo depende de cómo se llamó a la función, no cómo / cuándo / dónde se definió. No se ve afectado por ámbitos léxicos como otras variables (a excepción de las funciones de flecha, ver más abajo). Aquí hay unos ejemplos:

function foo() {
    console.log(this);
}

// normal function call
foo(); // `this` will refer to `window`

// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`

// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`

Para obtener más información sobre this , consulte la documentación de MDN .


Cómo referirse al correcto this

No uses this

En realidad, no desea acceder a this en particular, sino al objeto al que se refiere . Es por eso que una solución fácil es simplemente crear una nueva variable que también se refiera a ese objeto. La variable puede tener cualquier nombre, pero los comunes son self y that .

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

Como self es una variable normal, obedece las reglas de alcance léxico y es accesible dentro de la devolución de llamada. Esto también tiene la ventaja de que puede acceder al valor de this de la devolución de llamada en sí.

Establezca explícitamente this de la devolución de llamada - parte 1

Puede parecer que no tiene control sobre el valor de this porque su valor se establece automáticamente, pero ese no es el caso.

Cada función tiene el método .bind [docs] , que devuelve una nueva función con this vinculada a un valor. La función tiene exactamente el mismo comportamiento que el que llamó .bind , solo que this fue establecido por usted. No importa cómo o cuándo se llame a esa función, this siempre se referirá al valor pasado.

function MyConstructor(data, transport) {
    this.data = data;
    var boundFunction = (function() { // parenthesis are not necessary
        alert(this.data);             // but might improve readability
    }).bind(this); // <- here we are calling `.bind()` 
    transport.on('data', boundFunction);
}

En este caso, estamos vinculando la devolución de llamada this al valor de MyConstructor 's this .

Nota: Al vincular el contexto para jQuery, use jQuery.proxy [docs] en su lugar. La razón para hacerlo es para que no necesite almacenar la referencia a la función al desvincular una devolución de llamada de evento. jQuery maneja eso internamente.

ECMAScript 6: Usar funciones de flecha

ECMAScript 6 introduce funciones de flecha , que pueden considerarse funciones lambda. No tienen su propio enlace. En cambio, this se busca en el alcance al igual que una variable normal. Eso significa que no tienes que llamar a .bind . Ese no es el único comportamiento especial que tienen, consulte la documentación de MDN para obtener más información.

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', () => alert(this.data));
}

Establezca this de la devolución de llamada - parte 2

Algunas funciones / métodos que aceptan devoluciones de llamada también aceptan un valor al que debe referirse la devolución de llamada. Esto es básicamente lo mismo que vincularlo usted mismo, pero la función / método lo hace por usted. Array#map [docs] es un método de este tipo. Su firma es:

array.map(callback[, thisArg])

El primer argumento es la devolución de llamada y el segundo argumento es el valor al que debería referirse. Aquí hay un ejemplo artificial:

var arr = [1, 2, 3];
var obj = {multiplier: 42};

var new_arr = arr.map(function(v) {
    return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument

Nota: Si puede o no pasar un valor para this generalmente se menciona en la documentación de esa función / método. Por ejemplo, el método $.ajax [docs] de jQuery describe una opción llamada context o :

Este objeto será el contexto de todas las llamadas relacionadas con Ajax.


Problema común:Usar métodos de objetos como manejadores de eventos de llamada de retorno

Otra manifestación común de este problema es cuando se utiliza un método de objeto como manejador de eventos de retrollamada.Las funciones son ciudadanos de primera clase en JavaScript y el término "método" es sólo un término coloquial para una función que es un valor de una propiedad del objeto.Pero esa función no tiene un vínculo específico con su objeto "contenedor".

Considere el siguiente ejemplo:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

La función this.method se asigna como controlador de eventos click, pero si se hace clic en document.body , el valor registrado será undefined , porque dentro del controlador de eventos, this refiere al document.body , no a la instancia de Foo .
Como ya se mencionó al principio, a qué se refiere this depende de cómo se llama la función, no de cómo se define .
Si el código fuera como el siguiente,podría ser más obvio que la función no tiene una referencia implícita al objeto:

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

La solución es la misma que se mencionó anteriormente: si está disponible, use .bind para vincular explícitamente this a un valor específico

document.body.onclick = this.method.bind(this);

o llame explícitamente a la función como un "método" del objeto, utilizando una función anónima como devolución de llamada / controlador de eventos y asigne el objeto ( this ) a otra variable:

var self = this;
document.body.onclick = function() {
    self.method();
};

o usar una función de flecha:

document.body.onclick = () => this.method();



Answer 2 Mohan Dere


Aquí hay varias maneras de acceder al contexto de los padres dentro del contexto del niño -

  1. Puede usar la función bind() .
  2. Guarda la referencia al contexto dentro de otra variable (véase el ejemplo a continuación).
  3. Utilice las funciones de flecha ES6.
  4. Alterar el código / diseño de la función / arquitectura: para esto debe tener el comando sobre los patrones de diseño en JavaScript

1. Use la función bind()

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', ( function () {
        alert(this.data);
    }).bind(this) );
}
// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};
// called as
var obj = new MyConstructor('foo', transport);

Si está utilizando underscore.js - http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));

2 Almacenar la referencia al contexto dentro de otra variable

function MyConstructor(data, transport) {
  var self = this;
  this.data = data;
  transport.on('data', function() {
    alert(self.data);
  });
}

3 Función de la flecha

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}



Answer 3 Guffa


Todo está en la sintaxis "mágica" de llamar a un método:

object.property();

Cuando se obtiene la propiedad del objeto y se llama de una sola vez,el objeto será el contexto del método.Si llamas al mismo método,pero en pasos separados,el contexto es el ámbito global (ventana)en su lugar:

var f = object.property;
f();

Cuando se obtiene la referencia de un método,ya no está unido al objeto,es sólo una referencia a una función simple.Lo mismo ocurre cuando obtienes la referencia para usarla como una llamada de retorno:

this.saveNextLevelData(this.setAll);

Ahí es donde se vincularía el contexto a la función:

this.saveNextLevelData(this.setAll.bind(this));

Si está utilizando jQuery, debe utilizar el método $.proxy , ya que bind no es compatible con todos los navegadores:

this.saveNextLevelData($.proxy(this.setAll, this));



Answer 4 RobG


El problema con el "contexto"

El término "contexto" a veces se usa para referirse al objeto al que hace referencia. Su uso es inapropiado porque no encaja semántica o técnicamente con ECMAScript's this .

"Contexto" se refiere a las circunstancias que rodean algo que agrega significado, o alguna información anterior y siguiente que da un significado adicional. El término "contexto" se usa en ECMAScript para referirse al contexto de ejecución , que es todos los parámetros, alcance y esto dentro del alcance de algún código de ejecución.

Esto se muestra en ECMA-262 sección 10.4.2 :

Ponga el ThisBinding al mismo valor que el ThisBinding del contexto de ejecución de la llamada

que indica claramente que esto es parte de un contexto de ejecución.

Un contexto de ejecución proporciona la información circundante que agrega significado al código que se está ejecutando. Incluye mucha más información que solo thisBinding .

Entonces el valor de esto no es "contexto", es solo una parte de un contexto de ejecución. Es esencialmente una variable local que puede establecerse mediante la llamada a cualquier objeto y, en modo estricto, a cualquier valor.




Answer 5 Ashish


Debe saber sobre "esta" palabra clave.

Según mi punto de vista, puede implementar "esto" de tres maneras (Self / Función de flecha / Método de enlace)

Una función es que esta palabra clave se comporta un poco diferente en JavaScript en comparación con otros idiomas.

También tiene algunas diferencias entre el modo estricto y el no estricto.

En la mayoría de los casos,el valor de esto está determinado por la forma en que se llama una función.

No se puede establecer por asignación durante la ejecución,y puede ser diferente cada vez que se llama a la función.

ES5 introdujo el método bind()para establecer el valor de una función,independientemente de cómo se llame,

y ES2015 introdujeron funciones de flecha que no proporcionan por sí mismas esta vinculación (conserva el valor de este contexto léxico adjunto).

Método 1: Self - Self se está utilizando para mantener una referencia al original, incluso cuando el contexto está cambiando. Es una técnica de uso frecuente en los controladores de eventos (especialmente en cierres).

Referencia : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function () {
        alert(self.data);
    });
}

Método 2 : Función de flecha: una expresión de función de flecha es una alternativa sintácticamente compacta a una expresión de función regular,

aunque sin sus propias ataduras a las palabras clave this,arguments,super,or new.target.

Las expresiones de la función de las flechas no son adecuadas como métodos,y no pueden utilizarse como constructores.

Referencia : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',()=> {
        alert(this.data);
    });
}

Método 3 : Bind : el método bind () crea una nueva función que,

cuando se le llama,tiene su esta palabra clave establecida en el valor proporcionado,

con una determinada secuencia de argumentos que preceden a los proporcionados cuando se llama a la nueva función.

Reference:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',(function() {
        alert(this.data);
    }).bind(this);