Pregunta ¿Cómo acceder al 'this` correcto dentro de una devolución de llamada?


Tengo una función constructora que registra un controlador 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 al data propiedad 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 traté de usar un método de objeto 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?


914
2017-11-29 06:13


origen


Respuestas:


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 la función se llamó, no cómo / cuándo / dónde se definió. No se ve afectado por los ámbitos léxicos, como otras variables. 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 aprender más sobre this, echa un vistazo a Documentación de MDN.


Cómo referirse a la correcta this

No usar this

En realidad no quieres acceder this en particular, pero el 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);
    });
}

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

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

Puede parecer que no tienes 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 vinculado a un valor. La función tiene exactamente el mismo comportamiento que el que llamaste .bind en, solo eso this fue establecido por ti. 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 MyConstructores this.

Nota: Cuando enlace contexto para jQuery, use jQuery.proxy  [docs] en lugar. La razón para hacer esto 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: Uso funciones de flecha

ECMAScript 6 presenta funciones de flecha, que pueden considerarse funciones lambda. Ellos no tienen su propio this Unión. En lugar, this se busca en el ámbito como una variable normal. Eso significa que no tienes que llamar .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));
}

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

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

array.map(callback[, thisArg])

El primer argumento es la devolución de llamada y el segundo argumento es el valor this debe referirse a. 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 pasar o no un valor por this generalmente se menciona en la documentación de esa función / método. Por ejemplo, jQuery's $.ajax método [docs] describe una opción llamada context:

Este objeto se convertirá en el contexto de todas las devoluciones de llamada relacionadas con Ajax.


Problema común: el uso de métodos de objetos como devoluciones de llamada / controladores de eventos

Otra manifestación común de este problema es cuando un método de objeto se utiliza como controlador de devolución de llamada / evento. Las funciones son ciudadanos de primera clase en JavaScript y el término "método" es simplemente un término coloquial para una función que es un valor de una propiedad de objeto. Pero esa función no tiene un enlace específico a 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 de clic, pero si document.body se hace clic, el valor registrado será undefined, porque dentro del controlador de eventos, this se refiere a document.body, no la instancia de Foo.
Como ya se mencionó al principio, ¿qué this se refiere a depende de cómo es la función llamado, no cómo es definido.
Si el código era 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 el mismo que el mencionado anteriormente: si está disponible, use .bind para vincular explícitamente this a un valor específico

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

o llamar explícitamente a la función como un "método" del objeto, utilizando una función anónima como callback / evento controlador y asignar el objeto (this) a otra variable:

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

o usa una función de flecha:

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

1206
2017-11-29 06:13



Aquí hay varias formas de acceder al contexto principal dentro del contexto hijo -

  1. Puedes usar enlazar() función.
  2. Almacene la referencia al contexto / this dentro de otra variable (vea el ejemplo a continuación).
  3. Use ES6 Flecha funciones.
  4. Modifique el diseño / la arquitectura del código / función - para esto debe tener el control sobre patrones de diseño en javascript

1. Uso bind() función

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ás usando underscore.js - http://underscorejs.org/#bind 

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

2 Almacena la referencia al contexto / this 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 flecha

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

139
2017-08-13 10:26



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

object.property();

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

var f = object.property;
f();

Cuando obtiene la referencia de un método, ya no está vinculado al objeto, es solo una referencia a una función simple. Lo mismo ocurre cuando obtiene la referencia para usar como devolución de llamada:

this.saveNextLevelData(this.setAll);

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

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

Si está utilizando jQuery, debe usar $.proxy método en cambio, como bind no es compatible con todos los navegadores:

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

35
2018-05-21 00:11



El problema con el "contexto"

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

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

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

Establezca ThisBinding en el mismo valor que ThisBinding de   contexto de ejecución de llamada

lo que claramente indica que esta 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 el esteEnlazado.

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


20
2018-06-01 00:44



