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

===翻译开始===

你知道下面的JavaScript脚本执行结果是什么吗?

 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",这到底是怎么回事呢?这看起来很奇怪,事实上这恰好是语言的一个强大而又富有表现力的特性。我不知道这种特别的行为是否有一个标准的命名,但我喜欢把它叫做"hoisting"。接下来我会试着分析一下这种机制,但是我们有必要先理解一下JavaScript的作用域。

JavaScript中的作用域

对于JavaScript初学者来说,作用域常让他们感到困惑。事实上,一些资深的JavaScript开发者也不是完全理解作用域。JavaScript的作用域之所以让人如此困惑,因为它跟C系语言有点相似,请看下面的C程序:

 #include <stdio.h>
int main(){
int x=1;
printf("%d\n",x);//
if(1){
int x=2;
printf("%d\n",x);//
}
printf("%d\n",x);//
}

程序会依次输出1,2,1,这是因为C系语言有块级作用域。当程序运行到一个程序块的时候(比如if语句),在该程序块里定义的新变量不会影响到外部作用域。但在JavsScript中却不相同,试着执行下面的脚本:

 var x=1;
console.log(x);//
if(true){
var x=2;
console.log(x);//
}
console.log(x);//

脚本执行后会依次输出1,2,2,这是因为JavsScript只有函数级作用域,这和C系语言有着根本的不同,程序块(比如if语句)是不会创建新的作用域的,只有函数才会。

对于一些使用C、C++、C#或者Java语言的开发者来说,这简直让人难以接受。还好JavaScript的函数足够灵活,可以用其它变通方法。如果你一定要在函数内创建一个临时的作用域,可以这样做:

 function foo(){
var x=1;
if(x){
(function(){
var x=2;
//some other code
}());
}
//x is still 1.
}

这个方法相当灵活,在任何需要的地方都可以使用,不止在块语句里。但是我强烈建议你花一些时间来真正理解和欣赏JavaScript的作用域,这是我最喜欢的语言特性之一,它真的非常强大。如果你理解了作用域,那么对于声明提前你会更容易理解。

声明,变量名,声明提前

在JavaScript中,一个变量可以通过以下四种方式之一进入作用域:

1、语言内置:所有作用域都默认包含"this"和"arguments"变量。
2、函数形参:函数可以拥有形参,所属作用域就是该函数体。
3、函数声明:形如"function foo(){}"的声明。
4、变量声明:形如"var foo;"的声明。

函数声明和变量声明总会被JavaScript解释器自动放到所属作用域的顶端,函数参数和语言内置的变量默认都是在最顶端。举个例子,有如下代码:

 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(){ //function expression assigned to local variable "foo"
alert('this won't run!');
};
function bar(){//function declaration,given the name
alert('this will run!');
}
}
test();

在这个例子里,只有使用函数声明的函数体会被提前至顶端,而使用函数表达式赋值方式,只有名字"foo"被提前至顶端,函数体是没有的。

上面的例子基本覆盖了自动提前的情况,看起来并不是那么复杂让人迷惑。当然,一些其它比较特别的例子还是有一些复杂的。

变量识别顺序

我们要特别记住变量的识别顺序,前面说过变量名进入作用域有四种方式,我上面列举的顺序就是它们被识别的顺序。通常,如果一个变量名已经定义了,那么它就不会被其它相同名称的变量所覆盖。这意味着函数声明比变量声明优先级高,但这并不影响赋值操作,只是声明部分会被忽略而已。

PS:补充一段代码,表达作者的意思

function foo(){}
var foo=3;
console.log(foo);// 这段代码会被解析为:
function foo(){}
//var foo; //这条语句就被忽略了
foo=3;
console.log(foo); 依我理解是这样子:
var foo;
foo=function foo(){}
foo=3;
console.log(foo);

接着原文,以下是几种特别情况:

1、内置的变量"arguments"表现比较奇怪,它好像定义在函数形参和函数声明之间。这意味着如果形参中有个变量为"arguments",那么它的优先级将高于内置的"arguments",即使它是undefined。这不是一个好的特性,不要使用"arguments"作为形参变量名。
2、使用"this"作为一个标识符会引起语法错误,这是一个好的特性。
3、如果多个形参中出现同名,那么最后一个将拥有最高的优先级,即使它是undefined。

带有名字的函数表达式

你也可以给函数表达式中的函数起个名字,采用类似函数声明的语法。但这并不能使它变成一个函数声明,并且这个函数名不会被添加到作用域,函数体也不会被提前至顶端,下面用一些代码来演示我说的意思:

 foo(); //TypeError "undefined is not a function"
bar(); //valid
baz(); //TypeError "undefined is not a function"
spam();//ReferenceError "spam is not defined" var foo=function(){}; //anonymous function expression('foo' get hoisted)
function bar(){}; //function declaration ('bar' and the function body get hoisted)
var baz=function spam(){};//named function expression('only 'baz' get hoisted) foo(); //valid
bar(); //valid
baz(); //valid
spam();//ReferenceError "spam is not defined"

怎么利用这些知识编程

现在你已经理解作用域和声明提前特性了,那么这些在JavaScript编程中有什么影响?最重要的是声明变量时要使用"var"关键字,我强烈建议你在每个作用域的顶端只写一个var语句(多变量的时候,用逗号连接)。如果你强制自己这样做,就不会对声明提升产生困惑了。不过,这么做会让你在当前作用域中寻找已经声明的变量变得更困难,我建议使用"JSLint"的"onevar"选项来验证代码,如果你照做了,你的代码看起来会像这样子:

 /*jslint onevar: true [...]*/
