Q: 浏览器从输入url到渲染页面发生了什么?

一.进程与线程

  • 进程是操作系统资源分配的基本单位,进程中包含线程。
  • 线程是由进程所管理的。为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型,例如:一个浏览器窗口挂掉了,其他窗口还能正常运行,提供稳定性;窗口之间的通信不会混乱,提高安全性。

    #浏览器中的(5个)进程

    浏览器渲染原理 - 图1

  • 浏览器进程:负责界面显示、用户交互、子进程管理,提供存储等。

  • 渲染进程:每个也卡都有单独的渲染进程,核心用于渲染页面。
  • 网络进程:主要处理网络资源加载(HTML、CSS,、JS等)
  • GPU进程:3d绘制,提高性能,渲染过程可能有一些动画
  • 插件进程: chrome中安装的一些插件

    二.从输入URL到浏览器显示页面发生了什么?

    用户输入的是关键字还是URL? 如果是关键字则使用默认搜索引擎(www//google.com)生产URL地址

    1.浏览器进程的相互调用

  • 在浏览器进程中输入url地址开始导航。并准备渲染进程,在浏览器进程里面做

  • 在网络进程中发送请求,将响应后的结果交给渲染进程处理
  • 解析页面,加载页面中所需资源
  • 渲染完毕,展示结果

我们开始细化每一步流程,并且从流程中提取我们可以优化的点。
ipc?

2.URL请求过程(网络七层 。物(物理)数(数据链路层)网(网络层 ip)传(传输层 tcp,udp)会表应(应用层))

  • 浏览器查找当前URL是否存在缓存,如果有缓存、并且缓存未过期,直接从缓存中返回。
  • (去请求它的服务器)查看域名是否已经被解析过了,没有解析过进行DNS(基于UDP)解析将域名解析成IP地址,并增加端口号,,,,
    • tcp:安全可靠,因为需要三次握握手,速度比较慢 分包
    • udp:快速,一个包传输,缺点:丢包
    • 为什么不基于tcp:我们的服务分为一级域名,二级域名,迭代查找,每经过一个域名就需要三次握手,速度比较慢
  • 如果请求是HTTPS,进行SSL/TSL协商(另外的课程)
  • 利用IP地址进行寻址,请求排队。同一个域名下请求数量不能多余6个。
  • 排队后服务器创建TCP链接 (三次握手,为了保证双向连接)为什么不是四次?
    • tcp传输控制协议,可靠的,面向连接的协议,传输效率低,tcp提供全双工服务(就是双方都能通信,即数据在同一时间是双向传播的)
    • ACK:确认信号;PSH:应该从TCP缓冲区读走的数据,RST:断开重新连接,SYN:建立连接,FIN:表示断开连接
    • 发送一个随机的序列号给服务端,服务端接收后发送ACk为1的确认码,发数据需要维护序列号seq
    • 断开连接,
      1. 客户端:我们断开吧
      2. 服务器:好的,你的信息我接收到了,但是此时服务端还没有表态,有可能有未传完的数据,还可以继续和客户端通信
      3. 服务端:发现没有存在连接的意义了,该传的数据已经传完了,此时给客户端一个信息说:断开吧,没有要给你的数据的
      4. 客户端:好的,那就断吧

image.png

  • 利用TCP协议将大文件拆分成数据包进行传输(有序传输),可靠的传输给服务器(丢包重传),服务器收到后按照序号重排数据包 (增加TCP头部,IP头部)
  • 发过去的过程中需要http,发送HTTP请求(请求行,请求头,请求体)
  • HTTP 1.1中支持keep-alive属性,TCP链接不会立即关闭,后续请求可以省去建立链接时间。
  • 服务器响应结果(响应行,响应头,响应体)
  • 返回状态码为301、302时,浏览器会进行重定向操作。(重新进行导航)
  • 返回304则查找缓存。(服务端可以设置强制缓存)

    3.HTTP发展历程

  • HTTP/0.9 在传输过程中没有请求头和请求体,服务器响应没有返回头信息,内容采用ASCII字符流来进行传输 HTML

  • HTTP/1.0 增加了请求头和响应头,实现多类型数据传输
  • HTTP/1.1
    • 使用keep-alive建立长链接(优点)
    • tcp慢启动,慢启动是 TCP 为了减少网络拥塞的一种策略(缺点)
    • 同时开启多条tcp链接,那么这些连接会竞争带宽,影响关键资源的下载(缺点)
    • tcp对头阻塞(缺点):我们知道在 HTTP/1.1 中使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。这意味着我们不能随意在一个管道中发送请求和接收内容
  • HTTP/2.0 :用同一个tcp连接发送数据,一个域名对应一个tcp;http2建立在https上,高效是采用二进制分帧来进行数据传输,完全兼容http1.x
    • 二进制分帧:http2将传输内容分割为更小的消息和帧,采用二进制编码格式进行封装
    • 多路复用:多个请求共享一个tcp连接,多个请求并行请求,整个传输只建立一次连接只会慢启动一次,同时避免了多个 TCP 连接竞争带宽、对头阻塞问题
      • 实现原理:基于二进制分帧层;有了新的分帧机制后,http/2 不再依赖多个TCP连接去实现多流并行了,每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级,最后再在另一端把它们重新组合起来。解决tcp慢启动问题,
    • 设置请求的优先级;
    • 头部压缩
    • 服务器推送
    • 缺点 没有真正解决对头阻塞,目前所有压力都在一个tcp上,当这个tcp丢包等情况的时候,会导致整个请求阻塞。
  • HTTP/3.0 解决TCP队头阻塞问题, 采用QUIC协议。QUIC协议是基于UDP的 (目前:支持和部署是最大的问题)
  • HTTP明文传输,在传输过程中会经历路由器、运营商等环节,数据有可能被窃取或篡改 (安全问题

对比HTTP/1.1 和 HTTP/2 的差
HTTP/1.X :默认开启持久连接,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。
HTTP/2.0 :支持多路复用,这是 HTTP/1.x 持久连接的升级版。多路复用,就是在一个 TCP 连接中可以存在多条流,也就是可以发送多个请求,服务端则可以通过帧中的标识知道该帧属于哪个流(即请求),通过重新排序还原请求。多路复用允许并发的发起多个请求,每个请求及该请求的响应不需要等待其他的请求或响应,避免了线头阻塞问题。这样某个请求任务耗时严重,不会影响到其它连接的正常执行,极大的提高传输性能。
参考文档

4.渲染流程

浏览器渲染原理 - 图3

  • 1.浏览器无法直接使用HTML,需要将HTML转化成DOM树。(document)
  • 2.浏览器无法解析纯文本的CSS样式,需要对CSS进行解析,解析成styleSheets。CSSOM(document.styleSeets)
  • 3.计算出DOM树中每个节点的具体样式(Attachment)
  • 4.创建渲染(布局)树,将DOM树中可见节点,添加到布局树中。并计算节点渲染到页面的坐标位置。(layout)
  • 5.通过布局树,进行分层 (根据定位属性、透明属性、transform属性、clip属性等)生产图层树
  • 6.将不同图层进行绘制,转交给合成线程处理。最终生产页面,并显示到浏览器上 (Painting,Display)

查看layer并对图层进行绘制的列表
注意:

  • js会阻塞dom解析,需要暂停dom解析去执行js

    js可能会操作样式,所以需要等待样式加载完成,所以js会阻塞html解析,也会阻塞渲染,但是执行js之前要等待css加载完毕,保证js可以操作样式———-JavaScript 会阻塞 DOM 构建,而 CSSOM 的构建又回阻塞 JavaScript 的执行。

    总结:DOM如何生成的

  • 当服务端返回的类型是text/html时,浏览器会将收到的数据通过HTMLParser进行解析 (边下载边解析)

  • 在解析前会执行预解析操作,会预先加载JS、CSS等文件
  • 字节流 -> 分词器 -> Tokens -> 根据token生成节点 -> 插入到 DOM树中
  • 遇到js:在解析过程中遇到script标签,HTMLParser会停止解析,(下载)执行对应的脚本。
  • 在js执行前,需要等待当前脚本之上的所有CSS加载解析完毕(js是依赖css的加载)

浏览器渲染原理 - 图4

  • CSS样式文件尽量放在页面头部,CSS加载不会阻塞DOM tree解析,浏览器会用解析出的DOM TREE和 CSSOM 进行渲染,不会出现闪烁问题。如果CSS放在底部,浏览是边解析边渲染,渲染出的结果不包含样式,后续会发生重绘操作。(回流必将引起重绘,重绘不一定会引起回流
    • 当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流
    • 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。https://juejin.cn/post/6844903569087266823
  • JS文件放在HTML底部,防止JS的加载、解析、执行堵塞页面后续的正常渲染

通过PerformanceAPI 监控渲染流程

四.Perfomance API

浏览器渲染原理 - 图5

关键时间节点 描述 含义
TTFB time to first byte(首字节时间) 从请求到数据返回第一个字节所消耗时间
TTI Time to Interactive(可交互时间) DOM树构建完毕,代表可以绑定事件
DCL DOMContentLoaded (事件耗时) 当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发
L onLoad (事件耗时) 当依赖的资源全部加载完毕之后才会触发
FP First Paint(首次绘制) 第一个像素点绘制到屏幕的时间
FCP First Contentful Paint(首次内容绘制) 首次绘制任何文本,图像,非空白节点的时间
FMP First Meaningful paint(首次有意义绘制) 首次有意义绘制是页面可用性的量度标准
LCP Largest Contentful Paint(最大内容渲染) 在viewport中最大的页面元素加载的时间
FID First Input Delay(首次输入延迟) 用户首次和页面交互(单击链接,点击按钮等)到页面响应交互的时间
  1. <div style="background:red;height:100px;width:100px"></div>
  2. <h1 elementtiming="meaningful">珠峰架构</h1>
  3. <script>
  4. window.onload = function () {
  5. let ele = document.createElement('h1');
  6. ele.innerHTML = 'zf';
  7. document.body.appendChild(ele)
  8. }
  9. setTimeout(() => {
  10. const {
  11. fetchStart,// 开始访问
  12. requestStart,//请求开始
  13. responseStart,//响应开始
  14. domInteractive,// dom可交互的时间点
  15. domContentLoadedEventEnd,// dom加载完毕+domcontentLoaded完成的事件
  16. loadEventStart // 所有资源加载完毕
  17. } = performance.timing;
  18. let TTFB = responseStart - requestStart; // ttfb
  19. let TTI = domInteractive - fetchStart; // tti
  20. let DCL = domContentLoadedEventEnd - fetchStart // dcl
  21. let L = loadEventStart - fetchStart;
  22. console.log(TTFB, TTI, DCL, L)
  23. const paint = performance.getEntriesByType('paint');// 获取所有绘制相关的信息
  24. const FP = paint[0].startTime;
  25. const FCP = paint[1].startTime; // 2s~4s
  26. }, 2000);
  27. let FMP;
  28. new PerformanceObserver((entryList, observer) => {
  29. let entries = entryList.getEntries();
  30. FMP = entries[0];
  31. observer.disconnect(); 断开监控
  32. console.log(FMP)
  33. }).observe({ entryTypes: ['element'] });
  34. let LCP;
  35. new PerformanceObserver((entryList, observer) => {
  36. let entries = entryList.getEntries();
  37. LCP = entries[entries.length - 1]; // 取最后一个
  38. observer.disconnect();
  39. console.log(LCP); // 2.5s-4s
  40. }).observe({ entryTypes: ['largest-contentful-paint'] });
  41. let FID;
  42. new PerformanceObserver((entryList, observer) => {
  43. let firstInput = entryList.getEntries()[0];
  44. if (firstInput) {
  45. FID = firstInput.processingStart - firstInput.startTime;
  46. observer.disconnect();
  47. console.log(FID)
  48. }
  49. }).observe({ type: 'first-input', buffered: true });
  50. </script>

五.网络优化策略

  • 减少HTTP请求数,合并JS、CSS,合理内嵌CSS、JS(但是内联之后没发使用缓存)
  • 合理设置服务端缓存,提高服务器处理速度。 (强制缓存、对比缓存)

    1. // Expires/Cache-Control Etag/if-none-match/last-modified/if-modified-since
  • 避免重定向,重定向会降低响应速度 (301,302)

  • 使用dns-prefetch,进行DNS预解析
  • 采用域名分片技术,将资源放到不同的域名下。接触同一个域名最多处理6个TCP链接问题。
  • 采用CDN加速加快访问速度。(指派最近、高度可用)
  • gzip压缩优化 对传输资源进行体积压缩 (html,js,css)

    1. // Content-Encoding: gzip
  • 加载数据优先级 : preload(预先请求当前页面需要的资源) prefetch(将来页面中使用的资源) 将数据缓存到HTTP缓存中

    1. link rel="preload" href="style.css" as="style">

    六.关键渲染路径

    浏览器渲染原理 - 图6

  • 重排(回流)Reflow: 添加元素、删除元素、修改大小、移动元素位置、获取位置相关信息

  • 重绘 Repaint:页面中元素样式的改变并不影响它在文档流中的位置。

我们应当尽可能减少重绘和回流

1.强制同步布局问题

JavaScript强制将计算样式和布局操作提前到当前的任务中

  1. <div id="app"></div>
  2. <script>
  3. function reflow() {
  4. let el = document.getElementById('app');
  5. let node = document.createElement('h1');
  6. node.innerHTML = 'hello';
  7. el.appendChild(node);
  8. // 强制同步布局
  9. console.log(app.offsetHeight);
  10. }
  11. requestAnimationFrame(reflow)
  12. </script>

2.布局抖动(layout thrashing)问题

在一段js代码中,反复执行布局操作,就是布局抖动

  1. function reflow(){
  2. let el = document.getElementById('app');
  3. let node = document.createElement('h1');
  4. node.innerHTML = 'hello';
  5. el.appendChild(node);
  6. // 强制同步布局
  7. console.log(app.offsetHeight); // 不停的触发布局
  8. }
  9. window.addEventListener('load',function(){
  10. for(let i = 0 ; i<100;i++){
  11. reflow();
  12. }
  13. });

3.减少回流和重绘

  • 脱离文档流
  • 渲染时给图片增加固定宽高
  • 尽量使用css3 动画:css3绘制一次,比较少
  • 可以使用will-change提取到单独的图层中

    七.静态文件优化

    1.图片优化

    图片格式:

  • jpg:适合色彩丰富的照片、banner图;不适合图形文字、图标(纹理边缘有锯齿),不支持透明度

  • png:适合纯色、透明、图标,支持半透明;不适合色彩丰富图片,因为无损存储会导致存储体积大
  • gif:适合动画,可以动的图标;不支持半透明,不适和存储彩色图片
  • webp:适合半透明图片,可以保证图片质量和较小的体积
  • svg格式图片:相比于jpg和jpg它的体积更小,渲染成本过高,适合小且色彩单一的图标;

图片优化:

  • 避免空src的图片
  • 减小图片尺寸,节约用户流量
  • img标签设置alt属性, 提升图片加载失败时的用户体验
  • 原生的loading:lazy 图片懒加载

    1. <img loading="lazy" src="./images/1.jpg" width="300" height="450" />
  • 不同环境下,加载不同尺寸和像素的图片

    1. <img src="./images/1.jpg" sizes="(max-width:500px) 100px,(max-width:600px) 200px" srcset="./images/1.jpg 100w, ./images/3.jpg 200w">
  • 对于较大的图片可以考虑采用渐进式图片

  • 采用base64URL减少图片请求
  • 采用雪碧图合并图标图片等

    问题

  1. 启动页面时加载过多的图片
    1. 大部分图片都不是首屏所需的,因此可以延迟首屏不需要的图片加载,而优先加载首屏所需图片。这儿首屏的含义是指打开新首页首先进入屏幕视窗内的区域范围
  2. 部分图片体积过大

  3. 2.HTML优化

  4. 语义化HTML:代码简洁清晰,利于搜索引擎,便于团队开发

  5. 提前声明字符编码,让浏览器快速确定如何渲染网页内容
  6. 减少HTML嵌套关系、减少DOM节点数量
  7. 删除多余空格、空行、注释、及无用的属性等
  8. HTML减少iframes使用 (iframe会阻塞onload事件可以动态加载iframe),父页面要等待子页面完全加载,解决:动态加载
  9. 避免使用table布局

    3.CSS优化

  10. 减少伪类选择器、减少样式层数、减少使用通配符

  11. 避免使用CSS表达式,CSS表达式会频繁求值, 当滚动页面,或者移动鼠标时都会重新计算 (IE6,7)

    1. background-color: expression( (new Date()).getHours()%2 ? "red" : "yellow" );
  12. 删除空行、注释、减少无意义的单位、css进行压缩

  13. 使用外链css,可以对CSS进行缓存
  14. 添加媒体字段,只加载有效的css文件

    1. <link href="index.css" rel="stylesheet" media="screen and (min-width:1024px)" /> 1
  15. CSS contain属性,将元素进行隔离

  16. 减少@import使用,由于@import采用的是串行加载

    4.JS优化

  17. 通过async、defer异步加载文件浏览器渲染原理 - 图7

defer 特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行
async 脚本会在后台加载,并在加载就绪时运行。DOM 和其他脚本不会等待它们,它们也不会等待其它的东西。async 脚本就是一个会在加载完成时执行的完全独立的脚本。

  1. 减少DOM操作,缓存访问过的元素
  2. 操作不直接应用到DOM上,而应用到虚拟DOM上。最后一次性的应用到DOM上。
  3. 使用webworker解决程序阻塞问题
  4. IntersectionObserver

    1. const observer = new IntersectionObserver(function(changes) {
    2. changes.forEach(function(element, index) {
    3. if (element.intersectionRatio > 0) {
    4. observer.unobserve(element.target);
    5. element.target.src = element.target.dataset.src;
    6. }
    7. });
    8. });
    9. function initObserver() {
    10. const listItems = document.querySelectorAll('img');
    11. listItems.forEach(function(item) {
    12. observer.observe(item);
    13. });
    14. }
    15. initObserver();
  5. 虚拟滚动 vertual-scroll-list

  6. requestAnimationFrame、requestIdleCallback浏览器渲染原理 - 图8
  7. 尽量避免使用eval, 消耗时间久
  8. 使用事件委托,减少事件绑定个数。
  9. 尽量使用canvas动画、CSS动画

八. 优化策略

  • 关键资源个数越多,首次页面加载时间就会越长
  • 关键资源的大小,内容越小,下载时间越短
  • 优化白屏:内联css和内联js移除文件下载,较小文件体积
  • 预渲染,打包时进行预渲染 image.png
  • 使用SSR加速首屏加载(耗费服务端资源),有利于SEO优化。 首屏利用服务端渲染,后续交互采用客户端渲染

    九.浏览器的存储

  • cookie: cookie过期时间内一直有效,存储大小4k左右、同时限制字段个数,不适合大量的数据存储,每次请求会携带cookie,主要可以利用做身份检查。

    • 设置cookie有效期
    • 根据不同子域划分cookie较少传输
    • 静态资源域名和cookie域名采用不同域名,避免静态资源访问时携带cookie
  • localStorage: chrome下最大存储5M, 除非手动清除,否则一直存在。利用localStorage存储静态资源

    1. function cacheFile(url) {
    2. let fileContent = localStorage.getItem(url);
    3. if (fileContent) {
    4. eval(fileContent)
    5. } else {
    6. let xhr = new XMLHttpRequest();
    7. xhr.open('GET', url, true);
    8. xhr.onload = function () {
    9. let reponseText = xhr.responseText
    10. eval(reponseText);
    11. localStorage.setItem(url, reponseText)
    12. }
    13. xhr.send()
    14. }
    15. }
    16. cacheFile('/index.js');
  • sessionStorage: 会话级别存储,可用于页面间的传值

  • indexDB:浏览器的本地数据库 (基本无上限)

    1. let request = window.indexedDB.open('myDatabase');
    2. request.onsuccess = function(event){
    3. let db = event.target.result;
    4. let ts = db.transaction(['student'],'readwrite')
    5. ts.objectStore('student').add({name:'zf'})
    6. let r = ts.objectStore('student').get(5);
    7. r.onsuccess = function(e){
    8. console.log(e.target.result)
    9. }
    10. }
    11. request.onupgradeneeded = function (event) {
    12. let db = event.target.result;
    13. if (!db.objectStoreNames.contains('student')) {
    14. let store = db.createObjectStore('student', { autoIncrement: true });
    15. }
    16. }

    十.增加体验 PWA(Progressive Web App)

    webapp用户体验差(不能离线访问),用户粘性低(无法保存入口),pwa就是为了解决这一系列问题,让webapp具有快速,可靠,安全等特点

  • Web App Manifest:将网站添加到桌面、更类似native的体验

  • Service Worker:离线缓存内容,配合cache API
  • Push Api & Notification Api: 消息推送与提醒
  • App Shell & App Skeleton App壳、骨架屏

    十一.LightHouse使用

    npm install lighthouse -g
    lighthouse http://www.taobao.com

可以根据lighthouse中的建议进行页面的优化
requestFrameAnimation requestAdleCallback
图片 contain