En primer lugar, debe tener una comprensión clara de scope y el comportamiento de this palabra clave en el contexto de scope.

this & scope :


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

En resumen, el alcance global se refiere al objeto ventana. Las variables declaradas en un ámbito global son accesibles desde cualquier lugar. Por otro lado, el ámbito de función reside dentro de una función. La variable declarada dentro de una función no se puede acceder desde fuera del mundo normalmente.this palabra clave en alcance global se refiere al objeto ventana.this la función interna también se refiere al objeto window.So this siempre se referirá a la ventana hasta que encontremos una manera de manipular this para indicar un contexto de nuestra propia elección.

--------------------------------------------------------------------------------
-                                                                              -
-   Global Scope                                                               -
-   ( globally "this" refers to window object)                                 -     
-                                                                              -
-         function outer_function(callback){                                   -
-                                                                              -
-               // outer function scope                                        -
-               // inside outer function"this" keyword refers to window object -                                                                              -
-              callback() // "this" inside callback also refers window object  -

-         }                                                                    -
-                                                                              -
-         function callback_function(){                                        -
-                                                                              -
-                //  function to be passed as callback                         -
-                                                                              -
-                // here "THIS" refers to window object also                   -
-                                                                              -
-         }                                                                    -
-                                                                              -
-         outer_function(callback_function)                                    -
-         // invoke with callback                                              -
--------------------------------------------------------------------------------

Diferentes formas de manipular this dentro de las funciones de devolución de llamada:

Aquí tengo una función de constructor llamada Person. Tiene una propiedad llamada name y cuatro método llamado sayNameVersion1,sayNameVersion2,sayNameVersion3,sayNameVersion4. Los cuatro tienen una tarea específica. Aceptar una devolución de llamada e invocarla. La devolución de llamada tiene una tarea específica que es registrar la propiedad del nombre de una instancia de la función del constructor de la persona.

function Person(name){

    this.name = name

    this.sayNameVersion1 = function(callback){
        callback.bind(this)()
    }
    this.sayNameVersion2 = function(callback){
        callback()
    }

    this.sayNameVersion3 = function(callback){
        callback.call(this)
    }

    this.sayNameVersion4 = function(callback){
        callback.apply(this)
    }

}

function niceCallback(){

    // function to be used as callback

    var parentObject = this

    console.log(parentObject)

}

Ahora vamos a crear una instancia de persona constructor e invocar diferentes versiones de sayNameVersionX (X se refiere a 1,2,3,4) método con niceCallback para ver cuántas maneras podemos manipular el this devolución de llamada dentro para referirse a la person ejemplo.

var p1 = new Person('zami') // create an instance of Person constructor

bind: 

Lo que obligar es crear una nueva función con el this palabra clave establecida en el valor proporcionado.

sayNameVersion1 y sayNameVersion2 usar bind para manipular this de la función de devolución de llamada.

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

primer enlace this con devolución de llamada dentro del método en sí. Y para la segunda, se pasa una devolución de llamada con el objeto vinculado a ella.

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

llamada : 

los first argument del call método se utiliza como this dentro de la función que se invoca con calladjunto a.

sayNameVersion3 usos call para manipular el this para referirnos a la persona objeto que creamos, en lugar del objeto ventana.

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

y se llama como el siguiente:

p1.sayNameVersion3(niceCallback)

aplicar : 

Similar a callprimer argumento de apply se refiere al objeto que será indicado por this palabra clave.

sayNameVersion4 usos apply manipular this para referirse al objeto de persona

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

y se llama como el siguiente. Simplemente se pasa la devolución de llamada,

p1.sayNameVersion4(niceCallback)

15
2017-08-18 17:58



No podemos atar esto a setTimeout(), ya que siempre se ejecuta con objeto global (Ventana), si quieres acceder this contexto en la función de devolución de llamada luego mediante el uso de bind() a la función de devolución de llamada que podemos lograr como:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);

9
2017-11-17 14:32