# js执行机制与事件循环

# 常驻线程

浏览器常驻的线程有:

  1. js引擎线程(解释执行js、用户输入、网络请求)
  2. GUI线程(绘制用户界面、与js主线程是互斥的)
  3. http网络请求线程(处理用户的get、post等请求,等返回结果后将回调函数推入任务队列)
  4. 定时触发器线程(setTimeout、setInterval等待时间结束后把执行函数推入任务队列中)
  5. 浏览器事件处理线程(将click、mouse等交互事件发生后将这些事件放入事件队列中)

大部分浏览器中都是具有这5个线程的,这些线程是通过UI主线程进行协调运作的。

js引擎线程和GUI线程是互斥的: js可以操作DOM元素,进而会影响到GUI的渲染结果,因此js引擎线程与GUI渲染线程是互斥的。也就是说方js引擎线程处于运行状态时,GUI渲染线程将处于冻结状态。

# js单线程

JavaScript是基于单线程运行的,同时又是可以异步执行的,一般来说这种既是单线程又是异步的语言都是基于事件来驱动的,恰好浏览器就给JavaScript提供了这么一个环境。

js主线程

上图表达的原理

  1. 同步和异步任务分别进入不同的执行”场所“,同步的进入主线程(执行栈),异步的进入Event Table并注册函数
  2. 当指定的事情完成时,Event Table会将这个函数移入Event Queue(任务队列)
  3. 主线程内的任务执行完毕,会去Event Queue读取对应的函数,进入主线程执行

上述过程会不断重复,也就是常说的Event Loop(事件轮询)。事件轮询与宏任务和微任务密切相关。

# 定时器

setTimeout的等待时间结束后并不是直接执行的,而是先进入浏览器的一个任务队列。在同步队列结束后再依次调用任务队列中的任务。 setTimeout(function(){},0):如果定义为0ms,是指当js主线程中的执行栈为空时,再将此异步队列中的任务放入主线程执行。但是0ms实际上是达不到的,根据html标准,最低是4ms。况且如果js主线程的执行时间过长,也会依次等待主线程执行完毕,所以此函数的等待时间要超过0ms。 setInterval是每隔一段时间把任务放到Event Queue中,执行机制同setTimeout

# 宏任务 & 微任务

在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。

# 宏任务

常见宏任务:I/O、setTimeout、setInterval、setImmediate、requestAnimationFrame

宏任务

# 微任务

常见微任务:process.nextTick()、MutationObserver、Promise.then/catch/finally

微任务

# 【任务1】在主线程上添加宏任务与微任务

console.log('-------start--------');
setTimeout(() => {
    console.log('setTimeout');
}, 0);

new Promise((resolve, reject) => {
    for (let i = 0; i < 5; i++) {
        console.log(i);
    }
    resolve()
}).then(() => {
    console.log('Promise实例成功回调执行')
})

console.log('-------end--------');

执行顺序:

  1. 先执行主线程中的console.log('-------start--------'),后面接着的setTimeout的宏任务,放到下一个循环内去执行
  2. 再执行主线程中Promise的代码,分别打印0 1 2 3 4,后面接着的.then是微任务,在本轮循环的宏任务执行完后立即执行
  3. 再继续执行主线程最后的console.log('-------end--------')
  4. 执行微任务Promise.then,打印console.log('Promise实例成功回调执行')
  5. 执行宏任务setTimeout,打印console.log('setTimeout')

输出结果:

-------start--------
0
1
2
3
4
-------end--------
Promise实例成功回调执行
setTimeout

# 【任务2】在微任务中创建微任务

setTimeout(() => console.log(4))
new Promise(resolve => {
    resolve()
    console.log(1)
}).then(() => {
    console.log(3)
    Promise.resolve().then(() => {
        console.log('before timeout')
    }).then(() => {
        Promise.resolve().then(() => {
            console.log('also before timeout')
        })
    })
})
console.log(2)

