浏览器的底层渲染机制及前端性能优化总结

·  阅读 364

​​​​​​[线程 & 进程]

浏览器打开一个页面是开辟一个进程,在这个进程中还会做一些事情,这些事情交给线程来做,浏览器是多线程的:

  •   + GUI渲染线程 「自上而下解析渲染HTML/CSS代码」
  •   + JS引擎线程 「解析和渲染JS代码」
  •   + HTTP网络线程 「从服务器获取资源和数据的」
  •   + 定时器的监听线程
  •   + 事件监听线程
  •   + ...

  浏览器只会分配一个“JS引擎线程”去渲染JS,所以JS是单线程的

浏览器是如何渲染页面的

  • @1 生成DOM TREE「描述DOM/节点的层级关系」

  •   @2 生成CSSOM TREE「等待样式资源获取到,GUI会进行渲染,把样式按照“层叠样式表”渲染为CSSOM TREE」

  •   @3 把DOM TREE 和 CSSOM TREE结合在一起,生成RENDER TREE「规划出每个节点应该渲染的样式,后期渲染页面就是按照这个来的」

  •   @4 Layout布局「根据视口大小,计算每个节点在视口中的位置和大小等;如果后期的某些操作会导致视口中的节点位置、大小或者视口大小发生改变,此时我们需要重新计算每个节点在视口中的最新位置等,我们把这个操作叫做“回流/重排 Reflow”」
  •   @5 分层「按照样式,分成不同的层级(文档流),并计算出每一层文档流中的节点如何渲染」
  •   @6 Painting绘制「绘制完成的结果就是在浏览器中看到页面和效果;如果后期某些节点的样式发生改变(位置和大小等不变,只是样色、背景等样式改变),浏览器需要重新按照最新的样式去绘制,我们这个操作称之为“重绘 Repaint”」
  •   页面第一次渲染一定会引发一次Layout&Painting;后期发生重排一定也会引发重绘;但是重绘不一定非要重排;重排非常的消耗性能(这也是我们所谓操作DOM损耗性能的主要原因)!

同步,'异步

  GUI线程自上而下渲染代码

    • 遇到link/img...,会开辟新的HTTP线程去获取资源,GUI渲染线程继续向下执行「异步的」
  •   + 遇到,资源已经存在无需去服务器获取,但是需要等待其它外链资源获取回来后,GUI按照导入的先后顺序依次渲染CSS样式,以此保证样式的优先级!!
  •   + 遇到@import,也会开辟新的HTTP线程去获取资源,但是会阻碍GUI的渲染,什么时候资源获取到,什么时候GUI才会继续向下执行!「同步的」
  •   + 遇到

 + 我们一般都把JS放在页面底部「作用:不让其阻碍GUI的渲染;等待DOM渲染完再去执行JS,保证JS中可以正常的操作DOM;」

+ 如果不放在底部,还想获取DOM

  •        + 办法一:基于DOMContentLoaded事件监听处理「本质:script的获取和渲染还是同步的,还会阻碍GUI,只不过我们把操作DOM的代码放在事件监听中延后执行了」
  •        + 办法二:给script设置async或者defer属性「本质:把script的获取和渲染改为异步操作,不让其阻碍GUI的渲染」
  •          + async:遇到script分配一个HTTP线程去获取资源,此时GUI继续渲染,但是当资源获取到之后,立即停止GUI的渲染,先把获取的JS渲染...「不关注JS导入顺序」
  •          + defer:和link很相似,获取资源和代码的渲染都是异步的,都需要等到GUI渲染完,而且JS资源也都获取到了,按照JS的先后导入顺序,依次渲染解析JS「关注JS的相互依赖」

项目(前端)性能优化方案

针对于关键渲染路径的优化

 @1 不同的资源分服务器部署「优势:资源合理利用、提高服务器处理能力、提高HTTP的并发数   弊端:增加了DNS解析的次数」,所以在这个基础上,我们使用DNS Prefetch预解析「原理:利用link的异步性,让GUI渲染的同时,也去预先解析DNS,后面再获取资源的时候,直接把DNS缓存的信息拿出来用即可...」

