最近在网上看到一篇关于for循环的面试题,可以让自己回顾一下作用域的基础知识。
不多说,题目如下:
下面的代码输出的是什么
1 | for (var i = 0; i < 5; i++) { |
相信稍微了解过一些作用域相关知识的,都可以很快回答出,会打印出5个5,而且是在1s后同时打印出来。
其实这段代码相当于
1 | var i = 0 |
关键在于var
创建了一个全局的变量,而不是一个for
循环内的块作用域内的变量。
那么我们要怎么修改让输出改为每隔1s输出,且输出0-4呢。
既然块作用域解决不了我们的问题,那么我们可以创建一个函数作用域来保存我们的变量,创建一个函数作用域,很明显需要一个函数
1 | function timeOutPrint(i) { |
当然我们也可以使用IIFE
1 | for (var i = 0; i < 5; i++) { |
不过从代码的角度来说上面将函数封装起来可以更加便于阅读
我们换一个角度去看这个问题,其实我们是出现了一个异步的代码,那么处理异步的其中一个方法就是使用promise。
1 | function timeOutPrint(i) { |
我们换一个角度,其实当我们一开始写这段代码,心里其实是希望,for循环可以暂停,然后隔1s进入下一个循环,那么如果按照这个思路去改写呢
使用promise可以解决这个问题,但是因为js的事件循环,会导致Promise.resolve也会在for循环之后执行,所以不可以直接传入i,这个用了resolve传值得办法
1 | function timeOutPrint(i) { |
其实如果使用let
就可以有比较好的效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function timeOutPrint(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i)
resolve(i);
}, 1000);
});
}
var state = Promise.resolve(true);
for (var i = 0; i < 5; i++) {
let j = i;
state = state.then(() => {
return timeOutPrint(j);
})
}
这里我们看到了
let
的使用,后面我们会再次用到这个ES6的语法。
我们通过生成一系列顺序执行的Promise
来实现,但是并没有真正的使for
循环中断,而使代码中断的语法则可以使用ES6中的Generator
和yield
。
1 | function isPromise(obj) { |
这里我们写了一个简单的Generator
的执行函数loop
。
Generator
可以很好的实现代码暂停的功能,但是不可以执行像普通函数一样fn()
,而是需要搭配一个执行函数来执行.next()
方法。
那么是否有更好的方案,async
和await
出现了
1 | function timeOutPrint(i) { |
或者
1 | var sleep = (time) => new Promise((resolve) => { |
还记得上面我们用过的let
么1
2
3
4
5for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
},i*1000 );
}
说明可以参看块级作用域和let