浏览器学习笔记1:浏览器线程和Event Loop

本文主要针对浏览器渲染阶段发生的以下任务进行分析:

  • JS引擎线程运行js代码
  • 异步线程处理异步代码
  • Event Loop 轮询任务队列
  • 任务队列中的宏任务和微任务

参考文章

彻底明白JS线程

浏览器如何渲染

核心问题:从浏览器获取url到渲染完页面之间经历了什么

三个阶段:

  • 浏览器发送http请求
  • 服务器接收请求并发送响应报文
  • 浏览器接收响应报文并渲染

image-20220327162721724

解析HTML文档,生成DOM树

遇到 link href css,下载解析css,生成CSSOM

  • link 新http线程

  • 在构建 CSSOM 树时,会阻塞渲染,直至 CSSOM 树构建完成。并且构建 CSSOM 树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执行速度越慢

遇到 script src,下载运行js

  • defer/async

  • 当 HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且 CSS 也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM

浏览器与js进程

线程和进程

https://zhuanlan.zhihu.com/p/165950721

进程:可以理解为系统中正在运行的一个程序

  • 是正在执行的一个程序实例。在浏览器中打开一个网页就是开启了一个进程
  • 是资源拥有的最小单位。进程拥有独立的地址空间,不能直接访问其他进程的资源
  • 进程间相互访问资源需要进程间通讯:管道、文件、套接字…(websocket,localSotrage)

线程:可以理解为轻量级的进程。

  • 线程依附于进程。进程将任务分成很多子任务,创建不同线程执行子任务

  • 是调度和分配的最小单位。

  • 同一进程的线程之间共享进程的地址空间等资源

浏览器的线程

  • 类别A:GUI 渲染线程

  • 类别B:JS 引擎线程

  • 类别C:EventLoop轮询处理线程

  • 类别D:其他线程:

    • 定时器触发线程 (setTimeout)

    • http 异步线程(AJAX)

    • **浏览器事件线程 (onclick)**等等。

AB互斥,也就是GUI在渲染时会阻塞js引擎,因为如果在GUI渲染时js改变了DOM,就会导致渲染不同步

下面逐个介绍

类别B js引擎线程

https://segmentfault.com/a/1190000011198232

