Comment fonctionne cette syntaxe JavaScript / jQuery: (function (window, undefined) {}) (window)?

153

Avez-vous déjà jeté un œil sous le capot sur le code source de jQuery 1.4 et remarqué comment il est encapsulé de la manière suivante:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

J'ai lu un article sur l' espacement de noms JavaScript et un autre intitulé " Une paire importante de parenthèses ", donc j'en sais un peu sur ce qui se passe ici.

Mais je n'ai jamais vu cette syntaxe particulière auparavant. Qu'est-ce que ça undefinedfait là-bas? Et pourquoi windowfaut-il passer et ensuite réapparaître à la fin?

Dkinzer
la source
3
Je voulais ajouter que Paul Irish en parle dans cette vidéo: paulirish.com/2010/10-things-i-learned-from-the-jquery-source
Mottie
@Bergi vous avez marqué ma question comme un duplicata d'une autre mais j'ai posé la question presque un an avant le duplicata. Le casting devrait être l'inverse.
dkinzer le
@dkinzer: Il ne s'agit pas d'être demandé plus tôt, mais de répondre de la plus haute qualité. Certes, dans ce cas, c'est au coude à coude, mais j'ai trouvé que la réponse de CMS était la meilleure. Venez sur chat.stackoverflow.com/rooms/17 pour en discuter, cependant
Bergi

Réponses:

161

L'indéfini est une variable normale et peut être modifié simplement avec undefined = "new value";. Ainsi, jQuery crée une variable locale "non définie" qui est VRAIMENT indéfinie.

La variable window est rendue locale pour des raisons de performances. Parce que lorsque JavaScript recherche une variable, il passe d'abord par les variables locales jusqu'à ce qu'il trouve le nom de la variable. Lorsqu'il n'est pas trouvé, JavaScript passe par la portée suivante, etc. jusqu'à ce qu'il filtre les variables globales. Donc, si la variable window est rendue locale, JavaScript peut la rechercher plus rapidement. Plus d'informations: Accélérez votre JavaScript - Nicholas C. Zakas

Vincent
la source
1
Merci! c'était une vidéo très informative. Je pense que vous devez modifier votre réponse pour dire "la variable de fenêtre est rendue locale". Je pense que c'est la meilleure réponse. J'ai compté et trouvé que l'objet window est appelé au moins 17 fois dans le code source JQuery. Le déplacement de la fenêtre dans la portée locale doit donc avoir un effet significatif.
dkinzer
@DKinzer Oh oui, vous avez raison, bien sûr, c'est fait local. Heureux de pouvoir vous aider =)
Vincent
1
Selon ce test mis à jour jsperf.com/short-scope/5, le passage de 'window' est en fait plus lent, lorsqu'il s'agit d'obtenir une constante de fenêtre via jQuery, et particulièrement mauvais pour Safari. Ainsi, bien que «undefined» ait un cas d'utilisation, je ne suis pas tout à fait convaincu que «window» soit particulièrement utile dans tous les cas.
hexalys
1
Cela a également un effet positif sur la réduction du code. Ainsi, chaque utilisation de "window" et "undefined" peut être remplacée par un nom plus court par le minifyer.
TLindig
2
@ lukas.pukenis Lorsque vous créez une bibliothèque qui est utilisée dans des millions de sites Web, il est sage de ne pas faire d'hypothèses sur ce que les fous vont faire.
JLRishe
54

Indéfini

En déclarant undefineden tant qu'argument mais en ne lui passant jamais de valeur, il est toujours indéfini, car il s'agit simplement d'une variable dans la portée globale qui peut être écrasée. Cela constitue a === undefinedune alternative sûre à typeof a == 'undefined', qui enregistre quelques caractères. Cela rend également le code plus convivial pour les minificateurs, car il undefinedpeut être raccourci, upar exemple, en enregistrant quelques caractères supplémentaires.

La fenêtre

Passer windowen argument garde une copie dans la portée locale, ce qui affecte les performances: http://jsperf.com/short-scope . Tous les accès à windowdevront désormais parcourir un niveau de moins dans la chaîne de portée. Comme avec undefined, une copie locale permet à nouveau une minification plus agressive.


Sidenote:

Bien que cela n'ait peut-être pas été l'intention des développeurs jQuery, le passage windowpermet à la bibliothèque d'être plus facilement intégrée dans les environnements Javascript côté serveur, par exemple node.js - où il n'y a pas d' windowobjet global . Dans une telle situation, une seule ligne doit être modifiée pour remplacer l' windowobjet par une autre. Dans le cas de jQuery, un windowobjet fictif peut être créé et transmis à des fins de scraping HTML (une bibliothèque telle que jsdom peut le faire).

