web-dev-qa-db-fra.com

Comment le mot-clé "this" fonctionne-t-il dans une fonction?

Je viens de rencontrer une situation intéressante en JavaScript. J'ai une classe avec une méthode qui définit plusieurs objets en utilisant la notation objet-littéral. À l'intérieur de ces objets, le pointeur this est utilisé. D'après le comportement du programme, j'ai déduit que le pointeur this faisait référence à la classe sur laquelle la méthode était invoquée et non à l'objet créé par le littéral.

Cela semble arbitraire, bien que ce soit la façon dont je m'attendrais à ce que cela fonctionne. Est-ce que ce comportement est défini? Est-ce sécurisé entre navigateurs? Y a-t-il un raisonnement sous-jacent à la raison pour laquelle c'est au-delà de "la spécification le dit" (par exemple, est-ce une conséquence d'une décision/philosophie de conception plus large)? Exemple de code réduit:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}
246
rmeador

Cannibalisé depuis un autre de mes posts, voici plus que ce que vous avez toujours voulu savoir sur this.

Avant de commencer, voici la chose la plus importante à garder à l'esprit à propos de Javascript et à vous répéter quand cela ne vous dit rien. Javascript n'a pas de classes (ES6 class est sucre syntaxique ). Si quelque chose ressemble à une classe, c'est une astuce intelligente. Javascript a des objets et des fonctions . (ce n'est pas précis à 100%, les fonctions ne sont que des objets, mais il peut parfois être utile de les considérer comme des choses séparées)

La variable this est attachée à des fonctions. Chaque fois que vous appelez une fonction, this reçoit une certaine valeur, en fonction de la manière dont vous appelez la fonction. Cela s'appelle souvent le modèle d'invocation.

Il existe quatre façons d’appeler des fonctions en javascript. Vous pouvez appeler la fonction en tant que méthode, en tant que fonction, en tant que constructeur, et avec appliquer.

Comme méthode

Une méthode est une fonction attachée à un objet

var foo = {};
foo.someMethod = function(){
    alert(this);
}

Lorsque invoqué en tant que méthode, this sera lié à l'objet dont la fonction/méthode fait partie. Dans cet exemple, cela sera lié à foo.

En tant que fonction

Si vous avez une fonction autonome, la variable this sera liée à l'objet "global", presque toujours l'objet window dans le contexte d'un navigateur .

 var foo = function(){
    alert(this);
 }
 foo();

C'est peut-être ce qui vous fait trébucher , mais ne vous en faites pas. Beaucoup de gens considèrent cela comme une mauvaise décision de conception. Étant donné qu'un rappel est appelé en tant que fonction et non en tant que méthode, vous voyez ce qui semble être un comportement incohérent.

Beaucoup de gens contournent le problème en faisant quelque chose comme, euh, ceci

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Vous définissez une variable ça qui pointe vers ça. La fermeture (un sujet qui lui est propre) conserve ça autour, donc si vous appelez barre comme rappel, il a toujours une référence.

NOTE: Dans use strict mode si utilisé comme fonction, this n'est pas lié à global. (C'est undefined).

En tant que constructeur

Vous pouvez également appeler une fonction en tant que constructeur. Selon la convention de dénomination que vous utilisez (TestObject), ceci aussi peut être ce que vous êtes en train de faire et c'est ce qui vous a déclenché .

Vous appelez une fonction en tant que constructeur avec le nouveau mot-clé.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

Lorsqu'il est appelé en tant que constructeur, un nouvel objet sera créé et this sera lié à cet objet. Encore une fois, si vous avez des fonctions internes et qu'elles sont utilisées en tant que rappels, vous les invoquerez en tant que fonctions, et this sera lié à l'objet global. Utilisez cette variable qui = cette astuce/motif.

Certaines personnes pensent que le mot-clé constructeur/nouveau était un os jeté aux programmeurs Java/traditionnel OOP comme moyen de créer quelque chose de similaire aux classes.

Avec la méthode Apply

Enfin, chaque fonction a une méthode (oui, les fonctions sont des objets en Javascript) nommée "apply". Appliquer vous permet de déterminer la valeur de this et vous permet également de transmettre un tableau d'arguments. Voici un exemple inutile.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
557
Alan Storm

Appels de fonction

Les fonctions ne sont qu'un type d'objet.

Tous les objets Function ont les méthodes call et apply qui exécutent l'objet Function sur lequel ils ont été appelés.

Lorsqu'il est appelé, le premier argument de ces méthodes spécifie l'objet qui sera référencé par le mot clé this lors de l'exécution de la fonction - s'il s'agit de null ou undefined, l'objet global , window, est utilisé pour this.

