关于javascript:JS异步与EventLoop

1次阅读

共计 3541 个字符,预计需要花费 9 分钟才能阅读完成。

文章开始我想请问大家,什么是异步?为什么须要异步?我想很多人的答复会是 setTimeout,Promise,async await 等等;
然而其实异步是一种概念,setTimeout,Promise,async await 只是执行异步的办法;

咱们都晓得 JS 是单线程语言,也就是说咱们在 JS 代码中输出的代码会以工作的模式从前到后,从上到下顺次进行,如果要进行下一个工作就须要上一个工作完结;如果一个工作破费事件过长就会导致页面卡顿,咱们能够将这种运行模式叫做 同步模式 或者 同步编程

如果咱们能够使两个工作同时进行,而不是当一个工作完结之后进行下一个工作是否能够解决或者缓解这种卡顿景象?咱们把这种思维叫做 异步模式 或者 异步编程

为了更好的理解 事件循环(Event Loop) 咱们须要先理解一下什么是异步宏工作什么是异步微工作

异步宏工作与异步微工作

后面讲到 JS 是单线程语言,那么也就是说在页面中咱们的工作事件最终也是在主线程中运行的,这些事件包含,页面的渲染,用户的交互,js 的脚本执行,网络申请,文件读写等等; 为了协调这些工作井井有条地在主线程上执行,页面过程引入了音讯队列和事件循环机制,渲染过程外部会保护多个音讯队列,比方提早执行队列和一般的音讯队列。而后主线程采纳一个 for 循环,一直地从这些工作队列中取出工作并执行工作。咱们把这些音讯队列中的工作称为 宏工作 ,宏工作分为同步宏工作与异步宏工作,在咱们主线程执行栈当中进行的同步代码都能够称之为同步宏工作;
那么异步宏工作有哪些?
常见异步宏工作有:

  1. setTimeout
  2. setInterval
  3. 文件的读取
  4. dom 事件(包含滚动,点击,鼠标移入等)

那么什么是微工作?艰深点讲就是须要异步执行的回调函数
常见的微工作有哪些?次要包含 Promise 的类办法与 Promise 的对象办法,async await 中的 await 等;

什么是事件循环?

简略地说,对于 JS 运行中的工作,JS 有一套解决收集,排队,执行的非凡机制,咱们把这套解决机制称为 事件循环(Event Loop)

咱们都晓得浏览器执行 js 代码是在 栈内存(stack) 当中执行的, 假如当初有一段代码外面有同步代码与异步代码异步代码又分为宏工作与微工作,那么这段代码在浏览器当中是怎么执行的?执行程序又是怎么的呢?

首先当执行一段代码的时候会开拓两段内存,别离为 堆内存 栈内存 ;而后它会创立两个队列,别离是 工作监听队列 WebAPI 工作队列 EventQueue 而后浏览器会优先在执行栈当中对同步代码 (同步宏工作) 自上而下进行执行;如果在执行过程中遇到了异步工作会将异步工作放到 工作监听队列 WebAPI 当中去进行监听,当监听到异步工作能够执行了,会将异步代码放到 工作队列 EventQueue 当中期待;当同步代码执行结束会将 工作队列 EventQueue 当中的代码推到主线程当中依照程序(先进先出准则)进行执行;这里须要阐明的一点是 工作队列 EventQueue 分为 宏工作队列 macro task queue微工作队列 micro task queue 因为微工作队列的优先级比拟高,所以会将 微工作队列 micro task queue 中的工作先推到主线程当中进行运行,而后再运行 宏工作队列 macro task queue 当中的工作;因为浏览器是单线程运行,并且同步代码,微工作代码,宏工作代码都在一个线程当中运行;所以如果在这个过程中无论是宏工作还是微工作代码有阻塞,都会影响执行栈的向下运行;这便是事件循环机制

所以执行程序顺次为:
同步工作 -> 微工作 -> 宏工作

