EventLoop事件循环及浏览器首次渲染

什么是事件循环

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

同步和异步

虽然JavaScript是单线程的,可是浏览器内核是多线程的。
一些I/O操作、定时器的计时和事件监听(click, keydown…)等都是由浏览器提供的其他线程来完成的。

同步:是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务

异步:是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
当我们打开网站时,像图片加载等网络请求(ajax、axios)、定时任务(setTimeout),其实就是一个异步任务

宏任务与微任务

在JavaScript中,异步任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。

执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask)队列是否为空,如果为空的话,就执行Task(宏任务),否则就一次性执行完所有微任务。
每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。

上面的最好佐证:

    console.log(1)
    
    setTimeout(function(){
        console.log(2)
    },0)

    console.log(3)
//1 3 2

因此我们知道,setTimeout里的函数并没有立即执行,而是延迟了一段时间,满足一定条件后,才去执行的

console.log(1) 是同步任务,放入主线程里
setTimeout() 是异步任务,被放入event table, 0 秒之后被推入event queue里
console.log(3)是同步任务,放到主线程里
当 1、3被打印后,主线程去消息队列event queue里查看是否有可执行的函数,执行setTimeout里的函数

MacroTask(宏任务)

等待执行栈和微任务队列都执行完毕才会执行,并且在执行完每一个宏任务之后,会去看看微任务队列有没有新添加的任务,如果有,会先将微任务队列中的任务清空,才会继续执行下一个宏任务

script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。

MicroTask(微任务)

执行栈中的代码执行完毕,会在执行宏任务队列之前先看看微任务队列中有没有任务,如果有会先将微任务队列中的任务清空才会去执行宏任务队列
Process.nextTick(Node独有)、Promise、Object.observe(废弃,Proxy 对象替代)、MutationObserver(指路

执行的顺序是 执行栈中的代码 => 微任务 => 宏任务 => 微任务 => 宏任务 => ...。

例子:

setTimeout(function(){
     console.log(1)
 });
 
 new Promise(function(resolve){
     console.log(2);
     resolve();
 }).then(function(){
     console.log(3)
 });
 
 console.log(4);
// 2 4 3 1

此处promise 在 setTimeout 之前执行了
分析:

首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里
遇到 new Promise,构造函数也是一个同步代码,直接执行,打印 “2”
遇到then方法,是微任务,将其放到微任务的【队列】里
遇到console.log(4),同步任务,直接打印“4”
打印 “代码执行结束”
本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"3"
到此,本轮的event loop全部完成。
下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个setTimeout里的函数,执行打印"1"
所以最后的执行顺序是: 2 4 3 1

浏览器中的Event Loop

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

同步任务会在调用栈中按照顺序等待主线程依次执行,
异步任务会在event table中注册函数,当满足触发条件后将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
在这里插入图片描述
因此整个事件循环机制可以简要概括为:

1.首先判断JS是同步还是异步,同步就进入主线程,异步就进入event table
2.异步任务在event table中注册函数,当满足触发条件后,被推入消息队列(event queue)
3.同步任务进入主线程后一直执行,直到主线程空闲时,才会去消息队列中查看是否有可执行的异步任务,如果有就推入主线程中

在这里插入图片描述

来点笔试题

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('setTimeout') 
},0)  
async1();
new Promise(function(resolve){
  console.log('promise1')
  resolve();
}).then(function(){
  console.log('promise2')
})
console.log('script end')

解析指路:指路

答案:
在这里插入图片描述
根据题解画出的图:
白线代表第一次宏任务
粉线代表微任务
在这里插入图片描述
这题的知识点:
关于async 和 await
async 关键字的函数,它使得你的函数的返回值必定是 promise 对象,在语义上要理解,async表示函数内部有异步操作
await等的是右侧「表达式」的结果,也就是说右侧如果是函数,那么函数的return值就是「表达式的结果」,await会让出线程,但是await会先执行右边的函数再阻塞async的代码。
关于promise本身
new promise本身是同步代码 到promise.then()之类的微任务,会推入到「当前宏任务的微任务队列」中

再来一个:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

答案:
在这里插入图片描述

解析指路

浏览器首次渲染时间

查了一些资料感觉都是有共通的部分,也有一些不一样的地方,我更偏向这样的理解,如有不对欢迎交流探讨:

解析到 body
body 里的情况比较多,body 里可能只有 DOM 元素,可能既有 DOM、也有 css、js 等资源,js 资源又有可能异步加载 图片、css、js 等。DOM 结构不同,浏览器的解析机制也不同,我们分开来讨论。
只有 DOM 元素:
这种情况比较简单了,DOM 树构建完,页面首次渲染。
有 DOM 元素、外链 js:
当解析到外链 js 的时候,该 js 尚未下载到本地,则 js 之前的 DOM 会被渲染到页面上,同时 js 会阻止后面 DOM 的构建,即后面的 DOM 节点并不会添加到文档的 DOM 树中。所以,js 执行完之前,我们在页面上看不到该 js 后面的 DOM 元素。
有 DOM 元素、外链 css:
外链 css 不会影响 css 后面的 DOM 构建,但是会阻碍渲染。简单点说,外链 css 加载完之前,页面还是白屏。
有 DOM 元素、外链 js、外链 css(结合FOUC理解了):
一种选择是它可以在请求CSS的时候暂停后续DOM的解析,在CSS加载完成后再继续往后解析,此情况就会造成CSS阻塞DOM的解析,导致页面DOM解析及其样式渲染推迟,而此表现形式就与JS的情况相似。
另一种选择是它也可以在请求CSS的时候继续往后解析,此时CSS的加载与DOM的解析就是并行的,等到CSS加载完成后再更新样式,此情况下部分DOM样式就会从默认样式立即跳转到有样式状态,形成样式闪烁。

参考资料:
一次弄懂Event Loop
对浏览器首次渲染时间点的探究
再谈 load 与 DOMContentLoaded

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值