function foo(a,b,c){
var x=1,
bar,
baz="something";
}

看看规范怎么说

我发现经常查阅ECMAScript规范文档有助于直接理解这些机制是怎么运行的,以下是规范对于变量声明和作用域的描述:

 如果变量声明语句在函数声明里面,那么变量就是定义在函数内部作用域(参考章节10.1.3),否则它们就是定义在全局作用域内(作为全局对象的成员变量,参考章节10.1.3)。变量进入作用域的时候就会被创建,块语句不会定义一个新的执行作用域,只有程序和函数声明会产生新的作用域。变量在创建的时候会被初始化为"undefined",一个带有初始化语句的变量,在赋值语句执行的时候才会被赋上其赋值表达式对应的值,并不是变量创建的时候就赋值。

我希望这篇文章能够帮助JavaScript开发者理清一些困惑的问题,我已经尽可能的彻底把问题讲清楚,以免造成更多的疑惑。如果你发现我写错了或者遗漏了某些重要的东西,请一定让我知道。

===翻译完===

翻译参考:http://ju.outofmemory.cn/entry/85659

以下是一个例子:

 var x=0;
var f=function(){
x=1;
}
f();
console.log(x);
function f(){
x=2;
}
f();
console.log(x);

【翻译】JavaScript中的作用域和声明提前的更多相关文章

  1. JavaScript中的作用域和声明提前

    [翻译]JavaScript中的作用域和声明提前 原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译 ...

  2. javascript中函数作用域和声明提前

    javascript不像java等其他强类型语句,没有块级作用域(括号内的代码都有自己的作用域,变量在声明它们的代码段之外不可见)一说,但有自己的独特地方,即函数作用域. 函数作用域:变量在声明它们的 ...

  3. JavaScript权威设计--JavaScript变量,作用域,声明提前(简要学习笔记四)

    1.宿主对象与宿主环境 宿主对象:由ECMAScript实现的宿主环境提供的对象,可以理解为:浏览器提供的对象.所有的BOM和DOM都是宿主对象.   宿主环境:一般宿主环境由外壳程序创建与维护,只要 ...

  4. JavaScript函数作用域和声明提前(3.10.1 page.57)

    <h4>3.函数作用域和声明提前</h4> <p> <!--<script type="text/javascript">-- ...

  5. JavaScript中的作用域

    很多(JavaScript)开发者都在讨论"作用域",但它是什么?它们在JavaScript中的任何地方!我发现很多年轻的开发者不知道作用域是什么.他们中大多数人可以用jQuery ...

  6. JS的作用域和声明提前

    首先介绍下Javascript的函数作用域的概念,然后了解下什么是作用域和声明提前,最后通过一个例子剖析Javascript的作用域链. 1.变量的作用域 稍微有些编程背景的都知道,变量的作用域分为两 ...

  7. 认识javascript中的作用域和上下文

    javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的 ...

  8. 漫谈JavaScript中的作用域(scope)

    什么是作用域 程序的执行,离不开作用域,也必须在作用域中才能将代码正确的执行. 所以作用域到底是什么,通俗的说,可以这样理解:作用域就是定义变量的位置,是变量和函数的可访问范围,控制着变量和函数的可见 ...

  9. 深入理解JavaScript中的作用域和上下文

    介绍 JavaScript中有一个被称为作用域(Scope)的特性.虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,我会尽我所能用最简单的方式来解释作用域.理解作用域将使你的代码脱颖而出,减 ...

随机推荐

  1. Eclipse中查看JDK类库的源代码

    在Eclipse中查看JDK类库的源代码!!! 设置: 1.点 “window”-> "Preferences" -> "Java" -> & ...

  2. python之域与属性

    python, javascript中域与属性是二个不同的概念, 域就是变量, 而属性则是符合某些约束, 例如getter, setter...等的特殊"变量". python中使 ...

  3. SNV ConnerStore使用说明

    1.  上传自己新建的文件新建的类文件 后面的 会有A标示要先 Add To Working copy 再点击提交 2. 上传第三方库时 默认SVN是忽略.a文件的要找到.a文件把他设置成不是忽略的通 ...

  4. what are Datatypes in SQLite supporting android

    As said at Datatypes In SQLite Version 3: Datatypes In SQLite Version 3 Most SQL database engines (e ...

  5. 在MAC上安装虚拟机搭建Ubuntu开发环境

    由于工作需要,需要在LINUX环境搭建服务器,但是工作中使用的是MAC系统,只好用虚拟机来搭建LINUX服务器环境.下面记录介绍一下搭建步骤以供需要的人参考使用. 下载准备 虚拟机使用VMWare   ...

  6. 《Dive into Python》Chapter 2 and Chapter 3 笔记

    Example 2.1. odbchelper.py def buildConnectionString(params): """Build a connection s ...

  7. DevExpress控件使用系列--ASPxGridView+Popup+Tab

      1.控件功能     列表控件展示数据.弹框控件执行编辑操作.Tab控件实现多标签编辑操官方说明 2.官方示例       2.1 ASPxGridView                http ...

  8. nenu contest

    http://vjudge.net/vjudge/contest/view.action?cid=54393#overview A n^2能过 对第二个n我二分了一下,快了一点点,nlogn #inc ...

  9. boost序列化

    #include <iostream> #include <boost/serialization/serialization.hpp> #include <boost/ ...

  10. android C/C++ source files 全局宏定义 .

    \system\core\include\arch\linux-arm AndroidConfig.h * ============================================== ...