JavaScript
this
Explica cómo se determina this según la forma de invocar una función, qué cambia en métodos, arrows, clases, constructores y callbacks.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
- Fundamentos de programación
JavaScript
Explica cómo se determina this según la forma de invocar una función, qué cambia en métodos, arrows, clases, constructores y callbacks.
En una función tradicional, this es un valor que normalmente depende de cómo se invoca la función, no del lugar donde fue escrita.
const user = {
name: "Nicolás",
greet() {
return `Hello, ${this.name}`;
},
};
user.greet(); // "Hello, Nicolás"const name = "Outside";
const user = {
name: "Inside",
showName() {
return this.name;
},
};this.name no busca un binding llamado name mediante la scope chain. Busca una propiedad en el valor de this.
user.showName(); // "Inside"Scope léxico y this son mecanismos diferentes.
const account = {
balance: 100,
readBalance() {
return this.balance;
},
};
account.readBalance(); // 100La expresión a la izquierda del punto determina el receptor de esa llamada.
El mismo método puede utilizarse con otro objeto.
const anotherAccount = {
balance: 500,
readBalance: account.readBalance,
};
anotherAccount.readBalance(); // 500La función es la misma; el receptor cambia.
const readBalance = account.readBalance;
readBalance();Ya no existe una llamada de la forma account.readBalance(). Es una llamada directa.
En módulos y código estricto, this será undefined, así que acceder a this.balance produce un error.
const readBalance = account.readBalance.bind(account);
readBalance(); // 100bind crea otra función con un this fijado.
const logger = {
prefix: "APP",
log(message) {
console.log(`[${this.prefix}] ${message}`);
},
};
setTimeout(logger.log, 0, "Started");El temporizador recibe la función, no la llamada con logger como receptor. El método pierde ese contexto.
Opciones:
setTimeout(() => logger.log("Started"), 0);setTimeout(logger.log.bind(logger), 0, "Started");La primera arrow function conserva acceso léxico a logger. La segunda fija this.
Una arrow function no crea su propio this. Utiliza el this disponible en el entorno donde fue creada.
const user = {
name: "Nicolás",
greet: () => {
return this.name;
},
};No toma user como this, aunque esté escrita dentro del objeto.
Las arrows sí son útiles dentro de un método cuando quieres conservar el this del método exterior.
const team = {
name: "Frontend",
members: ["Ana", "Luis"],
printMembers() {
this.members.forEach((member) => {
console.log(`${member} belongs to ${this.name}`);
});
},
};La arrow del callback usa el this de printMembers.
const team = {
name: "Frontend",
print() {
function printName() {
console.log(this.name);
}
printName();
},
};printName() es una llamada directa; no hereda automáticamente el this del método exterior.
Soluciones frecuentes:
const printName = () => {
console.log(this.name);
};O pasar el valor explícitamente:
function printName(name) {
console.log(name);
}
printName(this.name);La segunda opción reduce dependencia de this.
function User(name) {
this.name = name;
}
const user = new User("Nicolás");Con new, this apunta al nuevo objeto que se está inicializando.
Las arrow functions no pueden utilizarse como constructor porque no tienen su propio this ni comportamiento [[Construct]].
const User = (name) => {
this.name = name;
};
new User("Nicolás"); // TypeErrorclass Counter {
constructor() {
this.value = 0;
}
increment() {
this.value++;
}
}Los métodos de clase también reciben this según la llamada.
const counter = new Counter();
counter.increment();Extraer el método presenta el mismo problema.
const increment = counter.increment;
increment(); // TypeErrorLas clases se ejecutan en modo estricto.
class Counter {
value = 0;
increment = () => {
this.value++;
};
}Cada instancia recibe una función arrow propia que captura su this.
Ventaja: el método conserva la instancia al pasarse como callback.
Costo: la función no se comparte mediante el prototipo; se crea una por instancia. No lo uses automáticamente para todos los métodos.
const rectangle = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
},
};this representa el objeto sobre el que se leyó la propiedad.
Esto también funciona con propiedades heredadas.
const base = {
get label() {
return this.name.toUpperCase();
},
};
const product = Object.create(base);
product.name = "Keyboard";
product.label; // "KEYBOARD"El getter está en base, pero this es product porque allí se realizó el acceso.
Con una función tradicional registrada mediante addEventListener, el navegador suele llamar el listener con el elemento como this.
button.addEventListener("click", function (event) {
console.log(this === event.currentTarget); // true
});Con arrow function:
button.addEventListener("click", (event) => {
console.log(this === event.currentTarget); // Normalmente false.
});Para código claro, event.currentTarget suele expresar mejor la intención que depender de this.
Su valor cambia según el entorno.
En ciertas condiciones, el this superior puede ser window.
console.log(this); // undefinedEl this superior de un archivo no equivale necesariamente al objeto global.
No escribas lógica portable asumiendo que el this superior siempre es el global. Usa globalThis cuando necesitas una referencia estándar al objeto global.
function showThis() {
"use strict";
return this;
}
showThis(); // undefinedEn código no estricto, una llamada directa tradicional puede sustituir undefined o null por el objeto global y envolver algunos primitivos. Es un comportamiento histórico que no conviene usar como base de diseño.
Los módulos y métodos de clase son estrictos automáticamente.
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const user = {
name: "Nicolás",
};
greet.call(user, "Hello");call, apply y bind permiten controlar this para funciones tradicionales. Las arrows ignoran ese intento porque ya capturaron el valor léxico.
const firstUser = {
name: "Nicolás",
greet() {
return `Hello, ${this.name}`;
},
};
const secondUser = {
name: "Ana",
};
firstUser.greet.call(secondUser); // "Hello, Ana"La función no está unida permanentemente al primer objeto.
function calculateTotal(order) {
return order.items.reduce(
(total, item) => total + item.price,
0,
);
}Una función con dependencias explícitas puede ser más sencilla que un método dependiente de contexto.
Usa this cuando el comportamiento pertenece naturalmente a un receptor y el contrato de llamada es claro. No lo uses solo porque el código está dentro de un objeto.
const cart = {
items: [],
addItem(item) {
this.items.push(item);
},
calculateTotal() {
return this.items.reduce(
(total, item) => total + item.price * item.quantity,
0,
);
},
};Los métodos actúan sobre el estado del carrito mediante su receptor.
Si se pasan como callbacks, deben invocarse conservando ese receptor.
this como “el objeto donde se escribió la función”.this con scope léxico.this exterior.this superior como objeto global.this cuando un parámetro explícito sería más claro.this depende principalmente de la llamada.object.method() utiliza object como receptor.this en undefined en modo estricto.this léxicamente.new proporciona un objeto nuevo como this.call, apply y bind controlan this en funciones tradicionales.this y scope léxico son mecanismos distintos.this.¿Qué ocurre al ejecutar greet?
const user = {
name: "Nicolás",
greet() {
return `Hello, ${this.name}`;
},
};
const greet = user.greet;
greet();En módulos o modo estricto, this es undefined y acceder a this.name produce un TypeError. La función se llamó directamente, no como método de user.
call, apply y bind muestra cómo invocar una función con un receptor explícito o crear una versión ligada.