js是单线程语言,但是宿主环境却是多线程的,目前有两种主流的宿主环境:

  • 浏览器(Chorme V8 参考
  • node

javascript引擎线程称为主线程,是运行JS代码的线程(包括异步)

它是基于事件驱动单线程执行的(可以修改DOM,简单化处理了),必须符合ECMAScript规范。

  • JS引擎一直等待着event loop中任务的到来,然后加以处理

  • 只有当前函数执行栈(处理同步操作)执行完毕,才会去任务队列(处理异步操作)中取任务执行

    • Event loop:一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
  • 浏览器无论什么时候都只有一个JS线程在运行JS程序。

图片描述

函数执行栈

所有同步任务都在主线程上执行,形成一个执行栈

主线程会生成执行栈,处理函数进栈出栈

例子:运行下面代码的执行栈变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function bar() {
console.log(1);
}

function foo() {
console.log(2);
bar();
}

setTimeout(() => {
console.log(3)
});

foo();

图片描述

任务队列 Task Queue

静态的队列存储结构,用于存储异步操作成功后的回调函数,注意是先判定异步操作成功再加入队列

有两种任务队列:

宏任务是由宿主发起的,而微任务由JavaScript自身发起

  • 宏任务 macro task,ES6中叫task:优先级低,先定义的先执行
    • script(全局任务全部代码)
    • setTimeout, setInterval, setImmediate
    • I/O, UI rendering,event listener,postMessage,MessageChannel(用于消息通讯)
  • 微任务 micro task,ES6中叫job:微任务,优先级高,并且可以插队,不是先定义先执行
    • process.nextTick(Node独有)
    • Object.observer(已废弃), MutationObserver.
    • Promise(有些实现的promise将then方法放到了宏任务中),Promise本身是同步的,Promise.then是异步的

类别D 异步操作相关线程

遇到异步代码就放到相应的线程

  • setTimeout(fun A) 定时器触发线程
    • 定时器触发线程在接收到代码时就开始计时,时间到了将回调函数扔进队列
  • ajax(fun B) http异步线程
    • http 异步线程立即发起http请求,请求成功后将回调函数扔进队列
  • dom.onclick(fun C) 浏览器事件线程
    • 浏览器事件线程会先监听dom,直到dom被点击了,才将回调函数扔进队列

1、执行主线程扔过来的异步代码,并执行代码

2、保存着回调函数(例中的ABC),异步代码执行成功后,通知 Event Loop轮询处理线程 过来取相应的回调函数

类别C Event Loop轮询处理线程

https://www.ruanyifeng.com/blog/2014/10/event-loop.html

上面我们已经知道了,有3个东西

1、主线程,处理同步代码

  • 先处理执行栈,再从任务队列取

2、类别D的线程,处理异步代码

  • 区分计时器、http、dom事件

3、任务队列,存储着异步成功后的回调函数,一个静态存储结构

  • 队列分为微任务和宏任务,微任务优先级更高

而发生的事情就是:

  1. 主线程中检测到的异步任务交给异步线程
  2. 把异步线程处理完的回调函数放到任务队列
  3. Event Loop:当函数执行栈为空时,取一个任务队列中的函数来执行

有两种任务队列:微任务和宏任务

微任务和宏任务

宏任务是由宿主(浏览器或Node)发起的,而微任务由JavaScript自身发起

  • 宏任务 macro task,ES6中叫task:执行慢,优先级低,先定义的先执行
    • script(全局任务全部代码)
    • setTimeout, setInterval, setImmediate
    • I/O, UI rendering,event listener,postMessage,MessageChannel(用于消息通讯)
  • 微任务 micro task,ES6中叫job:执行快,微任务,优先级高,并且可以插队,不是先定义先执行
    • process.nextTick(Node独有)
    • Object.observer(已废弃), MutationObserver.
    • Promise(有些实现的promise将then方法放到了宏任务中),Promise本身是同步的,Promise.then是异步的

浏览器中的Event Loop

浏览器对宏任务和微任务的处理不同:

  1. JavaScript引擎首先从宏任务队列(macrotask queue)中取出第一个任务;
  2. 执行完毕后,再将微任务(microtask queue)中的所有任务取出,按照顺序分别全部执行(这里包括不仅指开始执行时队列里的微任务),如果在这一步过程中产生新的微任务,也需要执行;
  3. 然后再从宏任务队列中取下一个,执行完毕后,再次将 microtask queue 中的全部取出
  4. 循环往复,直到两个 queue中的任务都取完。

'图片描述'

面经

为什么setTimeout不准时

因为需要将主线程里所有的代码运行,所有同步代码运行完后,才会取任务队列中的setTimeout的回调运行,很可能虽然setTimeout在3秒后被放入任务队列,但是其实这时候函数执行栈里还没有全执行完。

附,**requestAnimationFrame** 是准时的。

使用 requestAnimationFrame 实现的动画效果比 setTimeout 好的原因如下:

  • 使用 requestAnimationFrame 不需要设置具体的时间;
    • 它提供一个原生的API去执行动画的效果,它会在一帧(一般是 16ms)间隔内根据选择浏览器情况去执行相关动作。
    • setTimeout 是在特定的时间间隔去执行任务,不到时间间隔不会去执行,这样浏览器就没有办法去自动优化
  • requestAnimationFrame 里面的回调函数是在页面刷新之前执行,它跟着屏幕的刷新频率走,保证每个刷新间隔只执行一次;
  • 如果页面未激活的话,requestAnimationFrame 也会停止渲染,这样既可以保证页面的流畅性,又能节省主线程执行函数的开销。

看代码说输出

为什么promise.then比setTimeout先执行

因为Promise.then的回调是微任务,setTimeout的回调是宏任务,微任务可以插队

一般这个问题出现在看代码说输出的题型

详见:面经整理1:异步输出——Promise,SetTimeout与async

Todo

Nodejs

  • 和浏览器的区别

  • Event Loop

https://blog.csdn.net/qq_39200185/article/details/121201716

------ 本文结束 ❤ 感谢你的阅读 ------
------ 版权信息 ------

本文标题:浏览器学习笔记1:浏览器线程和Event Loop

文章作者:Lury

发布时间:2022年04月09日 - 21:33

最后更新:2022年04月14日 - 23:30

原始链接:https://luryzhu.github.io/2022/04/09/browser/browser1_EventLoop/

许可协议:署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。