Descubrir el valor de this
puede ser complicado en JavaScript. A continuación, te mostramos cómo hacerlo…
El this
de JavaScript es el blanco de muchos chistes, y eso se debe a que es bastante complicado.
Sin embargo, he visto a desarrolladores hacer tareas mucho más complejas y específicas del dominio para evitar lidiar con este this
. Si tienes dudas sobre this
, esperamos que esta información te resulte útil. Esta es mi guía de this
.
Comenzaré con la situación más específica y terminaré con la menos específica. Este artículo
es como un gran if (…) … else if () … else if (…) …
, por lo que puedes ir directamente a la primera
sección que coincida con el código que estás viendo.
- Si la función se define como una función flecha
- De lo contrario, si se llama a la función o clase con
new
- De lo contrario, si la función tiene un valor
this
"limitado" - De lo contrario, si
this
se configuró en el momento de la llamada - De lo contrario, si se llama a la función a través de un objeto superior (
parent.func()
) - De lo contrario, si la función o el alcance superior están en modo estricto
- De lo contrario
Si la función se define como una función flecha, haz lo siguiente:
const arrowFunction = () => {
console.log(this);
};
En este caso, el valor de this
siempre es el mismo que this
en el alcance superior:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Las funciones de flecha son excelentes porque el valor interno de this
no se puede cambiar, es siempre el mismo que el this
externo.
Otros ejemplos
Con las funciones de flecha, no se puede cambiar el valor de this
con bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Con las funciones de flecha, el valor de this
no se puede cambiar con call
o apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
Con las funciones de flecha, el valor de this
no se puede cambiar llamando a la función como miembro de otro objeto:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Con las funciones de flecha, el valor de this
no se puede cambiar llamando a la función como un constructor:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Métodos de instancia “vinculada”
Con los métodos de instancia, si quieres asegurarte de que this
siempre haga referencia a la instancia de la clase, la mejor manera es usar funciones de flecha y campos de clase:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Este patrón es muy útil cuando se usan métodos de instancia como objetos de escucha de eventos en componentes (como componentes de React o componentes web).
Lo anterior podría parecer que incumple la regla "this
será igual que this
en el alcance superior", pero comienza a tener sentido si consideras que los campos de clase son sintaxis enriquecida para configurar elementos en el constructor:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
Los patrones alternativos implican vincular una función existente en el constructor o asignar la función en el constructor. Si, por algún motivo, no puedes usar campos de clase, asignar funciones en el constructor es una alternativa razonable:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
De lo contrario, si se llama a la función o clase con new
:
new Whatever();
Lo anterior llamará a Whatever
(o a su función de constructor si es una clase) con this
establecido en el resultado de Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
Lo mismo ocurre con los constructores de estilo antiguo:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Otros ejemplos
Cuando se llama con new
, el valor de this
no se puede cambiar con bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Cuando se llama a new
, no se puede cambiar el valor de this
llamando a la función como miembro de otro objeto:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
De lo contrario, si la función tiene un valor this
"limitado", sucede lo siguiente:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Cada vez que se llame a boundFunction
, su valor this
será el objeto que se pasa a bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Otros ejemplos
Cuando llamas a una función vinculada, el valor de this
no se puede cambiar con call
o apply
:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
Cuando llamas a una función vinculada, el valor de this
no se puede cambiar llamando a la función como miembro de otro objeto:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
De lo contrario, si this
se configura en el momento de la llamada:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
El valor de this
es el objeto que se pasa a call
/apply
.
Lamentablemente, this
se establece en algún otro valor por elementos como los objetos de escucha de eventos del DOM, y su uso puede generar un código difícil de entender:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
Evito usar this
en casos como el anterior y, en su lugar, hago lo siguiente:
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); });
De lo contrario, si se llama a la función mediante un objeto superior (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
En este caso, se llama a la función como miembro de obj
, por lo que this
será obj
. Esto sucede en el momento de la llamada, por lo que el vínculo se rompe si se llama a la función sin su objeto superior o con un objeto superior diferente:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj
es falso porque someMethod
no se llama como miembro de obj
. Es posible que hayas encontrado este problema cuando intentaste algo como lo siguiente:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Esto se produce porque la implementación de querySelector
observa su propio valor this
y espera que sea un tipo de nodo DOM, y lo anterior interrumpe esa conexión. Para lograr lo anterior correctamente:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Dato curioso: No todas las APIs usan this
de forma interna. Se cambiaron los métodos de consola, como console.log
, para evitar referencias de this
, por lo que log
no necesita estar vinculado a console
.
De lo contrario, si la función o el alcance superior están en modo estricto:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
En este caso, el valor de this
no está definido. No se necesita 'use strict'
en la función si el alcance superior está en modo estricto (y todos los módulos están en modo estricto).
En caso contrario:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
En este caso, el valor de this
es el mismo que globalThis
.
¡Vaya!
Eso es todo. Eso es todo lo que sé sobre this
. ¿Alguna pregunta? ¿Me perdí de algo? No dudes en tuitearme.
Agradecemos a Mathias Bynens, Ingvar Stepanyan y Thomas Steiner por su opinión.