Il peut être difficile de déterminer la valeur de this
en JavaScript. Voici comment procéder...
this
de JavaScript est le but de nombreuses blagues, et c'est parce que c'est assez compliqué.
Cependant, j'ai vu des développeurs effectuer des tâches beaucoup plus complexes et spécifiques à un domaine pour éviter de gérer cette this
. Nous espérons que ces informations vous seront utiles si vous n'êtes pas sûr de this
. Ceci est mon guide this
.
Je vais commencer par la situation la plus spécifique et terminer par la moins spécifique. Cet article ressemble à un gros if (…) … else if () … else if (…) …
. Vous pouvez donc accéder directement à la première section qui correspond au code que vous examinez.
- Si la fonction est définie comme une fonction fléchée
- Sinon, si la fonction/classe est appelée avec
new
- Sinon, si la fonction a une valeur
this
"limitée" - Sinon, si
this
est défini au moment de l'appel - Sinon, si la fonction est appelée via un objet parent (
parent.func()
) - Sinon, si la fonction ou le champ d'application parent est en mode strict
- Sinon
Si la fonction est définie comme une fonction fléchée:
const arrowFunction = () => {
console.log(this);
};
Dans ce cas, la valeur de this
est toujours la même que celle de this
dans le champ d'application parent:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Les fonctions fléchées sont utiles, car la valeur interne de this
ne peut pas être modifiée. Elle est toujours identique à la valeur this
externe.
Autres exemples
Avec les fonctions fléchées, la valeur de this
ne peut pas être modifiée avec bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Avec les fonctions fléchées, la valeur de this
ne peut pas être modifiée avec call
ou apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
Avec les fonctions fléchées, la valeur de this
ne peut pas être modifiée en appelant la fonction en tant que membre d'un autre objet:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Avec les fonctions fléchées, la valeur de this
ne peut pas être modifiée en appelant la fonction en tant que constructeur:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
Méthodes d'instance "liées"
Avec les méthodes d'instance, si vous souhaitez vous assurer que this
fait toujours référence à l'instance de classe, le meilleur moyen consiste à utiliser des fonctions fléchées et des champs de classe:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
Ce modèle est très utile lorsque vous utilisez des méthodes d'instance en tant qu'écouteurs d'événements dans des composants (tels que les composants React ou Web).
Ce qui précède peut sembler entraver la règle "this
sera identique à this
dans le champ d'application parent", mais cela commence à être logique si vous considérez les champs de classe comme du sucre syntaxique pour définir des éléments dans le constructeur:
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);
};
}
}
D'autres modèles impliquent de lier une fonction existante dans le constructeur ou d'attribuer la fonction dans le constructeur. Si vous ne pouvez pas utiliser de champs de classe pour une raison quelconque, l'attribution de fonctions dans le constructeur est une alternative raisonnable:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
Sinon, si la fonction/classe est appelée avec new
:
new Whatever();
Ce qui précède appelle Whatever
(ou sa fonction constructeur s'il s'agit d'une classe) avec this
défini sur le résultat de Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
Il en va de même pour les constructeurs de style plus ancien:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Autres exemples
Lorsqu'elle est appelée avec new
, la valeur de this
ne peut pas être modifiée avec bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Lorsqu'elle est appelée avec new
, la valeur de this
ne peut pas être modifiée en appelant la fonction en tant que membre d'un autre objet:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Sinon, si la fonction a une valeur this
"limitée" :
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Chaque fois que boundFunction
est appelé, sa valeur this
correspond à l'objet transmis à bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Autres exemples
Lors de l'appel d'une fonction liée, la valeur de this
ne peut pas être modifiée avec call
ou 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);
Lorsque vous appelez une fonction liée, la valeur de this
ne peut pas être modifiée en appelant la fonction en tant que membre d'un autre objet:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Sinon, si this
est défini au moment de l'appel:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
La valeur de this
correspond à l'objet transmis à call
/apply
.
Malheureusement, this
est défini sur une autre valeur par des éléments tels que les écouteurs d'événements DOM. Son utilisation peut entraîner un code difficile à comprendre:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
Dans les cas ci-dessus, j'évite d'utiliser this
. À la place:
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); });
Sinon, si la fonction est appelée via un objet parent (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
Dans ce cas, la fonction est appelée en tant que membre de obj
. this
sera donc obj
. Cela se produit au moment de l'appel. Le lien est donc rompu si la fonction est appelée sans son objet parent ou avec un objet parent différent:
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
est "false", car someMethod
n'est pas appelé en tant que membre de obj
. Vous avez peut-être rencontré ce piège en essayant quelque chose comme ceci:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Cela ne fonctionne pas, car l'implémentation de querySelector
examine sa propre valeur this
et s'attend à ce qu'il s'agisse d'un nœud DOM. Ce qui précède rompt cette connexion. Pour effectuer correctement ce qui précède:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Fait intéressant: toutes les API n'utilisent pas this
en interne. Les méthodes de la console telles que console.log
ont été modifiées pour éviter les références this
. log
n'a donc pas besoin d'être lié à console
.
Sinon, si la fonction ou le champ d'application parent est en mode strict:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
Dans ce cas, la valeur de this
n'est pas définie. 'use strict'
n'est pas nécessaire dans la fonction si le champ d'application parent est en mode strict (et si tous les modules sont en mode strict).
Sinon :
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
Dans ce cas, la valeur de this
est identique à celle de globalThis
.
Ouf !
Voilà, c'est terminé ! C'est tout ce que je sais sur this
. Des questions ? Quelque chose que j'ai manqué ? N'hésitez pas à me tweeter.
Merci à Mathias Bynens, Ingvar Stepanyan et Thomas Steiner pour leurs commentaires.