JS-事件循环

120 阅读5分钟

进程和线程

线程进程操作系统中的两个概念

  • 进程process):计算机已经运行程序,是操作系统管理程序的一种方式
  • 线程thread):操作系统能够运行运算调度最小单位,通常被包含进程

总结定义

  • 进程:可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程)
  • 线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主进程
  • 可以说进程线程容器

形象的例子

  • 操作系统类似大工厂
  • 工厂中有很多车间,每个车间是一个进程
  • 每个车间都有一个以上的工人工作,这个工人就是线程

操作系统的工作方式

  • 操作系统如何让多个进程同时工作
    • CPU运算速度非常快,可以快速多个进程之间切换
    • 当进程中的线程获取时间片时,就可以快速执行代码
    • 速度很快,用户感受不到进程切换

浏览器中JS线程

  • JS单线程JS线程有自己的容器进程浏览器/Node
  • 多数浏览器多进程
    • 打开一个新的tab页面就会开启一个新进程
    • 防止一个页面卡死造成所有页面无法响应需要强制退出浏览器
  • 浏览器每个进程中有很多线程包括执行JS代码线程
  • JS代码执行是在一个单独的线程中执行的,
    • JS代码同一个时刻只能做一件事
    • 如果这件事非常耗时,当前线程就会被阻塞
  • 真正耗时操作不是JS线程执行
    • 当前进程其他线程完成这个耗时的操作
      • 比如网络请求定时器...
      • 我们只需在特定时间执行回调即可

浏览器的事件循环

  • 执行JS代码的过程中,有异步函数调用,这个函数会被放入到调用栈中,执行立即结束,并不会阻塞后续代码执行

浏览器的宏任务和微任务

  • 事件循环中并非只维护着一个队列,事实上是有两个队列
    • 宏任务队列macrotask queue):ajaxsetTimeoutsetIntervalDOM监听,UI Rendering......
    • 微任务队列micritask queue):Promisethen回调,Mutation Observer APIqueueMicrotask().......
  • 事件循环对于两个队列优先级
    • main script中的代码优先执行(编写的是顶层script代码)
    • 在执行任何一个宏任务(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
      • 宏任务执行之必须保证微任务队列是
      • 如果不为空,那么就优先执行微任务队列中的任务回调

Node的事件循环

  • 浏览器的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现
  • Node中的EventLoop是由libuv实现的
  • Node的架构图
    • libuv中主要维护了一个EventLoop和worker threads(线程池)
    • EventLoop负责调用系统的一些其他操作:文件的IO,Network,child-procecsses等
  • libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit,Julia,pyuv等其他地方
    • image.png

Node事件循环的阶段

  • 事件循环像一个桥梁,是连接着应用程序的JS系统调用之间的通道
    • 无论是文件IO数据库网络IO定时器子进程,在完成对应的操作后,都会将对应的结果回调函数放到事件循环任务队列)中
    • 事件循环会不断的从任务队列中取出对应的事件回调函数)来执行
  • 一次完整的事件循环Tick分成很多阶段
    • 定时器(Timers):本阶段执行已经被setTImeout()和setInterval()的调度回调函数
    • 待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收到ECONNREFUSED
    • idle,prepare:仅系统内部使用
    • 轮询(Poll):检索新的I/O事件;执行与I/O相关的回调
    • 检测(check):setImmediate()回调函数在这里执行
    • 关闭的回调函数:一些关闭的回调函数,如socket.on('close', ...)
  • image.png

Node的宏任务和微任务

  • 宏任务macrotask):setTimeoutsetIntervalIO事件setImmediateclose事件
  • 微任务microtask):Promise的then调用process.nextTickqueueMicrotask

执行顺序

  • 微任务队列
    1. next tick queue:process.nextTick
    2. other queue:Promise的回调,queueMicrotask
  • 宏任务队列
    1. timer queue:setTimeout,setInterval
    2. poll queue:IO事件
    3. check queue:setImmediate
    4. close queue: close事件

Promise面试题

promise

setTimeout(function () {
    console.log('set1');

    new Promise(function (resolve) {
        resolve()
    }).then(function () {
        new Promise(function(resolve) {
            resolve()
        }).then(function () {
            console.log('then4');
        })
        console.log('then2');
    })
});

new Promise(function(resolve) {
    console.log('p1');
    resolve()
}).then(function() {
    console.log('then1');
})

setTimeout(function() {
    console.log('set2');
});

console.log(2)

queueMicrotask(() => {
    console.log('queueMicrotask');
})

// p1
// 2
// then1
// queueMicrotask
// set1
// then2
// then4
// set2

promise async await

// 字节面试题
async function async1() {
    console.log('a1 start');
    await async2()
    console.log('a1 end');
}

async function async2() {
    console.log('async2');
}

console.log('script start');

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

async1()

new Promise (function(resolve) {
    console.log('promise1');
    resolve()
}).then(function () {
    console.log('promise2');
})


// script start
// a1 start
// async2
// promise1
// a1 end
// promise2
// setTimeout

promise较难

  • 原生promise在return thenable时会推迟一次微任务
  • 原生promise在return Promise时会推迟一次微任务,如果Promise.resolve(4).then(),则会再推迟一次
Promise.resolve().then(() => {
    console.log(0);
    // 1.直接return一个值 相当于resolve(4)
    // return 4 // 0 1 4 2 3 5 6

    // 2.return thenbale的值 推到下一次的微任务中
    // return {
    //   then(resolve) {
    //     resolve(4) // 0 1 2 4 3 5 6
    //   }
    // }

    // 3.return Promise 不是普通的值,会多加一次微任务
    // Promise.resolve(4),再多加一次微任务
    return Promise.resolve(4) // 0 1 2 3 4 5 6
}).then((res) => {
    console.log(res);
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6 );
})

Node事件循环面试题

async function async1() {
    console.log('async1 start');
    await async2()
    console.log('async1 end');
}

async function async2() {
    console.log('async2');
}

console.log('script start');

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

setTimeout(function() {
    console.log('setTimeout2');
}, 300);

setImmediate(() => console.log(setImmediate))

process.nextTick(() => {console.log('nextTick1')})

async1()

process.nextTick(() => {console.log('nextTick2');})

new Promise(function(resolve) {
    console.log('promise1');
    resolve()
    console.log('promise2');
}).then(function() {
    console.log('promise3');
})

console.log('script end');

// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nextTick1
// nextTick2
// async1 end
// promise3
// setTimeout0
// [Function: setImmediate] {
//   [Symbol(nodejs.util.promisify.custom)]: [Getter]
// }
// setTimeout2