Quelle est la portée des variables en javascript? Ont-ils la même portée à l'intérieur qu'à l'extérieur d'une fonction? Ou est-ce même important? De plus, où sont stockées les variables si elles sont définies globalement?
JavaScript a une portée et des fermetures lexicales (également appelées statiques). Cela signifie que vous pouvez déterminer la portée d'un identifiant en consultant le code source.
Les quatre champs d'application sont les suivants:
Global - visible par tout
Fonction - visible dans une fonction (et ses sous-fonctions et blocs)
Bloc - visible dans un bloc (et ses sous-blocs)
Module - visible dans un module
En dehors des cas particuliers de portée globale et de module, les variables sont déclarées à l'aide de var(portée de fonction), let(portée de bloc) et const(portée de bloc). La plupart des autres formes de déclaration d'identifiant ont une portée de bloc en mode strict.
Aperçu
La portée est la région de la base de code sur laquelle un identifiant est valide.
Un environnement lexical est un mappage entre les noms d'identifiants et les valeurs qui leur sont associées.
La portée est formée d'une imbrication liée d'environnements lexicaux, chaque niveau de l'imbrication correspondant à un environnement lexical d'un contexte d'exécution d'ancêtre.
Ces environnements lexicaux liés forment une "chaîne" de portée. La résolution de l'identifiant est le processus de recherche le long de cette chaîne d'un identifiant correspondant.
La résolution de l'identifiant ne se produit que dans une seule direction: vers l'extérieur. De cette façon, les environnements lexicaux externes ne peuvent pas "voir" dans les environnements lexicaux internes.
Il y a trois facteurs pertinents pour décider de la portée d'un identifiant dans JavaScript:
Certaines des façons dont les identifiants peuvent être déclarés:
var, letetconst
Paramètres de fonction
Paramètre de bloc de capture
Déclarations de fonctions
Expressions de fonction nommées
Propriétés définies implicitement sur l'objet global (c'est-à-dire manquant varen mode non strict)
import déclarations
eval
Certains des identifiants d'emplacements peuvent être déclarés:
Contexte global
Corps de fonction
Bloc ordinaire
Le sommet d'une structure de contrôle (par exemple, boucle, if, while, etc.)
Corps de la structure de contrôle
Modules
Styles de déclaration
var
Les identificateurs déclarés à l'aide varont une portée de fonction , sauf lorsqu'ils sont déclarés directement dans le contexte global, auquel cas ils sont ajoutés en tant que propriétés sur l'objet global et ont une portée globale. Il existe des règles distinctes pour leur utilisation dans les evalfonctions.
let et const
Les identifiants déclarés utilisant letet constont une portée de bloc , sauf lorsqu'ils sont déclarés directement dans le contexte global, auquel cas ils ont une portée globale.
Note: let, constet varsont tous hissés . Cela signifie que leur position logique de définition est au sommet de leur portée englobante (bloc ou fonction). Cependant, les variables ont déclaré utiliser letet constne peuvent pas être lues ou affectées jusqu'à ce que le contrôle ait passé le point de déclaration dans le code source. La période intérimaire est connue comme la zone morte temporelle.
function f(){function g(){
console.log(x)}let x =1
g()}
f()// 1 because x is hoisted even though declared with `let`!
Les noms des paramètres de fonction sont étendus au corps de la fonction. Notez qu'il y a une légère complexité à cela. Les fonctions déclarées comme arguments par défaut se ferment sur la liste des paramètres et non sur le corps de la fonction.
Déclarations de fonctions
Les déclarations de fonctions ont une portée de bloc en mode strict et une portée de fonction en mode non strict. Remarque: le mode non strict est un ensemble compliqué de règles émergentes basées sur les implémentations historiques originales de différents navigateurs.
Expressions de fonction nommées
Les expressions de fonction nommées sont limitées à elles-mêmes (par exemple, à des fins de récursivité).
Propriétés définies implicitement sur l'objet global
En mode non strict, les propriétés définies implicitement sur l'objet global ont une portée globale, car l'objet global se trouve en haut de la chaîne de portée. En mode strict, ceux-ci ne sont pas autorisés.
eval
Dans les evalchaînes, les variables déclarées à l'aide varseront placées dans la portée actuelle ou, si elles evalsont utilisées indirectement, comme propriétés sur l'objet global.
Exemples
Ce qui suit lancera une ReferenceError parce que les noms x, yet zn'ont aucune signification en dehors de la fonction f.
function f(){var x =1let y =1const z =1}
console.log(typeof x)// undefined (because var has function scope!)
console.log(typeof y)// undefined (because the body of the function is a block)
console.log(typeof z)// undefined (because the body of the function is a block)
Ce qui suit lancera une ReferenceError pour yet z, mais pas pour x, car la visibilité de xn'est pas limitée par le bloc. Les blocs qui définissent les corps des structures de contrôle comme if, foret while, se comportent de manière similaire.
{var x =1let y =1const z =1}
console.log(x)// 1
console.log(typeof y)// undefined because `y` has block scope
console.log(typeof z)// undefined because `z` has block scope
... à cause de ce comportement, vous devez faire attention à ne pas fermer les variables déclarées à l'aide varde boucles in. Il n'y a qu'une seule instance de variable xdéclarée ici, et elle se trouve logiquement en dehors de la boucle.
Les impressions suivantes 5, cinq fois, puis 5une sixième fois pour l' console.logextérieur de la boucle:
for(var x =0; x <5;++x){
setTimeout(()=> console.log(x))// closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop}
console.log(x)// note: visible outside the loop
Ce qui suit s'imprime undefinedcar xest de portée bloc. Les rappels sont exécutés un par un de manière asynchrone. Nouveau comportement pour des letmoyens variables que chaque fonction anonyme fermée sur une autre variable nommée x(contrairement à il l' aurait fait avec var), et ainsi entiers à 0travers 4sont imprimés .:
for(let x =0; x <5;++x){
setTimeout(()=> console.log(x))// `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables}
console.log(typeof x)// undefined
Ce qui suit ne lancera PAS un ReferenceErrorcar la visibilité de xn'est pas limitée par le bloc; il s'imprimera cependant undefinedcar la variable n'a pas été initialisée (à cause de l' ifinstruction).
if(false){var x =1}
console.log(x)// here, `x` has been declared, but not initialised
Les variables déclarées en utilisant var, letou constsont toutes étendues aux modules:
// module1.jsvar x =0exportfunction f(){}//module2.jsimport f from'module1.js'
console.log(x)// throws ReferenceError
Ce qui suit déclarera une propriété sur l'objet global, car les variables déclarées en utilisant vardans le contexte global, sont ajoutées en tant que propriétés à l'objet global:
var x =1
console.log(window.hasOwnProperty('x'))// true
La portée est définie comme la région lexicale du code sur laquelle un identifiant est valide.
En JavaScript, chaque objet-fonction a une [[Environment]]référence cachée qui est une référence à l' environnement lexical du contexte d'exécution (cadre de pile) dans lequel il a été créé.
Lorsque vous appelez une fonction, la [[Call]]méthode cachée est appelée. Cette méthode crée un nouveau contexte d'exécution et établit un lien entre le nouveau contexte d'exécution et l'environnement lexical de l'objet-fonction. Il le fait en copiant la [[Environment]]valeur sur l'objet-fonction, dans un champ de référence externe sur l'environnement lexical du nouveau contexte d'exécution.
Notez que ce lien entre le nouveau contexte d'exécution et l'environnement lexical de l'objet fonction est appelé fermeture .
Ainsi, en JavaScript, la portée est implémentée via des environnements lexicaux reliés entre eux dans une "chaîne" par des références externes. Cette chaîne d'environnements lexicaux est appelée la chaîne de portée, et la résolution de l'identifiant se produit en recherchant dans la chaîne un identifiant correspondant.
Ce n'est même pas près d'être complet, mais c'est peut-être l'ensemble des astuces de portée Javascript dont vous avez besoin pour même lire efficacement le javascript moderne.
Triptyque du
148
Une réponse très appréciée, je ne sais pas pourquoi. Ce n'est qu'un tas d'exemples sans explication appropriée, puis semble confondre l'héritage du prototype (c'est-à-dire la résolution de propriété) avec la chaîne de portée (c'est-à-dire la résolution variable). Une explication complète (et précise) de la résolution de l'étendue et des propriétés se trouve dans les notes de la FAQ comp.lang.javascript .
RobG
109
@RobG Il est très apprécié car il est utile et compréhensible pour un large éventail de programmeurs, malgré une catachrèse mineure. Le lien que vous avez publié, bien qu'utile pour certains professionnels, est incompréhensible pour la plupart des gens qui écrivent Javascript aujourd'hui. N'hésitez pas à résoudre les problèmes de nomenclature en modifiant la réponse.
Triptyque
7
@ triptyque: je modifie uniquement les réponses pour corriger des problèmes mineurs, pas majeurs. Changer "portée" en "propriété" corrigera l'erreur, mais pas le problème de mélanger l'héritage et la portée sans une distinction très claire.
RobG
24
Si vous définissez une variable dans la portée externe, puis que vous avez une instruction if définissez une variable à l'intérieur de la fonction avec le même nom, même si cette branche n'est pas atteinte, elle est redéfinie. Un exemple - jsfiddle.net/3CxVm
Chris S
233
Javascript utilise des chaînes de portée pour établir la portée d'une fonction donnée. Il existe généralement une étendue globale et chaque fonction définie a sa propre étendue imbriquée. Toute fonction définie dans une autre fonction a une portée locale qui est liée à la fonction externe. C'est toujours la position dans la source qui définit la portée.
Un élément de la chaîne de portée est essentiellement une carte avec un pointeur sur sa portée parent.
Lors de la résolution d'une variable, javascript démarre à l'étendue la plus intérieure et recherche vers l'extérieur.
Les chaînes de portée sont un autre terme pour les fermetures [mémoire] ... pour ceux qui lisent ici pour apprendre / entrer en javascript.
New Alexandria
108
Les variables déclarées globalement ont une portée globale. Les variables déclarées dans une fonction sont étendues à cette fonction et masquent les variables globales du même nom.
(Je suis sûr qu'il ya beaucoup de subtilités que les vrais programmeurs JavaScript seront en mesure de signaler dans d' autres réponses. En particulier , je suis tombé sur cette page sur ce que exactement les thismoyens à tout moment. Espérons que ce lien plus d' introduction est assez pour vous aider à démarrer si .)
J'ai même peur de commencer à répondre à cette question. En tant que vrai programmeur Javascript, je sais à quelle vitesse la réponse pourrait devenir incontrôlable. Bons articles.
Triptyque
10
@Triptych: Je sais ce que vous voulez dire des choses sortir de la main, mais s'il vous plaît ajouter une réponse de toute façon. J'ai obtenu ce qui précède juste en faisant quelques recherches ... une réponse écrite par quelqu'un ayant une expérience réelle est forcément meilleure. Veuillez corriger toute ma réponse qui est définitivement fausse cependant!
Jon Skeet
4
D'une certaine manière, Jon Skeet est responsable de MA réponse la plus populaire sur Stack Overflow.
Triptyque
75
JavaScript old school
Traditionnellement, JavaScript n'a vraiment que deux types de portée:
Portée globale : les variables sont connues tout au long de l'application, dès le début de l'application (*)
Portée fonctionnelle : les variables sont connues dans la fonction dans laquelle elles sont déclarées, depuis le début de la fonction (*)
Je ne m'étendrai pas là-dessus, car il existe déjà de nombreuses autres réponses expliquant la différence.
Portée du bloc : les identificateurs sont "connus" du haut de la portée dans laquelle ils sont déclarés , mais ils ne peuvent pas être attribués ou déréférencés (lus) avant la ligne de leur déclaration. Cette période intérimaire est appelée «zone morte temporelle».
Comment créer des variables d'étendue de bloc?
Traditionnellement, vous créez vos variables comme ceci:
var myVariable ="Some text";
Les variables de portée de bloc sont créées comme suit:
let myVariable ="Some text";
Quelle est donc la différence entre la portée fonctionnelle et la portée du bloc?
Pour comprendre la différence entre l'étendue fonctionnelle et l'étendue de bloc, tenez compte du code suivant:
// i IS NOT known here// j IS NOT known here// k IS known here, but undefined// l IS NOT known herefunction loop(arr){// i IS known here, but undefined// j IS NOT known here// k IS known here, but has a value only the second time loop is called// l IS NOT known herefor(var i =0; i < arr.length; i++){// i IS known here, and has a value// j IS NOT known here// k IS known here, but has a value only the second time loop is called// l IS NOT known here};// i IS known here, and has a value// j IS NOT known here// k IS known here, but has a value only the second time loop is called// l IS NOT known herefor(let j =0; j < arr.length; j++){// i IS known here, and has a value// j IS known here, and has a value// k IS known here, but has a value only the second time loop is called// l IS NOT known here};// i IS known here, and has a value// j IS NOT known here// k IS known here, but has a value only the second time loop is called// l IS NOT known here}
loop([1,2,3,4]);for(var k =0; k < arr.length; k++){// i IS NOT known here// j IS NOT known here// k IS known here, and has a value// l IS NOT known here};for(let l =0; l < arr.length; l++){// i IS NOT known here// j IS NOT known here// k IS known here, and has a value// l IS known here, and has a value};
loop([1,2,3,4]);// i IS NOT known here// j IS NOT known here// k IS known here, and has a value// l IS NOT known here
Ici, nous pouvons voir que notre variable jn'est connue que dans la première boucle for, mais pas avant ni après. Pourtant, notre variable iest connue dans toute la fonction.
Tenez également compte du fait que les variables de portée de bloc ne sont pas connues avant d'être déclarées car elles ne sont pas hissées. Vous n'êtes pas non plus autorisé à redéclarer la même variable de portée de bloc dans le même bloc. Cela rend les variables de portée de bloc moins sujettes aux erreurs que les variables de portée globale ou fonctionnelle, qui sont hissées et qui ne produisent aucune erreur en cas de déclarations multiples.
Est-il sûr d'utiliser des variables d'étendue de bloc aujourd'hui?
Que son utilisation soit sûre ou non aujourd'hui, cela dépend de votre environnement:
Si vous écrivez du code JavaScript côté serveur ( Node.js ), vous pouvez utiliser l' letinstruction en toute sécurité .
Si vous écrivez du code JavaScript côté client et utilisez un transpilateur basé sur un navigateur (comme Traceur ou babel-standalone ), vous pouvez utiliser en toute sécurité l' letinstruction, cependant votre code est susceptible d'être tout sauf optimal en termes de performances.
Si vous écrivez du code JavaScript côté client et utilisez un transpilateur basé sur Node (comme le script shell traceur ou Babel ), vous pouvez utiliser l' letinstruction en toute sécurité . Et comme votre navigateur ne connaîtra que le code transpilé, les inconvénients de performances devraient être limités.
Si vous écrivez du code JavaScript côté client et n'utilisez pas de transpilateur, vous devez considérer la prise en charge du navigateur.
Voici quelques navigateurs qui ne prennent pas en charge letdu tout:
Internet Explorer 10 et versions antérieures
Firefox 43 et inférieur
Safari 9 et inférieur
Navigateur Android 4 et inférieur
Opera 27 et inférieur
Chome 40 et moins
TOUTE version d' Opera Mini et Blackberry Browser
Comment suivre la prise en charge du navigateur
Pour une vue d'ensemble à jour des navigateurs qui prennent en charge la letdéclaration au moment de la lecture de cette réponse, consultez cette Can I Usepage .
(*) Les variables de portée globale et fonctionnelle peuvent être initialisées et utilisées avant d'être déclarées car les variables JavaScript sont hissées . Cela signifie que les déclarations sont toujours bien en haut de la portée.
"N'EST PAS connu" est trompeur, car la variable y est déclarée en raison d'un hissage.
Oriol
L'exemple ci-dessus est trompeur, les variables «i» et «j» ne sont pas connues en dehors du bloc. Les variables «Let» n'ont de portée que dans ce bloc particulier et non en dehors du bloc. Let a aussi d'autres avantages, vous ne pouvez pas redéclarer la variable à nouveau et elle a la portée lexicale.
zakir
1
Cela a été utile, merci! Je pense qu'il serait encore plus utile de préciser ce que vous entendez par "JavaScript moderne" et "JavaScript old school"; Je pense que ceux-ci correspondent à ECMAScript 6 / ES6 / ECMAScript 2015 et aux versions antérieures, respectivement?
Jon Schneider
1
@ JonSchneider: correct! Lorsque je dis «JavaScript old school», je parle d'ECMAScript 5 et lorsque je fais référence à «Javascript moderne», je parle d'ECMAScript 6 (alias ECMAScript 2015). Je ne pensais pas qu'il était vraiment si important d'entrer dans les détails ici, cependant, comme la plupart des gens veulent juste savoir (1) quelle est la différence entre l'étendue du bloc et l'étendue fonctionnelle, (2) quels navigateurs prennent en charge l'étendue du bloc et (3) s'il est sûr d'utiliser l'étendue des blocs aujourd'hui pour n'importe quel projet sur lequel ils travaillent. J'ai donc concentré ma réponse sur la résolution de ces problèmes.
John Slegers
1
@JonSchneider: (suite) Néanmoins, je viens d'ajouter un lien vers un article de Smashing Magazine sur ES6 / ES2015 pour ceux qui veulent en savoir plus sur les fonctionnalités qui ont été ajoutées à JavaScript au cours des deux dernières années ... de toute autre personne qui pourrait se demander ce que je veux dire par "JavaScript moderne".
John Slegers
39
Voici un exemple:
<script>var globalVariable =7;//==window.globalVariablefunction aGlobal( param ){//==window.aGlobal(); //param is only accessible in this functionvar scopedToFunction ={//can't be accessed outside of this function
nested :3//accessible by: scopedToFunction.nested};
anotherGlobal ={//global because there's no `var`};}</script>
Vous voudrez enquêter sur les fermetures et savoir comment les utiliser pour créer des membres privés .
Oui, mais est-ce sûr à utiliser? Je veux dire, choisirais-je de façon réaliste cette implémentation si mon code s'exécute dans WebKit?
IgorGanapolsky
10
@Python: Non, WebKit ne prend pas en charge let.
kennytm
Je suppose que la seule utilisation valable pour cela serait si vous saviez que tous les clients utiliseraient un navigateur Mozilla comme pour un système interne d'entreprise.
GazB
Ou si vous programmez en utilisant le framework XUL, le framework d'interface de Mozilla où vous construisez en utilisant css, xml et javascript.
Gerard ONeill
1
@GazB même c'est une idée horrible! Donc, aujourd'hui, vous savez que vos clients utilisent Mozilla, un nouveau mémo indique que maintenant ils utilisent autre chose. IE la raison pour laquelle notre système de paie est nul ... Vous devez utiliser IE8 et jamais IE9 ou IE10 ou Firefox ou Chrome car cela ne fonctionnera pas ...
buzzsawddog
25
L'idée de la portée en JavaScript lorsqu'elle a été conçue à l'origine par Brendan Eich est venue du langage de script HyperCard HyperTalk .
Dans cette langue, les affichages étaient similaires à ceux d'une pile de fiches. Il y avait une carte maîtresse appelée arrière-plan. Il était transparent et peut être vu comme la carte du bas. Tout le contenu de cette carte de base a été partagé avec des cartes placées dessus. Chaque carte placée au-dessus avait son propre contenu qui avait priorité sur la carte précédente, mais avait toujours accès aux cartes précédentes si désiré.
C'est exactement comme cela que le système de portée JavaScript est conçu. Il a juste des noms différents. Les cartes en JavaScript sont connues sous le nom de contextes d'exécution ECMA . Chacun de ces contextes contient trois parties principales. Un environnement variable, un environnement lexical et une cette liaison. Pour en revenir à la référence des cartes, l'environnement lexical contient tout le contenu des cartes précédentes plus bas dans la pile. Le contexte actuel est en haut de la pile et tout contenu déclaré y sera stocké dans l'environnement variable. L'environnement variable aura la priorité dans le cas de collisions de noms.
Cette liaison pointera vers l'objet contenant. Parfois, les étendues ou les contextes d'exécution changent sans que l'objet conteneur ne change, comme dans une fonction déclarée où l'objet conteneur peut être windowou une fonction constructeur.
Ces contextes d'exécution sont créés chaque fois que le contrôle est transféré. Le contrôle est transféré lorsque le code commence à s'exécuter, et cela se fait principalement à partir de l'exécution de la fonction.
Voilà donc l'explication technique. En pratique, il est important de se rappeler qu'en JavaScript
Les étendues sont techniquement des «contextes d'exécution»
Les contextes forment une pile d'environnements où les variables sont stockées
Le haut de la pile est prioritaire (le bas étant le contexte global)
Chaque fonction crée un contexte d'exécution (mais pas toujours une nouvelle cette liaison)
En appliquant cela à l'un des exemples précédents (5. "Clôture") de cette page, il est possible de suivre la pile des contextes d'exécution. Dans cet exemple, il y a trois contextes dans la pile. Ils sont définis par le contexte externe, le contexte de la fonction immédiatement invoquée appelée par var six et le contexte de la fonction retournée à l'intérieur de la fonction immédiatement invoquée de var six.
i ) Le contexte extérieur. Il a un environnement variable de a = 1 ii ) Le contexte IIFE, il a un environnement lexical de a = 1, mais un environnement variable de a = 6 qui a priorité dans la pile iii ) Le contexte de fonction retourné, il a un lexical environnement de a = 6 et qui est la valeur référencée dans l'alerte lors de l'appel.
1) Il existe une étendue globale, une étendue de fonction et les étendues with et catch. Il n'y a pas de portée de niveau «bloc» en général pour les variables - les instructions with et catch ajoutent des noms à leurs blocs.
2) Les portées sont imbriquées par des fonctions jusqu'à la portée globale.
3) Les propriétés sont résolues en passant par la chaîne du prototype. L'instruction with introduit les noms de propriété d'objet dans la portée lexicale définie par le bloc with.
EDIT: ECMAAScript 6 (Harmony) est spécifié pour prendre en charge let, et je sais que chrome autorise un drapeau «harmonie», donc peut-être qu'il le prend en charge ..
Soit un support pour la portée au niveau du bloc, mais vous devez utiliser le mot-clé pour y arriver.
EDIT: Sur la base des remarques de Benjamin sur les déclarations with et catch dans les commentaires, j'ai édité le post et ajouté plus. Tant le avec et les déclarations de capture des variables dans leurs introduisons blocs respectifs, et qui est un champ de bloc. Ces variables sont aliasées aux propriétés des objets qui leur sont passés.
//chrome (v8)var a ={'test1':'test1val'}
test1 // error not definedwith(a){var test1 ='replaced'}
test1 // undefined
a // a.test1 = 'replaced'
EDIT: Exemple de clarification:
test1 est limité au bloc with, mais a un alias vers a.test1. 'Var test1' crée une nouvelle variable test1 dans le contexte lexical supérieur (fonction ou global), à moins que ce ne soit une propriété de a - ce qu'elle est.
Oui! Soyez prudent en utilisant 'avec' - tout comme var est un noop si la variable est déjà définie dans la fonction, c'est aussi un noop en ce qui concerne les noms importés de l'objet! Un petit avertissement sur le nom déjà défini rendrait cela beaucoup plus sûr. Personnellement, je ne l'utiliserai jamais avec à cause de cela.
Vous avez quelques erreurs ici, car un JavaScript a des formes de portée de bloc.
Benjamin Gruenbaum
Mes oreilles (yeux) sont ouvertes, Benjamin - Mes déclarations ci-dessus montrent comment j'ai traité la portée de Javascript, mais elles ne sont pas basées sur la lecture des spécifications. Et j'espère que vous ne faites pas référence à l'instruction with (qui est une forme de portée d'objet), ou à la syntaxe spéciale «let» de Mozilla.
Gerard ONeill
Eh bien, l' withinstruction est une forme de portée de bloc, mais les catchclauses sont une forme beaucoup plus courante (Fait amusant, v8 implémente catchavec a with) - c'est à peu près les seules formes de portée de bloc dans JavaScript lui-même (c'est-à-dire, fonction, global, try / catch , avec et leurs dérivés), mais les environnements hôtes ont différentes notions de portée - par exemple les événements en ligne dans le navigateur et le module vm de NodeJS.
Benjamin Gruenbaum
Benjamin - d'après ce que je peux voir, à la fois avec et catch, n'introduisez l'objet que dans la portée actuelle (et donc les propriétés), mais après la fin du bloc respectif, les variables sont réinitialisées. Mais par exemple, une nouvelle variable introduite dans un catch aura la portée de la fonction / méthode englobante.
Gerard ONeill du
2
C'est exactement ce que signifie l'étendue des blocs :)
Benjamin Gruenbaum
9
J'ai constaté que de nombreuses personnes nouvelles dans JavaScript ont du mal à comprendre que l'héritage est disponible par défaut dans le langage et que la portée de la fonction est la seule portée jusqu'à présent. J'ai fourni une extension à un embellisseur que j'ai écrit à la fin de l'année dernière, appelé JSPretty. Les couleurs de fonction ont une portée dans le code et associent toujours une couleur à toutes les variables déclarées dans cette portée. La fermeture est démontrée visuellement lorsqu'une variable avec une couleur d'une étendue est utilisée dans une étendue différente.
Ne fonctionne pas pour moi avec Firefox 26. Je colle du code ou charge un fichier, cliquez sur Exécuter et rien ne se passe.
mplwork
La portée et l'héritage sont deux choses différentes.
Ben Aston
9
JavaScript n'a que deux types de portée:
Portée globale : Global n'est rien d'autre qu'une portée au niveau de la fenêtre. Ici, variable présente dans toute l'application.
Portée fonctionnelle : la variable déclarée dans une fonction avec un varmot clé a une portée fonctionnelle.
Chaque fois qu'une fonction est appelée, un objet de portée variable est créé (et inclus dans la chaîne de portée) qui est suivi par des variables en JavaScript.
a ="global";function outer(){
b ="local";
console.log(a+b);//"globallocal"}
outer();
Chaîne de lunette ->
Niveau de fenêtre - aet la outerfonction sont au niveau supérieur dans la chaîne de portée.
lorsque la fonction externe a appelé une nouvelle variable scope object(et incluse dans la chaîne de portée) ajoutée avec une variable à l' bintérieur.
Maintenant, lorsqu'une variable est arequise, elle recherche d'abord l'étendue de la variable la plus proche et si la variable n'est pas là, elle se déplace vers l'objet suivant de la chaîne d'étendue de la variable.
Je ne sais pas pourquoi ce n'est pas la réponse acceptée. Il n'y a en fait qu'une portée fonctionnelle (avant ECMA6 il n'y avait pas de "portée locale") et des liaisons globales
texasbruce
9
Juste pour ajouter aux autres réponses, la portée est une liste de recherche de tous les identificateurs déclarés (variables), et applique un ensemble strict de règles quant à la façon dont celles-ci sont accessibles au code en cours d'exécution. Cette recherche peut être effectuée dans le but d'affecter à la variable, qui est une référence LHS (côté gauche), ou dans le but de récupérer sa valeur, qui est une référence RHS (côté droit). Ces recherches sont ce que fait le moteur JavaScript en interne lorsqu'il compile et exécute le code.
Donc, de ce point de vue, je pense qu'une image serait utile que j'ai trouvée dans l'ebook Scopes and Closures de Kyle Simpson:
Citant de son ebook:
Le bâtiment représente l'ensemble de règles de portée imbriquée de notre programme. Le premier étage du bâtiment représente votre périmètre en cours d'exécution, où que vous soyez. Le niveau supérieur du bâtiment est la portée mondiale. Vous résolvez les références LHS et RHS en regardant à votre étage actuel, et si vous ne le trouvez pas, en prenant l'ascenseur à l'étage suivant, en y regardant, puis au suivant, etc. Une fois arrivé au dernier étage (la portée mondiale), soit vous trouvez ce que vous cherchez, soit vous ne le trouvez pas. Mais vous devez vous arrêter malgré tout.
Une chose à noter, "La recherche de portée s'arrête une fois qu'elle trouve la première correspondance".
Cette idée de «niveaux de portée» explique pourquoi «ceci» peut être modifié avec une portée nouvellement créée, si elle est recherchée dans une fonction imbriquée. Voici un lien qui rentre dans tous ces détails, Tout ce que vous vouliez savoir sur la portée javascript
Les variables globales sont exactement comme les étoiles globales (Jackie Chan, Nelson Mandela). Vous pouvez y accéder (obtenir ou définir la valeur), à partir de n'importe quelle partie de votre application. Les fonctions globales sont comme des événements mondiaux (Nouvel An, Noël). Vous pouvez les exécuter (appeler) à partir de n'importe quelle partie de votre application.
//global variablevar a =2;//global functionfunction b(){
console.log(a);//access global variable}
Portée locale:
Si vous êtes aux États-Unis, vous connaissez peut-être Kim Kardashian, une célébrité infâme (elle parvient en quelque sorte à faire les tabloïds). Mais les gens en dehors des États-Unis ne la reconnaîtront pas. C'est une star locale, liée à son territoire.
Les variables locales sont comme des étoiles locales. Vous ne pouvez y accéder (obtenir ou définir la valeur) qu'à l'intérieur de la portée. Une fonction locale est comme des événements locaux - vous ne pouvez exécuter (célébrer) que dans cette portée. Si vous souhaitez y accéder depuis l'extérieur de la portée, vous obtiendrez une erreur de référence
function b(){var d =21;//local variable
console.log(d);function dog(){ console.log(a);}
dog();//execute local function}
console.log(d);//ReferenceError: dddddd is not defined
Il existe PRESQUE seulement deux types d'étendues JavaScript:
la portée de chaque déclaration var est associée à la fonction englobante la plus immédiate
s'il n'y a pas de fonction englobante pour une déclaration var, c'est la portée globale
Ainsi, tous les blocs autres que les fonctions ne créent pas de nouvelle étendue. Cela explique pourquoi les boucles for remplacent les variables de portée externes:
var i =10, v =10;for(var i =0; i <5; i++){var v =5;}
console.log(i, v);// output 5 5
Utiliser des fonctions à la place:
var i =10, v =10;
$.each([0,1,2,3,4],function(i){var v =5;});
console.log(i,v);// output 10 10
Dans le premier exemple, il n'y avait pas de portée de bloc, donc les variables initialement déclarées ont été écrasées. Dans le deuxième exemple, il y avait une nouvelle portée en raison de la fonction, donc les variables initialement déclarées étaient SHADOWED, et non écrasées.
C'est presque tout ce que vous devez savoir en termes de portée de JavaScript, sauf:
try / catch introduit une nouvelle portée UNIQUEMENT pour la variable d'exception elle-même, les autres variables n'ont pas de nouvelle portée
Vous pouvez donc voir que la portée de JavaScript est en fait extrêmement simple, mais pas toujours intuitive. Quelques choses à savoir:
Les déclarations var sont hissées en haut de l'étendue. Cela signifie que peu importe où la déclaration var se produit, pour le compilateur, c'est comme si la var elle-même se produisait en haut
plusieurs déclarations var dans la même étendue sont combinées
Donc, ce code:
var i =1;function abc(){
i =2;var i =3;}
console.log(i);// outputs 1
est équivalent à:
var i =1;function abc(){var i;// var declaration moved to the top of the scope
i =2;
i =3;// the assignment stays where it is}
console.log(i);
Cela peut sembler contre-intuitif, mais il est logique du point de vue d'un concepteur de langage impératif.
Vous devez utiliser l'étendue des blocs pour chaque variable que vous créez, comme la plupart des autres langages principaux. varest obsolète . Cela rend votre code plus sûr et plus facile à gérer.
constdoit être utilisé dans 95% des cas . Il fait en sorte que la référence de variable ne puisse pas changer. Les propriétés des tableaux, des objets et des nœuds DOM peuvent changer et devraient probablement êtreconst .
letdoit être utilisé pour toute variable devant être réaffectée. Cela inclut une boucle for. Si vous changez la valeur au-delà de l'initialisation, utilisezlet .
La portée du bloc signifie que la variable ne sera disponible que dans les crochets dans lesquels elle est déclarée. Cela s'étend aux étendues internes, y compris les fonctions anonymes créées dans votre étendue.
Essayez cet exemple curieux. Dans l'exemple ci-dessous, si a était un numérique initialisé à 0, vous verriez 0 puis 1. Sauf que a est un objet et que javascript passera f1 un pointeur de a plutôt qu'une copie de celui-ci. Le résultat est que vous obtenez la même alerte les deux fois.
var a =newDate();function f1(b){
b.setDate(b.getDate()+1);
alert(b.getDate());}
f1(a);
alert(a.getDate());
Il n'y a que des étendues de fonction dans JS. Ne bloquez pas les portées! Vous pouvez également voir ce qui est hissé.
var global_variable ="global_variable";var hoisting_variable ="global_hoist";// Global variables printed
console.log("global_scope: - global_variable: "+ global_variable);
console.log("global_scope: - hoisting_variable: "+ hoisting_variable);if(true){// The variable block will be global, on true condition.var block ="block";}
console.log("global_scope: - block: "+ block);function local_function(){var local_variable ="local_variable";
console.log("local_scope: - local_variable: "+ local_variable);
console.log("local_scope: - global_variable: "+ global_variable);
console.log("local_scope: - block: "+ block);// The hoisting_variable is undefined at the moment.
console.log("local_scope: - hoisting_variable: "+ hoisting_variable);var hoisting_variable ="local_hoist";// The hoisting_variable is now set as a local one.
console.log("local_scope: - hoisting_variable: "+ hoisting_variable);}
local_function();// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: "+ local_variable);
Je crois comprendre qu'il y a 3 portées: portée mondiale, disponible à l'échelle mondiale; portée locale, disponible pour une fonction entière indépendamment des blocs; et la portée du bloc, uniquement disponible pour le bloc, l'instruction ou l'expression sur laquelle il a été utilisé. La portée globale et locale est indiquée par le mot-clé «var», à l'intérieur ou à l'extérieur d'une fonction, et la portée du bloc est indiquée par le mot-clé «let».
Pour ceux qui pensent qu'il n'y a qu'une portée globale et locale, veuillez expliquer pourquoi Mozilla aurait une page entière décrivant les nuances de la portée des blocs dans JS.
Un problème très courant qui n'est pas encore décrit et que les codeurs frontaux rencontrent souvent est l'étendue visible par un gestionnaire d'événements en ligne dans le HTML - par exemple, avec
<buttononclick="foo()"></button>
La portée des variables qu'un on*attribut peut référencer doit être soit:
global (les gestionnaires en ligne de travail font presque toujours référence aux variables globales)
une propriété du document (par exemple, querySelectorcomme une variable autonome pointera vers document.querySelector; rare)
une propriété de l'élément auquel le gestionnaire est attaché (comme ci-dessus; rare)
Sinon, vous obtiendrez une ReferenceError lorsque le gestionnaire est appelé. Ainsi, par exemple, si le gestionnaire en ligne fait référence à une fonction définie à l' intérieur dewindow.onload ou $(function() {, la référence échouera, car le gestionnaire en ligne peut uniquement référencer des variables dans la portée globale et la fonction n'est pas globale:
Les propriétés de documentet les propriétés de l'élément auquel le gestionnaire est attaché peuvent également être référencées en tant que variables autonomes dans les gestionnaires en ligne car les gestionnaires en ligne sont appelés à l' intérieur de deux withblocs , un pour le document, un pour l'élément. La chaîne de portée des variables à l'intérieur de ces gestionnaires est extrêmement peu intuitive , et un gestionnaire d'événements de travail nécessitera probablement une fonction pour être globale (et une pollution globale inutile devrait probablement être évitée ).
Étant donné que la chaîne de portée à l'intérieur des gestionnaires en ligne est si étrange et que les gestionnaires en ligne nécessitent une pollution globale pour fonctionner et que les gestionnaires en ligne nécessitent parfois un échappement de chaîne laid lors du passage d'arguments, il est probablement plus facile de les éviter. Au lieu de cela, attachez des gestionnaires d'événements à l'aide de Javascript (comme avec addEventListener), plutôt qu'avec du balisage HTML.
Sur une note différente, contrairement aux <script>balises normales , qui s'exécutent au niveau supérieur, le code à l'intérieur des modules ES6 s'exécute dans sa propre portée privée. Une variable définie en haut d'une <script>balise normale est globale, vous pouvez donc la référencer dans d'autres <script>balises, comme ceci:
Mais le niveau supérieur d'un module ES6 n'est pas global. Une variable déclarée en haut d'un module ES6 ne sera visible qu'à l'intérieur de ce module, à moins que la variable ne soit explicitement exportéditée, ou à moins qu'elle ne soit affectée à une propriété de l'objet global.
<scripttype="module">const foo ='foo';</script><script>// Can't access foo here, because the other script is a module
console.log(typeof foo);</script>
Le niveau supérieur d'un module ES6 est similaire à celui de l'intérieur d'un IIFE au niveau supérieur dans une normale <script>. Le module peut référencer toutes les variables qui sont globales, et rien ne peut référencer quoi que ce soit à l'intérieur du module à moins que le module ne soit explicitement conçu pour cela.
Les variables en Javascript avaient initialement une ES6portée (pré ) lexicale. Le terme à portée lexicale signifie que vous pouvez voir la portée des variables en «regardant» le code.
Chaque variable déclarée avec le varmot clé est étendue à la fonction. Cependant, si d'autres fonctions sont déclarées dans cette fonction, ces fonctions auront accès aux variables des fonctions externes. C'est ce qu'on appelle une chaîne de portée . Cela fonctionne de la manière suivante:
Lorsqu'une fonction cherche à résoudre une valeur de variable, elle examine d'abord sa propre portée. Ceci est le corps de la fonction, c'est-à-dire tout ce qui se trouve entre crochets {} (sauf pour les variables à l'intérieur autres fonctions qui sont dans cette portée).
S'il ne trouve pas la variable à l'intérieur du corps de la fonction, il montera jusqu'à la chaîne et examinera la portée de la variable dans la fonction dans laquelle la fonction a été définie . C'est ce que l'on entend par portée lexicale, nous pouvons voir dans le code où cette fonction a été définie et ainsi déterminer la chaîne de portée en regardant simplement le code.
Que se passe-t-il lorsque nous essayons de consigner les variables foo,bar et foobarla console est la suivante:
Nous essayons de connecter foo à la console, foo peut être trouvé dans la fonction innerFunc elle-même. Par conséquent, la valeur de foo est résolue dans la chaîne innerFunc.
Nous essayons de connecter la barre à la console, la barre est introuvable dans la fonction innerFuncelle-même. Par conséquent, nous devons gravir la chaîne de portée . Nous examinons d'abord la fonction extérieure dans laquelle la fonctioninnerFunc été définie. Telle est la fonction outerFunc. Dans le cadre de, outerFuncnous pouvons trouver la barre de variables, qui contient la chaîne 'externalFunc'.
foobar est introuvable dans innerFunc. . Par conséquent, nous devons escalader la chaîne de portée jusqu'à la portée innerFunc. Il ne peut pas non plus être trouvé ici, nous montons un autre niveau à la portée mondiale (c'est-à-dire la portée la plus externe). Nous trouvons ici la variable foobar qui contient la chaîne «global». S'il n'avait pas trouvé la variable après avoir grimpé la chaîne de portée, le moteur JS lancerait une référenceError .
ES6 (ES 2015) et plus ancien:
Les mêmes concepts de portée lexicale et de chaîne de portée s'appliquent toujours ES6. Cependant, de nouvelles façons de déclarer les variables ont été introduites. Il y a ce qui suit:
let: crée une variable de portée de bloc
const: crée une variable de portée de bloc qui doit être initialisée et ne peut pas être réaffectée
La plus grande différence entre varet let/ constest la varportée de la fonction tandis que let/ constest la portée du bloc. Voici un exemple pour illustrer cela:
let letVar ='global';var varVar ='global';function foo (){if(true){// this variable declared with let is scoped to the if block, block scopedlet letVar =5;// this variable declared with let is scoped to the function block, function scopedvar varVar =10;}
console.log(letVar);
console.log(varVar);}
foo();
Dans l'exemple ci-dessus, letVar enregistre la valeur globale car les variables déclarées avec letsont de portée bloc. Ils cessent d'exister en dehors de leur bloc respectif, de sorte que la variable n'est pas accessible en dehors du bloc if.
Dans EcmaScript5, il existe principalement deux étendues, portée locale et portée globale, mais dans EcmaScript6, nous avons principalement trois étendues, portée locale, portée globale et une nouvelle portée appelée étendue de bloc .
Exemple de portée de bloc: -
for(let i =0; i <10; i++){
statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.}
ECMAScript 6 a introduit les mots clés let et const. Ces mots clés peuvent être utilisés à la place du mot clé var. Contrairement au mot clé var, les mots clés let et const prennent en charge la déclaration de portée locale à l'intérieur des instructions de bloc.
var x =10let y =10const z =10{
x =20let y =20const z =20{
x =30// x is in the global scope because of the 'var' keywordlet y =30// y is in the local scope because of the 'let' keywordconst z =30// z is in the local scope because of the 'const' keyword
console.log(x)// 30
console.log(y)// 30
console.log(z)// 30}
console.log(x)// 30
console.log(y)// 20
console.log(z)// 20}
console.log(x)// 30
console.log(y)// 10
console.log(z)// 10
J'aime vraiment la réponse acceptée mais je veux ajouter ceci:
Scope collecte et gère une liste de recherche de tous les identifiants déclarés (variables) et applique un ensemble strict de règles sur la façon dont celles-ci sont accessibles au code en cours d'exécution.
La portée est un ensemble de règles pour rechercher des variables par leur nom d'identifiant.
Si une variable est introuvable dans la portée immédiate, le moteur consulte la prochaine portée extérieure contenante, en continuant jusqu'à ce qu'elle soit trouvée ou jusqu'à ce que la portée la plus externe (aka, globale) soit atteinte.
Ensemble de règles qui détermine où et comment une variable (identifiant) peut être recherchée. Cette recherche peut être effectuée dans le but d'affecter à la variable, qui est une référence LHS (côté gauche), ou dans le but de récupérer sa valeur, qui est une référence RHS (côté droit). .
Les références LHS résultent des opérations d'affectation. Les affectations liées à l'étendue peuvent se produire soit avec l'opérateur =, soit en passant des arguments aux paramètres de fonction (assign to).
Le moteur JavaScript compile d'abord le code avant de s'exécuter et, ce faisant, il divise les instructions comme var a = 2; en deux étapes distinctes: 1ère. Tout d'abord, var a pour le déclarer dans cette étendue. Ceci est effectué au début, avant l'exécution du code. 2e. Plus tard, a = 2 pour rechercher la variable (référence LHS) et l'affecter si elle est trouvée.
Les recherches de référence LHS et RHS commencent à la portée en cours d'exécution, et si besoin est (c'est-à-dire qu'elles ne trouvent pas ce qu'elles recherchent là-bas), elles remontent la portée imbriquée, une portée (étage ) à la fois, à la recherche de l'identifiant, jusqu'à ce qu'ils atteignent le niveau global (dernier étage) et s'arrêtent, et qu'ils le trouvent ou non. Les références RHS non remplies entraînent le renvoi de ReferenceError. Les références LHS non remplies se traduisent par un global automatique, implicitement créé de ce nom (s'il n'est pas en mode strict), ou une ReferenceError (s'il est en mode strict).
scope consiste en une série de «bulles» qui agissent chacune comme un conteneur ou un bucket, dans lequel des identifiants (variables, fonctions) sont déclarés. Ces bulles s'emboîtent parfaitement les unes dans les autres, et cette imbrication est définie au moment de l'auteur.
Réponses:
TLDR
JavaScript a une portée et des fermetures lexicales (également appelées statiques). Cela signifie que vous pouvez déterminer la portée d'un identifiant en consultant le code source.
Les quatre champs d'application sont les suivants:
En dehors des cas particuliers de portée globale et de module, les variables sont déclarées à l'aide de
var
(portée de fonction),let
(portée de bloc) etconst
(portée de bloc). La plupart des autres formes de déclaration d'identifiant ont une portée de bloc en mode strict.Aperçu
La portée est la région de la base de code sur laquelle un identifiant est valide.
Un environnement lexical est un mappage entre les noms d'identifiants et les valeurs qui leur sont associées.
La portée est formée d'une imbrication liée d'environnements lexicaux, chaque niveau de l'imbrication correspondant à un environnement lexical d'un contexte d'exécution d'ancêtre.
Ces environnements lexicaux liés forment une "chaîne" de portée. La résolution de l'identifiant est le processus de recherche le long de cette chaîne d'un identifiant correspondant.
La résolution de l'identifiant ne se produit que dans une seule direction: vers l'extérieur. De cette façon, les environnements lexicaux externes ne peuvent pas "voir" dans les environnements lexicaux internes.
Il y a trois facteurs pertinents pour décider de la portée d'un identifiant dans JavaScript:
Certaines des façons dont les identifiants peuvent être déclarés:
var
,let
etconst
var
en mode non strict)import
déclarationseval
Certains des identifiants d'emplacements peuvent être déclarés:
Styles de déclaration
var
Les identificateurs déclarés à l'aide
var
ont une portée de fonction , sauf lorsqu'ils sont déclarés directement dans le contexte global, auquel cas ils sont ajoutés en tant que propriétés sur l'objet global et ont une portée globale. Il existe des règles distinctes pour leur utilisation dans leseval
fonctions.let et const
Les identifiants déclarés utilisant
let
etconst
ont une portée de bloc , sauf lorsqu'ils sont déclarés directement dans le contexte global, auquel cas ils ont une portée globale.Note:
let
,const
etvar
sont tous hissés . Cela signifie que leur position logique de définition est au sommet de leur portée englobante (bloc ou fonction). Cependant, les variables ont déclaré utiliserlet
etconst
ne peuvent pas être lues ou affectées jusqu'à ce que le contrôle ait passé le point de déclaration dans le code source. La période intérimaire est connue comme la zone morte temporelle.Noms des paramètres de fonction
Les noms des paramètres de fonction sont étendus au corps de la fonction. Notez qu'il y a une légère complexité à cela. Les fonctions déclarées comme arguments par défaut se ferment sur la liste des paramètres et non sur le corps de la fonction.
Déclarations de fonctions
Les déclarations de fonctions ont une portée de bloc en mode strict et une portée de fonction en mode non strict. Remarque: le mode non strict est un ensemble compliqué de règles émergentes basées sur les implémentations historiques originales de différents navigateurs.
Expressions de fonction nommées
Les expressions de fonction nommées sont limitées à elles-mêmes (par exemple, à des fins de récursivité).
Propriétés définies implicitement sur l'objet global
En mode non strict, les propriétés définies implicitement sur l'objet global ont une portée globale, car l'objet global se trouve en haut de la chaîne de portée. En mode strict, ceux-ci ne sont pas autorisés.
eval
Dans les
eval
chaînes, les variables déclarées à l'aidevar
seront placées dans la portée actuelle ou, si elleseval
sont utilisées indirectement, comme propriétés sur l'objet global.Exemples
Ce qui suit lancera une ReferenceError parce que les noms
x
,y
etz
n'ont aucune signification en dehors de la fonctionf
.Ce qui suit lancera une ReferenceError pour
y
etz
, mais pas pourx
, car la visibilité dex
n'est pas limitée par le bloc. Les blocs qui définissent les corps des structures de contrôle commeif
,for
etwhile
, se comportent de manière similaire.Dans ce qui suit,
x
est visible en dehors de la boucle carvar
a une portée de fonction:... à cause de ce comportement, vous devez faire attention à ne pas fermer les variables déclarées à l'aide
var
de boucles in. Il n'y a qu'une seule instance de variablex
déclarée ici, et elle se trouve logiquement en dehors de la boucle.Les impressions suivantes
5
, cinq fois, puis5
une sixième fois pour l'console.log
extérieur de la boucle:Ce qui suit s'imprime
undefined
carx
est de portée bloc. Les rappels sont exécutés un par un de manière asynchrone. Nouveau comportement pour deslet
moyens variables que chaque fonction anonyme fermée sur une autre variable nomméex
(contrairement à il l' aurait fait avecvar
), et ainsi entiers à0
travers4
sont imprimés .:Ce qui suit ne lancera PAS un
ReferenceError
car la visibilité dex
n'est pas limitée par le bloc; il s'imprimera cependantundefined
car la variable n'a pas été initialisée (à cause de l'if
instruction).Une variable déclarée en haut d'une
for
boucle à l'aidelet
est portée au corps de la boucle:Ce qui suit lancera un
ReferenceError
car la visibilité dex
est limitée par le bloc:Les variables déclarées en utilisant
var
,let
ouconst
sont toutes étendues aux modules:Ce qui suit déclarera une propriété sur l'objet global, car les variables déclarées en utilisant
var
dans le contexte global, sont ajoutées en tant que propriétés à l'objet global:let
etconst
dans le contexte global n'ajoutent pas de propriétés à l'objet global, mais ont toujours une portée globale:Les paramètres de fonction peuvent être considérés comme déclarés dans le corps de la fonction:
Les paramètres du bloc de capture sont limités au corps du bloc de capture:
Les expressions de fonction nommées sont limitées à l'expression elle-même:
En mode non strict, les propriétés définies implicitement sur l'objet global ont une portée globale. En mode strict, vous obtenez une erreur.
En mode non strict, les déclarations de fonction ont une portée de fonction. En mode strict, ils ont une portée de bloc.
Comment ça marche sous le capot
La portée est définie comme la région lexicale du code sur laquelle un identifiant est valide.
En JavaScript, chaque objet-fonction a une
[[Environment]]
référence cachée qui est une référence à l' environnement lexical du contexte d'exécution (cadre de pile) dans lequel il a été créé.Lorsque vous appelez une fonction, la
[[Call]]
méthode cachée est appelée. Cette méthode crée un nouveau contexte d'exécution et établit un lien entre le nouveau contexte d'exécution et l'environnement lexical de l'objet-fonction. Il le fait en copiant la[[Environment]]
valeur sur l'objet-fonction, dans un champ de référence externe sur l'environnement lexical du nouveau contexte d'exécution.Notez que ce lien entre le nouveau contexte d'exécution et l'environnement lexical de l'objet fonction est appelé fermeture .
Ainsi, en JavaScript, la portée est implémentée via des environnements lexicaux reliés entre eux dans une "chaîne" par des références externes. Cette chaîne d'environnements lexicaux est appelée la chaîne de portée, et la résolution de l'identifiant se produit en recherchant dans la chaîne un identifiant correspondant.
Apprenez-en plus .
la source
Javascript utilise des chaînes de portée pour établir la portée d'une fonction donnée. Il existe généralement une étendue globale et chaque fonction définie a sa propre étendue imbriquée. Toute fonction définie dans une autre fonction a une portée locale qui est liée à la fonction externe. C'est toujours la position dans la source qui définit la portée.
Un élément de la chaîne de portée est essentiellement une carte avec un pointeur sur sa portée parent.
Lors de la résolution d'une variable, javascript démarre à l'étendue la plus intérieure et recherche vers l'extérieur.
la source
Les variables déclarées globalement ont une portée globale. Les variables déclarées dans une fonction sont étendues à cette fonction et masquent les variables globales du même nom.
(Je suis sûr qu'il ya beaucoup de subtilités que les vrais programmeurs JavaScript seront en mesure de signaler dans d' autres réponses. En particulier , je suis tombé sur cette page sur ce que exactement les
this
moyens à tout moment. Espérons que ce lien plus d' introduction est assez pour vous aider à démarrer si .)la source
JavaScript old school
Traditionnellement, JavaScript n'a vraiment que deux types de portée:
Je ne m'étendrai pas là-dessus, car il existe déjà de nombreuses autres réponses expliquant la différence.
JavaScript moderne
Les spécifications JavaScript les plus récentes autorisent désormais une troisième portée:
Comment créer des variables d'étendue de bloc?
Traditionnellement, vous créez vos variables comme ceci:
Les variables de portée de bloc sont créées comme suit:
Quelle est donc la différence entre la portée fonctionnelle et la portée du bloc?
Pour comprendre la différence entre l'étendue fonctionnelle et l'étendue de bloc, tenez compte du code suivant:
Ici, nous pouvons voir que notre variable
j
n'est connue que dans la première boucle for, mais pas avant ni après. Pourtant, notre variablei
est connue dans toute la fonction.Tenez également compte du fait que les variables de portée de bloc ne sont pas connues avant d'être déclarées car elles ne sont pas hissées. Vous n'êtes pas non plus autorisé à redéclarer la même variable de portée de bloc dans le même bloc. Cela rend les variables de portée de bloc moins sujettes aux erreurs que les variables de portée globale ou fonctionnelle, qui sont hissées et qui ne produisent aucune erreur en cas de déclarations multiples.
Est-il sûr d'utiliser des variables d'étendue de bloc aujourd'hui?
Que son utilisation soit sûre ou non aujourd'hui, cela dépend de votre environnement:
Si vous écrivez du code JavaScript côté serveur ( Node.js ), vous pouvez utiliser l'
let
instruction en toute sécurité .Si vous écrivez du code JavaScript côté client et utilisez un transpilateur basé sur un navigateur (comme Traceur ou babel-standalone ), vous pouvez utiliser en toute sécurité l'
let
instruction, cependant votre code est susceptible d'être tout sauf optimal en termes de performances.Si vous écrivez du code JavaScript côté client et utilisez un transpilateur basé sur Node (comme le script shell traceur ou Babel ), vous pouvez utiliser l'
let
instruction en toute sécurité . Et comme votre navigateur ne connaîtra que le code transpilé, les inconvénients de performances devraient être limités.Si vous écrivez du code JavaScript côté client et n'utilisez pas de transpilateur, vous devez considérer la prise en charge du navigateur.
Voici quelques navigateurs qui ne prennent pas en charge
let
du tout:Comment suivre la prise en charge du navigateur
Pour une vue d'ensemble à jour des navigateurs qui prennent en charge la
let
déclaration au moment de la lecture de cette réponse, consultez cetteCan I Use
page .(*) Les variables de portée globale et fonctionnelle peuvent être initialisées et utilisées avant d'être déclarées car les variables JavaScript sont hissées . Cela signifie que les déclarations sont toujours bien en haut de la portée.
la source
Voici un exemple:
Vous voudrez enquêter sur les fermetures et savoir comment les utiliser pour créer des membres privés .
la source
La clé, si je comprends bien, est que Javascript a une portée de niveau de fonction par rapport à la portée de bloc C la plus courante.
Voici un bon article sur le sujet.
la source
Dans "Javascript 1.7" (l'extension de Mozilla à Javascript), on peut également déclarer des variables de portée de bloc avec l'
let
instruction :la source
let
.L'idée de la portée en JavaScript lorsqu'elle a été conçue à l'origine par Brendan Eich est venue du langage de script HyperCard HyperTalk .
Dans cette langue, les affichages étaient similaires à ceux d'une pile de fiches. Il y avait une carte maîtresse appelée arrière-plan. Il était transparent et peut être vu comme la carte du bas. Tout le contenu de cette carte de base a été partagé avec des cartes placées dessus. Chaque carte placée au-dessus avait son propre contenu qui avait priorité sur la carte précédente, mais avait toujours accès aux cartes précédentes si désiré.
C'est exactement comme cela que le système de portée JavaScript est conçu. Il a juste des noms différents. Les cartes en JavaScript sont connues sous le nom de contextes d'exécution ECMA . Chacun de ces contextes contient trois parties principales. Un environnement variable, un environnement lexical et une cette liaison. Pour en revenir à la référence des cartes, l'environnement lexical contient tout le contenu des cartes précédentes plus bas dans la pile. Le contexte actuel est en haut de la pile et tout contenu déclaré y sera stocké dans l'environnement variable. L'environnement variable aura la priorité dans le cas de collisions de noms.
Cette liaison pointera vers l'objet contenant. Parfois, les étendues ou les contextes d'exécution changent sans que l'objet conteneur ne change, comme dans une fonction déclarée où l'objet conteneur peut être
window
ou une fonction constructeur.Ces contextes d'exécution sont créés chaque fois que le contrôle est transféré. Le contrôle est transféré lorsque le code commence à s'exécuter, et cela se fait principalement à partir de l'exécution de la fonction.
Voilà donc l'explication technique. En pratique, il est important de se rappeler qu'en JavaScript
En appliquant cela à l'un des exemples précédents (5. "Clôture") de cette page, il est possible de suivre la pile des contextes d'exécution. Dans cet exemple, il y a trois contextes dans la pile. Ils sont définis par le contexte externe, le contexte de la fonction immédiatement invoquée appelée par var six et le contexte de la fonction retournée à l'intérieur de la fonction immédiatement invoquée de var six.
i ) Le contexte extérieur. Il a un environnement variable de a = 1
ii ) Le contexte IIFE, il a un environnement lexical de a = 1, mais un environnement variable de a = 6 qui a priorité dans la pile
iii ) Le contexte de fonction retourné, il a un lexical environnement de a = 6 et qui est la valeur référencée dans l'alerte lors de l'appel.
la source
1) Il existe une étendue globale, une étendue de fonction et les étendues with et catch. Il n'y a pas de portée de niveau «bloc» en général pour les variables - les instructions with et catch ajoutent des noms à leurs blocs.
2) Les portées sont imbriquées par des fonctions jusqu'à la portée globale.
3) Les propriétés sont résolues en passant par la chaîne du prototype. L'instruction with introduit les noms de propriété d'objet dans la portée lexicale définie par le bloc with.
EDIT: ECMAAScript 6 (Harmony) est spécifié pour prendre en charge let, et je sais que chrome autorise un drapeau «harmonie», donc peut-être qu'il le prend en charge ..
Soit un support pour la portée au niveau du bloc, mais vous devez utiliser le mot-clé pour y arriver.
EDIT: Sur la base des remarques de Benjamin sur les déclarations with et catch dans les commentaires, j'ai édité le post et ajouté plus. Tant le avec et les déclarations de capture des variables dans leurs introduisons blocs respectifs, et qui est un champ de bloc. Ces variables sont aliasées aux propriétés des objets qui leur sont passés.
EDIT: Exemple de clarification:
test1 est limité au bloc with, mais a un alias vers a.test1. 'Var test1' crée une nouvelle variable test1 dans le contexte lexical supérieur (fonction ou global), à moins que ce ne soit une propriété de a - ce qu'elle est.
Oui! Soyez prudent en utilisant 'avec' - tout comme var est un noop si la variable est déjà définie dans la fonction, c'est aussi un noop en ce qui concerne les noms importés de l'objet! Un petit avertissement sur le nom déjà défini rendrait cela beaucoup plus sûr. Personnellement, je ne l'utiliserai jamais avec à cause de cela.
la source
with
instruction est une forme de portée de bloc, mais lescatch
clauses sont une forme beaucoup plus courante (Fait amusant, v8 implémentecatch
avec awith
) - c'est à peu près les seules formes de portée de bloc dans JavaScript lui-même (c'est-à-dire, fonction, global, try / catch , avec et leurs dérivés), mais les environnements hôtes ont différentes notions de portée - par exemple les événements en ligne dans le navigateur et le module vm de NodeJS.J'ai constaté que de nombreuses personnes nouvelles dans JavaScript ont du mal à comprendre que l'héritage est disponible par défaut dans le langage et que la portée de la fonction est la seule portée jusqu'à présent. J'ai fourni une extension à un embellisseur que j'ai écrit à la fin de l'année dernière, appelé JSPretty. Les couleurs de fonction ont une portée dans le code et associent toujours une couleur à toutes les variables déclarées dans cette portée. La fermeture est démontrée visuellement lorsqu'une variable avec une couleur d'une étendue est utilisée dans une étendue différente.
Essayez la fonctionnalité sur:
Voir une démo sur:
Consultez le code sur:
Actuellement, la fonctionnalité prend en charge une profondeur de 16 fonctions imbriquées, mais ne colore actuellement pas les variables globales.
la source
JavaScript n'a que deux types de portée:
var
mot clé a une portée fonctionnelle.Chaque fois qu'une fonction est appelée, un objet de portée variable est créé (et inclus dans la chaîne de portée) qui est suivi par des variables en JavaScript.
Chaîne de lunette ->
a
et laouter
fonction sont au niveau supérieur dans la chaîne de portée.variable scope object
(et incluse dans la chaîne de portée) ajoutée avec une variable à l'b
intérieur.Maintenant, lorsqu'une variable est
a
requise, elle recherche d'abord l'étendue de la variable la plus proche et si la variable n'est pas là, elle se déplace vers l'objet suivant de la chaîne d'étendue de la variable.la source
Juste pour ajouter aux autres réponses, la portée est une liste de recherche de tous les identificateurs déclarés (variables), et applique un ensemble strict de règles quant à la façon dont celles-ci sont accessibles au code en cours d'exécution. Cette recherche peut être effectuée dans le but d'affecter à la variable, qui est une référence LHS (côté gauche), ou dans le but de récupérer sa valeur, qui est une référence RHS (côté droit). Ces recherches sont ce que fait le moteur JavaScript en interne lorsqu'il compile et exécute le code.
Donc, de ce point de vue, je pense qu'une image serait utile que j'ai trouvée dans l'ebook Scopes and Closures de Kyle Simpson:
Citant de son ebook:
Une chose à noter, "La recherche de portée s'arrête une fois qu'elle trouve la première correspondance".
Cette idée de «niveaux de portée» explique pourquoi «ceci» peut être modifié avec une portée nouvellement créée, si elle est recherchée dans une fonction imbriquée. Voici un lien qui rentre dans tous ces détails, Tout ce que vous vouliez savoir sur la portée javascript
la source
exécutez le code. espérons que cela vous donnera une idée de la portée
la source
Portée mondiale:
Les variables globales sont exactement comme les étoiles globales (Jackie Chan, Nelson Mandela). Vous pouvez y accéder (obtenir ou définir la valeur), à partir de n'importe quelle partie de votre application. Les fonctions globales sont comme des événements mondiaux (Nouvel An, Noël). Vous pouvez les exécuter (appeler) à partir de n'importe quelle partie de votre application.
Portée locale:
Si vous êtes aux États-Unis, vous connaissez peut-être Kim Kardashian, une célébrité infâme (elle parvient en quelque sorte à faire les tabloïds). Mais les gens en dehors des États-Unis ne la reconnaîtront pas. C'est une star locale, liée à son territoire.
Les variables locales sont comme des étoiles locales. Vous ne pouvez y accéder (obtenir ou définir la valeur) qu'à l'intérieur de la portée. Une fonction locale est comme des événements locaux - vous ne pouvez exécuter (célébrer) que dans cette portée. Si vous souhaitez y accéder depuis l'extérieur de la portée, vous obtiendrez une erreur de référence
Consultez cet article pour une compréhension approfondie de la portée
la source
Il existe PRESQUE seulement deux types d'étendues JavaScript:
Ainsi, tous les blocs autres que les fonctions ne créent pas de nouvelle étendue. Cela explique pourquoi les boucles for remplacent les variables de portée externes:
Utiliser des fonctions à la place:
Dans le premier exemple, il n'y avait pas de portée de bloc, donc les variables initialement déclarées ont été écrasées. Dans le deuxième exemple, il y avait une nouvelle portée en raison de la fonction, donc les variables initialement déclarées étaient SHADOWED, et non écrasées.
C'est presque tout ce que vous devez savoir en termes de portée de JavaScript, sauf:
Vous pouvez donc voir que la portée de JavaScript est en fait extrêmement simple, mais pas toujours intuitive. Quelques choses à savoir:
Donc, ce code:
est équivalent à:
Cela peut sembler contre-intuitif, mais il est logique du point de vue d'un concepteur de langage impératif.
la source
Js modernes, ES6 +, '
const
' et 'let
'Vous devez utiliser l'étendue des blocs pour chaque variable que vous créez, comme la plupart des autres langages principaux.
var
est obsolète . Cela rend votre code plus sûr et plus facile à gérer.const
doit être utilisé dans 95% des cas . Il fait en sorte que la référence de variable ne puisse pas changer. Les propriétés des tableaux, des objets et des nœuds DOM peuvent changer et devraient probablement êtreconst
.let
doit être utilisé pour toute variable devant être réaffectée. Cela inclut une boucle for. Si vous changez la valeur au-delà de l'initialisation, utilisezlet
.La portée du bloc signifie que la variable ne sera disponible que dans les crochets dans lesquels elle est déclarée. Cela s'étend aux étendues internes, y compris les fonctions anonymes créées dans votre étendue.
la source
Essayez cet exemple curieux. Dans l'exemple ci-dessous, si a était un numérique initialisé à 0, vous verriez 0 puis 1. Sauf que a est un objet et que javascript passera f1 un pointeur de a plutôt qu'une copie de celui-ci. Le résultat est que vous obtenez la même alerte les deux fois.
la source
Il n'y a que des étendues de fonction dans JS. Ne bloquez pas les portées! Vous pouvez également voir ce qui est hissé.
la source
Je crois comprendre qu'il y a 3 portées: portée mondiale, disponible à l'échelle mondiale; portée locale, disponible pour une fonction entière indépendamment des blocs; et la portée du bloc, uniquement disponible pour le bloc, l'instruction ou l'expression sur laquelle il a été utilisé. La portée globale et locale est indiquée par le mot-clé «var», à l'intérieur ou à l'extérieur d'une fonction, et la portée du bloc est indiquée par le mot-clé «let».
Pour ceux qui pensent qu'il n'y a qu'une portée globale et locale, veuillez expliquer pourquoi Mozilla aurait une page entière décrivant les nuances de la portée des blocs dans JS.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
la source
Un problème très courant qui n'est pas encore décrit et que les codeurs frontaux rencontrent souvent est l'étendue visible par un gestionnaire d'événements en ligne dans le HTML - par exemple, avec
La portée des variables qu'un
on*
attribut peut référencer doit être soit:querySelector
comme une variable autonome pointera versdocument.querySelector
; rare)Sinon, vous obtiendrez une ReferenceError lorsque le gestionnaire est appelé. Ainsi, par exemple, si le gestionnaire en ligne fait référence à une fonction définie à l' intérieur de
window.onload
ou$(function() {
, la référence échouera, car le gestionnaire en ligne peut uniquement référencer des variables dans la portée globale et la fonction n'est pas globale:Afficher l'extrait de code
Les propriétés de
document
et les propriétés de l'élément auquel le gestionnaire est attaché peuvent également être référencées en tant que variables autonomes dans les gestionnaires en ligne car les gestionnaires en ligne sont appelés à l' intérieur de deuxwith
blocs , un pour ledocument
, un pour l'élément. La chaîne de portée des variables à l'intérieur de ces gestionnaires est extrêmement peu intuitive , et un gestionnaire d'événements de travail nécessitera probablement une fonction pour être globale (et une pollution globale inutile devrait probablement être évitée ).Étant donné que la chaîne de portée à l'intérieur des gestionnaires en ligne est si étrange et que les gestionnaires en ligne nécessitent une pollution globale pour fonctionner et que les gestionnaires en ligne nécessitent parfois un échappement de chaîne laid lors du passage d'arguments, il est probablement plus facile de les éviter. Au lieu de cela, attachez des gestionnaires d'événements à l'aide de Javascript (comme avec
addEventListener
), plutôt qu'avec du balisage HTML.Afficher l'extrait de code
Sur une note différente, contrairement aux
<script>
balises normales , qui s'exécutent au niveau supérieur, le code à l'intérieur des modules ES6 s'exécute dans sa propre portée privée. Une variable définie en haut d'une<script>
balise normale est globale, vous pouvez donc la référencer dans d'autres<script>
balises, comme ceci:Afficher l'extrait de code
Mais le niveau supérieur d'un module ES6 n'est pas global. Une variable déclarée en haut d'un module ES6 ne sera visible qu'à l'intérieur de ce module, à moins que la variable ne soit explicitement
export
éditée, ou à moins qu'elle ne soit affectée à une propriété de l'objet global.Afficher l'extrait de code
Le niveau supérieur d'un module ES6 est similaire à celui de l'intérieur d'un IIFE au niveau supérieur dans une normale
<script>
. Le module peut référencer toutes les variables qui sont globales, et rien ne peut référencer quoi que ce soit à l'intérieur du module à moins que le module ne soit explicitement conçu pour cela.la source
En JavaScript, il existe deux types de portée:
La fonction ci-dessous a une variable de portée locale
carName
. Et cette variable n'est pas accessible de l'extérieur de la fonction.La classe ci-dessous a une variable de portée globale
carName
. Et cette variable est accessible de partout dans la classe.la source
ES5
et plus tôt:Les variables en Javascript avaient initialement une
ES6
portée (pré ) lexicale. Le terme à portée lexicale signifie que vous pouvez voir la portée des variables en «regardant» le code.Chaque variable déclarée avec le
var
mot clé est étendue à la fonction. Cependant, si d'autres fonctions sont déclarées dans cette fonction, ces fonctions auront accès aux variables des fonctions externes. C'est ce qu'on appelle une chaîne de portée . Cela fonctionne de la manière suivante:Exemple:
Que se passe-t-il lorsque nous essayons de consigner les variables
foo
,bar
etfoobar
la console est la suivante:innerFunc
elle-même. Par conséquent, la valeur de foo est résolue dans la chaîneinnerFunc
.innerFunc
elle-même. Par conséquent, nous devons gravir la chaîne de portée . Nous examinons d'abord la fonction extérieure dans laquelle la fonctioninnerFunc
été définie. Telle est la fonctionouterFunc
. Dans le cadre de,outerFunc
nous pouvons trouver la barre de variables, qui contient la chaîne 'externalFunc'.ES6
(ES 2015) et plus ancien:Les mêmes concepts de portée lexicale et de chaîne de portée s'appliquent toujours
ES6
. Cependant, de nouvelles façons de déclarer les variables ont été introduites. Il y a ce qui suit:let
: crée une variable de portée de blocconst
: crée une variable de portée de bloc qui doit être initialisée et ne peut pas être réaffectéeLa plus grande différence entre
var
etlet
/const
est lavar
portée de la fonction tandis quelet
/const
est la portée du bloc. Voici un exemple pour illustrer cela:Dans l'exemple ci-dessus, letVar enregistre la valeur globale car les variables déclarées avec
let
sont de portée bloc. Ils cessent d'exister en dehors de leur bloc respectif, de sorte que la variable n'est pas accessible en dehors du bloc if.la source
Dans EcmaScript5, il existe principalement deux étendues, portée locale et portée globale, mais dans EcmaScript6, nous avons principalement trois étendues, portée locale, portée globale et une nouvelle portée appelée étendue de bloc .
Exemple de portée de bloc: -
la source
ECMAScript 6 a introduit les mots clés let et const. Ces mots clés peuvent être utilisés à la place du mot clé var. Contrairement au mot clé var, les mots clés let et const prennent en charge la déclaration de portée locale à l'intérieur des instructions de bloc.
la source
J'aime vraiment la réponse acceptée mais je veux ajouter ceci:
Scope collecte et gère une liste de recherche de tous les identifiants déclarés (variables) et applique un ensemble strict de règles sur la façon dont celles-ci sont accessibles au code en cours d'exécution.
La portée est un ensemble de règles pour rechercher des variables par leur nom d'identifiant.
la source
Il existe deux types de portées en JavaScript.
Portée globale : la variable annoncée dans la portée globale peut être utilisée n'importe où dans le programme très facilement. Par exemple:
Portée fonctionnelle ou Portée locale : la variable déclarée dans cette portée ne peut être utilisée que dans sa propre fonction. Par exemple:
la source