执行顺序:

  1. setTimeout是宏任务,放到下一个循环中
  2. 执行Promise中的代码,打印console.log(1)
  3. 继续执行主js代码,打印console.log(2)
  4. 进入Promise.then()微任务,打印console.log(3),继续执行其中包含的Promise,打印console.log('before timeout'),继续执行其下的微任务.then(),打印console.log('also before timeout')
  5. 进行下一轮循环,执行setTimeout宏任务,打印console.log(4)

输出结果:

1
2
3
before timeout
also before timeout
4

# 【任务3】在宏任务中创建微任务

// setTimeout-A
setTimeout(() => {
    console.log('timer_1');
    // setTimeout-C
    setTimeout(() => {
        console.log('timer_3')
    }, 0)
    new Promise(resolve => {
        resolve()
        console.log('new promise')
    }).then(() => { 
        console.log('promise then')
    })
}, 0)
// setTimeout-B
setTimeout(() => { 
    console.log('timer_2')
}, 0)
console.log('========== Sync queue ==========')

执行顺序:

  1. 先执行主线程js,前面的几个setTimeout放到下一轮循环中,console.log('========== Sync queue ==========')执行执行
  2. 执行setTimeout-A,打印console.log('timer_1'),new Promise中的代码,打印console.log('new promise'),继续执行其微任务代码,打印console.log('promise then')
  3. 执行setTimeout-B,打印console.log('timer_2')
  4. 执行setTimeout-C,打印console.log('timer_3')

输出结果:

========== Sync queue ==========
timer_1
new promise
promise then
timer_2
timer_3

# 【任务4】在微任务中创建宏任务

new Promise((resolve) => {
    console.log('new Promise(macro task 1)');
    resolve();
}).then(() => {
    // 微任务1 
    console.log('micro task 1');
    setTimeout(() => { // 宏任务3 
        console.log('macro task 3');
    }, 0)
})
setTimeout(() => { // 宏任务2 
    console.log('macro task 2');
}, 1000)
console.log('========== Sync queue(macro task 1) =========='); 

执行顺序:

  1. 先执行主线程js中new Promise内的代码console.log('new Promise(macro task 1)')
  2. 再继续执行主线程,遇到setTimeout放到下一个循环,继续执行console.log('========== Sync queue(macro task 1) ==========')
  3. 执行微任务Promise.then(),打印console.log('micro task 1')
  4. 继续执行这个微任务下的宏任务setTimeout,打印console.log('macro task 3')
  5. 执行主线程中的setTimeout,打印console.log('macro task 2')

输出结果:

new Promise(macro task 1)
========== Sync queue(macro task 1) ==========
micro task 1
macro task 3
macro task 2

# 【任务5】综合

console.log('开始');
new Promise(resolve => {
    console.log('主线程中  Promise1的console.log');
    resolve();
}).then(() => {
    console.log('主线程中  Promise1.then()的console.log');
    setTimeout(() => {
        console.log('主线程中  Promise.then()的setTimeout');
    }, 0);
})
console.log('主线程中  console.log');
setTimeout(() => {
    console.log('主线程中  setTimeout的console.log1');
    new Promise(resolve => {
        console.log('主线程中  setTimeout的Promise');
        resolve();
    }).then(() => {
        console.log('主线程中  setTimeout的Promise.then()');
    })
    console.log('主线程中  setTimeout的console.log2');
    setTimeout(() => {
        console.log('主线程中  setTimeout的setTimeout');
    }, 0);
}, 0);
new Promise(resolve => {
    console.log('主线程中  Promise2的console.log');
    resolve();
}).then(() => {
    console.log('主线程中  Promise2.then()的console.log');
})
console.log('结束');

输出结果:

开始
主线程中  Promise1的console.log
主线程中  console.log
主线程中  Promise2的console.log
结束
主线程中  Promise1.then()的console.log
主线程中  Promise2.then()的console.log
主线程中  setTimeout的console.log1
主线程中  setTimeout的Promise
主线程中  setTimeout的console.log2
主线程中  setTimeout的Promise.then()
主线程中  Promise.then()的setTimeout
主线程中  setTimeout的setTimeout