【面试题】浏览器

浏览器从 URL 输入到页面呈现经历了什么?

网络请求

  • 构造 HTTP 请求,包含请求头,请求体 内容
  • 匹配缓存 在发送请求之前,会检查请求头设置的强缓存失效时间,如果命中,则直接使用缓存数据,否则则执行下一步。
  • DNS 解析
    • 查找浏览器缓存 浏览器提供了 DNS 数据缓存功能,如果缓存命中,在缓存失效之前直接走浏览器缓存,不需要经过 DNS 解析提高速度。
    • 查找 Host 文件配置 浏览器找不到缓存则在本地 Host 配置中查找,如果存在域名对应的 IP 规则,则使用,否则执行下一步。
    • 浏览器发送 DNS 请求到本地 DNS 服务器 如果本地 Host 没有配置内容,浏览器会发送 DNS 请求到本地 DNS 服务器。本地 DNS 接受请求之后,会先递归查找自己的缓存记录,如果有缓存则直接返回。否则向根 DNS 发起请求。
    • 根 DNS 服务器 根 DNS 服务器接收到本地服务器请求,并不会返回域名和 IP 的对应关系,而是告诉本地服务器可以到域名服务器查找,并告知域服务器的地址
    • 本地 DNS 服务器向域名服务器请求 域服务器接收到请求后,也不会返回域名与 IP 之间的关系,而是告诉本地 DNS 服务器 请求的域名的解析服务器的地址
    • 本地 DNS 服务器向解析服务器请求 请求之后会获取到了一个域名和 IP 地址的对应关系。本地 DNS 服务器把这个对应关系返回给浏览器,并缓存在自己本地。
  • 排队 Chrome 在同一个域名下要求最多只能有 6 个 TCP 连接,超过 6 个就必须等待。
  • 建立 TCP 连接 一旦获取到服务器 IP 地址,浏览器就会通过 TCP”三次握手“与服务器建立连接。
  • TLS 协商 为了在 HTTPS 上建立安全连接,另一种握手是必须的。更确切的说是 TLS 协商 ,它决定了什么密码将会被用来加密通信,验证服务器,在进行真实的数据传输之前建立安全连接。
  • 发送 HTTP 请求
    • 请求行
    • 请求头
    • 请求体
  • 服务器处理请求 服务器从固定端口接收到 TCP 报文开始,它会对 TCP 连接进行处理,对 HTTP 协议进行解析,并按照报文格式进一步封装成 HTTP Request 对象供上层使用。
  • 响应 HTTP 请求 同请求类似,HTTP 响应也有三个必须携带的东西:

    • 请求行
    • 请求头
    • 请求体

    响应完成之后,这时候需要判断请求头或者响应头中的 Connection 字段是否为 keep-alive,表示建立持久连接。如果是,则 TCP 连接会一直保存,之后请求同一个服务的资源会复用这个连接,否则断开 TCP 连接,下次请求时再重新建立连接。

浏览器解析

