我在閉包一文中的結(jié)尾留下了一個(gè)關(guān)于setTimeout與循環(huán)閉包的思考題谷饿。
利用閉包惶我,修改下面的代碼,讓循環(huán)輸出的結(jié)果依次為1博投, 2绸贡, 3, 4毅哗, 5
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
值得高興的是听怕,很多朋友在閱讀了我的文章之后確實(shí)對(duì)閉包有了更加深刻的了解,并準(zhǔn)確的給出了好幾種寫(xiě)法虑绵。大家能夠認(rèn)真的閱讀我的文章并且一個(gè)例子一個(gè)例子的上手練習(xí)尿瞭,這種認(rèn)可對(duì)我而言真的非常感動(dòng)。
但是也有一些基礎(chǔ)稍差的朋友在閱讀了之后翅睛,對(duì)于這題的理解仍然感到困惑声搁,因此應(yīng)一些讀者老爺?shù)囊螅璐宋恼聦iT(mén)對(duì)setTimeout進(jìn)行一個(gè)相關(guān)的知識(shí)分享捕发,希望大家讀完之后都能夠有新的收獲疏旨。
初學(xué)setTimeout,我們很容易知道setTimeout有兩個(gè)參數(shù)扎酷,第一個(gè)參數(shù)為一個(gè)函數(shù)檐涝,我們通過(guò)該函數(shù)定義將要執(zhí)行的操作。第二個(gè)參數(shù)為一個(gè)時(shí)間毫秒數(shù),表示延遲執(zhí)行的時(shí)間谁榜。
setTimeout(function() {
console.log('一秒鐘之后我將被打印出來(lái)')
}, 1000)
執(zhí)行結(jié)果如圖
可能不少同學(xué)對(duì)于setTimeout的理解止步于此幅聘,但還是有不少人發(fā)現(xiàn)了一些其他的東西,并在評(píng)論里提出了疑問(wèn)窃植。比如上圖中的這個(gè)數(shù)字7帝蒿,是什么?
每一個(gè)setTimeout在執(zhí)行時(shí)撕瞧,會(huì)返回一個(gè)唯一ID陵叽,上圖中的數(shù)字7,就是這個(gè)唯一ID丛版。我們?cè)谑褂脮r(shí),常常會(huì)使用一個(gè)變量將這個(gè)唯一ID保存起來(lái)偏序,用以傳入clearTimeout页畦,清除定時(shí)器。
接下來(lái)研儒,我們還需要考慮另外一個(gè)重要的問(wèn)題豫缨,那就是setTimeout中定義的操作,在什么時(shí)候執(zhí)行端朵?為了引起大家的重視好芭,我們來(lái)看看下面的例子。
var timer = setTimeout(function() {
console.log('setTimeout actions.');
}, 0);
console.log('other actions.');
思考一下冲呢,當(dāng)我將setTimeout的延遲時(shí)間設(shè)置為0時(shí)舍败,上面的執(zhí)行順序會(huì)是什么?
在瀏覽器中的console中運(yùn)行試試看敬拓,很容易就能夠知道答案邻薯,如果你沒(méi)有猜中答案,那么我這篇文章就值得你點(diǎn)一個(gè)贊了乘凸,因?yàn)榻酉聛?lái)我分享的小知識(shí)厕诡,可能會(huì)在筆試中救你一命。
在對(duì)于執(zhí)行上下文的介紹中营勤,我與大家分享了函數(shù)調(diào)用棧這種特殊數(shù)據(jù)結(jié)構(gòu)的調(diào)用特性灵嫌。在這里,將會(huì)介紹另外一個(gè)特殊的隊(duì)列結(jié)構(gòu)葛作,頁(yè)面中所有由setTimeout定義的操作寿羞,都將放在同一個(gè)隊(duì)列中依次執(zhí)行。
我用下圖跟大家展示一下隊(duì)列數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)进鸠。
隊(duì)列:先進(jìn)先出
而這個(gè)隊(duì)列執(zhí)行的時(shí)間稠曼,需要等待到函數(shù)調(diào)用棧清空之后才開(kāi)始執(zhí)行。即所有可執(zhí)行代碼執(zhí)行完畢之后,才會(huì)開(kāi)始執(zhí)行由setTimeout定義的操作霞幅。而這些操作進(jìn)入隊(duì)列的順序漠吻,則由設(shè)定的延遲時(shí)間來(lái)決定。
更加詳細(xì)的執(zhí)行順序司恳,將會(huì)在事件循環(huán)的文中中描述
因此在上面這個(gè)例子中途乃,即使我們將延遲時(shí)間設(shè)置為0,它定義的操作仍然需要等待所有代碼執(zhí)行完畢之后才開(kāi)始執(zhí)行扔傅。這里的延遲時(shí)間耍共,并非相對(duì)于setTimeout執(zhí)行這一刻,而是相對(duì)于其他代碼執(zhí)行完畢這一刻猎塞。所以上面的例子執(zhí)行結(jié)果就非常容易理解了试读。
為了幫助大家理解,再來(lái)一個(gè)結(jié)合變量提升的更加復(fù)雜的例子荠耽。如果你能夠正確看出執(zhí)行順序钩骇,那么你對(duì)于函數(shù)的執(zhí)行就有了比較正確的認(rèn)識(shí)了,如果還不能铝量,就回過(guò)頭去看看其他幾篇文章倘屹。
setTimeout(function () {
console.log(a);
}, 0);
var a = 10;
console.log(b);
console.log(fn);
var b = 20;
function fn() {
setTimeout(function () {
console.log('setTImeout 10ms.');
}, 10);
}
fn.toString = function () {
return 30;
}
console.log(fn);
setTimeout(function () {
console.log('setTimeout 20ms.');
}, 20);
fn();
執(zhí)行結(jié)果如圖所示。
OK慢叨,關(guān)于setTimeout就暫時(shí)先介紹到這里纽匙,我們回過(guò)頭來(lái)看看那個(gè)循環(huán)閉包的思考題。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
如果我們直接這樣寫(xiě)拍谐,根據(jù)setTimeout定義的操作在函數(shù)調(diào)用棧清空之后才會(huì)執(zhí)行的特點(diǎn)烛缔,for循環(huán)里定義了5個(gè)setTimeout操作。而當(dāng)這些操作開(kāi)始執(zhí)行時(shí)赠尾,for循環(huán)的i值力穗,已經(jīng)先一步變成了6。因此輸出結(jié)果總為6气嫁。而我們想要讓輸出結(jié)果依次執(zhí)行当窗,我們就必須借助閉包的特性,每次循環(huán)時(shí)寸宵,將i值保存在一個(gè)閉包中崖面,當(dāng)setTimeout中定義的操作執(zhí)行時(shí),則訪問(wèn)對(duì)應(yīng)閉包保存的i值即可梯影。
而我們知道在函數(shù)中閉包判定的準(zhǔn)則巫员,即執(zhí)行時(shí)是否在內(nèi)部定義的函數(shù)中訪問(wèn)了上層作用域的變量。我們需要包裹一層自執(zhí)行函數(shù)為閉包的形成提供條件甲棍。
因此简识,我們只需要2個(gè)操作就可以完成題目需求,一是使用自執(zhí)行函數(shù)提供閉包條件,二是傳入i值并保存在閉包中七扰。
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i)
}
利用斷點(diǎn)調(diào)試奢赂,在chrome中查看執(zhí)行順序與每一個(gè)閉包中不同的i值
當(dāng)然,也可以在setTimeout的第一個(gè)參數(shù)處利用閉包颈走。
for (var i = 1; i <= 5; i++) {
setTimeout((function (i) {
return function () {
console.log(i);
}
})(i), i * 1000);
}
下一篇:前端基礎(chǔ)進(jìn)階(七):this
上一篇:前端基礎(chǔ)進(jìn)階(五):閉包
前端基礎(chǔ)進(jìn)階目錄