Ainsi, appeler une fonction ...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... avec des parenthèses - foo() - équivaut à foo.call(undefined) ou foo.apply(undefined), qui est effectivement identique à foo.call(window) ou foo.apply(window).

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Des arguments supplémentaires à call sont passés en tant qu'arguments à l'appel de fonction, alors qu'un seul argument supplémentaire à apply peut spécifier les arguments de l'appel de fonction en tant qu'objet de type tableau.

Ainsi, foo(1, 2, 3) équivaut à foo.call(null, 1, 2, 3) ou foo.apply(null, [1, 2, 3]).

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Si une fonction est une propriété d'un objet ...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... accéder à une référence à la fonction via l'objet et l'appeler avec des parenthèses - obj.foo() - équivaut à foo.call(obj) ou foo.apply(obj).

Cependant, les fonctions détenues en tant que propriétés d'objets ne sont pas "liées" à ces objets. Comme vous pouvez le voir dans la définition de obj ci-dessus, les fonctions n'étant qu'un type d'objet, elles peuvent être référencées (et peuvent donc être transmises par référence à un appel de fonction ou renvoyées par référence d'un appel de fonction) . Lorsqu'une référence à une fonction est transmise, aucune information supplémentaire sur l'endroit où elle a été transmise à partir de n'est transmise, raison pour laquelle:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

L'appel à notre référence de fonction, baz, ne fournit aucun contexte pour l'appel, il est donc identique à baz.call(undefined), donc this finit par référencer window. Si nous voulons que baz sache qu'il appartient à obj, nous devons en quelque sorte fournir ces informations lorsque baz est appelé, c'est là que le premier argument de call ou apply et les fermetures entrent en jeu.

Chaînes de portée

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Lorsqu'une fonction est exécutée, elle crée une nouvelle étendue et fait référence à toute étendue englobante. Lorsque la fonction anonyme est créée dans l'exemple ci-dessus, elle fait référence à la portée dans laquelle elle a été créée, qui est la portée de bind. Ceci est connu comme une "fermeture".

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

Lorsque vous essayez d'accéder à une variable, cette "chaîne d'étendue" recherche une variable portant le nom indiqué. Si l'étendue actuelle ne contient pas la variable, vous examinez l'étendue suivante de la chaîne, et ainsi de suite jusqu'à ce que vous atteigniez la portée globale. Lorsque la fonction anonyme est renvoyée et que bind est terminée, la fonction anonyme a toujours une référence à la portée de bind, de sorte que la portée de bind ne disparaît pas. ".

Compte tenu de tout ce qui précède, vous devriez maintenant être en mesure de comprendre le fonctionnement de scope dans l'exemple suivant, et pourquoi la technique permettant de faire passer une fonction autour de "pre-bound" avec une valeur particulière de this appelé œuvres:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
35
Jonny Buchanan

Est-ce que ce comportement est défini? Est-ce sécurisé entre navigateurs?

Oui. Et oui.

Y a-t-il un raisonnement sous-jacent à la raison pour laquelle c'est ainsi ...

La signification de this est assez simple à déduire:

  1. Si this est utilisé dans une fonction constructeur et que la fonction a été appelée avec le mot clé new, this fait référence à l'objet qui sera créé. this continuera à désigner l'objet même dans les méthodes publiques.
  2. Si this est utilisé n'importe où ailleurs, y compris dans les fonctions imbriquées protected, il fait référence à la portée globale (qui, dans le cas du navigateur, est l'objet window).

Le second cas est évidemment un défaut de conception, mais il est assez facile de contourner ce problème en utilisant des fermetures.

9
Rakesh Pai

Dans ce cas, le this interne est lié à l'objet global au lieu de la variable this de la fonction externe. C'est la façon dont la langue est conçue.

Voir "JavaScript: The Good Parts" de Douglas Crockford pour une bonne explication.

4
Santiago Cepas

J'ai trouvé un bon tutoriel sur l'ECMAScript this

A cette valeur est un objet spécial lié au contexte d'exécution. Par conséquent, il peut être nommé en tant qu’objet de contexte (c’est-à-dire un objet dans lequel le contexte d’exécution est activé).

N'importe quel objet peut être utilisé comme valeur du contexte.

a cette valeur est une propriété du contexte d'exécution, mais pas une propriété de l'objet variable.

Cette fonctionnalité est très importante car, contrairement aux variables, cette valeur ne participe jamais au processus de résolution d'identifiant. C'est à dire. lors de l'accès à cela dans un code, sa valeur est extraite directement du contexte d'exécution et sans aucune recherche de chaîne d'étendue. La valeur de this est déterminée une seule fois lors de la saisie du contexte.

Dans le contexte global, a cette valeur est l’objet global lui-même (c’est-à-dire que cette valeur est égale ici à un objet variable).

Dans le cas d'un contexte de fonction, cette valeur dans chaque appel de fonction peut être différente

Référence Javascript-the-core et Chapter-3-this

4
Damodaran