Determinar el valor de this
puede ser complicado en JavaScript. A continuación, te mostramos cómo hacerlo...
this
de JavaScript es el tema central de muchas bromas, y eso se debe a que, bueno, es bastante complicado.
Sin embargo, noté que los desarrolladores hacen tareas mucho más complicadas y específicas del dominio para evitar lidiar con este this
. Si no estás seguro sobre this
, esperamos que esta información te resulte útil. Esta es mi guía de this
.
Voy a empezar con la situación más específica y terminaré con la menos específica. Este artículo es como una if (…) … else if () … else if (…) …
grande, 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 de 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
"vinculado" - De lo contrario, si
this
se configura al 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
- En caso contrario
Si la función se define como una función flecha:
const arrowFunction = () => {
console.log(this);
};
En este caso, el valor de this
es siempre 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 y siempre es el mismo que el this
externo.
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 si se llama 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 constructor:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Métodos de instancia “vinculados”
Con los métodos de instancia, si deseas asegurarte de que this
siempre haga referencia a la instancia de 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 puede parecer que rompe la regla "this
será lo mismo que this
en el alcance superior", pero comienza a tener sentido si piensas en los campos de clase como azúcar sintáctica 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);
};
}
}
Las plantillas alternativas 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 la función o clase se llama con new
, sucederá lo siguiente:
new Whatever();
El comando anterior llamará a Whatever
(o a su función de constructor si es una clase) con this
configurado como resultado de Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
Lo mismo sucede 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 lo contrario, si la función tiene un valor this
"vinculado":
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 pasará a bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Otros ejemplos
Cuando se llama a una función vinculada, el valor de this
no se puede cambiar con call
ni 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 si se llama 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 al 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, elementos como los objetos de escucha de eventos del DOM establecen this
con otro valor, 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 los anteriores y, en su lugar:
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 a través de un objeto superior (parent.func()
), ejecuta el siguiente comando:
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 no se llama a someMethod
como miembro de obj
. Es posible que te hayas encontrado con este problema al intentar algo como lo siguiente:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Este error se interrumpe porque la implementación de querySelector
analiza su propio valor de this
y espera que sea un tipo de nodo del DOM, por lo que lo anterior interrumpe esa conexión. Para lograr lo anterior correctamente, haz lo siguiente:
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 la consola, como console.log
, para evitar referencias this
, por lo que log
no necesita estar vinculado a console
.
De lo contrario, si el alcance de la función o superior está en modo estricto, ocurre lo siguiente:
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 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 el de globalThis
.
¡Vaya!
Eso es todo. Eso es todo lo que sé sobre this
. ¿Alguna pregunta? ¿Hay algo que me perdí? No dudes en enviarme un tweet.
Gracias a Mathias Bynens, Ingvar Stepanyan y Thomas Steiner por sus comentarios.