作用域的巢狀將形成作用域鏈,函式的巢狀將形成閉包。閉包與作用域鏈是 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); // 1alert(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-scopeconsole.log([three, two, one, zero].join(" "));console.log(scope); // 3})();console.log(typeof three); // undefinedconsole.log(scope); // 2})();console.log(typeof two); // undefinedconsole.log(scope); // 1})();console.log(typeof one); // undefinedconsole.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-scopeconsole.log([three, two, one, zero].join(" "));console.log(scope); // 3})();console.log(typeof three); // undefinedconsole.log(scope); // 2})();console.log(typeof two); // undefinedconsole.log(scope); // 1})();console.log(typeof one); // undefinedconsole.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 的當前值)成為了其內層函式的狀態的一部分被儲存了下來。