浏览器渲染原理

  • 判断资源类型类型 完成网络请求之后,如果响应头中的 Content-Type 是 图片或者音视频媒体,浏览器则会单独加载资源,如果是 text/html 则执行浏览器的解析渲染工作

    浏览器解析步骤如下

    • 解析 html 元素,生成 DOM 树
    • 解析 css 生成 CCSOM
    • 将 DOM 与 CSSOM 结合,创建渲染树(Render Tree)。
    • 布局(layout),找到所有内容都处于网页的哪个位置。
    • 绘制(painting),浏览器开始在屏幕上绘制像素。
  • 解析 html 元素,生成 DOM 树

    • token 化 将字符串转换成 Token,标识出当前 Token 是“开始标签”或是“结束标签”亦或是“文本”等信息,用于维护节点与节点之间的关系
    • 生成节点对象 一边生成 token 一边将 token 生成节点对象,结束标签的 token 不会创建节点对象,节点对象包含所有的属性。
    • 生成 DOM 树 当所有 Token 都生成并消耗完毕后,我们就得到了一颗完整的 DOM 树

    HTML 对于非阻塞资源如图片,或者 CSS 文件的时候,解析可以继续进行,但是对于 script 标签(特别是没有 async 或者 defer 属性)会阻塞渲染并停止 HTML 的解析。

  • 预加载扫描器 浏览器构建 DOM 树时,这个过程占用了主线程。当这种情况发生时,预加载扫描仪将解析可用的内容并请求高优先级资源,如 CSS、JavaScript 和 web 字体。预加载扫描仪提供的优化减少了阻塞。

    1
    2
    3
    4
    <link rel="stylesheet" src="styles.css" />
    <script src="myscript.js" async></script>
    <img src="myimage.jpg" alt="image description" />
    <script src="anotherscript.js" async></script>

    当主线程在解析 HTML 和 CSS 时,预加载扫描器将找到脚本和图像,并开始下载它们。

  • 解析 css,构建 CSSOM 树 构建 CSSOM 并不需要等待所有 DOM 都构建完毕。而是在解析 HTML 构建 DOM 时,若遇见 CSS 会立刻构建 CSSOM。即 DOM 和 CSSOM 是并行构建的。

    进入下一个构建渲染树阶段必须要等待 CSSOM 构建完毕后才能进行。CSS 加载速度与 CSSOM 构建速度将直接影响首屏渲染速度。因为渲染需要 render Tree,render Tree 需要 CSSOM 构建成功才可以进入下一个阶段,同时 js 也会阻塞页面解析,因此 CSS 放头部,可以提高页面的性能。早构建早渲染。

  • 其他过程 - JavaScript 编译 当 CSS 被解析并创建 CSSOM 时,其他资源,包括 JavaScript 文件正在下载(多亏了 preload scanner)。JavaScript 被解释、编译、解析和执行。脚本被解析为抽象语法树。一些浏览器引擎使用”Abstract Syntax Tree“并将其传递到解释器中,输出在主线程上执行的字节码。这就是所谓的 JavaScript 编译。

  • 其他过程 - 构建辅助功能树 浏览器还构建辅助设备用于分析和解释内容的辅助功能(accessibility )树。可访问性对象模型(AOM)类似于 DOM 的语义版本。当 DOM 更新时,浏览器会更新辅助功能树。

  • 构建 Rendering Tree 通过 DOM Tree 和 CSS Rule Tree 构建 Rendering Tree, Rendering Tree 每个节点都有样式信息。

  • 布局(layout) 在渲染树上运行布局以计算每个节点的几何体,布局是确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。 为了确定每个对象的确切大小和位置,浏览器从渲染树的根开始遍历它。

    第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。

  • 绘制(painting) 最后一步是将各个节点绘制到屏幕上,第一次出现的节点称为 first meaningful paint。在绘制或光栅化阶段,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。

    为了确保平滑滚动和动画,占据主线程的所有内容,包括计算样式,以及回流和绘制,必须让浏览器在 16.67 毫秒内完成。

    绘制可以将布局树中的元素分解为多个层。将内容提升到 GPU 上的层(而不是 CPU 上的主线程)可以提高绘制和重新绘制性能。

  • 合成(Compositing) 当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。

    当页面继续加载资产时,可能会发生回流,回流会触发重新绘制和重新组合。如果我们定义了图像的大小,就不需要重新绘制,只需要重新绘制需要重新绘制的层,

  • 交互 一旦主线程绘制页面完成,你会认为我们已经“准备好了”,但事实并非如此。如果加载包含 JavaScript(并且延迟到 onload 事件激发后执行),则主线程可能很忙,无法用于滚动、触摸和其他交互。

浏览器的重绘与回流

  • 回流: 当 render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流。
  • 重绘: 当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color。则就叫称为重绘。

回流一定会引起重绘,但是重绘不一定会回流 ,回流 reflow 的成本开销 要高于重绘 repaint 当你获取布局信息的操作的时候,会强制队列刷新,因为获取最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。

说说浏览器和 Node 事件循环的区别

浏览器 和 Node 事件循环主要是 Node 11 之前版本的区别。

Node 10 及以前:

Node 事件循环 Libuv 是一个高性能的,事件驱动的异步 I/O 库,它本身是由 C 语言编写的,具有很高的可移植性。 事件循环的 7 个主要阶段 times: setTimeout setInterval 回调 I/O callbacks: 处理异步事件的回调,比如网络 I/O,比如文件读取 I/O。当这些 I/O 动作都结束的时候,在这个阶段会触发它们的回调。(上一轮循环的回调) idle, prepare: 这个阶段内部做一些动作,仅 node 内部使用 I/O poll 阶段: 获取新的 I/O 事件,适当的条件下 node 将阻塞在这里 check: 执行 setImmediate() 回调 close callback: 关闭 I/O 的动作,比如文件描述符的关闭,socket 断开,等等等

