hello大家好,我是disguiseFish~在这篇文章里,我给大家分享一下自己曾经遇到的面试题,这道面试题会一点点变难,你能答对几道题呢?
咱们先简单来看一下下面的代码
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
他先是给了我这几行简单的代码,然后问我打印出来的是什么 其实这么简单的代码看似简单,但它其实能衍生出很多问题
大多数同学应该都是知道这几行代码是做什么的吧? 先是打印最外层的console然后内部循环一秒后打印定时器内的console
因为setTimeout相当于是一个异步处理,所以会先执行外面的for循环,也就是循环了5次后 i等于5。这里用var声明的变量,所以每次i++都会重新赋值给var的第一个变量。
分析到这一步,需要你对js的同步异步的区别以及变量作用域,闭包等概念有正确的理解
这个时候在过了1000ms后会打印5次5,打印出来的时间都是一样的标准时间,除了第一个打印的是等了1秒外,之后的间隔都是可以忽略的;分析到这一步需要你对定时器的工作机制有一定的了解
这里代码的实际输出是:
Mon Apr 19 2021 16:49:53 GMT+0800 (GMT+08:00) 5
(1秒后)
Mon Apr 19 2021 16:49:53 GMT+0800 (GMT+08:00) 5
Mon Apr 19 2021 16:49:53 GMT+0800 (GMT+08:00) 5
Mon Apr 19 2021 16:49:53 GMT+0800 (GMT+08:00) 5
Mon Apr 19 2021 16:49:53 GMT+0800 (GMT+08:00) 5
Mon Apr 19 2021 16:49:53 GMT+0800 (GMT+08:00) 5
1 考点 闭包
这个时候 他继续追问我 希望输出变成 501234 ,让我改造一下代码
因为js的执行是个单线程,是通过任务队列(回调函数)实现异步操作的,我首选的是通过闭包的方式把每次for循环的值通过入参的方式传进去再打印
for (var i = 0; i < 5; i++) {
(function(j) { // 这里 j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);
这里先是for和最外层的console都是同步,setTimeout是异步
执行顺序是这样的:
先执行for循环,这里创建了五个入参不一样的定时器排在任务队列里,接着执行最外层的console,最后再依次执行在排队的五个定时器,最后分别打印出这五个不同入参定时器的入参值~~
经过学习补增:
我们在敲代码的时候 经常会用到settimeout这个方法来做异步的处理,我们通常都知道它的两个参数,其实它还有第三个参数,如果这里知道它的第三个参数的用途的话,这边的代码会简洁很多:
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(new Date, j);
}, 1000, i);
}
console.log(new Date, i);
// 输出是:
Tue Apr 20 2021 14:51:15 GMT+0800 (中国标准时间) 5
(1s后)
Tue Apr 20 2021 14:51:16 GMT+0800 (中国标准时间) 0
Tue Apr 20 2021 14:51:16 GMT+0800 (中国标准时间) 1
Tue Apr 20 2021 14:51:16 GMT+0800 (中国标准时间) 2
Tue Apr 20 2021 14:51:16 GMT+0800 (中国标准时间) 3
Tue Apr 20 2021 14:51:16 GMT+0800 (中国标准时间) 4
setTimeout的第三个参数是给setTimeout第一个函数的参数
比如:
function sum(x,y,z){
console.log(x+y+z);
}
setTimeout(sum,1000,1,2,3);
// 输出是:6
还有更直接的做法,对循环体做一下处理,让每次循环都能把i值拿到;我们可以利用js中几本呢类型的参数传递是 按值传递 的特征改造代码:
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 会传到方法内部
}
console.log(new Date, i);
// 这里的输出也是我们要的
执行步骤: 先for循环5次,执行了不同入参的方法五次,生成了5个不同入参的定时器,打印最外层的console,然后再执行定时器内的打印就达到了我们要的效果
2 考点 ES6
继续上面的代码题,现在希望代码立即输出0,之后每隔一秒一次输出1234,循环结束后大概在第5s的时候输出5,这个代码改造要不动两处的console
立即输出0 然后每隔1s输出递增的i 这样不难想到可以开个定时器 1000 *i 这样的逻辑
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(new Date, j);
}, 1000 * j); // 这里通过循环的i修改 0~4 的定时器间隔时间
})(i);
}
setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
console.log(new Date, i);
}, 1000 * i);
或者 用setTimeout的第3个参数
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(new Date(), j)
}, 1000 * i, i) // 这里修改 0~4 的定时器时间
}
setTimeout(function() {
console.log(new Date(), i)
}, 1000 * i)
这两部分代码的输出都符合要求
执行顺序: 先for循环,循环5遍生成了五个不同入参(0 1 2 3 4)的定时器在任务序列中排队,for循环结束后i为5,走最下面的定时器生成i为5 的定时器拍在任务序列后面,这个时候再依次执行任务序列中的回调函数。依次打印出来的值为 0 1 2 3 4 5 ,且输出的时间间隔均为1s~~
但是,其实面试官想得到的答案不是这个~~
他想考你es6,看你是否能熟练的使用这些知识,处理一部的代码随处可见,但熟悉和掌握异步操作的流程控制是成为合格开发者的基本素养。所以最好用es6的代码编写:
const tasks = [];
for (var i = 0; i < 5; i++) { // 这里 i 的声明不能改成 let,如果要改该怎么做?
((j) => {
tasks.push(new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, j);
resolve(); // 这里一定要 resolve,否则代码不会按预期 work
}, 1000 * j); // 定时器的超时时间逐步增加
}));
})(i);
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000); // 注意这里只需要把超时设置为 1 秒
});
再进一步简化:
const tasks = []; // 这里存放异步操作的 Promise
const output = (i) => new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, i);
resolve();
}, 1000 * i);
});
// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
tasks.push(output(i));
}
// 异步操作完成之后,输出最后的 i
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000);
});
使用Promis处理异步比回调机制让代码可读性更强,Promise的问题就是要处理好reject,不然会抛出错误~~
3 考点 ES7
接下来我们可以用es7的async/await来让这段代码更简洁,
const sleep = (timeountMS) => new Promise((resolve) => {
setTimeout(resolve, timeountMS);
});
(async () => { // 声明即执行的 async 函数表达式
for (var i = 0; i < 5; i++) {
if (i > 0) {
await sleep(1000);
}
console.log(new Date, i);
}
await sleep(1000);
console.log(new Date, i);
})();
总结
以上的总结补充出处:juejin.cn/post/684490…