理解javascript中event loop,

console.log(1)
    setTimeout(() => console.log(2), 0)
    new Promise((resolve, reject) => {
        console.log(3)
        resolve()
    }).then(() => {
        console.log(4)
    })

//    1 3 4 2

javaScript

javascript 是单线程语言,至于其为什么是单线程呢,而不采用多线程。原因就在于,javascipt 是面向用户端的一门语言,其主要作用是与用户交互,渲染数据,操作dom,如果是多线程,就会出现一个问题,比如说,一个线程删除了一个dom节点,另外一个线程添加了一个dom节点,以那个线程为主呢,就会出现混乱的情况。当然,我们可以在操作一个dom之后,加上锁,只允许一个线程操作,但这样,无形之中,程序又平添了复杂程度,未必是一个好的办法。另外,HTML5 中提供了 web worker 等 api,用来处理例如因大量计算而占用主线程的情况,但按照规定,其也受制于主线程,而且不能操作dom。所以,javascript 是一门单线程语言,也只可能是单线程语言.

任务队列

为什么会有任务队列呢,还是因为 javascript 单线程的原因,单线程,就意味着一个任务一个任务的执行,执行完当前任务,执行下一个任务,这样也会遇到一个问题,就比如说,要向服务端通信,加载大量数据,如果是同步执行,js 主线程就得等着这个通信完成,然后才能渲染数据,为了高效率的利用cpu, 就有了 同步任务 异步任务 之分。

- 同步任务,进入主线程,一个一个执行
- 异步任务, 进入 `event table ` , 注册回调函数 ` callback `, 任务完成之后,
将 `callback` 移入 `event queue`, 等待主线程调用

流程图

任务队列是一中先进先出的数据结构,排在最前面的优先被主线程读取执行,只要当前执行栈一清空,主线程马上会读取任务队列中的第一任务执行。但当遇到定时器时,需要先检查当前时间是否满足定时器需求,满足执行,不满足自动执行下一个

console.log("主线程开始执行任务")
const data = {}
console.log("发现异步任务,执行异步任务,注册回调函数 success 和 fail")
$.ajax({
    method: 'post',
    url: 'https://localhost:8080/user/new',
    data: data,
    success: res => {
        console.log('异步任务执行完成,服务器响应成功,向event queue 推入 success') 
    },
    fail: err => {
        console.log('异步任务执行,服务器响应失败,向event queue 推入 fail')  
    }
})

console.log("同步任务执行完成,主线程检查 event queue ")
console.log("ajax 请求成功响应, 检测到 event queue 中的 success ,执行success")

上面的代码,我已经对 event loop 有了初步了解,那么接下来梳理一下,同样是异步任务,为何不同类型的异步任务表现却不一样

setTimeout

    setTimeout(() => {
        console.log("五秒了,我要执行了")
    }, 5000)
//一般情况下,我们设置一个定时器,如果不去特意的测试,是发现不了,定时器时间并不准确这个问题的,我们做个测试

    console.time('timer')
    setTimeout(() => console.timeEnd('timer'), 5000)
    sleep(1000000) // 或者执行一大堆复杂的逻辑
//我们这个时候发现,输出的时间,并不是 精确的 5000 ,而是根据你后面同步任务执行的时间会有所影响

- 主线程执行console.time('timer'), 开始及时
// - 执行到setTimeout(), 进入到 `event table` 中并注册回调函数
// - 开始执行sleep(), sleep 执行的很慢,这个时候,5 秒到了,定时器时间到了之后向
//   `event queue` 中推送了回调函数,但主线程一直在忙,没有时间去检查 
//   `event queue`, 一直等到 `sleep()` 执行完成,主线程才检查 `event queue`,
//   并执行回调函数
//上述的流程走完,我们知道 setTimeout 这个函数,是经过指定时间后,把要执行的任务加入到 event queue 中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。

setTimeout(fn, 0)  

假设主线程并不繁忙,那么 fn 一定就会在 不延迟执行吗,并不会,
即便主线程为空,0ms 实际上也是达不到的。根据HTML的标准,最低是 4ms。所以不可能做到 0ms

setInterval 重复执行,每隔多少秒之后,将已经注册好的回调函数推入到event queue中,等待主线程调用

promise

    console.log(1)
    setTimeout(() => console.log(2), 0)
    new Promise((resolve, reject) => {
        console.log(3)
        resolve()
    }).then(() => {
        console.log(4)
    })
// 答案应该是 1 3 2 4, 但为什么会是 1 3 4 2 呢,

这里就要说到 微任务 和 宏任务 了, 除了广义的 同步任务 和 异步任务 之分,对 异步任务 还有更细致的区分,就是 micro Task (微任务) 和 macro Task (宏任务)

micor Task(微任务) 包括 promise, 当然还有 Node 环境中的,但这里先不梳理

macro Task(宏任务) 包括 script、setTimeout、setInterval setTimeout和setInterval的回调会推入到宏任务

不同类型的任务,会进入不同的 event queue, 同是相同类型的任务,进入相同的 event queue, 例如: setTimeout 和 setInterval 会进入相同的 event queue

 测试

console.log(1)

setTimeout(() => {
    console.log(2)
    new Promise((resolve, reject) => {
        console.log(3)
        resolve()
    }).then(() => {
        console.log(4)
    })
}, 0)

new Promise((resolve, reject) => {
    console.log(5)
    resolve()
}).then(() => {
    console.log(6)
})

setTimeout(() => {
    console.log(7)
    new Promise((resolve, reject) => {
        console.log(8)
        resolve()
    }).then(() => {
        console.log(9)
    })
}, 0)

console.log(10)

|

 

原文地址:https://www.cnblogs.com/chenzxl/p/14447684.html