Recherche…
Remarques
Scope est le contexte dans lequel les variables vivent et peut être accédé par un autre code dans la même portée. JavaScript pouvant être largement utilisé comme langage de programmation fonctionnel, il est important de connaître la portée des variables et des fonctions, car cela permet d'éviter les bogues et les comportements inattendus à l'exécution.
Différence entre var et let
(Remarque: tous les exemples utilisant let
sont également valables pour const
)
var
est disponible dans toutes les versions de JavaScript, alors que let
et const
font partie d'ECMAScript 6 et sont uniquement disponibles dans certains navigateurs récents .
var
est portée à la fonction contenant ou à l'espace global, selon qu'elle est déclarée:
var x = 4; // global scope
function DoThings() {
var x = 7; // function scope
console.log(x);
}
console.log(x); // >> 4
DoThings(); // >> 7
console.log(x); // >> 4
Cela signifie qu'il "échappe" if
instructions et toutes les constructions de blocs similaires:
var x = 4;
if (true) {
var x = 7;
}
console.log(x); // >> 7
for (var i = 0; i < 4; i++) {
var j = 10;
}
console.log(i); // >> 4
console.log(j); // >> 10
À titre de comparaison, let
champ d’attente:
let x = 4;
if (true) {
let x = 7;
console.log(x); // >> 7
}
console.log(x); // >> 4
for (let i = 0; i < 4; i++) {
let j = 10;
}
console.log(i); // >> "ReferenceError: i is not defined"
console.log(j); // >> "ReferenceError: j is not defined"
Notez que i
et j
ne sont déclarés que dans la boucle for
et sont donc non déclarés en dehors de celle-ci.
Il existe plusieurs autres différences cruciales:
Déclaration de variable globale
Dans la portée supérieure (en dehors des fonctions et des blocs), les déclarations var
placent un élément dans l'objet global. let
ne pas:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Re-déclaration
Déclarer une variable deux fois en utilisant var
ne produit pas d'erreur (même si cela équivaut à la déclarer une fois):
var x = 4;
var x = 7;
Avec let
, cela produit une erreur:
let x = 4;
let x = 7;
TypeError: l'identifiant
x
a déjà été déclaré
La même chose est vraie lorsque y
est déclaré avec var
:
var y = 4;
let y = 7;
TypeError: l'identifiant
y
a déjà été déclaré
Cependant les variables déclarées avec let peuvent être réutilisées (non re-déclarées) dans un bloc imbriqué
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
Au sein du bloc l'extérieur i
est inaccessible, mais si le bloc a une à l' intérieur let
déclaration pour i
, l'extérieur i
ne peux pas être accessible et jetterai un ReferenceError
si elle est utilisée avant la seconde est déclarée.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i n'est pas défini
Levage
Les variables déclarées à la fois avec var
et let
sont hissées . La différence est qu’une variable déclarée avec var
peut être référencée avant sa propre affectation, car elle est automatiquement attribuée (avec une valeur undefined
), mais let
peut pas - elle exige spécifiquement que la variable soit déclarée avant d’être invoquée:
console.log(x); // >> undefined
console.log(y); // >> "ReferenceError: `y` is not defined"
//OR >> "ReferenceError: can't access lexical declaration `y` before initialization"
var x = 4;
let y = 7;
La zone située entre le début d'un bloc et une déclaration let
ou const
est appelée zone morte temporelle , et toute référence à la variable dans cette zone provoquera une ReferenceError
. Cela se produit même si la variable est affectée avant d'être déclarée :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
En mode non strict, l' affectation d'une valeur à une variable sans déclaration déclare automatiquement la variable dans la portée globale . Dans ce cas, au lieu de y
étant automatiquement déclaré dans la portée globale, let
le nom de la variable ( y
) en réserve et n'autorisez aucun accès ou affectation avant la ligne où il est déclaré / initialisé.
Fermetures
Lorsqu'une fonction est déclarée, les variables dans le contexte de sa déclaration sont capturées dans sa portée. Par exemple, dans le code ci-dessous, la variable x
est liée à une valeur dans la portée externe, puis la référence à x
est capturée dans le contexte de la bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Sortie de l'échantillon:
4
Ce concept de portée "capturer" est intéressant car nous pouvons utiliser et modifier des variables depuis une étendue externe même après la fin de la portée externe. Par exemple, prenez en compte les éléments suivants:
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
Sortie de l'échantillon:
4
Dans l'exemple ci-dessus, lorsque foo
est appelé, son contexte est capturé dans la bar
fonctions. Donc, même après son retour, la bar
peut toujours accéder à la variable x
et la modifier. La fonction foo
, dont le contexte est capturé dans une autre fonction, est considérée comme une fermeture .
Données privées
Cela nous permet de faire des choses intéressantes, telles que la définition de variables "privées" visibles uniquement pour une fonction ou un ensemble de fonctions spécifique. Un exemple artificiel (mais populaire):
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
Sortie de l'échantillon:
1 0
Lorsque makeCounter()
est appelé, un instantané du contexte de cette fonction est enregistré. Tout le code de makeCounter()
utilisera cet instantané dans leur exécution. Deux appels de makeCounter()
créeront donc deux instantanés différents, avec leur propre copie du counter
.
Expressions de fonction immédiatement appelées (IIFE)
Les fermetures sont également utilisées pour empêcher la pollution globale des espaces de noms, souvent grâce à l'utilisation d'expressions de fonctions invoquées immédiatement.
Les expressions de fonction invoquées immédiatement (ou, peut-être de manière plus intuitive, des fonctions anonymes auto-exécutables ) sont essentiellement des fermetures appelées juste après la déclaration. L’idée générale de l’IECF est d’invoquer l’effet secondaire de la création d’un contexte distinct accessible uniquement au code de l’IECF.
Supposons que nous voulions pouvoir référencer jQuery
avec $
. Considérons la méthode naïve, sans utiliser un IIFE:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
Dans l'exemple suivant, un IIFE est utilisé pour garantir que $
est lié à jQuery
uniquement dans le contexte créé par la fermeture:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
Voir la réponse canonique sur Stackoverflow pour plus d'informations sur les fermetures.
Levage
Qu'est-ce que le levage?
Le levage est un mécanisme qui déplace toutes les déclarations de variables et de fonctions au sommet de leur portée. Cependant, les affectations de variables se produisent toujours là où elles étaient à l'origine.
Par exemple, considérez le code suivant:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
Le code ci-dessus est le même que:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Notez que le undefined
ci-dessus n'est pas le même que celui not defined
résultant de l'exécution:
console.log(foo); // → foo is not defined
Un principe similaire s'applique aux fonctions. Lorsque des fonctions sont affectées à une variable (c'est-à-dire une expression de fonction ), la déclaration de variable est hissée pendant que l'affectation reste au même endroit. Les deux extraits de code suivants sont équivalents.
console.log(foo(2, 3)); // → foo is not a function
var foo = function(a, b) {
return a * b;
}
var foo;
console.log(foo(2, 3)); // → foo is not a function
foo = function(a, b) {
return a * b;
}
Lors de la déclaration des instructions de fonction , un scénario différent se produit. Contrairement aux instructions de fonction, les déclarations de fonction sont placées au sommet de leur portée. Considérez le code suivant:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
Le code ci-dessus est identique à l'extrait de code suivant en raison du levage:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Voici quelques exemples de ce qui est ou non:
// Valid code:
foo();
function foo() {}
// Invalid code:
bar(); // → TypeError: bar is not a function
var bar = function () {};
// Valid code:
foo();
function foo() {
bar();
}
function bar() {}
// Invalid code:
foo();
function foo() {
bar(); // → TypeError: bar is not a function
}
var bar = function () {};
// (E) valid:
function foo() {
bar();
}
var bar = function(){};
foo();
Limites du levage
L'initialisation d'une variable ne peut pas être levée ou In simple JavaScript déclenche des déclarations non initialisées.
Par exemple: Les scripts ci-dessous donneront des sorties différentes.
var x = 2;
var y = 4;
alert(x + y);
Cela vous donnera une sortie de 6. Mais cela ...
var x = 2;
alert(x + y);
var y = 4;
Cela vous donnera une sortie de NaN. Puisque nous initialisons la valeur de y, le levage de JavaScript ne se produit pas, donc la valeur de y sera indéfinie. Le JavaScript considérera que y n'est pas encore déclaré.
Le deuxième exemple est donc le même que ci-dessous.
var x = 2;
var y;
alert(x + y);
y = 4;
Cela vous donnera une sortie de NaN.
Utiliser let in loops au lieu de var (exemple des gestionnaires de clic)
Disons que nous devons ajouter un bouton pour chaque élément du tableau loadedData
(par exemple, chaque bouton doit être un curseur affichant les données; pour des raisons de simplicité, nous allons simplement alerter un message). On peut essayer quelque chose comme ça:
for(var i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Mais au lieu d'alerter, chaque bouton provoquera la
TypeError: loadedData [i] n'est pas défini
Erreur. C'est parce que la portée de i
est la portée globale (ou une portée de fonction) et après la boucle, i == 3
. Ce dont nous avons besoin, ce n'est pas de "nous souvenir de l'état de i
". Cela peut être fait en utilisant let
:
for(let i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Un exemple de loadedData
à tester avec ce code:
var loadedData = [
{ label:"apple", content:"green and round" },
{ label:"blackberry", content:"small black or blue" },
{ label:"pineapple", content:"weird stuff.. difficult to explain the shape" }
];
Invocation de méthode
Invoquer une fonction comme méthode d'un objet, la valeur de this
objet sera cet objet.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
Nous pouvons maintenant appeler print comme méthode d'obj. this
sera obj
obj.print();
Cela va donc se connecter:
Foo
Invocation anonyme
En appelant une fonction en tant que fonction anonyme, this
sera l'objet global ( self
dans le navigateur).
function func() {
return this;
}
func() === window; // true
Dans le mode strict d'ECMAScript 5 , this
ne sera pas undefined
si la fonction est invoquée de manière anonyme.
(function () {
"use strict";
func();
}())
Cela va sortir
undefined
Invocation du constructeur
Lorsqu'une fonction est appelée en tant que constructeur avec le new
mot this
clé, this
prend la valeur de l'objet en cours de construction
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
Cela se connectera
{name: "Foo"}
Invocation de la fonction flèche
Lors de l' utilisation des fonctions de direction this
prend la valeur du contexte d'exécution d'enceinte est this
(à savoir, this
dans des fonctions de direction a une portée lexicale plutôt que la portée dynamique d' habitude). En code global (code qui n'appartient à aucune fonction), ce serait l'objet global. Et cela se maintient, même si vous invoquez la fonction déclarée avec la notation en flèche de l'une des autres méthodes décrites ici.
var globalThis = this; //"window" in a browser, or "global" in Node.js
var foo = (() => this);
console.log(foo() === globalThis); //true
var obj = { name: "Foo" };
console.log(foo.call(obj) === globalThis); //true
Voyez comment this
hérite du contexte plutôt que de faire référence à l'objet sur lequel la méthode a été appelée.
var globalThis = this;
var obj = {
withoutArrow: function() {
return this;
},
withArrow: () => this
};
console.log(obj.withoutArrow() === obj); //true
console.log(obj.withArrow() === globalThis); //true
var fn = obj.withoutArrow; //no longer calling withoutArrow as a method
var fn2 = obj.withArrow;
console.log(fn() === globalThis); //true
console.log(fn2() === globalThis); //true
Appliquer et appeler la syntaxe et l'appel.
Les méthodes apply
et call
de chaque fonction lui permettent de fournir une valeur personnalisée pour this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Vous remarquerez peut-être que la syntaxe des deux invocations utilisées ci-dessus est la même. c'est-à-dire que la signature est similaire.
Mais il y a une petite différence dans leur utilisation, étant donné que nous traitons des fonctions et modifions leurs étendues, nous devons toujours conserver les arguments originaux transmis à la fonction. Les deux apply
et call
support transmettent des arguments à la fonction cible comme suit:
function speak() {
var sentences = Array.prototype.slice.call(arguments);
console.log(this.name+": "+sentences);
}
var person = { name: "Sunny" };
speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups"
speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
Notez que apply
vous permet de passer un objet Array
ou un objet arguments
(comme un tableau) en tant que liste d'arguments, alors que call
nécessite que vous transmettiez chaque argument séparément.
Ces deux méthodes vous donnent la liberté d'obtenir autant de fantaisie que vous le souhaitez, comme l'implémentation d'une mauvaise version du bind
natif d'ECMAScript pour créer une fonction qui sera toujours appelée comme méthode d'un objet à partir d'une fonction d'origine.
function bind (func, obj) {
return function () {
return func.apply(obj, Array.prototype.slice.call(arguments, 1));
}
}
var obj = { name: "Foo" };
function print() {
console.log(this.name);
}
printObj = bind(print, obj);
printObj();
Cela se connectera
"Foo"
La fonction bind
a beaucoup à faire
-
obj
sera utilisé comme valeur dethis
- transmettre les arguments à la fonction
- puis retourne la valeur
Invocation liée
La méthode bind
de chaque fonction vous permet de créer une nouvelle version de cette fonction avec le contexte strictement lié à un objet spécifique. Il est particulièrement utile de forcer l'appel d'une fonction en tant que méthode d'un objet.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Cela va se connecter:
bar