Averiguar el valor de "this" puede ser complicado en JavaScript, aquí explicamos cómo hacerlo…
La palabra clave this
es el blanco de muchas bromas, y eso es porque, bueno, es bastante complicado. Sin embargo, he visto a los desarrolladores hacer cosas mucho más complicadas y específicas del dominio para evitar lidiar con this
. Si no se siente seguro usando this
, esperamos que esto le ayude. Esta es mi guía para 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 (…) …
, así que puedes ir directamente a la primera sección que coincide con el código que estás viendo.
- Si la función se define como una función de flecha
- De otro modo, si se llama a la función/clase con
new
- De otro modo, si la función tiene un 'límite' use
this
con este valor - De otro modo, si
this
se establece en el momento de la llamada - De otro modo, si la función se llama a través de un objeto principal (
parent.func()
) - De otro modo, si la función o el ámbito principal está en modo estricto
- De otro modo
Si la función se define como una función de flecha:
const arrowFunction = () => {
console.log(this);
};
En este caso, el valor de this
es siempre el mismo que this
en el ámbito primario:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Las funciones de flecha son geniales porque el valor interno de this
no se puede cambiar, siempre es el mismo que el valor externo de this
.
Otros ejemplos
Con las funciones de flecha, el valor de this
no se puede cambiar 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 con 'bound'
Con los métodos de instancia, si desea asegurarse de que this
siempre se refiera 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 realmente útil cuando se utilizan métodos de instancia como detectores de eventos en componentes (como componentes React o componentes web).
Lo anterior puede parecer que está quebrando la regla "this
será igual que this
en el ámbito principal", pero comienza a tener sentido si piensa en los campos de clase como azúcar sintáctico para configurar cosas 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 o asignar la función en el constructor. Si no puede usar campos de clase por alguna razón, asignar funciones en el constructor es una alternativa razonable:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
De otro modo, si se llama la función/clase con new
:
new Whatever();
Lo anterior llamará a Whatever
(o su función constructora si es una clase) con this
configurado con 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 con new
, el valor de this
no se puede cambiar llamando a la función como miembro de otro objeto:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
De otro modo, si la función tiene un 'bound', el valor de this
:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Siempre que se llama boundFunction
, el valor de this
será el objeto pasado a bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Otros ejemplos
Al llamar 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 se llama 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 otro modo, si this
se ha establecido en call-time:
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
.
Desafortunadamente, se establece algún otro valor para this
por elementos como los detectores de eventos DOM, cuyo uso puede dar como resultado 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); });
Yo evito usar this
en casos como el anterior, y en vez de eso utilizo:
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 otro modo, si la función se llama a través de un objeto principal (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
En este caso, la función se llama como miembro de obj
, por lo que this
será un obj
. Esto sucede en el momento de la llamada, por lo que el enlace se corta si se llama a la función sin su objeto principal o con un objeto principal 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 se haya encontrado con este problema al intentar algo como esto:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Esto da un error como resultado porque la implementación de querySelector
ve su proprio valor de this
y espera que sea una especie de nodo DOM, y lo anterior corta esa conexión. Para conseguir el resultado deseado con el código anterior correctamente use:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Dato curioso: no todas las API utilizan this
internamente. Los métodos de consola como console.log
se cambiaron para evitar las referencias a this
, por lo que no es necesario que el log
esté vinculado a console
.
De otro modo, si la función o el ámbito principal está 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. 'use strict'
no es necesario en la función si el ámbito principal está en modo estricto (y todos los módulos están en modo estricto).
De otro modo:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
En este caso, el valor de this
es el mismo que globalThis
.
¡Uf!
¡Y eso es! Eso es todo lo que sé sobre this
. ¿Alguna pregunta? ¿Se me ha escapado algo? Envíame un tuit.
Gracias a Mathias Bynens, Ingvar Stepanyan y Thomas Steiner por la revisión.