變量作用域和閉包(Variable scoping and closures)

2018-06-15 18:48 更新

在JavaScript中,你必須使用變量之前,通過var聲明變量:

> var x;
> x = 3;
> y = 4;
ReferenceError: y is not defined

你可以用一條var語句聲明和初始化多個變量:

var x = 1, y = 2, z = 3;

但我建議每個變量使用一條語句。因此,我將上面的語句重寫為:

var x = 1;
var y = 2;
var z = 3;

由于提升(見下文),最好在函數(shù)頂部聲明變量。

變量和函數(shù)作用域(Variables are function-scoped)

變量的作用域總是整個函數(shù)(沒有塊級作用域)。例如:

function foo() {
    var x = -3;
    if (x < 0) {  // (*)
        var tmp = -x;
        ...
    }
    console.log(tmp);  // 3
}

我們可以看到tmp變量不僅在(*)所在行的語句塊存在,它在整個函數(shù)內(nèi)都存在。

變量提升(Variables are hoisted)

變量聲明會被提升:聲明會被移到函數(shù)的頂部,但賦值過程不會。舉個例子,在下面的函數(shù)中(*)行位置聲明了一個變量。

function foo() {
    console.log(tmp); // undefined
    if (false) {
        var tmp = 3;  // (*)
    }
}

在內(nèi)部,上面的函數(shù)被執(zhí)行像下面這樣:

function foo() {
    var tmp;  // declaration is hoisted
    console.log(tmp);
    if (false) {
        tmp = 3;  // assignment stays put
    }
}

閉包(Closures)

每個函數(shù)保持和函數(shù)體內(nèi)部變量的連接,甚至離開創(chuàng)建它的作用域之后。例如:

function createIncrementor(start) {
    return function () {  // (*)
        return start++;
    }
}

在(*)行開始的函數(shù)在它創(chuàng)建時保留上下文,并在內(nèi)部保存一個start活動值:

> var inc = createIncrementor(5);
> inc()
5
> inc()
6
> inc()
7

閉包是一個函數(shù)加上和其作用域鏈的鏈接。因此,createIncrementor() 返回的是一個閉包。

IIFE:模擬塊級作用域(IIFE: Simulating block scoping)

有時你想模擬一個塊,例如你想將變量從全局作用域隔離。完成這個工作的模式叫做 IIFE(立即執(zhí)行函數(shù)表達式(Immediately Invoked Function Expression)):

(function () {  // 塊開始
    var tmp = ...;  // 非全局變量
}());  // 塊結束

上面你會看到函數(shù)表達式被立即執(zhí)行。外面的括號用來阻止它被解析成函數(shù)聲明;只有函數(shù)表達式能被立即調用。函數(shù)體產(chǎn)生一個新的作用域并使 tmp 變?yōu)榫植孔兞俊?/p>

閉包實現(xiàn)變量共享(Inadvertent sharing via closures)

下面是個經(jīng)典問題,如果你不知道,會讓你費盡思量。因此,先瀏覽下,對問題有個大概的了解。 閉包保持和外部變量的連接,有時可能和你想像的行為不一致:

var result = [];
for (var i=0; i < 5; i++) {
    result.push(function () { return i });  // (*)
}
console.log(result[1]()); // 5 (不是 1)
console.log(result[3]()); // 5 (不是 3)

(*)行的返回值總是當前的i值,而不是當函數(shù)被創(chuàng)建時的i值。當循環(huán)結束后,i的值是5,這是為什么數(shù)組中的所有函數(shù)的返回值總是一樣的。如果你想捕獲當前變量的快照,你可以使用 IIFE:

for (var i=0; i < 5; i++) {
    (function (i2) {
        result.push(function () { return i2 });
    }(i));  // 復制當前的i
}

深入閱讀

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號