Comment fonctionne ce mot-clé dans une fonction?

248

Je viens de découvrir une situation intéressante en JavaScript. J'ai une classe avec une méthode qui définit plusieurs objets en utilisant la notation littérale objet. À l'intérieur de ces objets, le thispointeur est utilisé. Du comportement du programme, j'ai déduit que le thispointeur fait référence à la classe sur laquelle la méthode a été 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 un comportement défini? Est-il sûr pour tous les navigateurs? Y a-t-il un raisonnement sous-jacent expliquant pourquoi cela se situe 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 simplifié:

// 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
}
rmeador
la source
ça arrive quand je fais ça aussi var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons
consultez ce post . Possède une bonne explication de divers usages et comportements de ce mot clé.
Love Hasija
consultez
AL-zami

Réponses:

558

Cannibalisé d'un autre poste à moi, voici plus que vous ne l'avez jamais voulu savoir à ce sujet .

Avant de commencer, voici la chose la plus importante à garder à l'esprit à propos de Javascript et à répéter lorsque cela n'a pas de sens. Javascript n'a pas de classes (ES6 classest du sucre syntaxique ). Si quelque chose ressemble à une classe, c'est une astuce astucieuse. 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 distinctes)

La variable this est attachée aux fonctions. Chaque fois que vous appelez une fonction, celle -ci reçoit une certaine valeur, selon la façon dont vous appelez la fonction. Ceci est souvent appelé le modèle d'invocation.

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

Comme méthode

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

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

Lorsqu'elle est invoquée en tant que méthode, elle sera liée à 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 sentez pas mal. Beaucoup de gens considèrent cela comme une mauvaise décision de conception. Puisqu'un rappel est invoqué en tant que fonction et non en tant que méthode, c'est pourquoi vous voyez ce qui semble être un comportement incohérent.

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

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

Vous définissez une variable qui qui pointe vers ce . La fermeture (un sujet qui lui est propre) conserve cela , donc si vous appelez la barre en tant que rappel, elle a toujours une référence.

REMARQUE: en use strictmode si utilisé comme fonction, thisn'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), cela peut également être ce que vous faites et c'est ce qui vous fait trébucher .

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 invoqué comme constructeur, un nouvel objet sera créé, et ce sera lié à cet objet. Encore une fois, si vous avez des fonctions internes et qu'elles sont utilisées comme rappels, vous les invoquerez en tant que fonctions, et cela sera lié à l'objet global. Utilisez ce var qui = cette astuce / modèle.

Certaines personnes pensent que le constructeur / nouveau mot-clé était un os jeté aux programmeurs Java / OOP traditionnels comme un 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 "appliquer". Appliquer vous permet de déterminer la valeur de cette valeur et vous permet également de passer 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);
Alan Storm
la source
8
Remarque: En mode strict , ce thissera undefinedpour les invocations de fonctions.
Miscreant
1
Une déclaration de fonction, par exemple. La fonction myfunction () {}, est un cas particulier de "as a method" où "this" est la portée globale (fenêtre).
richard
1
@richard: Sauf en mode strict, et thisn'a rien à voir avec la portée. Vous voulez dire l' objet global .
TJ Crowder
@ alan-storm. Dans le cas de "En tant que constructeur" est this.confusing = 'hell yeah';le même que var confusing = 'hell yeah';? Alors les deux permettront myObject.confusing? Ce serait bien si ce n'est pas seulement pour que vous puissiez utiliser thispour créer les propriétés et autres variables pour le travail interne.
2017 à 16h23
Mais là encore , je suppose que les choses de travail peut se faire en dehors de la fonction et la valeur transmise par au constructeur: function Foo(thought){ this.confusing = thought; }puisvar myObject = new Foo("hell yeah");
wunth
35

Appels de fonction

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

Tous les objets Function ont des méthodes d' appel et d' application qui exécutent l'objet Function auquel ils sont appelés.

Lorsqu'il est appelé, le premier argument de ces méthodes spécifie l'objet qui sera référencé par le thismot clé lors de l'exécution de la fonction - si c'est nullou 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()- est équivalent à foo.call(undefined)ou foo.apply(undefined), ce qui est en fait 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 à callsont passés comme arguments à l'appel de fonction, tandis qu'un seul argument supplémentaire à applypeut spécifier les arguments de l'appel de fonction en tant qu'objet de type tableau.

