¿Cómo acceder al "this" correcto dentro de una devolución de llamada?

Nov 29 2013

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 a la datapropiedad del objeto creado dentro de la devolución de llamada. Parece que thisno se refiere al objeto que se creó sino a otro.

También intenté 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 presenta los mismos problemas.

¿Cómo puedo acceder al objeto correcto?

Respuestas

1910 FelixKling Nov 29 2013 at 13:13

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 de cómo / cuándo / dónde se definió. No se ve afectado por los á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 this, consulte la documentación de MDN .


Cómo referirse a la correcta this

Usar funciones de flecha

ECMAScript 6 introdujo funciones de flecha , que pueden considerarse funciones lambda. No tienen su propia thisatadura. En cambio, thisse busca en el alcance como una variable normal. Eso significa que no tiene 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));
}

No use this

En realidad, no desea acceder thisen 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 más comunes son selfy that.

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

Dado que selfes una variable normal, obedece a 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 thisvalor de la devolución de llamada en sí.

Establecimiento explícito thisde la devolución de llamada - parte 1

Puede parecer que no tiene control sobre el valor de thisporque 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 thisvinculada a un valor. La función tiene exactamente el mismo comportamiento que la que solicitó .bind, solo que thisusted la configuró. No importa cómo o cuándo se llame a esa función, thissiempre 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 las devoluciones de llamada thisal valor de MyConstructor's this.

Nota: Cuando sea un contexto de enlace para jQuery, use jQuery.proxy [docs] en su lugar. La razón para hacer esto es que no necesita almacenar la referencia a la función al desvincular una devolución de llamada de evento. jQuery maneja eso internamente.

Conjunto thisde devolución de llamada - parte 2

Algunas funciones / métodos que aceptan devoluciones de llamada también aceptan un valor al que thisdeben referirse las devoluciones 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 thisdebe hacer referencia. 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:this En la documentación de esa función / método se suele mencionar si puede o no pasar un valor . Por ejemplo, el $.ajaxmétodo de jQuery [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: uso de métodos de objeto como devoluciones de llamada / controladores de eventos

Otra manifestación común de este problema es cuando se usa un método de objeto como controlador de eventos / devolución de llamada. Las funciones son ciudadanos de primera clase en JavaScript y el término "método" es solo un término coloquial para una función que es un valor de una propiedad de objeto. Pero esa función no tiene un vínculo 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.methodse asigna como controlador de eventos de clic, pero si document.bodyse hace clic en, el valor registrado será undefined, porque dentro del controlador de eventos, se thisrefiere a document.body, no a la instancia de Foo.
Como ya se mencionó al principio, a qué se thisrefiere depende de cómo se llame a la función , no de cómo se defina .
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 .bindpara enlazar explícitamente thisa 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, mediante el uso de una función anónima como devolución de llamada / controlador de eventos y asignar el objeto ( this) a otra variable:

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

o use una función de flecha:

document.body.onclick = () => this.method();
225 MohanDere Aug 13 2016 at 17:26

Aquí hay varias formas de acceder al contexto principal dentro del contexto secundario:

  1. Puede utilizar la bind()función.
  2. Almacene la referencia al contexto / esto dentro de otra variable (vea el ejemplo a continuación).
  3. Utilice las funciones de flecha ES6 .
  4. Modifique el código / el diseño de la función / la arquitectura: para esto, debe tener control sobre los patrones de diseño en javascript.

1. bind()Función de uso

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 / esto 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);
  });
}
59 Guffa May 21 2014 at 07:11

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

object.property();

Cuando obtiene la propiedad del objeto y lo llama de una vez, el objeto será el contexto del 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 obtienes la referencia de un método, ya no está adjunto al objeto, es solo una referencia a una función simple. Lo mismo sucede cuando obtiene la referencia para usar como devolución de llamada:

this.saveNextLevelData(this.setAll);

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

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

Si está usando jQuery, debe usar el $.proxymétodo en su lugar, ya bindque no es compatible con todos los navegadores:

this.saveNextLevelData($.proxy(this.setAll, this));
34 RobG Jun 01 2014 at 07:44

El problema con el "contexto"

El término "contexto" se utiliza a veces para referirse al objeto referenciado por este . Su uso es inadecuado porque no encaja bien semánticamente o técnicamente con ECMAScript de este .

"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 utiliza en ECMAScript para referirse al contexto de ejecución , que son todos los parámetros, el 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 :

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

