一、javascript是一门单线程语言

二、js运行机制

1、浏览器中

同步任务和异步任务
  • 同步任务:比如页面骨架和页面元素的渲染
  • 异步任务:比如加载图片音乐之类占用资源大耗时久的任务

* 看图==>

(1)同步的进入主线程,异步的进入Event Table并注册函数
(2)当指定的事情完成时,Event Table会将这个函数移入任务队列
(3)主线程内的任务执行完毕为空,会去任务队列读取对应的函数,进入主线程执行
(4)上述过程会不断重复,也就是常说的Event Loop(事件循环)。

macro-task(宏任务) 和 micro-task(微任务)

macro-task(宏任务):整体代码script、setTimeout、setInterval
micro-task(微任务):Promise、await、process.nextTick(nodejs)

* 碰到宏任务和微任务怎么执行呢
(1)执行整体代码script(宏任务)
(2)执行所有的微任务
(3)再执行一个宏任务
(4)然后再执行所有的微任务,一直循环

鄙视题了解一下
console.log('1');

setTimeout(function() { // 宏1
    console.log('2');
    process.nextTick(function() { // 微1
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() { // 微2
        console.log('5')
    })
})
process.nextTick(function() { // 微3
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() { // 微4
    console.log('8')
})

setTimeout(function() { // 宏2
    console.log('9');
    process.nextTick(function() { // 微5
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() { // 微6
        console.log('12')
    })
})

解析==============>
(1)执行整体代码(宏), 打印出 1 7,在这期间,挂起的异步任务有

宏1 宏2微3 微4

(2)开始执行所有微任务(微3、微4),打印出6 8 ,此时任务队列剩余

宏1 宏2

(3)执行一个宏任务(宏1),打印出2 4,并且在这期间,又挂载了新的异步任务(微1,微2),因此任务队列剩余

宏2微1 微2

(4)执行所有微任务(微1,微2),打印出3 5,任务队列剩余

宏2

(5)执行一个宏任务(宏2),打印出9 11, 同时又挂载了新的异步任务(微5, 微6),任务队列剩余

微5 微6

(6)执行所有微任务(微5, 微6),打印出10 12
因此,最后执行结果为 1 7 6 8 2 4 3 5 9 11 10 12

再看一道=============================>

async function async1() {
    console.log('async1 start'); //=>2
    await async2();  
    console.log('async1 end');  //=>6  // 微1
}
async function async2() {
    console.log('async2');  //=>3
}
console.log('script start');  //=>1
setTimeout(function () { // 宏1
    console.log('setTimeout');  //=>9
}, 0);
async1();
new Promise(function (resolve) {
    console.log('promise1');  //=>4
    resolve();
}).then(function () { // 微2
    console.log('promise2');  //=>7
}).then(function () { // 微3
    console.log('promise3'); //=>8
});
console.log('script end');  //=>5

上面的数字代表执行顺序,解析开始==========》
(1)执行整体代码(宏),打印出
script start
async1 start
async2
promise1
script end
此时任务队列剩余

宏1微1 微2 微3

(2)依次执行所有微任务(微1 微2 微3),打印出
async1 end
promise2
promise3
此时任务队列剩余

宏1

(3)执行一个宏任务(宏1),打印出
setTimeout

因此执行结果==============>
script start
async1 start
async2
promise1
script end
async1 end
promise2
promise3
setTimeout

再鄙视

setTimeout(function () {
  console.log('a');
  new Promise(function (resolve, reject) {
    resolve();
    console.log('b');
  }).then(function () { 
    console.log('c')
  })
}, 1000)

setTimeout(function () {
  console.log('d'); 
}, 0)

new Promise(function (resolve, reject) {
  reject();
  console.log('e')
}).then(function () {
  console.log('f')
}).catch(() => {
  console.log('g')
})
console.log('h')

2、nodejs运行机制

nodejs的event loop分为6个阶段,它们会按照顺序反复运行,分别如下:

  1. timers:执行setTimeout() 和 setInterval()中到期的callback。
  2. I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
除了以下操作的回调函数,其他的回调函数都在这个阶段执行。
  • setTimeout()和setInterval()的回调函数
  • setImmediate()的回调函数
  • 用于关闭请求的回调函数,比如socket.on('close', ...)
  1. idle, prepare:队列的移动,仅内部使用
  2. poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段
  3. check:执行setImmediate的callback
  4. close callbacks:执行close事件的callback,例如socket.on("close",func)

图片描述

node中的几个特点需要说明一下

  • 上面六个阶段的任务都是宏任务
  • promise.then和process.nextTick()是微任务;且process.nextTick()优先级要高于promise
  • setTimeout(fn,x); x>0时候会跳过,x=0也可能跳过,因为node至少也得1ms左右

node中的执行顺序如下

(1)执行整体代码(同步宏任务)
(2)执行微任务(process.nextTick()、promise.then)
(3)按event loop六个阶段执行(中间夹杂着微任务)

练习题1

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() { // 微1
        console.log('promise1') 
    })
}, 0)

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() { // 微2
        console.log('promise2')
    })
}, 0)

解析:
1、执行整体代码,没啥输出
2、执行微任务 process.nextTick()、promise.then,也没有
3、开始执行event loop得timer阶段,发现有两个setTimeout,执行后输入timer1、time2, 同时又挂载了两个微任务(微1,微2)
4、执行微任务(微1 微2)输出promise1,promise2

结果:timer1、time2、promise1,promise2

练习题2

const fs = require('fs');

fs.readFile('test.js', () => {
  setTimeout(() => console.log(1));
  setImmediate(() => console.log(2));
});

解析:
1、执行整体代码,没啥输出
2、执行微任务 process.nextTick()、promise.then,也没有
3、开始执行event loop, 但timer阶段也没发现setTimout,和setInterval,那就跳过了
4、然后到了I/O callback, 发现有readFile得回调,执行里面代码,但没输出值,只是将 setTimeout放到了timer间段, 把setImmediate放到了check间段
5、然后执行微任务,还是没有process.nextTick()、promise.then
6、依次进入到check阶段,发现有setImmediate,执行输入出2
7、依次进入下一次循环,进入timer, 发现了setTimout, 执行输出1

结果:2 1
练习题3

setTimeout(function () {
   console.log(1);
},0);
console.log(2);
process.nextTick(() => {
   console.log(3);
});
new Promise(function (resolve, rejected) {
   console.log(4);
   resolve()
}).then(res=>{
   console.log(5);
})
setImmediate(function () {
   console.log(6)
})
console.log('end');

就不解析了,一个道理,结果:2 4 end 3 5 1 6

参考链接

这一次,彻底弄懂 JavaScript 执行机制
一篇文章教会你Event loop——浏览器和Node


lihaixing
463 声望719 粉丝

前端就爱瞎折腾