JS是一个单线程的脚本语言。
主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前有微任务,那么要先执行微任务,全部执行完之后等待主线程的调用,调用完之后再去任务队列中查看是否有异步任务,这样一个循环往复的过程就是事件循环!
- 宏任务包括setTimeout、setInterval、I/O操作、ajax、事件绑定等;
- 微任务包括new Promise()后的then与catch函数、MutationObserver、process.nextTick等。
下面这些是我收集整理的关于事件循环的练习题,希望能帮助大家更好的理解事件循环的概念。
总结做题口诀:先同步,后异步,先微任务,后宏任务。
1、下面哪些操作是异步的?
下面哪些操作是异步的?
a. 将数组元素进行排序
b. 发送请求获取数据
c. 计算两个数的和
d. 读取本地文件
答案:b和d
2、下面的代码输出什么?
console.log('start')
setTimeout(() => {
console.log('timeout')
}, 0)
console.log('end')
答案:start, end, timeout
解析:setTimeout函数中的第二个参数表示延迟的时间,当设置为0时,setTimeout函数会被放到任务队列的末尾,等待执行栈中所有任务执行完成后再执行setTimeout函数中的回调函数。
3、下面的代码输出什么?
console.log('start')
setTimeout(() => {
console.log('timeout1')
}, 0)
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('then1')
})
console.log('end')
答案:start, promise1, end, then1, timeout1
解析:Promise对象是同步执行的,所以会先输出promise1。但是.then()方法是异步执行的,会被放到任务队列中等待执行,因此end会先输出。然后在任务队列中执行.then()方法,输出then1。最后在任务队列中执行setTimeout中的回调函数,输出timeout1。
4、下面代码输出什么?
console.log('start')
setTimeout(() => {
console.log('timeout1')
Promise.resolve().then(() => console.log('then2'))
}, 0)
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('then1')
setTimeout(() => {
console.log('timeout2')
}, 0)
})
console.log('end')
答案:start, promise1, end, then1, timeout1, then2, timeout2
解析:同样的,Promise对象和.then()方法是同步执行的,但是回调函数中包含的Promise对象和.then()方法时异步执行的,会被放到任务队列中等待执行。因此start, promise1, end, then1会先输出。然后在任务队列中执行setTimeout中的回调函数,输出timeout1,然后将包含的Promise对象和.then()方法放到任务队列中。在任务队列中执行.then()方法,输出then2。最后在任务队列中执行第二个setTimeout中的回调函数,输出timeout2。
5、下面代码输出什么?
console.log('start'); // 1
setTimeout(function() {
console.log('setTimeout'); // 4
}, 0);
Promise.resolve().then(function() {
console.log('promise'); // 3
});
console.log('end'); // 2
这段代码中,我们依次执行了以下操作:
打印"start"
打印"end"
创建一个Promise对象,并将其添加到微任务队列中
执行Promise中的回调函数,打印"promise"
执行setTimeout中的回调函数,打印"setTimeout"
根据JavaScript事件循环机制的规则,它的执行过程如下:
全局上下文入栈,开始执行同步任务
打印"start"
全局上下文出栈
全局上下文入栈,开始执行同步任务
打印"end"
全局上下文出栈
全局上下文入栈,开始执行同步任务
创建Promise对象,并将其添加到微任务队列中
执行Promise对象中的回调函数,打印"promise"
全局上下文出栈
执行微任务队列中的任务,打印"setTimeout"
因此,最终输出结果应该是:
start
end
promise
setTimeout
6、下面代码的输出结果是什么?
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
输出结果是 1 4 3 2。
解析:代码执行顺序为同步任务先执行,输出1,然后将异步任务放入任务队列中。setTimeout是一个宏任务,Promise.then是一个微任务。由于Promise是微任务,会优先执行,所以先输出3。然后执行完同步任务后,会依次执行微任务。所以输出顺序为1,4,3。最后执行宏任务,输出2。
7、下面代码的输出结果是什么?
console.log("start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("end");
输出结果是 start end Promise setTimeout。
解析:代码执行顺序同第一题,先输出同步任务 start 和 end,然后将异步任务放入任务队列中。由于Promise.then是一个微任务,所以会优先执行,输出 Promise。接着执行完同步任务后,依次执行微任务中的 Promise。最后执行宏任务 setTimeout 输出结果。
8、下面代码的输出结果是什么?
setTimeout(function () {
new Promise(function (resolve, reject) {
console.log('异步宏任务promise');
resolve();
}).then(function () {
console.log('异步微任务then')
})
console.log('异步宏任务');
}, 0)
new Promise(function (resolve, reject) {
console.log('同步宏任务promise');
resolve();
}).then(function () {
console.log('同步微任务then')
})
console.log('同步宏任务')
9、下面代码的输出结果是什么?
setTimeout(() => {
console.log('异步1任务time1');
new Promise(function (resolve, reject) {
console.log('异步1宏任务promise');
setTimeout(() => {
console.log('异步1任务time2');
}, 0);
resolve();
}).then(function () {
console.log('异步1微任务then')
})
}, 0);
console.log('主线程宏任务');
setTimeout(() => {
console.log('异步2任务time2');
}, 0);
new Promise(function (resolve, reject) {
console.log('宏任务promise');
// reject();
resolve();
}).then(function () {
console.log('微任务then')
}).catch(function () {
console.log('微任务catch')
})
console.log('主线程宏任务2');
10、下面代码的输出结果是什么?
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
输出结果:10个10
解析:因为setTimeout是异步任务,会首先执行主任务。再执行异步任务。而i是全局变量。所以等异步任务执行的时候,i一直都是10
11、下面代码的输出结果是什么?
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000);
}
10
10
10
10
10
10
10
10
10
10
12、下面代码的输出结果是什么?
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000);
}
0
1
2
3
4
5
6
7
8
9
解析:大家想想,这是为什么?
13、下面代码的输出结果是什么?
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000);
}
结果:0-9
解析:let 声明的变量会在当前的块级作用域里面(for 循环的 body 体,也即两个花括号之间的内容区域)创建一个全局词法环境(Lexical Environment),该环境里面包括了当前 for 循环过程中的 i。