lo 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 se puede establecer mediante la llamada a cualquier objeto y, en modo estricto, a cualquier valor.

32 Ashish Jan 30 2019 at 18:01

Debe conocer "esta" palabra clave.

Según mi punto de vista, puede implementar "esto" de tres maneras (función Auto / Flecha / Método de vinculación)

Esta palabra clave de una función se comporta un poco diferente en JavaScript en comparación con otros lenguajes.

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

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

No se puede configurar 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 this de una función independientemente de cómo se llame,

y ES2015 introdujo funciones de flecha que no proporcionan su propio enlace (retiene este valor del 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 que se usa a menudo en 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 propios enlaces a las palabras clave this, argumentos, super o new.target.

Las expresiones de función de flecha no son adecuadas como métodos y no se pueden usar 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 llama, tiene esta palabra clave establecida en el valor proporcionado,

con una secuencia dada de argumentos que preceden a cualquiera proporcionado cuando se llama a la nueva función.

Referencia: 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);
26 AL-zami Aug 19 2017 at 00:58

En primer lugar, debe tener una comprensión clara scopey el comportamiento de las thispalabras 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 de la ventana. Las variables declaradas en un alcance global son accesibles desde cualquier lugar. Por otro lado, el alcance de la función reside dentro de una función. La variable declarada dentro de una función no se puede acceder normalmente desde el mundo exterior. thisLa palabra clave en el ámbito global se refiere al objeto de ventana. thisLa función inside también se refiere al objeto de la ventana, por thislo que siempre nos referiremos a la ventana hasta que encontremos una manera de manipular thispara 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 las thisfunciones de devolución de llamada internas:

Aquí tengo una función constructora llamada Persona. Tiene una propiedad llamada namey 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 constructora Person.

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 creemos una instancia a partir del constructor de persona e invocamos diferentes versiones del sayNameVersionXmétodo (X se refiere a 1,2,3,4) niceCallbackpara ver de cuántas formas podemos manipular la thisdevolución de llamada interna para hacer referencia a la personinstancia.

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

enlazar:

Lo que bind hace es crear una nueva función con la thispalabra clave establecida en el valor proporcionado.

sayNameVersion1y sayNameVersion2use bind para manipular thisla función de devolución de llamada.

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

el primero se vincula thiscon la devolución de llamada dentro del método mismo, y para la segunda, se pasa la devolución de llamada con el objeto vinculado a él.

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 :

El first argumentdel callmétodo se usa como thisdentro de la función que se invoca con calladjunta.

sayNameVersion3utiliza callpara manipular el thispara hacer referencia al objeto persona que creamos, en lugar del objeto ventana.

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

y se llama así:

p1.sayNameVersion3(niceCallback)

aplicar :

Similar a call, el primer argumento de se applyrefiere al objeto que se indicará con la thispalabra clave.

sayNameVersion4usa applypara manipular thispara referirse a la persona objeto

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

y se llama así: simplemente se pasa la devolución de llamada,

p1.sayNameVersion4(niceCallback)
20 DattaChanewad Nov 17 2017 at 21:32

No podemos vincular esto setTimeout(), ya que siempre se ejecuta con un objeto global (Ventana) , si desea acceder al thiscontexto en la función de devolución de llamada, al usar bind()la función de devolución de llamada, podemos lograr lo siguiente:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
13 Code_Mode Feb 18 2019 at 16:10

La pregunta gira en torno a cómo se thiscomporta la palabra clave en javascript. thisse comporta de manera diferente como se muestra a continuación,

  1. El valor de thissuele estar determinado por el contexto de ejecución de una función.
  2. En el ámbito global, se thisrefiere al objeto global (el windowobjeto).
  3. Si el modo estricto está habilitado para cualquier función, entonces el valor de thisserá undefinedcomo en el modo estricto, el objeto global se refiere undefineden lugar del windowobjeto.
  4. El objeto que está delante del punto es a lo que estará vinculada esta palabra clave.
  5. Podemos establecer el valor de esta explícitamente call(), bind()yapply()
  6. Cuando newse usa la palabra clave (un constructor), está vinculada al nuevo objeto que se está creando.
  7. Las funciones de flecha no se vinculan this ; en cambio, thisestán vinculadas léxicamente (es decir, según el contexto original)

Como sugieren la mayoría de las respuestas, podemos usar la función Flecha o bind()Método o Var. Citaría un punto sobre lambdas (función de flecha) de la Guía de estilo de JavaScript de Google

