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?
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?
Réponses:
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) 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.
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:
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 les eval
fonctions.
Les identifiants déclarés utilisant let
et const
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
et var
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é utiliser let
et const
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.
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.
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.
Les expressions de fonction nommées sont limitées à elles-mêmes (par exemple, à des fins de récursivité).
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.
Dans les eval
chaînes, les variables déclarées à l'aide var
seront placées dans la portée actuelle ou, si elles eval
sont utilisées indirectement, comme propriétés sur l'objet global.
Ce qui suit lancera une ReferenceError parce que les noms x
, y
et z
n'ont aucune signification en dehors de la fonction f
.
function f() {
var x = 1
let y = 1
const 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 y
et z
, mais pas pour x
, car la visibilité de x
n'est pas limitée par le bloc. Les blocs qui définissent les corps des structures de contrôle comme if
, for
et while
, se comportent de manière similaire.
{
var x = 1
let y = 1
const 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
Dans ce qui suit, x
est visible en dehors de la boucle car var
a une portée de fonction:
for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)
... à 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 variable x
déclarée ici, et elle se trouve logiquement en dehors de la boucle.
Les impressions suivantes 5
, cinq fois, puis 5
une sixième fois pour l' console.log
exté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 undefined
car x
est de portée bloc. Les rappels sont exécutés un par un de manière asynchrone. Nouveau comportement pour des let
moyens variables que chaque fonction anonyme fermée sur une autre variable nommée x
(contrairement à il l' aurait fait avec var
), et ainsi entiers à 0
travers 4
sont 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 ReferenceError
car la visibilité de x
n'est pas limitée par le bloc; il s'imprimera cependant undefined
car la variable n'a pas été initialisée (à cause de l' if
instruction).
if(false) {
var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised
Une variable déclarée en haut d'une for
boucle à l'aide let
est portée au corps de la boucle:
for(let x = 0; x < 10; ++x) {}
console.log(typeof x) // undefined, because `x` is block-scoped
Ce qui suit lancera un ReferenceError
car la visibilité de x
est limitée par le bloc:
if(false) {
let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped
Les variables déclarées en utilisant var
, let
ou const
sont toutes étendues aux modules:
// module1.js
var x = 0
export function f() {}
//module2.js
import 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 var
dans le contexte global, sont ajoutées en tant que propriétés à l'objet global:
var x = 1
console.log(window.hasOwnProperty('x')) // true
let
et const
dans le contexte global n'ajoutent pas de propriétés à l'objet global, mais ont toujours une portée globale:
let x = 1
console.log(window.hasOwnProperty('x')) // false
Les paramètres de fonction peuvent être considérés comme déclarés dans le corps de la fonction:
function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function
Les paramètres du bloc de capture sont limités au corps du bloc de capture:
try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block
Les expressions de fonction nommées sont limitées à l'expression elle-même:
(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression
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.
x = 1 // implicitly defined property on the global object (no "var"!)
console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true
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.
'use strict'
{
function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped
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 .
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 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 .)
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.
Les spécifications JavaScript les plus récentes autorisent désormais une troisième portée:
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";
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 here
function 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 here
for( 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 here
for( 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 j
n'est connue que dans la première boucle for, mais pas avant ni après. Pourtant, notre variable i
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.
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:
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 cette Can 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.
Voici un exemple:
<script>
var globalVariable = 7; //==window.globalVariable
function aGlobal( param ) { //==window.aGlobal();
//param is only accessible in this function
var 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 .
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.
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 :
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
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.
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 defined
with (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.
with
instruction est une forme de portée de bloc, mais les catch
clauses sont une forme beaucoup plus courante (Fait amusant, v8 implémente catch
avec 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.
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.
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.
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
Chaîne de lunette ->
a
et la outer
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.
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
exécutez le code. espérons que cela vous donnera une idée de la portée
Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
Name: 'object data',
f: function(){
alert(this.Name);
}
};
myObj.newFun = function(){
alert(this.Name);
}
function testFun(){
alert("Window Scope : " + window.Name +
"\nLocal Scope : " + Name +
"\nObject Scope : " + this.Name +
"\nCurrent document Scope : " + document.Name
);
}
testFun.call(myObj);
})(window,document);
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 variable
var a = 2;
//global function
function b(){
console.log(a); //access global variable
}
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
Consultez cet article pour une compréhension approfondie de la portée
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:
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:
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:
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.
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.
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 = new Date();
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.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
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
<button onclick="foo()"></button>
La portée des variables qu'un on*
attribut peut référencer doit être soit:
querySelector
comme une variable autonome pointera vers document.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:
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 deux with
blocs , 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.
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.
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.
function myFunction() {
var carName = "Volvo";
alert(carName);
// code here can use carName
}
La classe ci-dessous a une variable de portée globale carName
. Et cette variable est accessible de partout dans la classe.
class {
var carName = " Volvo";
// code here can use carName
function myFunction() {
alert(carName);
// code here can use carName
}
}
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:
// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';
function outerFunc () {
// outerFunc scope
var foo = 'outerFunc';
var foobar = 'outerFunc';
innerFunc();
function innerFunc(){
// innerFunc scope
var foo = 'innerFunc';
console.log(foo);
console.log(bar);
console.log(foobar);
}
}
outerFunc();
Que se passe-t-il lorsque nous essayons de consigner les variables foo
,bar
et foobar
la console est la suivante:
innerFunc
elle-même. Par conséquent, la valeur de foo est résolue dans la chaîne innerFunc
.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 fonction outerFunc
. 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
et let
/ const
est la var
portée de la fonction tandis que let
/ const
est 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 scoped
let letVar = 5;
// this variable declared with let is scoped to the function block, function scoped
var 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 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.
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 = 10
let y = 10
const z = 10
{
x = 20
let y = 20
const z = 20
{
x = 30
// x is in the global scope because of the 'var' keyword
let y = 30
// y is in the local scope because of the 'let' keyword
const 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.
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:
var carName = " BMW";
// code here can use carName
function myFunction() {
// code here can use carName
}
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:
// code here can not use carName
function myFunction() {
var carName = "BMW";
// code here can use carName
}