javascript作用域和閉包使用詳解
作用域的嵌套將形成作用域鏈,函數(shù)的嵌套將形成閉包。閉包與作用域鏈?zhǔn)?JavaScript 區(qū)別于其它語言的重要特性之一。
作用域
JavaScript 中有兩種作用域:函數(shù)作用域和全局作用域。
在一個(gè)函數(shù)中聲明的變量以及該函數(shù)的參數(shù)享有同一個(gè)作用域,即函數(shù)作用域。一個(gè)簡單的函數(shù)作用域的例子:
function foo() {
var bar = 1;
{
var bar = 2;
}
return bar; // 2
}
不同于C等其它有塊作用域的語言,這里將始終返回 2 。
全局作用域,對(duì)于瀏覽器來說可以理解為 window 對(duì)象(Node.js則是 global):
var bar = 1;
function foo() {}
alert(window.bar); // 1
alert(window.foo); // "function foo() {}"
對(duì)于變量 bar 和函數(shù) foo 都屬于全局作用域,都是 window 的一個(gè)屬性。
作用域鏈
在 JavaScript 中訪問一個(gè)變量時(shí),將從本地變量和參數(shù)開始,逐級(jí)向上遍歷作用域直到全局作用域。
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ù)中,各個(gè)變量都能被逐級(jí)遍歷并輸出。而倒數(shù)第二層的函數(shù)中,變量 three 無法遍歷找到,所以輸出了 undefined 。
舉一個(gè)通俗點(diǎn)的例子,你準(zhǔn)備要花錢買點(diǎn)東西時(shí),會(huì)先摸摸自己的錢包,沒了你可以找你爸要,你爸也沒有就再找你爺爺,... 。而你爸沒錢買東西時(shí),他并不會(huì)來找你要。
閉包
在一個(gè)函數(shù)中,定義另一個(gè)函數(shù),稱為函數(shù)嵌套。函數(shù)的嵌套將形成一個(gè)閉包。
閉包與作用域鏈相輔相成,函數(shù)的嵌套在產(chǎn)生了鏈?zhǔn)疥P(guān)系的多個(gè)作用域的同時(shí),也形成了一個(gè)閉包。
function bind(func, target) {
return function() {
func.apply(target, arguments);
};
}
那么怎么理解閉包呢?
外部函數(shù)不能訪問內(nèi)嵌函數(shù)
外部函數(shù)也不能訪問內(nèi)嵌函數(shù)的參數(shù)和變量
而內(nèi)嵌函數(shù)可以訪問外部函數(shù)的參數(shù)和變量
換一個(gè)說法:內(nèi)嵌函數(shù)包含了外部函數(shù)的作用域
我們?cè)倏纯粗爸v述的作用域鏈的例子,這次從閉包的角度來理解下:
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ù)無法訪問到最里層的變量,同時(shí),最里層的 scope = 3 這個(gè)賦值操作并沒有對(duì)其外部的同名變量產(chǎn)生影響。
再換個(gè)角度來理解閉包:
每次外部函數(shù)的調(diào)用,內(nèi)嵌函數(shù)都會(huì)被創(chuàng)建一次
在它被創(chuàng)建時(shí),外部函數(shù)的作用域(包括任何本地變量、參數(shù)等上下文), 會(huì)成為每個(gè)內(nèi)嵌函數(shù)對(duì)象的內(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" ,這是因?yàn)樵?list 中的兩個(gè)函數(shù)訪問的變量 i 都是其上一層作用域的同一個(gè)變量。
我們改動(dòng)下代碼,以利用閉包來解決這個(gè)問題:
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ù)”接收了一個(gè)參數(shù)變量 i ,在其函數(shù)內(nèi)以參數(shù) j 的形式存在,它與被返回的內(nèi)層函數(shù)中的名稱 j 指向同一個(gè)引用。外層函數(shù)執(zhí)行并退出后,參數(shù) j (此時(shí)它的值為 i 的當(dāng)前值)成為了其內(nèi)層函數(shù)的狀態(tài)的一部分被保存了下來。
相關(guān)文章
Javascript學(xué)習(xí)筆記之?dāng)?shù)組的遍歷和 length 屬性
我們一般用循環(huán)來遍歷數(shù)組,而循環(huán)一直是 JavaScript 性能問題的常見來源,有時(shí)循環(huán)用得不好會(huì)嚴(yán)重降低代碼的運(yùn)行速度。數(shù)組的屬性可以分為三種:length屬性,索引屬性,其他屬性.和普通對(duì)象相比,數(shù)組對(duì)象特殊的地方就是它的length屬性和索引屬性。2014-11-11
JavaScript數(shù)據(jù)結(jié)構(gòu)和算法之二叉樹詳解
這篇文章主要介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)和算法之二叉樹詳解,本文講解了二叉樹的概念、二叉樹的特點(diǎn)、二叉樹節(jié)點(diǎn)的定義、查找最大和最小值等內(nèi)容,需要的朋友可以參考下2015-02-02
總結(jié)javascript三元運(yùn)算符知識(shí)點(diǎn)
這是一篇關(guān)于javascript三元運(yùn)算符的相關(guān)基礎(chǔ)知識(shí)點(diǎn)內(nèi)容,大家可以學(xué)習(xí)一下鞏固基礎(chǔ)知識(shí)。2018-09-09
深入解析JavaScript中的數(shù)字對(duì)象與字符串對(duì)象
這篇文章主要介紹了JavaScript中的數(shù)字對(duì)象與字符串對(duì)象,是JavaScript入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10
解析js中獲得父窗口鏈接getParent方法以及各種打開窗口的方法
本篇文章是對(duì)js中獲得父窗口鏈接getParent方法以及各種打開窗口的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06