Prefiere usar funciones de flecha sobre f.bind (this), y especialmente sobre goog.bind (f, this). Evite escribir const self = this. Las funciones de flecha son particularmente útiles para devoluciones de llamada, que a veces pasan argumentos adicionales inesperados.

Google recomienda claramente usar lambdas en lugar de enlazar o const self = this

Entonces, la mejor solución sería usar lambdas como se muestra a continuación,

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

Referencias:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. funciones-de-flecha-vs-bind
8 skyboyer Sep 22 2018 at 20:38

Actualmente, existe otro enfoque posible si las clases se utilizan en el código.

Con el apoyo de los campos de clase , es posible hacerlo de la siguiente manera:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

Seguro que bajo el capó todo es una buena función de flecha antigua que enlaza el contexto, pero de esta forma parece mucho más claro que el enlace explícito.

Dado que se trata de la propuesta de la etapa 3, necesitará babel y el complemento de babel adecuado para procesarla como por ahora (08/2018).

5 AndreaPuddu Aug 28 2018 at 16:10

Otro enfoque, que es la forma estándar desde DOM2 de vincularse thisdentro del detector de eventos, que le permite eliminar siempre el detector (entre otros beneficios), es el handleEvent(evt)método de la EventListenerinterfaz:

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

Puede encontrar información detallada sobre el uso handleEventaquí: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

2 WillemvanderVeen May 05 2020 at 15:48

this en JS:

El valor de thisen JS está determinado al 100% por cómo se llama a una función y no por cómo se define. Podemos encontrar con relativa facilidad el valor de thispor la 'regla a la izquierda del punto' :

  1. Cuando la función se crea usando la palabra clave de función, el valor de thises el objeto a la izquierda del punto de la función que se llama
  2. Si no queda ningún objeto del punto, el valor de thisdentro de una función suele ser el objeto global ( globalen el nodo, windowen el navegador). ¡No recomendaría usar la thispalabra clave aquí porque es menos explícita que usar algo como window!
  3. Existen ciertas construcciones como funciones de flecha y funciones creadas usando la Function.prototype.bind()función a que puede fijar el valor de this. Estas son excepciones de la regla, pero es realmente útil corregir el valor de this.

Ejemplo en nodeJS

module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);

const obj1 = {
    data: "obj1 data",
    met1: function () {
        console.log(this.data);
    },
    met2: () => {
        console.log(this.data);
    },
};

const obj2 = {
    data: "obj2 data",
    test1: function () {
        console.log(this.data);
    },
    test2: function () {
        console.log(this.data);
    }.bind(obj1),
    test3: obj1.met1,
    test4: obj1.met2,
};

obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);

Salida:

Déjame guiarte a través de las salidas 1 por 1 (ignorando el primer registro comenzando desde el segundo):

  1. thisse obj2debe a la regla del punto a la izquierda, podemos ver cómo test1se llama obj2.test1();. obj2queda a la izquierda del punto y, por tanto, del thisvalor.
  2. Aunque obj2queda a la izquierda del punto, test2está vinculado a obj1través del bind()método. Entonces el thisvalor es obj1.
  3. obj2que queda del punto de la función que se llama: obj2.test3(). Por obj2lo tanto será el valor de this.
  4. En este caso: obj2.test4() obj2queda a la izquierda del punto. Sin embargo, las funciones de flecha no tienen su propio thisenlace. Por lo tanto, se vinculará al thisvalor del ámbito externo, que es el module.exportsobjeto que se registró al principio.
  5. También podemos especificar el valor de thisusando la callfunción. Aquí podemos pasar el thisvalor deseado como argumento, que es obj2en este caso.
1 Md.TazbirUrRahmanBhuiyan Oct 12 2020 at 23:07

Yo estaba frente a un problema con el Ngxgráfico de líneas xAxisTickFormattingfunción que se llama desde HTML como esto: [xAxisTickFormatting]="xFormat". No pude acceder a la variable de mi componente desde la función declarada. Esta solución me ayudó a resolver el problema para encontrar la solución correcta. Espero que esto ayude al Ngxgráfico de líneas, usuarios.

en lugar de usar la función de esta manera:

xFormat (value): string {
  return value.toString() + this.oneComponentVariable; //gives wrong result 
}

Utilizar esta:

 xFormat = (value) => {
   // console.log(this);
   // now you have access to your component variables
   return value + this.oneComponentVariable
 }