Capire il valore di this
può essere difficile in JavaScript, ecco come fare...
L'this
di JavaScript è il frutto di molte battute il motivo è che è piuttosto complicato.
Tuttavia, ho notato che gli sviluppatori fanno cose molto più complicate e specifiche del dominio per evitare di avere a che fare con questo this
. Se hai dubbi su this
, speriamo che queste informazioni ti siano di aiuto. Questa è la mia guida di this
.
Inizierò con la situazione più specifica, per finire con quella meno specifica. Questo articolo è un po' come un if (…) … else if () … else if (…) …
grande, quindi puoi andare direttamente alla prima sezione che corrisponde al codice che stai guardando.
- Se la funzione è definita come una funzione a forma di freccia
- In caso contrario, se la funzione/classe viene chiamata con
new
- In caso contrario, se la funzione ha un valore
this
"bound" - Altrimenti, se
this
è impostato al momento della chiamata - In caso contrario, se la funzione viene chiamata tramite un oggetto padre (
parent.func()
) - Altrimenti, se l'ambito funzione o principale è in modalità con restrizioni
- In caso contrario
Se la funzione è definita come una funzione a freccia:
const arrowFunction = () => {
console.log(this);
};
In questo caso, il valore di this
è sempre uguale a this
nell'ambito principale:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Le funzioni a freccia sono molto utili perché il valore interno di this
non può essere modificato, ed è sempre uguale al valore this
esterno.
Altri esempi
Con le funzioni a freccia, il valore di this
non può essere modificato con bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Con le funzioni a freccia, il valore di this
non può essere modificato 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 le funzioni a freccia, il valore di this
non può essere modificato chiamando la funzione come membro di un altro oggetto:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Con le funzioni a freccia, il valore di this
non può essere modificato chiamando la funzione come costruttore:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Metodi di istanza "associati"
Con i metodi di istanza, se vuoi assicurarti che this
faccia sempre riferimento all'istanza della classe, il modo migliore è utilizzare le funzioni a freccia e i campi della classe:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Questo pattern è davvero utile quando si utilizzano metodi di istanza come listener di eventi nei componenti (ad esempio componenti di reazione o componenti web).
Quanto riportato sopra potrebbe sembrare che violi la regola "this
è uguale a this
nell'ambito padre", ma inizia ad avere senso se pensi ai campi di classe come allo zucchero sintattico per impostare cose nel costruttore:
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);
};
}
}
I brevetti alternativi prevedono l'associazione di una funzione esistente nel costruttore o l'assegnazione della funzione nel costruttore. Se per qualche motivo non puoi utilizzare i campi della classe, l'assegnazione di funzioni nel costruttore è un'alternativa ragionevole:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
Altrimenti, se la funzione/classe viene chiamata con new
:
new Whatever();
Quanto sopra chiamerà Whatever
(o la sua funzione di costruttore se è una classe) con this
impostato sul
risultato di Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
Lo stesso vale per i costruttori meno recenti:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Altri esempi
Quando viene chiamato con new
, il valore di this
non può essere modificato con bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Quando viene chiamato con new
, il valore di this
non può essere modificato chiamando la funzione come membro di un altro oggetto:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Altrimenti, se la funzione ha un valore this
"associato":
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Ogni volta che boundFunction
viene chiamato, il suo valore this
sarà l'oggetto passato a bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Altri esempi
Quando chiami una funzione associata, il valore di this
non può essere modificato 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);
Quando si chiama una funzione associata, il valore di this
non può essere modificato chiamando la funzione come membro di un altro oggetto:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Altrimenti, se this
è impostato al momento della chiamata:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
Il valore di this
è l'oggetto passato a call
/apply
.
Purtroppo il valore this
è impostato su un altro valore da elementi come i listener di eventi DOM e il suo utilizzo può causare codice difficile da comprendere:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
Nei casi come sopra, evito di utilizzare this
, ma invece:
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); });
Altrimenti, se la funzione viene chiamata tramite un oggetto padre (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
In questo caso la funzione viene chiamata come membro di obj
, quindi this
sarà obj
. Questo accade in fase di chiamata, quindi il collegamento viene interrotto se la funzione viene chiamata senza l'oggetto padre o con un oggetto padre diverso:
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
è falso perché someMethod
non è chiamato come membro di obj
. Potresti
riscontrare questo recupero quando provi qualcosa del genere:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Questo comando interrompe perché l'implementazione di querySelector
esamina il proprio valore this
e prevede che sia una sorta di nodo DOM. Quanto riportato sopra, interrompe la connessione. Per eseguire correttamente questi passaggi:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Curiosità: non tutte le API utilizzano this
internamente. I metodi della console come console.log
sono stati modificati per evitare i riferimenti a this
, quindi non è necessario associare log
a console
.
Altrimenti, se la funzione o l'ambito principale è in modalità con restrizioni:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
In questo caso, il valore di this
non è definito. 'use strict'
non è necessario nella funzione se l'ambito padre è in modalità rigida (e tutti i moduli sono in modalità con restrizioni).
Altrimenti:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
In questo caso, il valore di this
è uguale a globalThis
.
Finalmente.
e il gioco è fatto. Questo è tutto quello che so su this
. Domande? Mi è sfuggito qualcosa? Non esitare a inviarmi un tweet.
Ringraziamo Mathias Bynens, Ingvar Stepanyan e Thomas Steiner per la revisione.