javascript範圍和閉包使用詳解

來源:互聯網
上載者:User

範圍的嵌套將形成範圍鏈,函數的嵌套將形成閉包。閉包與範圍鏈是 JavaScript 區別於其它語言的重要特性之一。

範圍
JavaScript 中有兩種範圍:函數範圍和全域範圍。

在一個函數中聲明的變數以及該函數的參數享有同一個範圍,即函數範圍。一個簡單的函數範圍的例子:

複製代碼 代碼如下:
function foo() {
    var bar = 1;
    {
        var bar = 2;
    }
    return bar; // 2
}

不同於C等其它有塊範圍的語言,這裡將始終返回 2 。

全域範圍,對於瀏覽器來說可以理解為 window 對象(Node.js則是 global):
複製代碼 代碼如下:
var bar = 1;
function foo() {}
alert(window.bar); // 1
alert(window.foo); // "function foo() {}"

對於變數 bar 和函數 foo 都屬於全域範圍,都是 window 的一個屬性。

範圍鏈
在 JavaScript 中訪問一個變數時,將從本地變數和參數開始,逐級向上遍曆範圍直到全域範圍。

複製代碼 代碼如下:
var scope = 0, zero = "global-scope";
(function(){
    var scope = 1, one = "scope-1";
    (function(){
        var scope = 2, two = "scope-2";
        (function(){
            var scope = 3, three = "scope-3";
            // scope-3 scope-2 scope-1 global-scope
            console.log([three, two, one, zero].join(" "));
            console.log(scope); // 3
        })();
        console.log(typeof three); // undefined
        console.log(scope); // 2
    })();
    console.log(typeof two); // undefined
    console.log(scope); // 1
})();
console.log(typeof one); // undefined
console.log(scope); // 0

在最裡層的函數中,各個變數都能被逐級遍曆並輸出。而倒數第二層的函數中,變數 three 無法遍曆找到,所以輸出了 undefined 。

舉一個通俗點的例子,你準備要花錢買點東西時,會先摸摸自己的錢包,沒了你可以找你爸要,你爸也沒有就再找你爺爺,... 。而你爸沒錢買東西時,他並不會來找你要。

閉包
在一個函數中,定義另一個函數,稱為函數嵌套。函數的嵌套將形成一個閉包。

閉包與範圍鏈相輔相成,函數的嵌套在產生了鏈式關係的多個範圍的同時,也形成了一個閉包。
複製代碼 代碼如下:
function bind(func, target) {
    return function() {
        func.apply(target, arguments);
    };
}

那麼怎麼理解閉包呢?

外部函數不能訪問內嵌函數
外部函數也不能訪問內嵌函數的參數和變數
而內嵌函數可以訪問外部函數的參數和變數
換一個說法:內嵌函數包含了外部函數的範圍
我們再看看之前講述的範圍鏈的例子,這次從閉包的角度來理解下:

複製代碼 代碼如下:
var scope = 0, zero = "global-scope";
(function(){
    var scope = 1, one = "scope-1";
    (function(){
        var scope = 2, two = "scope-2";
        (function(){
            var scope = 3, three = "scope-3";
            // scope-3 scope-2 scope-1 global-scope
            console.log([three, two, one, zero].join(" "));
            console.log(scope); // 3
        })();
        console.log(typeof three); // undefined
        console.log(scope); // 2
    })();
    console.log(typeof two); // undefined
    console.log(scope); // 1
})();
console.log(typeof one); // undefined
console.log(scope); // 0

最裡層的函數能訪問到其內部和外部定義的所有變數。而倒數第二層的函數無法訪問到最裡層的變數,同時,最裡層的 scope = 3 這個賦值操作並沒有對其外部的同名變數產生影響。

再換個角度來理解閉包:

每次外部函數的調用,內嵌函數都會被建立一次
在它被建立時,外部函數的範圍(包括任何本地變數、參數等上下文), 會成為每個內嵌函數對象的內部狀態的一部分,即使在外部函數執行完並退出後
看下面的例子:

複製代碼 代碼如下:
var i, list = [];
for (i = 0; i < 2; i += 1) {
    list.push(function(){
        console.log(i);
    });
}
list.forEach(function(func){
  func();
});

我們將得到兩次 "2" ,而不是預期的 "1" 和 "2" ,這是因為在 list 中的兩個函數訪問的變數 i 都是其上一層範圍的同一個變數。

我們改動下代碼,以利用閉包來解決這個問題:

複製代碼 代碼如下:
var i, list = [];
for (i = 0; i < 2; i += 1) {
    list.push((function(j){
        return function(){
            console.log(j);
        };
    })(i));
}
list.forEach(function(func){
  func();
});

外層的“立即執行函數”接收了一個參數變數 i ,在其函數內以參數 j 的形式存在,它與被返回的內層函數中的名稱 j 指向同一個引用。外層函數執行並退出後,參數 j (此時它的值為 i 的當前值)成為了其內層函數的狀態的一部分被儲存了下來。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.