¿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 a la 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 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
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 this
atadura. En cambio, this
se 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 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 más comunes son self
y that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Dado que self
es 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 this
valor de la devolución de llamada en sí.
Establecimiento explícito 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 this
vinculada a un valor. La función tiene exactamente el mismo comportamiento que la que solicitó .bind
, solo que this
usted la configuró. 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 las devoluciones de llamada this
al 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 this
de devolución de llamada - parte 2
Algunas funciones / métodos que aceptan devoluciones de llamada también aceptan un valor al que this
deben 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 this
debe 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 $.ajax
mé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.method
se asigna como controlador de eventos de clic, pero si document.body
se hace clic en, el valor registrado será undefined
, porque dentro del controlador de eventos, se this
refiere a document.body
, no a la instancia de Foo
.
Como ya se mencionó al principio, a qué se this
refiere 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 .bind
para enlazar 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, 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();
Aquí hay varias formas de acceder al contexto principal dentro del contexto secundario:
- Puede utilizar la
bind()
función. - Almacene la referencia al contexto / esto dentro de otra variable (vea el ejemplo a continuación).
- Utilice las funciones de flecha ES6 .
- 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);
});
}
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 $.proxy
método en su lugar, ya bind
que no es compatible con todos los navegadores:
this.saveNextLevelData($.proxy(this.setAll, this));
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.
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);
En primer lugar, debe tener una comprensión clara scope
y el comportamiento de las this
palabras 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. this
La palabra clave en el ámbito global se refiere al objeto de ventana. this
La función inside también se refiere al objeto de la ventana, por this
lo que siempre nos referiremos 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 las this
funciones de devolución de llamada internas:
Aquí tengo una función constructora llamada Persona. 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 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 sayNameVersionX
método (X se refiere a 1,2,3,4) niceCallback
para ver de cuántas formas podemos manipular la this
devolución de llamada interna para hacer referencia a la person
instancia.
var p1 = new Person('zami') // create an instance of Person constructor
Lo que bind hace es crear una nueva función con la this
palabra clave establecida en el valor proporcionado.
sayNameVersion1
y sayNameVersion2
use bind para manipular this
la función de devolución de llamada.
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
el primero se vincula this
con 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
El first argument
del call
método se usa como this
dentro de la función que se invoca con call
adjunta.
sayNameVersion3
utiliza call
para manipular el this
para 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)
Similar a call
, el primer argumento de se apply
refiere al objeto que se indicará con la this
palabra clave.
sayNameVersion4
usa apply
para manipular this
para 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)
No podemos vincular esto setTimeout()
, ya que siempre se ejecuta con un objeto global (Ventana) , si desea acceder al this
contexto 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);
La pregunta gira en torno a cómo se this
comporta la palabra clave en javascript. this
se comporta de manera diferente como se muestra a continuación,
- El valor de
this
suele estar determinado por el contexto de ejecución de una función. - En el ámbito global, se
this
refiere al objeto global (elwindow
objeto). - Si el modo estricto está habilitado para cualquier función, entonces el valor de
this
seráundefined
como en el modo estricto, el objeto global se refiereundefined
en lugar delwindow
objeto. - El objeto que está delante del punto es a lo que estará vinculada esta palabra clave.
- Podemos establecer el valor de esta explícitamente
call()
,bind()
yapply()
- Cuando
new
se usa la palabra clave (un constructor), está vinculada al nuevo objeto que se está creando. - Las funciones de flecha no se vinculan
this
; en cambio,this
está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:
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).
Otro enfoque, que es la forma estándar desde DOM2 de vincularse this
dentro del detector de eventos, que le permite eliminar siempre el detector (entre otros beneficios), es el handleEvent(evt)
método de la EventListener
interfaz:
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
Puede encontrar información detallada sobre el uso handleEvent
aquí: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
this
en JS:
El valor de this
en 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 this
por la 'regla a la izquierda del punto' :
- Cuando la función se crea usando la palabra clave de función, el valor de
this
es el objeto a la izquierda del punto de la función que se llama - Si no queda ningún objeto del punto, el valor de
this
dentro de una función suele ser el objeto global (global
en el nodo,window
en el navegador). ¡No recomendaría usar lathis
palabra clave aquí porque es menos explícita que usar algo comowindow
! - Existen ciertas construcciones como funciones de flecha y funciones creadas usando la
Function.prototype.bind()
función a que puede fijar el valor dethis
. Estas son excepciones de la regla, pero es realmente útil corregir el valor dethis
.
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):
this
seobj2
debe a la regla del punto a la izquierda, podemos ver cómotest1
se llamaobj2.test1();
.obj2
queda a la izquierda del punto y, por tanto, delthis
valor.- Aunque
obj2
queda a la izquierda del punto,test2
está vinculado aobj1
través delbind()
método. Entonces elthis
valor esobj1
. obj2
que queda del punto de la función que se llama:obj2.test3()
. Porobj2
lo tanto será el valor dethis
.- En este caso:
obj2.test4()
obj2
queda a la izquierda del punto. Sin embargo, las funciones de flecha no tienen su propiothis
enlace. Por lo tanto, se vinculará althis
valor del ámbito externo, que es elmodule.exports
objeto que se registró al principio. - También podemos especificar el valor de
this
usando lacall
función. Aquí podemos pasar elthis
valor deseado como argumento, que esobj2
en este caso.
Yo estaba frente a un problema con el Ngx
gráfico de líneas xAxisTickFormatting
funció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 Ngx
grá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
}