1.JS 是单线程
- JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这是因为JavaScript使用来操作DOM的,比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除;
- 单线程就意味着只有前一个任务执行完了再执行下一个任务,如果上一个任务比较耗时则下一个任务一直在等待,这样就会引起阻塞的问题。为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程。于是,JS 中出现了同步和异步。
2. 同步和异步
同步任务
- 前一个任务执行完成,才会执行下一个任务
- 同步任务都会在主线程上执行,形成一个执行栈
异步任务
- 在执行异步任务时不会阻塞后续任务的执行
- 异步任务都会放在任务队列中,当同步任务完成后,主线程会从任务队列取出异步任务放到主线程执行栈中执 行
- 常见异步任务:定时器,普通事件处理函数,资源加载(load,error),Promise实例对象的then方法等
3. JS 执行机制
- 先执行同步任务
- 遇到异步任务会将异步任务先放到 Event Table 里面,当满足异步任务的执行条件后,将异步任务(的回调函数)放到 Event Queue(任务队列,消息队列)中
异步任务分为微任务和宏任务
遇到微任务会将微任务先放到微任务的 Event Table 里面,当满足微任务的执行条件后,将微任务(的回调函数)放到 微任务的 Event Queue(任务队列,消息队列)中
遇到宏任务会将宏任务先放到宏任务的 Event Table 里面,当满足宏任务的执行条件后,将宏任务(的回调函数)放到 宏任务的 Event Queue(任务队列,消息队列)中
异步任务优先执行微任务,在执行宏任务
- 一旦所有同步任务执行完成后,主线程的执行栈为空,系统会从异步任务的任务队列中获取要执行的异步任务,放入到主线程的执行栈执行,执行完成后,系统又会从异步任务的任务队列中获取要执行的异步任务,一直循环此操作
- 像这样主线程不断从任务队列获取任务执行任务又获取任务又执行任务,这种机制称为事件循环(event loop)
4. 图示
5.例题
第一个
setTimeout(() => {
// timer1
console.log(1)
}, 1000)
console.log(2)
new Promise(function (resolve) {
console.log(3)
resolve()
}).then(function () {
console.log(4)
})
// timer2
setTimeout(() => {
let p = new Promise(function (resolve) {
console.log(5)
resolve()
})
p.then(function () {
console.log(7)
})
// timer3
setTimeout(() => {
console.log(6)
}, 1000)
}, 0)
// 正确答案为:2 3 4 5 7 1 6
1.定时器timer1为异步任务的宏任务,放到宏任务任务队列
2.输出2
3.new Promise为同步任务,输出3,但是 then方法为异步任务的微任务,放到微任务任务队列
4.定时器timer2位异步任务的宏任务,放到宏任务任务队列
5.同步任务执行完毕,开始执行异步任务,异步任务有微任务任务队列的then方法和宏任务任务队列的timer1(1000ms),timer2(0ms),先执行微任务,输出4
6.执行timer2,new Promise为同步任务,输出5,p.then()放到微任务任务队列,定时器timer3放到宏任务任务队列
7.有微任务(p.then()),所以先执行微任务,输出7
8.微任务执行完毕,执行宏任务(timer1(1000ms),timer3(1000ms))由于timer1先放到任务队列,所以执行timer1,输出1
9.然后执行timer3,输出6
第二个
let d1, d2
function setD1() {
d1 = Date.now()
}
function setD2() {
d2 = Date.now()
console.log('d2-d1:' + (d2 - d1))
}
setTimeout(setD1, 100) // timer1
setTimeout(setD2, 200) // timer2
function delay() {
let now = Date.now()
while (Date.now() - now < 500) {}
}
delay()
// d2-d1:1
1.首先timer1,timer2,放到Event Table中
2.执行同步任务 delay(),因为while循环的条件,所以此同步任务耗时500ms,而此时,timer1,timer2,早已满足执行条件,他们的回调函数已经被注册到任务队列,所以当主线程读取任务队列时会立马执行timer1和timer2的回调函数,时间非常短。等同于如下代码
let d3, d4
setTimeout(() => {
d3 = Date.now()
}, 0)
setTimeout(() => {
d4 = Date.now()
console.log('d4-d3:' + (d4 - d3)) // d4-d3:0
}, 0)
新手一个,以上如有错误的地方请指正
参考文章:从一到面试题谈js的运行机制
JS执行机制详解