@2 减少操作DOM产生重排和重绘

  •  + 放弃直接操作DOM,我们主攻操作数据,把操作DOM的事情交给vue/react框架来完成「框架内部做了很多操作DOM的优化」:虚拟DOM->DOM DIFF->渲染差异内容
  •      + 利用浏览器的渲染队列机制,我们操作DOM的样式进行“读写分离「集中修改样式」”
  •      + 动态创建多个DOM节点,我们基于文档碎片或者模板字符串,实现批量增加
  •      + 基于transform修改元素的样式,不会引发重排
  •      + 尽量操作在单独文档流中的节点,这样节点位置或大小改变,只会把这一层中的节点重新Layout,虽然也引发了重排,但是总比所有节点都重新计算位置强...

   @3 对于静态资源文件设置强缓存和协商缓存,目的是保证第二次及以后加载页面更快

   @4 基于CDN做资源分布式部署

   @5 降低HTML嵌套的层级、使用语义化标签

   @6 保持TCP通道的长链接 Connection:keep-alive

   @7 尽可能使用HTTP2.0

   @8 对于script标签来讲,尽可能放在页面末尾导入,如果非要放在前面导入,需要加async/defer,避免对GUI阻塞

   @9 如果样式资源比较少,直接内嵌式即可,减少一次HTTP请求;如果内容多,则合并到一个css中,使用外链式link导入;坚决不用@import导入式,因为他会阻塞GUI渲染!!

   @10 使用本地存储方案,对不经常更新的数据做数据缓存

   @11 link导入样式的操作放在HEAD中,让GUI渲染DOM TREE的同时,也去请求CSS资源,这样等到DOM TREE完成,可能CSS资源已经拿回来了

   @12 开启服务器端的GZIP压缩「让每一次返回的资源压缩40%+」

  @13 合理使用图片base64

  •        + 需要基于webpack自己编译BASE64「不要自己手动写BASE64,因为代码太恶了」
  •       + 零散的小图片一般可以base64
  •       + 如果这个图片很大,并且还很重要(不能延迟加载),如果想尽各种办法加载还是慢,不妨使用BASE64

====针对于提高页面第一次打开速度的优化{减少白屏等待时间}====

 @1 图片懒加载「第一次渲染页面不去加载真实图片(页面中基于默认图占位):减少了HTTP请求次数、不占用HTTP并发资源、第一次加载页面也无需渲染图片... 让页面第一次加载更快」

 @2 骨架屏

    • 服务器骨架屏(SSR渲染):页面首屏需要展示的结构、样式、数据等都由服务器处理好,第一次加载页面,只要获取到内容,直接渲染即可(真实数据也有了) -> 前提服务器抗压能力需要好
  •      + 前端骨架屏:渲染之前的Loading效果;在真实内容没有渲染出来之前,先把架子搭起来,用一些灰色的框框占位,给用户正在加载的友好效果...

 @3 减少HTTP的请求次数和大小「因为HTTP的并发性、TCP的三握四挥、网络通道可能会被阻塞等众多原因,决定了HTTP请求次数越少越好」

  • CSS和JS资源合并为一个「如果一个文件过大,第一次加载页面不需要这么多东西,我们也可以切割成多个,但是第一次只加载一个必须的,其余的动态异步加载」
  •      + 使用CSS Sprite技术,多张图片合并为一个
  •      + 文件要压缩,图片资源在保证清晰度的前提下,尽可能压缩
  •      + 使用字体图标/SVG(矢量图)代替位图(jpg/png/gif...

   @4 音视频资源一定要做延迟加载和播放

   @5 数据的分页处理、异步加载、下拉刷新...

====运行时的代码优化====

  @1 基于事件委托处理事件绑定「优势:减少堆栈内存的开辟、可以给动态创建的元素做事件绑定...提高了整体性能」

   @2 减少cookie的使用「因为每一次向服务器发送请求,都会在请求头中把cookie传递给服务器,不论服务器是否想要,如果本地cookie存储信息多,则每次传输都会携带一些没必要的内容...」

   @3 对于一些操作应该使用函数的防抖和节流

   @4 CSS选择器前缀不要过长「CSS选择器的渲染方向:右->左」

   @5 合理使用闭包「闭包会产生不释放的栈内存」

   @6 避免出现死递归(因为会导致栈溢出)、避免出现死循环(因为会阻塞JS引擎线程的渲染)

   @7 动画处理的原则:能用CSS搞定的不用JS,能用requestAnimationFrame搞定的不用定时器,如果最后定时器动画都搞不定的,换需求...

   @8 避免内存泄漏  -> javascript高级程序设计第三版

   @9 避免使用CSS的表达式{expression}

webpack层面:优化打包部署

vue/react层面

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改