Cadrage et levage
var a = 1; function foo() { if (!a) { var a = 2; } alert(a); }; foo();
Quels résultats le code ci-dessus produira-t-il une fois exécuté ?
Bien que ce ne soit qu'un jeu d'enfant pour les programmeurs expérimentés, je suis toujours les idées communes des débutants et je les décris :
1. Créé la variable globale a et défini sa valeur comme 1
2. Fonction créée foo
3. Dans le corps de la fonction foo, l'instruction if ne sera pas exécutée car !a convertira la variable a en une valeur booléenne fausse, c'est-à-dire false
4. Ignorez la branche conditionnelle, variable d'alerte a, le résultat final devrait être la sortie 1
Eh bien, cela semble être un raisonnement impeccable, mais ce qui est surprenant c'est : la réponse est en fait 2 ! Pourquoi?
Ne vous inquiétez pas, je vais vous l'expliquer. Laissez-moi d'abord vous dire qu'il ne s'agit pas d'un bug, mais d'une fonctionnalité (non officielle) de l'interpréteur du langage JavaScript. Quelqu'un (Ben Cherry) a appelé cette fonctionnalité : Hoisting<. 🎜> (Il n'existe pas encore de traduction standard, la plus courante est Promouvoir).
Déclarations et définitions
Afin de comprendre le levage, regardons d’abord une situation simple :var a = 1;
Avez-vous déjà pensé à ce qui se passe exactement lorsque le code ci-dessus est exécuté ?
Savez-vous, en ce qui concerne ce code, laquelle des deux instructions « déclarer la variable a » ou « définir la variable a » est correcte ?
•L'exemple suivant est appelé « déclaration de variables » :
•L'exemple suivant est appelé « définition de variables » :
var a = 1;
•Déclaration : Cela signifie que vous revendiquez l'existence de quelque chose, comme une variable ou une fonction ; mais vous n'expliquez pas ce qu'est une telle chose, vous dites simplement à l'interprète qu'une telle chose existe ; • Définition : cela signifie que vous spécifiez l'implémentation spécifique de quelque chose, comme la valeur d'une variable, le corps d'une fonction, et que vous exprimez exactement la signification d'une telle chose.
var a; // Ceci est une déclaration
a = 1; // Ceci est la définition (affectation)
var a = 1; // Combiner deux en un : déclarer l'existence d'une variable et lui attribuer une valeur
Voici le point clé : lorsque vous pensez n'avoir fait qu'une seule chose (var a = 1), l'interprète a en fait décomposé cette chose en deux étapes, l'une est la déclaration (var a), et l'autre est la définition ( une = 1).
Qu'est-ce que cela a à voir avec le levage ?
Pour en revenir à l'exemple déroutant du début, laissez-moi vous expliquer comment l'interpréteur analyse votre code :
var a; a = 1; function foo() { var a; // 关键在这里 if (!a) { a = 2; } alert(a); // 此时的 a 并非函数体外的那个全局变量 }
Quelqu'un peut demander : "Pourquoi ne pas déclarer la variable a dans l'instruction if
Parce que JavaScript n'a pas de portée de bloc (Block Scoping), seulement une portée de fonction (Function Scoping), donc si vous voyez une paire d'accolades {}, cela signifie qu'une nouvelle portée a été générée, qui est différente de C !
Lorsque l'analyseur lit l'instruction if, il voit qu'il y a une déclaration et une affectation de variable, donc l'analyseur élèvera sa déclaration au sommet de la portée actuelle (c'est le comportement par défaut et ne peut pas être modifié), ce le comportement est appelé levage.
OK, tout le monde comprend, vous comprenez...
Comprendre ne veut pas dire que vous l'utiliserez. Prenons l'exemple initial, si je veux juste que alert(a) produise le 1, que dois-je faire ?
Créer une nouvelle portéeLorsque alert(a) est exécuté, il recherchera l'emplacement de la variable a. Il recherchera vers le haut (ou vers l'extérieur) de la portée actuelle vers la portée de niveau supérieur. S'il ne la trouve pas, il signalera un état indéfini. .
Parce que dans la portée sœur de alert(a), nous avons déclaré à nouveau la variable locale a, donc elle rapporte 2 afin que nous puissions déplacer la déclaration de la variable locale a vers le bas (ou vers l'intérieur), de sorte que alert (a) ; Il est introuvable.
Rappelez-vous : JavaScript n'a qu'une portée fonctionnelle !
你或许在无数的 JavaScript 书籍和文章里读到过:“请始终保持作用域内所有变量的声明放置在作用域的顶部”,现在你应该明白为什么有此一说了吧?因为这样可以避免 Hoisting 特性给你带来的困扰(我不是很情愿这么说,因为 Hoisting 本身并没有什么错),也可以很明确的告诉所有阅读代码的人(包括你自己)在当前作用域内有哪些变量可以访问。但是,变量声明的提升并非 Hoisting 的全部。在 JavaScript 中,有四种方式可以让命名进入到作用域中(按优先级):
1.语言定义的命名:比如 this 或者 arguments,它们在所有作用域内都有效且优先级最高,所以在任何地方你都不能把变量命名为 this 之类的,这样是没有意义的
2.形式参数:函数定义时声明的形式参数会作为变量被 hoisting 至该函数的作用域内。所以形式参数是本地的,不是外部的或者全局的。当然你可以在执行函数的时候把外部变量传进来,但是传进来之后就是本地的了
3.函数声明:函数体内部还可以声明函数,不过它们也都是本地的了
4.变量声明:这个优先级其实还是最低的,不过它们也都是最常用的
另外,还记得之前我们讨论过 声明 和 定义 的区别吧?当时我并没有说为什么要理解这个区别,不过现在是时候了,记住:
Hosting 只提升了命名,没有提升定义
这一点和我们接下来要讲到的东西息息相关,请看:
函数声明与函数表达式的差别
先看两个例子:
function test() { foo(); function foo() { alert("我是会出现的啦……"); } } test();
function test() { foo(); var foo = function() { alert("我不会出现的哦……"); } } test();
同学,在了解了 Scoping & Hoisting 之后,你知道怎么解释这一切了吧?
在第一个例子里,函数 foo 是一个声明,既然是声明就会被提升(我特意包裹了一个外层作用域,因为全局作用域需要你的想象,不是那么直观,但是道理是一样的),所以在执行 foo() 之前,作用域就知道函数 foo 的存在了。这叫做函数声明(Function Declaration),函数声明会连通命名和函数体一起被提升至作用域顶部。
然而在第二个例子里,被提升的仅仅是变量名 foo,至于它的定义依然停留在原处。因此在执行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。
尾记:Ben Cherry 的原文解释的更加详细,只不过是英文而已。我这篇是借花献佛,主要是更浅显的解释给初学者听,若要看更多的示例,请移步原作,谢谢。