[Medium] 📄 this Binding
1. Cos'è this in JavaScript?
Cos'è
thisin JavaScript?
this è una parola chiave in JavaScript che punta all'oggetto del contesto di esecuzione di una funzione.
Il valore di this dipende da come la funzione viene chiamata, non da dove è definita.
Regole di binding di this
Ci sono quattro regole di binding per this in JavaScript (dalla priorità più alta alla più bassa):
- new binding: funzione chiamata con
new - binding esplicito:
call,applyobindimpostano esplicitamentethis - binding implicito: chiamata come metodo di un oggetto
- binding predefinito: comportamento di fallback in altri siti di chiamata
2. Spiega la differenza di this in contesti diversi
Spiega come si comporta
thisin contesti diversi.
1. this nel contesto globale
console.log(this); // browser: window, Node.js: global
function globalFunction() {
console.log(this); // non-strict: window/global, strict: undefined
}
globalFunction();
'use strict';
function strictFunction() {
console.log(this); // undefined
}
strictFunction();
2. this nelle funzioni regolari
Per le funzioni regolari, this dipende dal sito di chiamata:
function regularFunction() {
console.log(this);
}
// chiamata diretta
regularFunction(); // window (non-strict) o undefined (strict)
// chiamata come metodo
const obj = {
method: regularFunction,
};
obj.method(); // obj
// call/apply/bind
const customObj = { name: 'Custom' };
regularFunction.call(customObj); // customObj
3. this nelle arrow function
Le arrow function non hanno un proprio this.
Ereditano this dallo scope lessicale esterno.
const obj = {
name: 'Object',
// metodo regolare
regularMethod: function () {
console.log('regularMethod this:', this); // obj
// funzione regolare interna: this cambia
function innerRegular() {
console.log('innerRegular this:', this); // window/undefined
}
innerRegular();
// arrow function interna: this è ereditato
const innerArrow = () => {
console.log('innerArrow this:', this); // obj
};
innerArrow();
},
// arrow function come proprietà dell'oggetto
arrowMethod: () => {
console.log('arrowMethod this:', this); // window/scope lessicale globale
},
};
obj.regularMethod();
obj.arrowMethod();
4. this nei metodi degli oggetti
const person = {
name: 'John',
age: 30,
// funzione regolare: this -> person
greet: function () {
console.log(`Ciao, sono ${this.name}`); // "Ciao, sono John"
},
// abbreviazione metodo ES6: this -> person
introduce() {
console.log(`Sono ${this.name}, ${this.age} anni`);
},
// arrow function: this ereditato dallo scope esterno
arrowGreet: () => {
console.log(`Ciao, sono ${this.name}`); // solitamente undefined per name
},
};
person.greet();
person.introduce();
person.arrowGreet();
5. this nelle funzioni costruttore
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function () {
console.log(`Ciao, sono ${this.name}`);
};
}
const john = new Person('John', 30);
john.greet();
console.log(john.name); // "John"
6. this nelle classi
class Person {
constructor(name) {
this.name = name;
}
// metodo regolare: this -> istanza
greet() {
console.log(`Ciao, sono ${this.name}`);
}
// campo di classe con arrow function: this legato permanentemente all'istanza
arrowGreet = () => {
console.log(`Ciao, sono ${this.name}`);
};
}
const john = new Person('John');
john.greet(); // "Ciao, sono John"
// l'estrazione del metodo perde this
const greet = john.greet;
greet(); // errore in strict mode
// il campo arrow mantiene this
const arrowGreet = john.arrowGreet;
arrowGreet(); // "Ciao, sono John"
3. Quiz: Cosa verrà stampato?
Quiz: cosa stamperà il seguente codice?
Domanda 1: metodo dell'oggetto vs arrow function
const obj = {
name: 'Object',
regularFunc: function () {
console.log('A:', this.name);
},
arrowFunc: () => {
console.log('B:', this.name);
},
};
obj.regularFunc();
obj.arrowFunc();
Clicca per vedere la risposta
// A: Object
// B: undefined
Spiegazione:
regularFuncè chiamata comeobj.regularFunc(), quindithisèobjarrowFuncnon ha un propriothis; eredita ilthislessicale esterno/globale
Domanda 2: passare una funzione come argomento
const person = {
name: 'John',
greet: function () {
console.log(`Ciao, ${this.name}`);
},
};
person.greet(); // 1
const greet = person.greet;
greet(); // 2
setTimeout(person.greet, 1000); // 3
Clicca per vedere la risposta
// 1: "Ciao, John"
// 2: "Ciao, undefined" o errore in strict mode
// 3: "Ciao, undefined" o errore in strict mode
Spiegazione:
person.greet()-> binding implicito,thisèperson- Chiamata della funzione estratta ->
thisviene perso - Callback passata a
setTimeout->thisnon èperson
Domanda 3: funzioni annidate
const obj = {
name: 'Outer',
method: function () {
console.log('A:', this.name);
function inner() {
console.log('B:', this.name);
}
inner();
const arrow = () => {
console.log('C:', this.name);
};
arrow();
},
};
obj.method();
Clicca per vedere la risposta
// A: Outer
// B: undefined
// C: Outer
Spiegazione:
A:methodè chiamato daobjB:innerè una chiamata diretta regolareC: la arrow function eredita ilthisdimethodesterno
Domanda 4: setTimeout e arrow function
const obj = {
name: 'Object',
method1: function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
},
method2: function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
},
};
obj.method1();
obj.method2();
Clicca per vedere la risposta
// A: undefined
// B: Object
Spiegazione:
A: il callback regolare insetTimeoutperde il contesto del metodoB: il callback arrow ereditathisdamethod2
Domanda 5: binding complesso di this
const obj1 = {
name: 'obj1',
getThis: function () {
return this;
},
};
const obj2 = {
name: 'obj2',
};
console.log('A:', obj1.getThis().name);
const getThis = obj1.getThis;
console.log('B:', getThis() === window); // assunzione browser
obj2.getThis = obj1.getThis;
console.log('C:', obj2.getThis().name);
const boundGetThis = obj1.getThis.bind(obj2);
console.log('D:', boundGetThis().name);
Clicca per vedere la risposta
// A: obj1
// B: true
// C: obj2
// D: obj2
Spiegazione:
A: chiamato daobj1B: chiamata diretta usa il binding predefinito (window nel browser non-strict)C: chiamato daobj2D: legato esplicitamente conbind(obj2)
Domanda 6: costruttore e prototype
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Ciao, sono ${this.name}`);
};
Person.prototype.delayedGreet = function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
};
Person.prototype.arrowDelayedGreet = function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
};
const john = new Person('John');
john.delayedGreet();
john.arrowDelayedGreet();
Clicca per vedere la risposta
// A: undefined
// B: John
Spiegazione:
A: il callback regolare nel timeout usa il binding predefinito/globaleB: il callback arrow nel timeout eredita ilthisdell'istanza
Domanda 7: variabile globale vs proprietà dell'oggetto
var name = 'jjjj';
var obj = {
a: function () {
name = 'john';
console.log(this.name);
},
};
obj.a();
Clicca per vedere la risposta
// undefined
Spiegazione:
La chiave è la differenza tra variabili globali e proprietà dell'oggetto.
thisinobj.a()punta aobjname = 'john'(senza dichiarazione) aggiorna la variabile globalethis.nameleggeobj.nameobjnon ha una proprietàname, quindi èundefined
Flusso di esecuzione:
// iniziale
window.name = 'jjjj';
obj = {
a: function () {
/* ... */
},
// obj non ha proprietà name
};
obj.a();
↓
window.name = 'john'; // valore globale cambiato
this.name; // equivale a obj.name
obj.name; // undefined
Se vuoi 'john', assegna tramite this.name = 'john'.
var obj = {
a: function () {
this.name = 'john';
console.log(this.name); // 'john'
},
};
obj.a();
console.log(obj.name); // 'john'
Domanda 8: trappola della variabile globale (estesa)
var name = 'global';
var obj = {
name: 'object',
a: function () {
name = 'modified';
console.log('1:', name); // variabile globale
console.log('2:', this.name); // proprietà dell'oggetto
},
};
obj.a();
console.log('3:', name); // variabile globale
console.log('4:', obj.name); // proprietà dell'oggetto
Clicca per vedere la risposta
// 1: modified
// 2: object
// 3: modified
// 4: object
Punto chiave:
namesenzathis.-> variabile globalethis.name-> proprietà dell'oggetto- Sono valori diversi
4. Come preservare this nei callback?
Come preservare
thisdentro le funzioni callback?
Metodo 1: arrow function
const obj = {
name: 'Object',
method: function () {
setTimeout(() => {
console.log(this.name); // "Object"
}, 1000);
},
};
obj.method();
Metodo 2: bind()
const obj = {
name: 'Object',
method: function () {
setTimeout(
function () {
console.log(this.name); // "Object"
}.bind(this),
1000
);
},
};
obj.method();
Metodo 3: salvare this in una variabile (pattern legacy)
const obj = {
name: 'Object',
method: function () {
const self = this;
setTimeout(function () {
console.log(self.name); // "Object"
}, 1000);
},
};
obj.method();
Metodo 4: call() / apply()
function greet() {
console.log(`Ciao, sono ${this.name}`);
}
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
greet.call(person1); // "Ciao, sono John"
greet.apply(person2); // "Ciao, sono Jane"
5. Insidie comuni di this
Insidie comuni di
this
Insidia 1: estrazione del metodo dell'oggetto
const obj = {
name: 'Object',
greet: function () {
console.log(this.name);
},
};
obj.greet(); // ✅ "Object"
const greet = obj.greet;
greet(); // ❌ this perso
const boundGreet = obj.greet.bind(obj);
boundGreet(); // ✅ "Object"
Insidia 2: this nei listener di eventi
const button = document.querySelector('button');
const obj = {
name: 'Object',
// ❌ la arrow function qui non si lega al button
handleClick1: () => {
console.log(this); // window/scope lessicale globale
},
// ✅ la funzione regolare nel listener ottiene l'event target come this
handleClick2: function () {
console.log(this); // elemento button
},
// ✅ usa un wrapper arrow quando serve il this dell'oggetto dentro il callback
handleClick3: function () {
button.addEventListener('click', () => {
console.log(this.name); // "Object"
});
},
};
Insidia 3: callback nei metodi degli array
const obj = {
name: 'Object',
items: [1, 2, 3],
// ❌ il callback regolare perde this
processItems1: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
});
},
// ✅ il callback arrow mantiene il this lessicale
processItems2: function () {
this.items.forEach((item) => {
console.log(this.name, item);
});
},
// ✅ usa thisArg
processItems3: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
}, this);
},
};
6. Riepilogo delle regole di binding di this
Riepilogo delle regole di binding di
this
Priorità (alta -> bassa)
// 1. new binding (più alta)
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // "John"
// 2. binding esplicito (call/apply/bind)
function greet() {
console.log(this.name);
}
const obj = { name: 'Object' };
greet.call(obj); // "Object"
// 3. binding implicito (metodo dell'oggetto)
const obj2 = {
name: 'Object2',
greet: greet,
};
obj2.greet(); // "Object2"
// 4. binding predefinito (più bassa)
greet(); // undefined (strict) o nome globale (non-strict)
Funzione vs Arrow Function
| Caratteristica | Funzione | Arrow Function |
|---|---|---|
Ha il proprio this | ✅ Sì | ❌ No |
this determinato da | Sito di chiamata | Scope lessicale di definizione |
call/apply/bind possono cambiare this | ✅ Sì | ❌ No |
| Può essere costruttore | ✅ Sì | ❌ No |
Ha arguments | ✅ Sì | ❌ No |
| Migliore per | Metodi di oggetti, costruttori | Callback, this esterno ereditato |
Frase mnemonica
"Arrow eredita, function dipende dalla chiamata."
- Arrow function:
thisè ereditato dallo scope lessicale esterno- Funzione regolare:
thisè deciso a runtime dal sito di chiamata
7. Best practice
Best practice
✅ Raccomandato
// 1. Usa funzione regolare o abbreviazione di metodo per i metodi degli oggetti
const obj = {
name: 'Object',
// ✅ buono
greet: function () {
console.log(this.name);
},
// ✅ buono
introduce() {
console.log(this.name);
},
};
// 2. Usa arrow function per i callback che dovrebbero mantenere il this esterno
class Component {
constructor() {
this.name = 'Component';
}
mount() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
}
// 3. Usa funzione regolare quando serve un this dinamico
const button = {
label: 'Cliccami',
handleClick: function () {
console.log(this); // event target / oggetto ricevente
},
};
❌ Non raccomandato
// 1. Evita arrow function come metodi degli oggetti
const obj = {
name: 'Object',
greet: () => {
console.log(this.name); // undefined nella maggior parte dei casi
},
};
// 2. Evita arrow function come costruttore
const Person = (name) => {
this.name = name; // sbagliato
};
// 3. Evita arrow quando serve l'oggetto arguments
const sum = () => {
console.log(arguments); // ReferenceError
};