process.nextTick 的操作,会在每一轮事件循环的最后执行

  • 执行全局 Script 的同步代码
  • 执行 microtask 微任务,先执行所有 Next Tick Queue 中的所有任务
  • 在执行 Other Microtask Queue 中的所有任务
  • 执行完成之后开始执行 macrotask 宏任务,共 6 个阶段,每个阶段红任务执行完成之后,再执行 步骤 2 ,然后继续执行下一个阶段的宏任务,再执行 步骤 2。
  • 如:Times Queue -> 步骤 2 -> I/O Queue -> 步骤 2 -> Check Queue -> 步骤 2 -> Close Callback -> 步骤 2 -> Times Queue -> ...
  • 这就是 Node 的 Event Loop

浏览器: 浏览器是执行完一个宏任务就会去清空微任务队列

cookie 和 token 都存放在 header 中,为什么不会劫持 token

  • CSRF 攻击的原因是浏览器会自动带上 cookie,而浏览器不会自动带上 token

浏览器请求 from memory cache 和 from disk cache 的依据是什么

对于大文件来说,大概率是不存储在内存中的,反之优先 当前系统内存使用率高的话,文件优先存储进硬盘

  • Service Worker:可以自由控制缓存的文件,数据等内容
  • memory cache 主要用于短期的资源缓存(读取速度快)
  • disk cache 读取速度慢,比之 Memory Cache 胜在容量和存储时效性上

发送数据埋点请求的时候使用 1*1 的透明 gif 图片?

  • 能够完成整个 HTTP 请求+响应
  • 跨域友好(img 天然支持跨域)
  • 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
  • GIF 的最低合法体积最小(最小的 BMP 文件需要 74 个字节,PNG 需要 67 个字节,而合法的 GIF,只需要 43 个字节)
  • 不会阻塞页面加载,影响用户的体验,只要 new Image 对象就好了,一般情况下也不需要 append 到 DOM 中,通过它的 onerror 和 onload 事件来检测发送状态。

介绍下如何实现 token 加密

jwt 举例

  • 需要一个 secret(随机数)
  • 后端利用 secret 和加密算法(如:HMAC-SHA256)对 payload(如账号密码)生成一个字符串(token),返回前端
  • 前端每次 request 在 header 中带上 token
  • 后端用同样的算法解密

浏览器有哪些进程?渲染进程中有哪些线程?

  • 浏览器主进程 负责包括地址栏,书签栏,前进,后退按钮等部分工作
  • 渲染进程 负责关于 tab 内网页呈现的所有事情
  • GPU 进程 负责处理 GPU 相关任务
  • 网络进程 网络请求,文件访问等等
  • 其他插件进程 负责控制网页用到的所有插件,如 flash

渲染进程中有哪些线程

  • GUI 渲染线程
    • 负责渲染浏览器界面,解析 HTML,CSS 构建 DOM 树和 RenderObject 🌲,布局绘制等。
    • 当界面需要重绘或者由于某种操作发生回流的时候,该线程就会执行
    • GUIJS 引擎线程是互斥的,JS 线程优先级高于 GUI 渲染线程,当 JS 引擎执行时,GUI 线程会被挂起,GUI 更新会保存在一个队列中等到 JS 引擎空闲时立即执行
  • JS 引擎线程
    • 负责处理解析 Javascript 脚本程序,运行代码,如 V8 引擎。
    • JS 引擎一直等待这个任务队列中的任务,然后加以处理,render 进程中永远之后一个 JS 线程运行 js 程序。
    • GUI 与 JS 引擎除斥,JS 引擎执行时间过长,会造成页面渲染不连贯,导致页面渲染加载阻塞
  • 事件触发线程
    • 属于浏览器,而不是 JS 引擎,用于控制时间循环。
    • 当执行 setTimeout点击事件Ajax异步请求,会将对应的任务添加到事件线程中。
    • 对应的事件服务触发条件是,该线程会把事件添加到待处理队列的队尾,等 JS 引擎来处理。
  • 定时处理线程
    • setInterval, setTimeout 所在的线程
  • 异步 http 请求线程
    • 在 XMLHttpRequest 连接后通过浏览器重新开一个线程请求。
    • 检测状态变更是,如果设置有回调函数,异步线程会产生状态变更事件,回调放入事件队列,由 JS 引擎执行。

什么是同源策略?什么会导致跨域?如何解决?

什么是同源策略 同源策略指的是浏览器对不同源的脚本或者文本的访问方式进行的限制。

