翻译 - JavaScript中的做用域与变量声明提高

本文地址:http://blog.163.com/jinlu_hz/blog/static/113830152201131132035178/
原文地址:http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting
原文做者:ben cherry

在前篇小议javascript之全局对象建立译文中提到过JavaScript Hoisting概念,当时不甚了解。google以后发现ben cherry解释的最为明了,因而有了本文这篇干货翻译稿。


翻译能力有限,辞不达意的地方请自动跳过或者直接阅读原文

——如下为翻译——

JavaScript Scoping and Hoisting(JavaScript中的做用域与变量声明提高/声明时机提高)

你知道下面JavaScript代码的执行结果是什么吗?
javascript

 foo = ; 
 bar()  
     (!foo)  
         foo = ; 
     
    alert(foo); 
 
bar();
若是你对foo的值实际上为"10"而感到诧异的话,再看一下下面这个例子:
 a = ; 
 b()  
    a = ; 
    ; 
     a()  
 
b(); 
alert(a);

发现浏览器会弹出alert(1)。怎么回事呢?看起来很诡异,这刚好是JavaScript语言的特性。我不肯定这种表现行为是否已经有一个标准的命名了,因而我将此称为变量声明提高。本文会对这种机制进行揭秘,先从做用域谈起好了。


Scoping in JavaScript( JavaScript 中的做用域)

对于不少JavaScript语言初学者而言,做用域带来许多困惑。固然也不只仅是初学者会遇到这些问题,很多资深JavaScript程序员也未能彻底掌握做用域的精髓。之因此带来困惑,其中一个缘由是因为JavaScript实在太像C风格的语言了。以下是一段C程序:
 <stdio.h> 
 main() { 
     x = ; 
    printf(, x);  
     () { 
         x = ; 
        printf(, x);  
    } 
    printf(, x);  
}

程序输出结果是 121 。由于C风格的语言有 块级做用域(block-level scope) 。当函数鱼运行到  if  语句中时,当前做用域中的新变量会被声明,而且不会影响到外部做用域。但在JavaScript中状况并非这样:
 x = ; 
console.log(x);  
 (true)  
     x = ; 
    console.log(x);  
 
console.log(x);

程序输出结果 122 。缘由是JavaScript支持 函数做用域(function-level scope) ,这个特性与C风格的语言格格不入。 if  语句中的代码块并不会建立新的做用域,只有函数才会。

对于许多熟谙C\C++\C#\Java语言的程序员来讲,JavaScript这个语言特性很是出乎意料。幸运的是,正因为JavaScript函数的灵活性,产生了一个变通方案。若是你必须在函数中建立一个临时做用域,能够这么作:
 foo()  
     x = ; 
     (x)  
        ( ()  
             x = ; 
             
        ());
这个函数很是灵活,不只仅在块级语句内,须要时能在任何地方使用。然而我强烈建议你多花点时间来理解JavaScript的做用域。


Declarations, Names, and Hoisting(声明、名称以及 变量声明提高/声明时机提高

当访问函数内的变量时,JavaScript会按照下面顺序查找:

  1. 语言级别:默认在所用做用域下会定义thisargumentshtml

  2. 传入参数:函数命名的参数,做用域是当前函数体java

  3. 函数声明:例如function foo() {}程序员

  4. 变量声明:例如var foo;浏览器

//以上翻译地比较晦涩,建议直接看原文
函数声明与变量声明常常被JavaScript引擎隐式地提高到当前做用域的顶部,也就是说:
ide

 foo()  
    bar(); 
     x = ;
实际上会被解释成:
 foo()  
     x; 
    bar(); 
    x = ;
也就是说,下面两种声明方式是等价的:
 foo()  
     (false)  
         x = ; 
     
    ; 
     y = ; 
 
 foo()  
     x, y; 
     (false)  
        x = ; 
     
    ; 
    y = ;

能够发现,声明语句中的赋值部分并无被提高声明,只有名称被提高了。两种函数声明方式:
 test()  
    foo();  
    bar();  
     foo =  ()   
        alert(); 
     
     bar()   
        alert(); 
     
 
test();
这个例子中,只有包含函数体的函数声明会被提高声明。foo虽然会被提高声明,可是函数体却在执行中被赋值。以上就是提高声明时机的基本概念,看起来一点也不复杂。


Name resolution order(名称解析顺序)


名称解析顺序有4步,通常来讲,若是一个名称已经被定义了,它就不会被另外一个具备同名称的属性所覆盖。这也就意味着,函数声明会比变量声明优先。并非名称的赋值操做不会被执行,只是说声明部分被忽略了而已。有些例外:

  • 原生变量arguments特立独行,包含了传递到函数中的参数。若是自定义以arguments为命名的参数,将会阻止原生arguments对象的建立。因此勿使用arguments为名称的参数。函数

  • 胡乱使用this标识符会引发语法错误。this

  • 若是多个参数具备相同的命名,那么最后一个参数会优先于先前的,即时这个参数未定义。google



Named Function Expressions(函数命名表达式)

你能够经过函数表达式给函数命名,语法上看起来像是函数声明,实则不是。上一段代码:
编码

foo();  
bar();  
baz();  
spam();  
 
 foo =  () ;  
 bar() ;  
 baz =  spam() ;  
 
foo();  
bar();  
baz();  
spam();

How to Code With This Knowledge(如何编码)

文章到此你应该已经理解什么是做用域和提高声明时机了,那么如何在实战中运用呢?切记,使用var表达式建立变量,在此强烈建议在代码块顶部使用一个var表达式来建立变量。然而,这么作的同时也致使开发者很难对当前做用域下实际被声明的变量进行跟踪。我建议开发者使用 JSLint 来进行验证:
 
 foo(a, b, c)  
     x = , 
        bar, 
        baz = ;

What the Standard Says(看看规范上是怎么说的)

ECMAScript Standard  中的section12.2.2里写到:
If the variable statement occurs inside a FunctionDeclaration, the variables are defined with function-local scope in that function, as described in section 10.1.3. Otherwise, they are defined with global scope (that is, they are created as members of the global object, as described in section 10.1.3) using property attributes { DontDelete }. Variables are created when the execution scope is entered. A Block does not define a new execution scope. Only Program and FunctionDeclaration produce a new scope. Variables are initialised to undefined when created. A variable with an Initialiser is assigned the value of its AssignmentExpression when the VariableStatement is executed, not when the variable is created. //对ECMAScript不甚了解,故在此不做翻译

——翻译结束——
2011-04-11 17:44:39

参考资料:
做用域与命名空间
相关文章
相关标签/搜索