`

[译]Javascript 作用域和变量提升

阅读更多

下面的程序是什么结果?

 
  1. var foo = 1;  
  2. function bar() {  
  3.     if (!foo) {  
  4.         var foo = 10;  
  5.     }  
  6.     alert(foo);  
  7. }  
  8. bar();  

结果是10;

 

那么下面这个呢?

 
  1. var a = 1;  
  2. function b() {  
  3.     a = 10;  
  4.     return;  
  5.     function a() {}  
  6. }  
  7. b();  
  8. alert(a);  

结果是1.

 

吓你一跳吧?发生了什么事情?这可能是陌生的,危险的,迷惑的,同样事实上也是非常有用和印象深刻的javascript语言特性。对于这种表现行为,我不知道有没有一个标准的称呼,但是我喜欢这个术语:“Hoisting (变量提升)”。这篇文章将对这种机制做一个抛砖引玉式的讲解,但是,首先让我们对javascript的作用域有一些必要的理解。

Javascript的作用域

对于Javascript初学者来说,一个最迷惑的地方就是作用域;事实上,不光是初学者。我就见过一些有经验的javascript程序员,但他们对scope理解不深。javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言,像下面的C程序:

 
  1. #include <stdio.h>  
  2. int main() {  
  3.     int x = 1;  
  4.     printf("%d, ", x); // 1  
  5.     if (1) {  
  6.         int x = 2;  
  7.         printf("%d, ", x); // 2  
  8.     }  
  9.     printf("%d\n", x); // 1  
  10. }  

输出结果是1 2 1,这是因为C家族的语言有块作用域,当程序控制走进一个块,比如if块,只作用于该块的变量可以被声明,而不会影响块外面的作用域。但是在Javascript里面,这样不行。看看下面的代码:

 
  1. var x = 1;  
  2. console.log(x); // 1  
  3. if (true) {  
  4.     var x = 2;  
  5.     console.log(x); // 2  
  6. }  
  7. console.log(x); // 2  

结果会是1 2 2。因为javascript是函数作用域。这是和c家族语言最大的不同。该程序里面的if并不会创建新的作用域。

 

对于很多C,c++,java程序员来说,这不是他们期望和欢迎的。幸运的是,基于javascript函数的灵活性,这里有可变通的地方。如果你必须创建临时的作用域,可以像下面这样:

 
  1. function foo() {  
  2.     var x = 1;  
  3.     if (x) {  
  4.         (function () {  
  5.             var x = 2;  
  6.             // some other code  
  7.         }());  
  8.     }  
  9.     // x is still 1.  
  10. }  

这种方法很灵活,可以用在任何你想创建临时的作用域的地方。不光是块内。但是,我强烈推荐你花点时间理解javascript的作用域。它很有用,是我最喜欢的javascript特性之一。如果你理解了作用域,那么变量提升就对你显得更有意义。

 

变量声明,命名,和提升

在javascript,变量有4种基本方式进入作用域:

  • 1 语言内置:所有的作用域里都有this和arguments;(译者注:经过测试arguments在全局作用域是不可见的)
  • 2 形式参数:函数的形式参数会作为函数体作用域的一部分;
  • 3 函数声明:像这种形式:function foo(){};
  • 4 变量声明:像这样:var foo;

函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部。这个意思是,像下面的代码:

 
  1. function foo() {  
  2.     bar();  
  3.     var x = 1;  
  4. }  

实际上会被解释成:

 
  1. function foo() {  
  2.     var x;  
  3.     bar();  
  4.     x = 1;  
  5. }  

无论定义该变量的块是否能被执行。下面的两个函数实际上是一回事:

 
  1. function foo() {  
  2.     if (false) {  
  3.         var x = 1;  
  4.     }  
  5.     return;  
  6.     var y = 1;  
  7. }  
  8. function foo() {  
  9.     var x, y;  
  10.     if (false) {  
  11.         x = 1;  
  12.     }  
  13.     return;  
  14.     y = 1;  
  15. }  

请注意,变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升。但是请注意,函数的声明有两种方式:

 
  1. function test() {  
  2.     foo(); // TypeError "foo is not a function"  
  3.     bar(); // "this will run!"  
  4.     var foo = function () { // 变量指向函数表达式  
  5.         alert("this won't run!");  
  6.     }  
  7.     function bar() { // 函数声明 函数名为bar  
  8.         alert("this will run!");  
  9.     }  
  10. }  
  11. test();  

这个例子里面,只有函数式的声明才会连同函数体一起被提升。foo的声明会被提升,但是它指向的函数体只会在执行的时候才被赋值。

 

上面的东西涵盖了提升的一些基本知识,它们看起来也没有那么迷惑。但是,在一些特殊场景,还是有一定的复杂度的。

变量解析顺序

最需要牢记在心的是变量解析顺序。记得我前面给出的命名进入作用域的4种方式吗?变量解析的顺序就是我列出来的顺序。一般来说,如果一个名称已经被定义,则不会被其他相同名称的属性覆盖。这是说,(译者没理解这句,所以先做删除样式) 函数的声明比变量的声明具有高的优先级这并不是说给那个变量赋值不管用,而是声明不会被忽略了。 (译者注: 关于函数的声明比变量的声明具有高的优先级,下面的程序能帮助你理解)

 
  1. <script>  
  2. function a(){     
  3. }  
  4. var a;  
  5. alert(a);//打印出a的函数体  
  6. </script>  
  7.   
  8. <script>  
  9.   
  10. var a;  
  11. function a(){     
  12. }  
  13. alert(a);//打印出a的函数体  
  14. </script>  
  15. //但是要注意区分和下面两个写法的区别:  
  16. <script>  
  17. var a=1;  
  18. function a(){     
  19. }  
  20. alert(a);//打印出1  
  21. </script>  
  22.   
  23. <script>  
  24. function a(){     
  25. }  
  26.   
  27. var a=1;  
  28.   
  29. alert(a);//打印出1  
  30. </script>  


这里有3个例外:
1 内置的名称arguments表现得很奇怪,他看起来应该是声明在函数形式参数之后,但是却在函数声明之前。这是说,如果形参里面有arguments,它会比内置的那个有优先级。这是很不好的特性,所以要杜绝在形参里面使用arguments;
2 在任何地方定义this变量都会出语法错误,这是个好特性;
3 如果多个形式参数拥有相同的名称,最后的那个具有优先级,即便实际运行的时候它的值是undefined;

 

命名函数

你可以给一个函数一个名字。如果这样的话,它就不是一个函数声明,同时,函数体定义里面的指定的函数名( 如果有的话,如下面的spam, 译者注)将不会被提升, 而是被忽略。这里一些代码帮助你理解:

 
  1. foo(); // TypeError "foo is not a function"  
  2. bar(); // valid  
  3. baz(); // TypeError "baz is not a function"  
  4. spam(); // ReferenceError "spam is not defined"  
  5.   
  6. var foo = function () {}; // foo指向匿名函数  
  7. function bar() {}; // 函数声明  
  8. var baz = function spam() {}; // 命名函数,只有baz被提升,spam不会被提升。  
  9.   
  10. foo(); // valid  
  11. bar(); // valid  
  12. baz(); // valid  
  13. spam(); // ReferenceError "spam is not defined"  

 

怎么写代码

现在你理解了作用域和变量提升,那么这对于javascript编码意味着什么?最重要的一点是,总是用var定义你的变量。而且我强烈推荐,对于一个名称,在一个作用域里面永远只有一次var声明。如果你这么做,你就不会遇到作用域和变量提升问题。

语言规范怎么说

我发现ECMAScript参考文档总是很有用。下面是我找到的关于作用域和变量提升的部分:
如果变量在函数体类声明,则它是函数作用域。否则,它是全局作用域(作为global的属性)。变量将会在执行进入作用域的时候被创建。块不会定义新的作用域,只有函数声明和程序(译者以为,就是全局性质的代码执行)才会创造新的作用域。变量在创建的时候会被初始化为undefined。如果变量声明语句里面带有赋值操作,则赋值操作只有被执行到的时候才会发生,而不是创建的时候。

我期待这篇文章会对那些对javascript比较迷惑的程序员带来一丝光明。我自己也尽最大的可能去避免带来更多的迷惑。如果我说错了什么,或者忽略了什么,请告知。

原文地址:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

31
56
分享到:
评论
3 楼 sxlzll 2013-10-11  
一般来说,如果一个名称已经被定义,则不会被其他相同名称的属性覆盖。
原文是In general, if a name has already been defined, it is never overridden by another property of the same name.

我的理解是(配合例子)如果一个变量已经被赋值了,就不会被那四种进入作用域方式的其他方法覆盖。

var test =1;
var test = function test2(){};
alert(test);//函数体


var test =1;
var test2 = function test(){};
alert(test);//1
2 楼 xiaowife20 2013-09-06  
大部分都看懂了,你里面的那句话
一般来说,如果一个名称已经被定义,则不会被其他相同名称的属性覆盖
意思应该是
函数声明的优先级比变量优先级别高
比如
<script> 
function a(){    

var a; 
alert(a);//打印出a的函数体 
</script> 
变量a没有覆盖掉函数a
他说一般情况,特殊情况应该是
<script> 
function a(){    

var a=1; 
alert(a);//打印出1
</script> 
但是我觉得那句话说的仅仅是变量的声明,而var a=1不仅声明了变量,而且还给他赋值了,所以不算。
1 楼 光进藤 2013-09-03  
我都不是很懂啊

相关推荐

    javascript变量作用域

    javascript变量作用域 希望对大家有帮助

    深入理解Javascript作用域与变量提升

    主要是对Javascript作用域与变量提升进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助

    理解JavaScript变量作用域.pdf

    理解JavaScript变量作用域.pdf 本人还有几十本经典javascript书籍以及无数javascript资料,要的加我qq 568094881,本人网址:www.maoshanhai.com

    深入理解变量作用域

    每个javascript执行环境都有一个和它关联在一起的作用域链(scope chain).这个作用域链是一个对象列表或对象链。当javascript需要查询变量x的值时,它就开始查看该链的第一个对象。如果那个对象有一个叫x的属性,那么...

    理解JavaScript作用域和作用域链

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。任何程序...

    深入理解JavaScript作用域和作用域链

    JavaScript作用域 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。 1...

    JavaScript 作用域 和作用域链

    换句话说,作用域决定了代码区块中变量和其他资源的可见性。 function fun() { var str = 函数内的变量; } fun();//要先执行这个函数,否则根本不知道里面是啥 console.log(str); // Uncaught ReferenceError: in...

    JavaScript 变量提升 | 执行上下文 | 作用域

    文章目录变量提升执行上下文全局执行上下文函数执行上下文执行上下文栈作用域例题举个栗子 执行上下文栈:举个栗子 变量提升+执行上下文栈:举个栗子 预处理顺序举个栗子 预处理举个栗子 顺序执行举个栗子 作用域举...

    javascript中的变量作用域以及变量提升详细介绍

    变量作用域分为局部作用域和全局作用域。 局部变量(处于函数级别的作用域)不像其他对面对象的编程语言(比方说C++,Java等等),javascript没有块级作用域(被花括号包围的);当是,javascript有拥有函数级别的...

    JavaScript作用域示例详解

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域示例详解的介绍,希望能帮助大家更好的学习JavaScript。 任何程序设计...

    Web前端开发技术-认识函数作用域.pptx

    认识函数作用域 作用域的分类 JavaScript 认识函数作用域 思考:声明变量后就可以在任意位置使用该变量嘛? 回答:不是。 举例:函数内var关键字声明的变量,不能在函数外访问。 示例 1.作用域分类 认识函数作用域 ...

    JS中作用域和变量提升(hoisting)的深入理解

    相信大家也都发现了,在网上关于JS的变量和作用域的文章有很多,但真正能讲清楚,能深入理解的文章很少。在阅读了很多人的文章以后,我决定综合起来,结合实际代码,希望能够以一个比较清楚完整的方式让大家真正理解...

    JavaScript作用域与作用域链深入解析

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 ...

    聊一聊JavaScript作用域和作用域链

    作用域是变量的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在 JavaScript 中, 对象和函数同样也是变量,变量在声明他们的函数体以及这个函数体嵌套的任意函数体内部都是有定义的。 一、静态作用域和...

    Javascript作用域和作用域链原理解析

    因此,在本文中,我将会向大家解释清楚什么是作用域和作用域链,以及Javascript引擎在内部是如何通过它们操作和查找变量的。 事不宜迟,正文开始 :slightly_smiling_face: 什么是作用域 Javascript中的作用域说的是...

    深入理解javascript作用域第二篇之词法作用域和动态作用域

    本文是深入理解javascript作用域系列第二篇——词法作用域和动态作用域 词法作用域  第一篇介绍过,编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成词法单元。这个概念是理解词法作用域的基础  ...

    【JavaScript源代码】JavaScript高级程序设计之变量与作用域.docx

    JavaScript高级程序设计之变量与作用域  目录 1、原始值与引用值2、instanceof3、作用域 1、原始值与引用值 2、instanceof 3、作用域 1、原始值与引用值 6种简单数据类型的值都是原始值, 原始值通过变量赋值...

Global site tag (gtag.js) - Google Analytics