javascript作用域和閉包使用詳解
作用域的嵌套將形成作用域鏈,函數(shù)的嵌套將形成閉包。閉包與作用域鏈?zhǔn)?JavaScript 區(qū)別于其它語言的重要特性之一。
作用域
JavaScript 中有兩種作用域:函數(shù)作用域和全局作用域。
在一個函數(shù)中聲明的變量以及該函數(shù)的參數(shù)享有同一個作用域,即函數(shù)作用域。一個簡單的函數(shù)作用域的例子:
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 和函數(shù) foo 都屬于全局作用域,都是 window 的一個屬性。
作用域鏈
在 JavaScript 中訪問一個變量時,將從本地變量和參數(shù)開始,逐級向上遍歷作用域直到全局作用域。
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
在最里層的函數(shù)中,各個變量都能被逐級遍歷并輸出。而倒數(shù)第二層的函數(shù)中,變量 three 無法遍歷找到,所以輸出了 undefined 。
舉一個通俗點的例子,你準(zhǔn)備要花錢買點東西時,會先摸摸自己的錢包,沒了你可以找你爸要,你爸也沒有就再找你爺爺,... 。而你爸沒錢買東西時,他并不會來找你要。
閉包
在一個函數(shù)中,定義另一個函數(shù),稱為函數(shù)嵌套。函數(shù)的嵌套將形成一個閉包。
閉包與作用域鏈相輔相成,函數(shù)的嵌套在產(chǎn)生了鏈?zhǔn)疥P(guān)系的多個作用域的同時,也形成了一個閉包。
function bind(func, target) {
return function() {
func.apply(target, arguments);
};
}
那么怎么理解閉包呢?
外部函數(shù)不能訪問內(nèi)嵌函數(shù)
外部函數(shù)也不能訪問內(nèi)嵌函數(shù)的參數(shù)和變量
而內(nèi)嵌函數(shù)可以訪問外部函數(shù)的參數(shù)和變量
換一個說法:內(nèi)嵌函數(shù)包含了外部函數(shù)的作用域
我們再看看之前講述的作用域鏈的例子,這次從閉包的角度來理解下:
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
最里層的函數(shù)能訪問到其內(nèi)部和外部定義的所有變量。而倒數(shù)第二層的函數(shù)無法訪問到最里層的變量,同時,最里層的 scope = 3 這個賦值操作并沒有對其外部的同名變量產(chǎn)生影響。
再換個角度來理解閉包:
每次外部函數(shù)的調(diào)用,內(nèi)嵌函數(shù)都會被創(chuàng)建一次
在它被創(chuàng)建時,外部函數(shù)的作用域(包括任何本地變量、參數(shù)等上下文), 會成為每個內(nèi)嵌函數(shù)對象的內(nèi)部狀態(tài)的一部分,即使在外部函數(shù)執(zhí)行完并退出后
看下面的例子:
var i, list = [];
for (i = 0; i < 2; i += 1) {
list.push(function(){
console.log(i);
});
}
list.forEach(function(func){
func();
});
我們將得到兩次 "2" ,而不是預(yù)期的 "1" 和 "2" ,這是因為在 list 中的兩個函數(shù)訪問的變量 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();
});
外層的“立即執(zhí)行函數(shù)”接收了一個參數(shù)變量 i ,在其函數(shù)內(nèi)以參數(shù) j 的形式存在,它與被返回的內(nèi)層函數(shù)中的名稱 j 指向同一個引用。外層函數(shù)執(zhí)行并退出后,參數(shù) j (此時它的值為 i 的當(dāng)前值)成為了其內(nèi)層函數(shù)的狀態(tài)的一部分被保存了下來。
相關(guān)文章
javascript學(xué)習(xí)筆記(五)正則表達(dá)式
正則表達(dá)式在web開發(fā)中會經(jīng)常用到,主要用于驗證用戶輸入的數(shù)據(jù)的格式。2011-04-04Javascript WebSocket使用實例介紹(簡明入門教程)
網(wǎng)絡(luò)套接字是下一代WEB應(yīng)用程序雙向通信技術(shù),它是基于一個獨立的socket并且需要客戶端瀏覽器支持HTML52014-04-04簡單談?wù)凧avascript函數(shù)中的arguments
在JavaScript中,arguments對象是比較特別的一個對象,實際上是當(dāng)前函數(shù)的一個內(nèi)置屬性。下面這篇文章主要介紹了關(guān)于Javascript函數(shù)中的arguments面貌以及如何轉(zhuǎn)化為數(shù)組的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-02-02Angularjs 設(shè)置全局變量的方法總結(jié)
這篇文章主要介紹了Angularjs 設(shè)置全局變量的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2016-10-10JavaScript編程中window的location與history對象詳解
這篇文章主要介紹了JavaScript編程中window的location與history對象,是JavaScript入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10