詳解JavaScript作用域 閉包
JavaScript閉包,是JS開發(fā)工程師必須深入了解的知識。3月份自己曾撰寫博客《JavaScript閉包》,博客中只是簡單闡述了閉包的工作過程和列舉了幾個示例,并沒有去刨根問底,將其弄明白!
現(xiàn)在隨著對JavaScript更深入的了解,也剛讀完《你不知道的JavaScript(上卷)》這本書,所以乘機整理下,從底層和原理上去刨一下。
JavaScript并不具有動態(tài)作用域,它只有詞法作用域。詞法作用域是在寫代碼或者說定義時確定的,而動態(tài)作用域是在運行時確定的。了解閉包前,首先我們得知道什么是詞法作用域(作用域是由書寫代碼時函數(shù)聲明的位置來決定的)。
一、何為閉包
示例1:
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
bzz(); //2
在foo()執(zhí)行后,通常認為垃圾回收機制會將foo()的整個內(nèi)部作用域都被銷毀;而閉包可以阻止這樣事情發(fā)生,讓其內(nèi)部作用域依然存在。因為bar()處于foo()內(nèi)部,它擁有涵蓋foo()作用域的閉包,使得該作用域能夠一直存活,以供bar()在之后任何時間進行引用。
bar()依然持有對該作用域的引用,而這個引用就叫作閉包。
簡言之:當函數(shù)可以記住并訪問所在的詞法作用域,即使函數(shù)是在當前詞法作用域之外執(zhí)行,這時就產(chǎn)生了閉包。
示例2:
無論使用何種方式對函數(shù)類型的值進行傳遞,當函數(shù)在別處被調(diào)用時都可以觀察到閉包。
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn(); // 這就是閉包
}
示例3:
將一個內(nèi)部函數(shù)(timer)傳遞給setTimeout。timer具有涵蓋wait()作用域的閉包,保有對變量message的引用。
wait()執(zhí)行1000毫秒后,它的作用域并不會消失,timer依然保有wait()作用域的閉包。
function wait(message){
setTimeout( function timer(){
console.log(message);
},1000);
}
wait("Hello,ligang");
示例4:
下述activator()具有涵蓋setupBot()作用域的閉包!
function setupBot(name, selector){
$(selector).click(function activator(){
console.log("Activating: "+ name);
});
}
setupBot("Closure Bot 1", "#bot_1");
setupBot("Closure Bot 2", "#bot_2");
二、循環(huán)和閉包
for(var i=1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}
// 期望:每秒一次的頻率輸出1~5
// 結果:每秒一次的頻率輸出五次6
先解釋一下:“i*1000”,5個定時分別在1s、2s、3s、4s、5s后執(zhí)行,并不是1s、3s、6s、10s、15s。也就是頻率為1s,不是每次間隔增加1s。如果去掉i寫成“1000”,會在for執(zhí)行完1s后直接輸出五次6。
回調(diào)函數(shù)在循環(huán)結束后才被執(zhí)行,因此輸出的是循環(huán)終止條件是i值。事實上,當定時器運行時即使每個迭代中執(zhí)行的是setTimeout(..., 0),所有的回調(diào)函數(shù)依然是在循環(huán)結束后才被執(zhí)行。
根據(jù)作用域的工作原理,盡管五個函數(shù)是在各個迭代中分別定義的,但是它們都被封閉在一個共享的全局作用域中,因此實際上只有一個i。
解決方案1:
for(var i=0; i<=5; i++){
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000 );
})(i);
}
// 結果:每秒一次的頻率輸出1~5
每個迭代都生成一個新的作用域,使得延遲函數(shù)的回調(diào)可以將新的作用封閉在每個迭代內(nèi)部,每個迭代中都會含有一個具有正確值的變量供我們訪問。
解決方案2(ES6):
for(var i=0; i<=5; i++){
let j = i;
setTimeout(function timer(){
console.log(j);
}, j*1000 );
}
// 結果:每秒一次的頻率輸出1~5
for(let i=0; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000 );
}
// 結果:每秒一次的頻率輸出五次6
三、模塊
模塊需要具備兩個必要條件:
(1)必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會創(chuàng)建一個新的模塊實例)。
(2)封閉函數(shù)必須返回至少一個內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
典型的模塊化:
function CoolMoudle(){
var something = "cool";
var doSomething = function(){
console.log(something);
}
return{
doSomething: doSomething
};
}
var foo = CoolMoudle(); //如果不執(zhí)行外部函數(shù)CoolMoudle(),內(nèi)部作用域和閉包都無法創(chuàng)建
foo.doSomething(); //cool
單例模式:
var foo = (function CoolModule(id){
function change(){
// 修改公共API
publicAPI.identify = identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})("foo module");
foo.identify(); //foo module
foo.change();
foo.identify(); //FOO MODULE
以上就是詳解JavaScript作用域 閉包的詳細內(nèi)容,更多關于JavaScript作用域 閉包的資料請關注腳本之家其它相關文章!
相關文章
js數(shù)組方法reduce經(jīng)典用法代碼分享
本文給大家整理了很多關于js數(shù)組方法reduce的經(jīng)典代碼片段,能夠讓大家更好的理解reduce的實例用法,一起學習下吧。2018-01-01
Bootstrap模態(tài)對話框中顯示動態(tài)內(nèi)容的方法
今天小編就為大家分享一篇Bootstrap模態(tài)對話框中顯示動態(tài)內(nèi)容的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
javascript中的previousSibling和nextSibling的正確用法
這篇文章主要介紹了javascript中的previousSibling和nextSibling的正確用法的相關資料,需要的朋友可以參考下2015-09-09

