https://www.cnblogs.com/vipzhou/p/6519552.html
關於for循環里面異步操作的問題
首先來看一個比較簡單的問題,我們想實現的就是每隔1s輸出0-4的值,就是這么簡單,看下錯誤寫法:
1
2
3
4
5
6
7
8
|
function test() {
for
(
var
i = 0; i < 5; ++i) {
setTimeout(function() {
console.log(
"index is :"
, i);
}, 1000);
}
}
test();
|
以上代碼會如何輸出?輸出如下:
1
2
3
4
5
|
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,正確的實現有幾種,一般情況下,我們使用遞歸實現,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 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打印輸出如下:
1
2
3
4
5
|
index
is
: 0
index
is
: 1
index
is
: 2
index
is
: 3
index
is
: 4
|
使用遞歸實現的倒計時:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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實現,可以如下寫法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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正確地打印輸出如下:
1
2
3
4
5
6
|
index
is
: 0
index
is
: 1
index
is
: 2
index
is
: 3
index
is
: 4
[ 0, 1, 2, 3, 4 ]
|
接下來再看個需求:構建一個函數數組,該數組每一項函數的功能是依次輸出0-4,錯誤寫法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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();
|
輸出如下:
1
2
3
|
item3 undefined
item3 undefined
item3 undefined
|
for循環里面使用匿名函數和直接寫setTimeout調用比較類似,但是這里又有點不同,for循環執行結束后,匿名函數開始調用,發現里面存在“item”變量,這時依次會向上級查找,恰好找到循環結束時的item變量值為“list[2]”即為3,item為3但是i的值已經變為3,又因為list[3]的值為undefined,所以這里輸出3遍item3 undefined。不信可以修改下數組如下,其余代碼不變:
1
2
3
4
5
6
7
8
9
10
|
function buildList(list) {
...
}
function testList() {
var
fnlist = buildList([6, 7, 8]);
...
}
testList();
|
這里絕對輸出的是:
1
2
3
|
item8 undefined
item8 undefined
item8 undefined
|
再來看下正確的實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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();
|
輸出如下:
1
2
3
|
item6 6
item7 7
item8 8
|
這里主要使用的是即時執行函數,什么是即時執行函數?可以理解為一個封閉的代碼塊,該代碼塊中的代碼會在定義時立即執行一遍,各個代碼塊的作用域彼此獨立,不會污染外部環境,寫法其實有很多種,上面只是一種,同樣的還有使用void、+、-、!等等,jquery源碼就是直接使用的這里的圓括號寫法的這種。
再看幾個測試例子:
1
2
3
4
5
6
7
8
9
10
|
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 ]
|
1
2
3
4
5
6
7
8
9
10
11
12
|
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這種寫法其實跟上面有一種是一致的,就不多說了,其實主要就是閉包,稍微改變一下代碼,實現的結果卻截然不同,共勉吧。。。