Déterminer la valeur de this
peut être délicat en JavaScript. Voici comment procéder :
La this
de JavaScript est l'objet de nombreuses blagues, car elle est assez complexe.
Toutefois, j'ai vu des développeurs effectuer des tâches beaucoup plus complexes et propres au domaine pour éviter de gérer cette this
. J'espère que ces informations vous seront utiles.this
Voici mon guide this
.
Je vais commencer par la situation la plus spécifique, et finir par la moins spécifique. Cet article est un peu comme un grand if (…) … else if () … else if (…) …
. Vous pouvez donc passer directement à la première section correspondant au code que vous consultez.
- Si la fonction est définie comme une fonction flèche
- Sinon, si la fonction/classe est appelée avec
new
- Sinon, si la fonction possède une valeur
this
"liée" - Sinon, si
this
est défini sur "call-time" - 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èche:
const arrowFunction = () => {
console.log(this);
};
Dans ce cas, la valeur de this
est toujours identique à 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 très utiles, car la valeur interne de this
ne peut pas être modifiée. Elle est toujours la même que l'this
externe.
Autres exemples
Avec les fonctions flèche, 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 est d'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 des composants React ou des composants Web).
Il peut sembler que le code ci-dessus ne respecte pas la règle "this
sera identique à this
dans le champ parent", mais cela commence à avoir du sens 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, attribuer des 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();
La commande ci-dessus appelle Whatever
(ou sa fonction de 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 plus anciens:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Autres exemples
Lorsqu'il est appelé 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 possède une valeur this
"liée" :
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Chaque fois que boundFunction
est appelé, sa valeur this
est l'objet transmis à bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Autres exemples
Lorsque vous appelez 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
est 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, et 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);
});
J'évite d'utiliser this
dans les cas ci-dessus et je fais plutôt ce qui suit:
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 faux, car someMethod
n'est pas appelé en tant que membre de obj
. Vous avez peut-être rencontré ce problème lorsque vous avez essayé 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'elle soit un nœud DOM, et ce qui précède interrompt cette connexion. Pour y parvenir correctement:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Fait amusant: toutes les API n'utilisent pas this
en interne. Les méthodes de 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 que 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 !
Et voilà ! C'est tout ce que je sais sur this
. Des questions ? Ai-je manqué quelque chose ? N'hésitez pas à m'envoyer un tweet.
Merci à Mathias Bynens, Ingvar Stepanyan et Thomas Steiner pour leur examen.