Ainsi, foo(1, 2, 3)est équivalent à 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 entre parenthèses - obj.foo()- est équivalent à foo.call(obj)ou foo.apply(obj).

Cependant, les fonctions détenues comme propriétés d'objets ne sont pas "liées" à ces objets. Comme vous pouvez le voir dans la définition objci-dessus, puisque les fonctions ne sont qu'un type d'objet, elles peuvent être référencées (et peuvent donc être passées par référence à un appel de fonction ou renvoyées par référence à partir d'un appel de fonction). Lorsqu'une référence à une fonction est passée, aucune autre information sur l' endroit où il a été passé de se fait avec elle, ce qui explique pourquoi les éléments suivants se produit:

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

L'appel à notre référence Function baz, ne fournit aucun contexte pour l'appel, il est donc en fait le même que baz.call(undefined), donc thisfinit par faire référence window. Si nous voulons bazsavoir à quoi il appartient obj, nous devons d'une manière ou d'une autre fournir ces informations lors de l' bazappel, c'est là que le premier argument de callou applyet 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 a une référence à toute étendue englobante. Lorsque la fonction anonyme est créée dans l'exemple ci-dessus, elle fait référence à l'étendue dans laquelle elle a été créée, qui est bindl'étendue de. C'est ce qu'on appelle 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 de portée" est parcourue pour trouver une variable avec le nom donné - si la portée actuelle ne contient pas la variable, vous regardez la portée suivante de la chaîne, et ainsi de suite jusqu'à ce que vous atteigniez la portée mondiale. Lorsque la fonction anonyme est renvoyée et bindtermine son exécution, la fonction anonyme a toujours une référence à bindla portée de ', donc bindla portée de' ne disparaît pas '.

Compte tenu de tout ce qui précède, vous devriez maintenant être en mesure de comprendre comment fonctionne la portée dans l'exemple suivant, et pourquoi la technique pour passer une fonction autour de "pré-lié" avec une valeur particulière thisaura quand elle sera appelée fonctionne:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
Jonny Buchanan
la source
"Lorsqu'une référence à une fonction est transmise, aucune information supplémentaire sur son origine n'est transmise avec elle" merci @insin pour cela.
Alex Marandon
9

Est-ce un comportement défini? Est-il sûr pour tous les navigateurs?

Oui. Et oui.

Y a-t-il un raisonnement qui explique pourquoi il en est ainsi ...

Le sens de thisest assez simple à déduire:

  1. Si thisest utilisé dans une fonction constructeur et que la fonction a été invoquée avec le newmot clé, thisfait référence à l'objet qui sera créé. thiscontinuera à signifier l'objet même dans les méthodes publiques.
  2. Si thisest utilisé ailleurs, y compris les fonctions protégées imbriquées , il se réfère à la portée globale (qui dans le cas du navigateur est l'objet fenêtre).

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

Rakesh Pai
la source
4

Dans ce cas, l'intérieur thisest lié à l'objet global au lieu de la thisvariable de la fonction extérieure. C'est la façon dont la langue est conçue.

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

Santiago Cepas
la source
4

J'ai trouvé un joli tutoriel sur l' ECMAScript ce

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

Tout objet peut être utilisé comme cette 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 des identifiants. C'est-à-dire lorsque vous accédez à cela dans un code, sa valeur est prise directement du contexte d'exécution et sans aucune recherche de chaîne de portée. La valeur de ceci n'est déterminée qu'une seule fois lors de la saisie du contexte.

Dans le contexte global, cette valeur est l'objet global lui-même (cela signifie que cette valeur est ici égale à l'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

Damodaran
la source
" Dans le contexte global, une cette valeur est l'objet global lui-même (cela signifie que cette valeur est ici égale à l'objet variable) ". L' objet global fait partie du contexte d'exécution global, tout comme le (es4) «objet variable» et l'enregistrement d'environnement ES5. Mais ce sont des entités différentes de l'objet global (par exemple, un enregistrement d'environnement ne peut pas être référencé directement, il est interdit par la spécification, mais l'objet global peut l'être).
RobG