下面的程序是什么结果?
- var foo = 1;
- function bar() {
- if (!foo) {
- var foo = 10;
- }
- alert(foo);
- }
- bar();
结果是10;
那么下面这个呢?
- var a = 1;
- function b() {
- a = 10;
- return;
- function a() {}
- }
- b();
- alert(a);
结果是1.
吓你一跳吧?发生了什么事情?这可能是陌生的,危险的,迷惑的,同样事实上也是非常有用和印象深刻的javascript语言特性。对于这种表现行为,我不知道有没有一个标准的称呼,但是我喜欢这个术语:“Hoisting (变量提升)”。这篇文章将对这种机制做一个抛砖引玉式的讲解,但是,首先让我们对javascript的作用域有一些必要的理解。
Javascript的作用域
对于Javascript初学者来说,一个最迷惑的地方就是作用域;事实上,不光是初学者。我就见过一些有经验的javascript程序员,但他们对scope理解不深。javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言,像下面的C程序:
- #include <stdio.h>
- int main() {
- int x = 1;
- printf("%d, ", x); // 1
- if (1) {
- int x = 2;
- printf("%d, ", x); // 2
- }
- printf("%d\n", x); // 1
- }
输出结果是1 2 1,这是因为C家族的语言有块作用域,当程序控制走进一个块,比如if块,只作用于该块的变量可以被声明,而不会影响块外面的作用域。但是在Javascript里面,这样不行。看看下面的代码:
- var x = 1;
- console.log(x); // 1
- if (true) {
- var x = 2;
- console.log(x); // 2
- }
- console.log(x); // 2
结果会是1 2 2。因为javascript是函数作用域。这是和c家族语言最大的不同。该程序里面的if并不会创建新的作用域。
对于很多C,c++,java程序员来说,这不是他们期望和欢迎的。幸运的是,基于javascript函数的灵活性,这里有可变通的地方。如果你必须创建临时的作用域,可以像下面这样:
- function foo() {
- var x = 1;
- if (x) {
- (function () {
- var x = 2;
- // some other code
- }());
- }
- // x is still 1.
- }
这种方法很灵活,可以用在任何你想创建临时的作用域的地方。不光是块内。但是,我强烈推荐你花点时间理解javascript的作用域。它很有用,是我最喜欢的javascript特性之一。如果你理解了作用域,那么变量提升就对你显得更有意义。
变量声明,命名,和提升
在javascript,变量有4种基本方式进入作用域:
- 1 语言内置:所有的作用域里都有this和arguments;(译者注:经过测试arguments在全局作用域是不可见的)
- 2 形式参数:函数的形式参数会作为函数体作用域的一部分;
- 3 函数声明:像这种形式:function foo(){};
- 4 变量声明:像这样:var foo;
函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部。这个意思是,像下面的代码:
- function foo() {
- bar();
- var x = 1;
- }
实际上会被解释成:
- function foo() {
- var x;
- bar();
- x = 1;
- }
无论定义该变量的块是否能被执行。下面的两个函数实际上是一回事:
- function foo() {
- if (false) {
- var x = 1;
- }
- return;
- var y = 1;
- }
- function foo() {
- var x, y;
- if (false) {
- x = 1;
- }
- return;
- y = 1;
- }
请注意,变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升。但是请注意,函数的声明有两种方式:
- function test() {
- foo(); // TypeError "foo is not a function"
- bar(); // "this will run!"
- var foo = function () { // 变量指向函数表达式
- alert("this won't run!");
- }
- function bar() { // 函数声明 函数名为bar
- alert("this will run!");
- }
- }
- test();
这个例子里面,只有函数式的声明才会连同函数体一起被提升。foo的声明会被提升,但是它指向的函数体只会在执行的时候才被赋值。
上面的东西涵盖了提升的一些基本知识,它们看起来也没有那么迷惑。但是,在一些特殊场景,还是有一定的复杂度的。
变量解析顺序
最需要牢记在心的是变量解析顺序。记得我前面给出的命名进入作用域的4种方式吗?变量解析的顺序就是我列出来的顺序。一般来说,如果一个名称已经被定义,则不会被其他相同名称的属性覆盖。这是说,(译者没理解这句,所以先做删除样式) 函数的声明比变量的声明具有高的优先级。这并不是说给那个变量赋值不管用,而是声明不会被忽略了。 (译者注: 关于函数的声明比变量的声明具有高的优先级,下面的程序能帮助你理解)
- <script>
- function a(){
- }
- var a;
- alert(a);//打印出a的函数体
- </script>
- <script>
- var a;
- function a(){
- }
- alert(a);//打印出a的函数体
- </script>
- //但是要注意区分和下面两个写法的区别:
- <script>
- var a=1;
- function a(){
- }
- alert(a);//打印出1
- </script>
- <script>
- function a(){
- }
- var a=1;
- alert(a);//打印出1
- </script>
这里有3个例外:
1 内置的名称arguments表现得很奇怪,他看起来应该是声明在函数形式参数之后,但是却在函数声明之前。这是说,如果形参里面有arguments,它会比内置的那个有优先级。这是很不好的特性,所以要杜绝在形参里面使用arguments;
2 在任何地方定义this变量都会出语法错误,这是个好特性;
3 如果多个形式参数拥有相同的名称,最后的那个具有优先级,即便实际运行的时候它的值是undefined;
命名函数
你可以给一个函数一个名字。如果这样的话,它就不是一个函数声明,同时,函数体定义里面的指定的函数名( 如果有的话,如下面的spam, 译者注)将不会被提升, 而是被忽略。这里一些代码帮助你理解:
- foo(); // TypeError "foo is not a function"
- bar(); // valid
- baz(); // TypeError "baz is not a function"
- spam(); // ReferenceError "spam is not defined"
- var foo = function () {}; // foo指向匿名函数
- function bar() {}; // 函数声明
- var baz = function spam() {}; // 命名函数,只有baz被提升,spam不会被提升。
- foo(); // valid
- bar(); // valid
- baz(); // valid
- spam(); // ReferenceError "spam is not defined"
怎么写代码
现在你理解了作用域和变量提升,那么这对于javascript编码意味着什么?最重要的一点是,总是用var定义你的变量。而且我强烈推荐,对于一个名称,在一个作用域里面永远只有一次var声明。如果你这么做,你就不会遇到作用域和变量提升问题。
语言规范怎么说
我发现ECMAScript参考文档总是很有用。下面是我找到的关于作用域和变量提升的部分:
如果变量在函数体类声明,则它是函数作用域。否则,它是全局作用域(作为global的属性)。变量将会在执行进入作用域的时候被创建。块不会定义新的作用域,只有函数声明和程序(译者以为,就是全局性质的代码执行)才会创造新的作用域。变量在创建的时候会被初始化为undefined。如果变量声明语句里面带有赋值操作,则赋值操作只有被执行到的时候才会发生,而不是创建的时候。
我期待这篇文章会对那些对javascript比较迷惑的程序员带来一丝光明。我自己也尽最大的可能去避免带来更多的迷惑。如果我说错了什么,或者忽略了什么,请告知。
原文地址:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
相关推荐
在 JavaScript 中,变量作用域是指变量可以被访问和修改的范围。 JavaScript 没有块级作用域,而是基于函数作用域和全局作用域的。 首先,看看 JavaScript 的作用域链是如何工作的。JavaScript 首先在当前作用域中...
Javascript作用域和变量提升是该语言中的核心概念之一,初学者经常对这些概念感到困惑。在深入讨论这些主题之前,我们需要理解什么是作用域(Scope)和变量提升(Hoisting)。 作用域是指变量在特定区域内是否可见...
JavaScript作用域是编程中至关重要的概念,它规定了变量和函数的可见性及生命周期。JavaScript主要有两种作用域:全局作用域和局部作用域。 全局作用域是指在代码的任何位置都可以访问的变量或函数,这通常包括在最...
JavaScript引擎在执行代码之前会进行预解析,这个过程中会将变量和函数的声明提升到它们所在作用域的顶部,但不包括赋值操作。这意味着,即使在变量声明之前使用,也不会报错,但变量的值会是`undefined`。函数声明...
这意味着即使变量和函数的声明在代码中位于下方,它们在执行时看起来就像是在作用域的最开始就被声明了。例如: ```javascript func(); // 函数声明提升 function func() { alert("Funciton has been called"); } ...
理解JavaScript变量作用域.pdf 本人还有几十本经典javascript书籍以及无数javascript资料,要的加我qq 568094881,本人网址:www.maoshanhai.com
作用域链是一个对象列表,用于存储变量和函数声明。 - 当JavaScript引擎需要查找变量时,它会从当前作用域开始,沿着作用域链向上查找,直到找到相应的变量为止。 - 如果在当前作用域找不到变量,则继续在上一层...
JavaScript作用域是编程中至关重要的概念,尤其是在JavaScript这种动态类型的脚本语言中。它规定了变量、函数以及其它标识符的可见性和生命周期,是代码组织和管理的关键元素。本资料"深入理解JavaScript作用域共12...
JavaScript作用域是指在JavaScript代码中,变量、常量、对象和函数能够访问的范围。在编程中,变量和函数的使用都受到作用域的限制,决定了它们能够在哪些代码块中被引用。作用域有助于防止变量命名冲突,也使得程序...
深化理解javascript作用域其次篇之词法作用域和动态作用域_ 深化理解javascript作用域其次篇之词法作用域和动态作用域,是javascript中非常重要的一部分。理解词法作用域和动态作用域对javascript的编程至关重要。...
变量提升是指在JavaScript语言中,变量声明会被提升到当前作用域的顶端。这意味着,即使变量声明在代码的后面,但是在执行时,它会被提升到当前作用域的顶端。例如,在 1中,`console.log(num)`会输出`undefined`,...
变量提升(Hoisting)是JavaScript中的一个关键特性,它指的是var声明的变量和函数声明会被提升到它们所在的作用域顶部。然而,let和const声明的变量不会被提升,这称为暂时性死区(Temporal Dead Zone, TDZ)。如果...
本篇文章旨在通过一系列示例和解释,帮助读者掌握JavaScript中的变量作用域机制。 #### 二、全局变量与作用域划分 ##### 1. 全局变量 在JavaScript中,全局变量实际上是`window`对象的一个属性。例如,如果在一个...
变量提升是JavaScript特有的一个行为,指的是在当前作用域中,变量和函数声明会被移动到其作用域的最顶端。变量提升仅限于声明,不包括变量的赋值操作。这意味着在函数内部或全局作用域中使用`var`关键字声明的变量...
在ES6中,引入了`let`和`const`两个新的关键字,用于声明块级作用域的变量,它们的使用更加严格,不允许变量提升,并且在同一作用域内不允许重复声明,这为JavaScript带来了更多的灵活性和更好的管理变量的能力。...
作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。任何程序...
本文将深入探讨JavaScript中的变量作用域,以及它如何影响代码的运行和组织。 首先,我们来理解什么是变量作用域。在JavaScript中,变量作用域决定了变量在何处可以被访问和识别。主要有三种类型的作用域:全局作用...