David Tang
la source
2
+1 pour mentionner la minification, j'aimerais pouvoir +2 pour le jsperf - La version minifiée est (function(a,b){})(window);- aet best beaucoup plus courte que windowet undefined:)
gnarf
+1 J'avais entendu parler de la chaîne de portée avant, mais j'ai oublié le terme. Merci!
alex
C'est aussi une alternative à l'utilisation de void (0) qui a été conçue pour la même raison: void (0) est un moyen sûr d'obtenir la valeur non définie.
glmxndr
"Ce que vous dites" fait référence à cette autre question: stackoverflow.com/questions/4945265
...
@gnarf, merci pour la notification que les questions ont été fusionnées. Je vais modifier ma réponse pour avoir un peu plus de sens;)
David Tang
16

D'autres ont expliqué undefined. undefinedest comme une variable globale qui peut être redéfinie à n'importe quelle valeur. Cette technique permet d'éviter que tous les contrôles non définis ne se rompent si quelqu'un écrit par exemple, undefined = 10quelque part. Un argument qui n'est jamais passé est garanti réel undefinedquelle que soit la valeur de la variable undefined .

La raison de passer la fenêtre peut être illustrée par l'exemple suivant.

(function() {
   console.log(window);
   ...
   ...
   ...
   var window = 10;
})();

Que consigne la console? La valeur de l' windowobjet, non? Faux! dix? Faux! Il enregistre undefined. L'interpréteur Javascript (ou compilateur JIT) le réécrit de cette façon -

(function() {
   var window; //and every other var in this function

   console.log(window);
   ...
   ...
   ...
   window = 10;

})();

Cependant, si vous obtenez la windowvariable comme argument, il n'y a pas de var et donc pas de surprises.

Je ne sais pas si jQuery le fait, mais si vous redéfinissez windowune variable locale n'importe où dans votre fonction pour une raison quelconque, c'est une bonne idée de l'emprunter à une portée globale.

Chetan S
la source
6

windowest passé comme ça juste au cas où quelqu'un déciderait de redéfinir l'objet window dans IE, je suppose la même chose pour undefined, au cas où il serait réaffecté d'une manière ou d'une autre plus tard.

Le début windowde ce script est juste le nom de l'argument "window", un argument qui est plus local que la windowréférence globale et ce que le code à l'intérieur de cette fermeture utilisera. Le windowà la fin spécifie en fait ce qu'il faut passer pour le premier argument, dans ce cas, le sens actuel de window... l'espoir est que vous n'avez pas foiré windowavant que cela n'arrive.

Cela peut être plus facile à penser en montrant le cas le plus typique utilisé dans jQuery, la .noConflict()gestion des plugins , donc pour la majorité du code, vous pouvez toujours utiliser $, même si cela signifie autre chose qu'en jQuerydehors de cette portée:

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);
Nick Craver
la source
Merci. Cela a beaucoup de sens. Cependant, je pense que la réponse est une combinaison de ceci et de ce que dit @ C.Zakas.
dkinzer
@DKinzer j'ai peur de ne pas être C.Zakas;)
Vincent
@Vincent, désolé pour ça :-)
dkinzer
5

Testé avec 1000000 itérations. Ce type de localisation n'a eu aucun effet sur les performances. Pas même une seule milliseconde en 1000000 itérations. C'est tout simplement inutile.

Semra
la source
2
Sans oublier, la fermeture porte toutes les variables avec l'instance de la fonction, donc si vous avez 100 octets en fermeture et 1000 instances, c'est 100 Ko de mémoire supplémentaire.
Akash Kava
@AkashKava et @Semra, je ne pense pas que ce test explique vraiment pourquoi vous passeriez en fenêtre dans une situation réelle. Le gain de performances ne sera visible que lorsque vous aurez des fermetures profondément imbriquées qui accèdent à cette variable plus loin dans la chaîne de portée. évidemment, si votre fonction n'est qu'à un pas de la chaîne de portée de la variable, vous ne verrez pas beaucoup de gain. mais si c'était waaaay là-bas et qu'il fallait l'obtenir, windowvous pourriez voir un gain avec une copie locale.
gabereal
@gabereal nouveaux moteurs JavaScript résout les fermetures au moment de la compilation elle-même, il n'y a pas de gain de performance en fermeture lors de l'exécution. Je doute qu'il y ait un gain de performance mesurable, si vous êtes si confiant, vous pouvez créer un exemple jsperf pour démontrer les performances. La seule performance que nous obtenons est d'isoler les fermetures, ce qui se traduit par une meilleure minification qui réduit la taille et les performances sont obtenues par un téléchargement et une analyse plus rapides du script.
Akash Kava
@AkashKava, qu'est-ce qui vous fait penser que je suis si confiant? J'ai juste dit "pourrait voir un gain" et "je ne pense pas". Je pourrais créer un test, mais je préfère ne pas le faire car je n'utilise de toute façon jamais ce modèle. pouvez-vous expliquer ce que vous entendez par «résout les fermetures au moment de la compilation elle-même»? par opposition à quelle alternative? comment les moteurs Javascript faisaient-ils les choses différemment avant?
gabereal
Avoir un code de recherche où la fenêtre est curée à la fin mais pas entrée dans les paramètres de la fonction, qu'est-ce que cela signifie? il s'assure que les paramètres suivent l'objet de portée du global d'origine même s'il n'y a pas de paramètre de fenêtre que je suppose?
Webwoman