翻譯 - 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

參考資料:
做用域與命名空間
相關文章
相關標籤/搜索