一道题彻底搞懂 eventLoop

理解 eventloop 的运行机制之后咱们来做一道题来坚固:

// 阻塞办法,用于 js 阻塞
//delayTime 单位毫秒
function wait(delayTime){let nowStamp = new Date().getTime()
    const endTime = nowStamp + delayTime
    while (true){if (nowStamp < endTime) {return}
        nowStamp = new  Date().getTime()
    }
}
console.log('sync1')
setTimeout(()=>{console.log('setTimeout1')
},100)
const p1 = new Promise(resolve=>{console.log('p1')
    resolve()}).then(()=>{console.log('then1')
})
const p2 = new Promise(resolve => {console.log('p2')
    resolve()}).then(()=>{setTimeout(()=>{console.log('setTimeout2')
    },100)
})
setTimeout(()=>{
    const p3 = new Promise(resolve => {console.log('p3')
    }).then(()=>{console.log('then3')
    })
},100)
wait(4000)
setTimeout(()=>{console.log('setTimeout4')
},5000)
setTimeout(()=>{console.log('setTimeout3')
},100)
console.log('sync2')

输入后果为:

`
sync1
p1
p2
sync2
then1
setTimeout1
p3
setTimeout3
setTimeout2
setTimeout4`

在这道题中蕴含了 promise,setTimeout,主线程代码以及主线程阻塞;

咱们自上而下解读:
咱们运行主线程代码,首先会输入 sync1,再往下

setTimeout(()=>{console.log('setTimeout1')
},800)

因为是异步宏工作,会被推到 WepAPI 当中进行监听,在 100 毫秒之后推入 eventQueue 当中的异步宏工作队列当中期待主线程与异步微工作队列执行结束;

再往下

const p1 = new Promise(resolve=>{console.log('p1')
    resolve()}).then(()=>{console.log('then1')
})

因为在 promise 当中的 p1 属于同步工作所以会被主线程输,而 then1 会被推到异步微工作对列当中期待主线程执行结束;

const p2 = new Promise(resolve => {console.log('p2')
    resolve()}).then(()=>{setTimeout(()=>{console.log('setTimeout2')
    },100)
})

同上,会先执行主线程代码输入 p2,再将

setTimeout(()=>{console.log('setTimeout2')
  },100)

推到微工作队列

再往下

setTimeout(()=>{
    const p3 = new Promise(resolve => {console.log('p3')
    })
},100)

宏工作,推送到 WebAPI 并在 100 毫秒之后推入异步宏工作队列

wait(4000)

阻塞 4000 毫秒,同步代码不会向下执行,在此时 setTimeout1 曾经被推入宏工作执行队列

setTimeout(()=>{console.log('setTimeout4')
},5000)

执行完阻塞之后进入 WebAPI 并在 5000 毫秒之后进入异步宏工作队列;

setTimeout(()=>{console.log('setTimeout3')
},100)

执行完阻塞之后进入 WebAPI 并在 100 毫秒之后进入异步宏工作队列;

console.log('sync2')

同步代码,间接输入

此时咱们间接输入的同步代码有

sync1
p1
p2
sync2

他们将按程序在浏览器输入

在微工作队列中的代码有
then1
以及 P2 then 办法中的宏工作

setTimeout(()=>{console.log('setTimeout2')
},100)

在到这里的时候会将 setTimeout2 推入 WebApi 并在 100 毫秒之后推入异步宏工作队列

至此异步微工作输入结束
此时的输入后果为

sync1
p1
p2
sync2
then1

再而后咱们会去执行异步宏工作队列当中的工作;
因为 webApi 监听推入的工夫不同,此时咱们在异步宏工作队列当中的程序顺次为

setTimeout1
p3
setTimeout3
setTimeout2
setTimeout4
并顺次输入,最终后果为
`
sync1
p1
p2
sync2
then1
setTimeout1
p3
setTimeout3
setTimeout2
setTimeout4`

正文完
 0