消息队列:单行道->先进先出。又被称为宏任务,执行宏任务的时候,如果DOM有变化,将变化提交到任务中。当宏任务执行完再执行微任务。优先级问题。
消息队列和事件循环类似于观察者发布订阅模式,事件循环(单线程运行过程中能接收并执行新任务)是按顺序执行消息队列中任务的。for
主线程——>消息队列。主线程——>消息队列——>消息队列中微任务。
消息队列中任务:解析DOM事件、重新布局事件、垃圾回收任务、IO任务、点击事件。
定时器需要在指定间隔才能调用回调函数。不能直接添加到消息队列,只能放在延迟队列。
消息队列执行完会执行延迟队列,嵌套调用最短时间为
setTimeout是将延迟任务添加到延迟队列中,XMLHttpRequest发起请求是由浏览器的网络进程去执行,再将结果利用IPC方式通过渲染进程再添加到消息队列。
页面中大部分任务都是在主线程执行的:
- 渲染事件(解析DOM、计算布局、绘制)
- 用户交互事件(鼠标点击、滚动页面、放大缩小等)
- JS脚本执行事件
- 网络请求完成,文件读写完成事件
渲染进程内部会有多个消息队列,比如延迟和普通的消息队列。然后主线程采用for循环,不断从这些任务队列中取出任务并执行任务。消息队列中的任务叫宏任务,消息队列中的任务是通过事件循环来执行的。setTimeout的函数触发也都在宏任务。为了执行时间精度的控制,Promise执行放到微任务,当前执行完执行微任务然后再执行宏任务,所以比setTimout快。
微任务中的微任务,在微任务执行之后,继续执行微任务。
在当前宏任务中的JS快执行完成时,JS引擎准备退出全局执行上下文,并清空调用栈时,JS引擎会检查全局执行上下文的微任务队列,然后顺序执行微任务。微任务是V8引擎在创建全局执行上下文时创建的队列,当有微任务时再存放进去。
监听DOM,MutationObserver将响应函数改成异步,等多次DOM变化后,合成一次触发异步调用,微任务通知变化,异步+微任务。
Promise的出现是改变回调的编码风格,但它excutor和then里面用的还是回调函数。
模拟Promise:
function Bromise(executor){
var onResolve=null;
var onReject=null;
this.then=function(onResolve,onReject){
onResolve=onResolve;
}
funtion resolve(value){
setTimeout(()=>{onResolve(value)},0)
}
executor(resolve,null)
}
调用:
function executor(resolve,reject){
resolve(100)
};
let demo=new Bromise(executor);
function onResolve(value){
console.log(value)
}
demo.then(onResolve);
Promise通过回调函数延迟绑定,回调函数返回值穿透和错误冒泡技术解决了多层嵌套和错误捕获。
Generator
生成器Generator函数是一个带星号的函数,可以暂停执行和恢复执行的。
协程是比线程更加轻量级的存在,一个线程上可以存在多个协程,但同期只能执行一个协程,如果从A协程启动B协程,A就是B的父协程,协程由用户控制执行。
function* genDemo(){
console.log('执行第一段');
yield 'generator1';
console.log('执行第二段');
yield 'generator2';
console.log('执行结束');
return 'generator3'
}
console.log('main0');
let gen=genDemo();
console.log(gen.next().value);
console.log('main1');
console.log(gen.next().value);
console.log('main2');
console.log(gen.next().value);
console.log('main3');
输出结果全局和genDemo交替执行,生成器特性可以暂停,恢复执行。
- 生成器内部执行一段代码,遇到yield,JS引擎返回后面的内容给外部,并暂停
- 外部函数可以通过next恢复函数执行。(1)通过调用genDemo创建一个协程gen,创建后gen协程没有立即执行。(2)让gen协程执行,要通过调用gen.next()。(3)当协程正在执行的时候,通过yield来暂停gen协程的执行,并返回主要信息给父协程。(4)协程执行期,遇到return,JS引擎会结束当前协程,将return后面的内容返回给父协程。
async
【浏览器工作原理与实践(四)】async/await:使用了同步的方式写异步代码
async function foo() { console.log(1) let a=await 100; console.log(a); console.log(2) } console.log(0) foo(); console.log(3)
- 执行console.log(0)打印0
- 执行foo,由于被async标记过,JS引擎会保存当前调用栈,执行console.log(1)
- 执行await 100,JS会创建一个promise,let promise=new Promise(resolve,reject){resolve(100)},JS引擎将任务提交给微任务队列promise。
- JS引擎暂停当前协程执行,将主线程控制权交给父协程,同时将promise对象返回给父协程
- 主线程控制权给过父协程,父协程要调promise.then来监控promise状态的改变
- 接下来继续父协程流程执行,执行console.log(3),父协程执行结束,结束之前进入微任务检查,执行微任务,resolve(100),resolve回调函数被激活后,将主线程控制权交给foo函数的协程,将value值传给该协程
- foo协程激活后,把value值给a,然后foo协程继续执行console.log(a),console.log(2)
推荐阅读
- 前端|「性能优化」首屏时间从12.67s到1.06s,我是如何做到的()
- 突发奇想|Vue + Element做个个人中心玩玩~
- 浏览器工作原理与实践(三)
- JS系列(认识迭代器和可迭代对象)
- Vue|ES6学习——一文搞懂ES6
- #|ES6 对象——扩展运算符、对象新方法
- 笔记|电话面试-----海康威视
- javascript|JavaScript面试题看这一篇就够了,简单全面一发入魂(持续更新 step2)
- 笔记-javascript事件循环