首先來看一個比較簡單的問題,咱們想實現的就是每隔1s輸出0-4的值,就是這麼簡單,看下錯誤寫法:jquery
function test() { for (var i = 0; i < 5; ++i) { setTimeout(function() { console.log("index is :", i); }, 1000); } } test();
以上代碼會如何輸出?輸出以下:數組
index is : 5 index is : 5 index is : 5 index is : 5 index is : 5
並且該操做幾乎是在同一時間完成,setTimeout定時根本就沒有起做用,這是由於:單線程的js在操做時,對於這種異步操做,會先進行一次「保存」,等到整個for循環執行結束後,此時i的值已經變成5,由於setTimeout是寫在for循環中的,至關於存在5次定時調用,這5次調用均是在for循環結束後進行的,因此天然而然輸出都是5,正確的實現有幾種,通常狀況下,咱們使用遞歸實現,以下:閉包
// var i = 0; // var arr = [0, 1, 2, 3, 4]; // function box6() { // if (i < arr.length) { // setTimeout(function() { // console.log("index is : ", i); // i++; // box6(); // }, 1000); // } // } box6(); function box7(param) { if (param < 5) { console.log("index is :", param); setTimeout(function() { box7(param + 1); }, 1000) } } box7(0);
正確實現每隔1s打印輸出以下:異步
index is : 0 index is : 1 index is : 2 index is : 3 index is : 4
使用遞歸實現的倒計時:async
function showTime(count) { console.log("count is : ", count); if (count == 0) { console.log("All is Done!"); } else { count -= 1; setTimeout(function() { showTime(count); }, 1000); } } showTime(20);
遞歸調用很好的解決了setTimeout同時執行的狀況,若是使用async、await實現,能夠以下寫法:函數
var asyncFunc = function(arr, i) { return new Promise(function(resolve, reject) { setTimeout(function() { arr.push(i); console.log("index is : ", i); resolve(); }, 1000); }); } var box5 = async function() { var arr = []; for (var i = 0; i < 5; i++) { await asyncFunc(arr, i); } console.log(arr); } box5();
一樣實現每隔1s正確地打印輸出以下:測試
index is : 0 index is : 1 index is : 2 index is : 3 index is : 4 [ 0, 1, 2, 3, 4 ]
接下來再看個需求:構建一個函數數組,該數組每一項函數的功能是依次輸出0-4,錯誤寫法以下:ui
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + list[i]; result.push(function() { console.log(item + ' ' + list[i]) }); } return result; } function testList() { var fnlist = buildList([1, 2, 3]); for (var j = 0; j < fnlist.length; j++) { fnlist[j](); } } testList();
輸出以下:線程
item3 undefined item3 undefined item3 undefined
for循環裏面使用匿名函數和直接寫setTimeout調用比較相似,可是這裏又有點不一樣,for循環執行結束後,匿名函數開始調用,發現裏面存在「item」變量,這時依次會向上級查找,剛好找到循環結束時的item變量值爲「list[2]」即爲3,item爲3可是i的值已經變爲3,又由於list[3]的值爲undefined,因此這裏輸出3遍item3 undefined。不信能夠修改下數組以下,其他代碼不變:blog
function buildList(list) { ... } function testList() { var fnlist = buildList([6, 7, 8]); ... } testList();
這裏絕對輸出的是:
item8 undefined item8 undefined item8 undefined
再來看下正確的實現:
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + list[i]; result.push(function(index, it) { return function() { console.log(it + ' ' + list[index]); } }(i, item)); } return result; } function testList() { var fnlist = buildList([6, 7, 8]); for (var j = 0; j < fnlist.length; j++) { fnlist[j](); } } testList();
輸出以下:
item6 6 item7 7 item8 8
這裏主要使用的是即時執行函數,什麼是即時執行函數?能夠理解爲一個封閉的代碼塊,該代碼塊中的代碼會在定義時當即執行一遍,各個代碼塊的做用域彼此獨立,不會污染外部環境,寫法其實有不少種,上面只是一種,一樣的還有使用void、+、-、!等等,jquery源碼就是直接使用的這裏的圓括號寫法的這種。
再看幾個測試例子:
function box2() { var arr = []; for (var i = 0; i < 5; i++) { arr[i] = (function(num) { //自我執行,並傳參(將匿名函數造成一個表達式)(傳遞一個參數) return num; //這裏的num寫什麼均可以 })(i); //這時候這個括號裏面的i和上面arr[i]的值是同樣的都是取自for循環裏面的i } return arr; } console.log(box2()); //[ 0, 1, 2, 3, 4 ]
function box4() { var arr = []; for (var i = 0; i < 5; i++) { arr[i] = (function(num) { //自我執行,並傳參(將匿名函數造成一個表達式)(傳遞一個參數),在這個閉包裏面再寫一個匿名函數 return function() { return num; } })(i); //這時候這個括號裏面的i和上面arr[i]的值是同樣的都是取自for循環裏面的i } return arr; } console.log(box4()); //[ [Function], [Function], [Function], [Function], [Function] ]
box4這種寫法其實跟上面有一種是一致的,就很少說了,其實主要就是閉包,稍微改變一下代碼,實現的結果卻大相徑庭,共勉吧。。。