本文纯粹是无意间看到知乎一篇前端面试总结,作为面试官角度分析的一篇文章,深有感触。
附上链接:https://zhuanlan.zhihu.com/p/25855075
写给最初的我。
#一个不起眼的问题:
for(var i=0;i<5;i++){
setTimeout(function(){console.log(new Date,i)},1000);
}
console.log(new Date,i);
如果对同步和异步,变量作用域,闭包等概念有正确的理解的话,应该知道正确答案:
如果约定用箭头表示前后两次输出之间有1秒的时间间隔,用逗号表示前后的两次输出时间间隔可忽略,那么实际运行的结果可以描述为:
5->5,5,5,5,5
#追问1:闭包
如果期望输出为5->0,1,2,3,4,如何改造代码?
for(var i=0;i<5;i++){
(function(ind){
setTimeout(function(){console.log(new Date,ind)},1000);
})(i)
}
console.log(new Date,i);
还有一个熟悉的陌生人:按值传递,也可实现。
var input=function(i){
setTimeout(function(){
console.log(new Date,i)
},1000);
}
for(var i=0;i<5;i++){
input(i);
}
console.log(new Date,i);
最后,熟悉ES6的小伙伴们:
for(let i=0;i<5;i++){
setTimeout(function(){console.log(new Date,i)},1000);
}
console.log(new Date,i);
上面只改了声明关键词var,用let代替后,会发现运行的结果是这样的(代码的最后一行i未定义):
#追问2:ES6
如果将问题升级,新的需求是期望输出代码为0->1->2->3->4->5,要求原有的循环和两处console.log不变。再精确点描述:代码执行时立即输出0,之后每隔一秒输出1,2,3,4,循环结束后大概在第5秒输出5。
如果不使用ES6的话,下面这个粗暴的方法也是有效的。
for(var i=0;i<5;i++){
(function(ind){
setTimeout(function(){
console.log(new Date,ind)
},ind*1000);
})(i);
}
setTimeout(function(){
console.log(new Date,i)
},i*1000);
如果了解ES6,那么就会知道其中有个对象是可以完美解决这样的异步调用问题,没错,就是Promise!
const task=[];
for(var i=0;i<5;i++){ //这里的var不能改为let
(function(ind){
task.push(new Promise(function(resolve){
setTimeout(function(){
console.log(new Date,ind);
resolve(); //这里一定要有resolve(),
},ind*1000);
}));
})(i)
}
Promise.all(task)
.then(function(){
setTimeout(function(){
console.log(new Date,i)
},1000);
});
还是看看运行结果吧:
既然都用了ES6的Promise了,索性就用箭头函数改写一下吧!
const task=[];
for(var i=0;i<5;i++){
((ind)=>{
task.push(new Promise((resolve)=>{
setTimeout(()=>{
console.log(new Date,ind);
resolve();
},ind*1000);
}))
})(i);
}
Promise.all(task)
.then(()=>{
setTimeout(()=>{console.log(new Date,i)},1000);
});
进一步用按值传递优化代码:
const task=[];
const input=(ind)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
console.log(new Date,ind);
resolve();
},ind*1000)
})
}
for(var i=0;i<5;i++){
task.push(input(i));
}
Promise.all(task)
.then(()=>{
setTimeout(()=>{
console.log(new Date,i);
},1000)
});
#追问3:ES7
如果Promise已经掌握,想要更近一步操作异步的话(特别是针对重复的回调),ES7中的Async函数会表现得更好。ES7的Async函数可以被认为是异步编程的终极解决方案。其实,从表现形式上看,Async函数就是将Generator函数的星号*替换成async,将yield替换成await。
const sleep=(timeoutMs)=>{
return new Promise((resolve)=>{
setTimeout(resolve,timeoutMs);
})
}
(async () =>{
for(var i=0;i<5;i++){
await sleep(1000);
console.log(new Date,i);
}
await sleep(1000);
console.log(new Date,i);
})();
有没有更简洁一些,且可读性更好一些呢?