JavaScript-异步&EventLoop
文章目录
并发与并行
并发是宏观概念,有任务A和任务B,在一段时间内通过任务间的切换完成这两种任务,这种情况称为并发
并行是微观概念,假设CPU中存在两个核心,同时完成任务A/B,这种同时完成多个任务的情况可以称之为并行
进程与线程
进程与线程?JS 单线程?
进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。
线程是进程中的更小单位,描述了执行一段指令所需的时间。
在浏览器中来说,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
在 JS 运行的时候可能会阻止 UI 渲染,这说明了两个线程是互斥的。
这其中的原因是因为 JS 可以修改 DOM,如果在 JS 执行的时候 UI 线程还在工作,就可能导致不能安全的渲染 UI。
这其实也是一个单线程的好处,得益于 JS 是单线程运行的,可以达到节省内存,节约上下文切换时间,没有锁的问题的好处
回调函数
ajax(url,()=>{
// 回调函数
})
回调函数用于出现回调地狱,如多个请求存在依赖性,即会出现
ajax(url,()=>{
ajax(url2,()=>{
ajax(url2,()=>{
})
})
})
- 回调函数的嵌套存在耦合性,牵一发而动全身
- 回调函数过多,不容易处理错误
Promise
特征
Promise
有三种状态等待中(pending)
、已完成(resolved)
和拒绝了(rejected)
状态一旦从等待中改变就不能再改变了
在构造 Promise 的时候,构造函数内部的代码是立即执行的
链式调用
Promise
实现了链式调用,即每次调用then
返回的也是一个Promise
。如果在then
中使用了return
,return
的内容也会被Promise.resolved
包装
优缺点
Promise
解决了回调函数的回调地狱问题
Promise
也存在无法取消的问题
async和await
如果函数加上async
就会返回一个Promise
async funtion as(){
return '1'
}
console.log(as()) // Promise{<resolved>:'1'>}
async
就是将函数返回值使用 Promise.resolve()
包裹了下,和 then
中处理返回值一样,并且 await
只能配套 async
使用async
中遇到await
就会先返回,等到异步操作完成,在执行函数体后面的语句
async
函数对 Generator
函数的改进,体现在以下四点
- 内置执行器。Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器
- 更好的语义。
async
和await
,比起星号和yield
,语义更清楚了 - 更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值
- 返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
async
和await
是异步的终极解决方案
相比Promise
优势在于处理调用链,逻辑更为清楚,没有那么多的then
缺点是,await
会将代码当做同步代码处理,如果多个异步请求没有依赖关系会导致性能降低
定时器
setTimeout
setTimeout
的延迟时间不能保证准确执行,会受到同步代码的执行时间影响
setInterval
setInterval
不仅存在执行时间不准确的问题,还有累积执行的问题
如果在定时器执行期间有大量消耗时间的操作,定时器累积的回调函数会在同一时间执行
requestAnimationFrame
window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行requestAnimationFrame
自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题
EventLoop
执行 JS 代码的时候其实就是往执行栈中放入函数
当遇到异步的代码时,会被挂起并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行
不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)
微任务包括 process.nextTick ,promise ,MutationObserver。
宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
/*
script start
async2 end await让出线程不影响函数执行 返回promise
Promise
script end
promise1
promise2
async1 end
setTimeout
*/
- 首先执行同步代码
script
,这属于宏任务 - 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
- 执行所有微任务
- 当执行完所有微任务后,如有必要会渲染页面
- 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数