首先来看一个比较简单的问题,我们想实现的就是每隔1s输出0-4的值,就是这么简单,看下错误写法:
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
使用递归实现的倒计时:
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,错误写法如下:
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。不信可以修改下数组如下,其余代码不变:
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这种写法其实跟上面有一种是一致的,就不多说了,其实主要就是闭包,稍微改变一下代码,实现的结果却截然不同,共勉吧。。。