什么会导致跨域 协议,域名,端口,或者服务端设置跨域限制。

如何解决

  • JSONP 动态加载 script 脚本,url 携带 callback 名称,在传递到服务端之后,服务端会将请求的数据作为参数返回执行的回调方法字符串。客户端接受这段 Javascript 代码后,将 script 脚本挂载到 head 或者 body 上,会自动执行这个回调函数。 优点:兼容性强 缺点:只支持 GET 请求
  • CORS 服务端或者 nginx 配置域名允许跨域即可。
  • node 中间层代理 服务端没有跨域的说法,可在 node 做中间层代理,配置允许跨域。

浏览器垃圾回收机制

如何判定为垃圾

  • 对象不再被引用;
  • 对象不能从根上访问到。

如何执行垃圾回收

  • 标记空间中「可达」值。
    • 从根节点(Root)出发,遍历所有的对象。
    • 可以遍历到的对象,是可达的(reachable)
    • 没有被遍历到的对象,不可达的(unreachable)
  • 回收「不可达」的值所占据的内存。
  • 内存整理。

垃圾回收算法

  • 引用计数(现代浏览器不再使用,循环引用的问题)
  • 标记清除(常用)

局部变量和全局变量的销毁

  • 局部变量:局部作用域中,当函数执行完毕,局部变量没有存在的必要则可以被回收。
  • 全局变量:全局变量什么时候需要自动释放内存空间则很难判断,所以在开发中尽量避免使用全局变量。

V8 引擎对堆内存中的 JS 对象进行分代管理

  • 新生代:存活周期较短的 JS 对象,如临时变量、字符串等。
  • 老生代:经过多次垃圾回收仍然存活,存活周期较长的对象,如主控制器、服务器对象等。

垃圾回收代码层优化

  • 避开闭包陷阱
  • 事件绑定优化,该清除清除
  • 减少循环体中的复杂代码
  • 减少数据读取次数
  • 避免使用全局变量,尽量使用局部作用域数据

hash 与 history 路由

hash

  • hash 也称作锚点,本身是用来做页面定位的 #/tab 这种格式。
  • hash 不会导致浏览器向服务器发起请求。
1
2
3
window.addEventListener("hashchange", function () {
  //监听hash变化,点击浏览器的前进后退会触发
});

history

  • history 模式不仅可以在 url 里放参数,还可以将数据存放在一个特定的对象中。
  • pushState, replaceState

刷新 404 问题,可以通过 nginx 配置

FCP,FMP,TTI,FMP,TTI,LCP,SI,CLS,FID

  • FCP (First Contentful Paint)首次内容绘制 标记浏览器渲染来自 DOM 第一位内容的时间点,该内容可能是文本、图像、SVG 甚至 元素.
  • FMP (First Meaningful Paint)首次有效绘制 浏览器渲染主要内容时的时间点,YouTube 主内容为视频,微博为文章内容
  • TTI (Time to Interactive)可交互时间 用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点
  • LCP (Largest Contentful Paint)最大渲染时间 视窗最大可见图片或者文本块的渲染时间。
  • TBT (Total Blocking Time)页面阻塞总时长 TBT 汇总所有加载过程中阻塞用户操作的时长,在 FCP 和 TTI 之间任何 long task 中阻塞部分都会被汇总
  • SI (Speed Index) 用于显示页面可见部分的显示速度, 单位是时间,
  • CLS (Cumulative Layout Shift)累积布局偏移 在页面整个生命周期中发生的每个意外的版式移位的所有单独版式移位分数的总和
  • FID(First Input Delay)首次输入延迟 测量的是当用户第一次在页面上交互的时候(点击链接、点击按钮或者自定义基于 js 的事件),到浏览器实际开始处理这个事件的时间。

线程和进程的区别

  • 进程 是资源分配的最小单位,线程 是程序执行的最小单位(资源调度的最小单位)
  • 线程 之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据。进程 的通信需要以通信的方式进行。
  • 一个 进程 可以包含多个线程

对称加密和非对称加密的区别

对称加密: 加密和解密的秘钥使用的是同一个.

  • 优点: 算法公开、计算量小、加密速度快、加密效率高
  • 缺点: 安全性

非对称加密:与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。

  • 优点: 算法公开、计算量小、加密速度快、加密效率高
  • 缺点: 安全性
✏️ 如有问题,欢迎指正
上一篇 : 【面试题】设计模式下一篇 : 【面试题】工程化