1. 程式人生 > >javascript 的作用域

javascript 的作用域

sco 身體 最新 something 發生 使用 -a 只有一個 例子

翻譯自:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

如果以下代碼執行,你知道出什麽結果嗎?

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的範圍。

javascript的作用域

JavaScript初學者最為混亂的原因之一就是範圍界定。 其實這不只是初學者。 我遇到了很多經驗豐富的JavaScript程序員,他們不完全了解範圍界定。 在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和C系列的其余部分具有塊級範圍。 當控件進入塊(如if語句)時,可以在該範圍內聲明新變量,而不影響外部作用域。 JavaScript並非如此。 在Firebug中嘗試以下操作:

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

在這種情況下,Firebug將顯示1,2,2.這是因為JavaScript具有功能級的作用域。 這與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範圍。 這是非常強大的,是我最喜歡的語言功能之一。 如果你明白範圍界定,hosting將會更有意義。

hosting

在JavaScript中,一個名稱以四種基本方式之一輸入範圍:

語言定義:默認情況下,所有作用域都給出了這個和參數的名稱。
形式參數:函數可以具有命名形式參數,這些參數範圍是該函數的正文。
函數聲明:這些是函數foo(){}的形式。
變量聲明:這些形式為var foo ;

函數聲明和變量聲明始終被JavaScript解釋器移動(“hosting”)到其包含範圍的頂部。 功能參數和語言定義的名稱顯然已經在那裏。 這意味著這樣的代碼:

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;
}

請註意,聲明的轉讓部分未被吊起。 只有這個名字已經懸掛。 函數聲明不是這樣,整個函數體也將被掛起。 但請記住,有兩種正常的方法來聲明函數。 考慮以下JavaScript:

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 ‘bar‘
        alert("this will run!");
    }
}
test();

在這種情況下,只有函數聲明的主體已經懸掛在頂部。 “foo”的名字已經懸掛,但身體被遺棄,在執行過程中被分配。

這涵蓋了起重機的基礎知識,並不像看起來那麽復雜或混亂。 當然,這是JavaScript,在某些特殊情況下還有一點復雜性。

名稱解析順序

要記住的最重要的特殊情況是名稱解析順序。請記住,名稱輸入給定範圍有四種方式。我上面列出的順序是它們解決的順序。一般來說,如果一個名稱已經被定義,它不會被同名的另一個屬性所覆蓋。這意味著函數聲明優先於變量聲明。這並不意味著對該名稱的分配將不起作用,只是聲明部分將被忽略。還有一些例外:

內置的名稱參數的行為奇怪。它似乎是在形式參數之後聲明的,但在函數聲明之前。這意味著具有name參數的形式參數將優先於內置,即使未定義。這是一個不好的功能。不要使用name參數作為形式參數。
嘗試使用名稱作為任何位置的標識符將導致一個SyntaxError。這是一個很好的功能。
如果多個形式參數具有相同的名稱,則列表中最新出現的一個參數將優先,即使未定義。

命名函數表達式

您可以給函數表達式中定義的函數命名,其語法類似函數聲明。 這不是一個函數聲明,並且這個名字沒有被引入到範圍內,而且這個名字也沒有被提起來。 這裏有一些代碼來說明我的意思:

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 () {}; // anonymous function expression (‘foo‘ gets hoisted)
function bar() {}; // function declaration (‘bar‘ and the function body get hoisted)
var baz = function spam() {}; // named function expression (only ‘baz‘ gets 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標準(pdf)通常是有用的,以了解這些事情如何工作。 以下是對變量聲明和範圍的說明(舊版本中的第12.2.2節):

如果變量語句發生在FunctionDeclaration內,則在該函數中使用函數局部作用域定義變量,如第10.1.3節所述。 否則,使用屬性屬性{DontDelete}定義全局範圍(即,它們被創建為全局對象的成員,如第10.1.3節所述)。 在輸入執行範圍時創建變量。 塊不定義新的執行範圍。 只有程序和函數聲明才能產生一個新的範圍。 變量在創建時被初始化為未定義。 當執行VariableStatement時,具有Initialiser的變量將分配給其AssignmentExpression的值,而不是創建變量時。

我希望這篇文章揭示了JavaScript程序員最常見的困惑之一。 我試圖盡可能的徹底,避免造成更多的混亂。 如果我有任何錯誤或遺漏,請通知我。





javascript 的作用域