zoukankan      html  css  js  c++  java
  • 前端性能优化

    渲染进程是多线程的:

    1. GUI渲染线程
      o 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
      o 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
      o 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
    2. js引擎线程
      o 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
      o JS引擎线程负责解析Javascript脚本,运行代码。
      o JS引擎一直等待任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中只有一个JS线程在运行
      o 同样注意,GUI渲染线程与JS引擎线程是互斥的。所以如果JS执行的时间过长,要放在body下面,否则就会导致页面渲染加载阻塞。
    3. 事件触发线程
      o 管理着事件队列
      o 监听事件,符合条件时把回调函数放入事件队列中
    4. 定时触发器线程
      o setInterval与setTimeout在此线程中计时完毕后,把回调函数放入事件队列中
      o 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确),因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
      o 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
    5. 异步http请求线程
      o 检测到XHR对象状态变化时,将回调函数放入事件队列中
      o 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

    JS阻塞页面加载

    从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。
    譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。 然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。
    所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
    总结一下:
    • css加载不会阻塞DOM树解析(异步加载时DOM照常构建),但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)
    • Javascript 阻塞 DOM 解析

    具体的流程:
    • DOM 树与 CSSOM 树合并后形成渲染树。
    • 渲染树只包含渲染网页所需的节点。
    • 布局计算每个对象的精确位置和大小。
    • 最后一步是绘制,使用最终渲染树将像素渲染到屏幕上
    总结一下:
    浏览器的渲染流程分为:
    • DOM树构建
    • CSSOM树构建
    • RenderObject树构建
    • 布局
    • 绘制

    • script标签的处理
    • JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器在解析HTML时,一旦碰到script,就会立即停止HTML的解析(而CSS不会),执行JS,再返还控制权。
    • 事实上,JS执行前不仅仅是停止了HTML的解析,它还必须等待CSS的解析完成。当浏览器碰到script元素时,发现该元素前面的CSS还未解析完,就会等待CSS解析完成,再去执行JS。
    • JS阻塞了HTML的解析,也阻塞了其后的CSS解析,整个解析进程必须等待JS的执行完成才能够继续,这就是所谓的JS阻塞页面。一个script标签,推迟了DOM的生成、CSSOM的生成以及之后的所有渲染过程,从性能角度上讲,将script放在页面底部,也就合情合理了。
    • 简单来说:渲染线程与JS引擎线程是互斥的,当JS引擎执行时渲染线程会被挂起(相当于被冻结了),渲染更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

    重排reflow与重绘repaint

    重排reflow
    reflow指的是重新计算页面布局。
    某个节点reflow时会重新计算节点的尺寸和位置,而且还有可能触发其子节点、祖先节点和页面上的其他节点reflow。在这之后再触发一次repaint。
    当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流,每个页面至少需要一次回流,就是在页面第一次加载的时候。
    导致reflow的操作
    • 调整窗口大小
    • 改变字体
    • 增加或者移除样式表
    • 内容变化,比如用户在input框中输入文字
    • 激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)
    • 操作 class 属性
    • 脚本操作 DOM
    • 计算 offsetWidth 和 offsetHeight 属性
    • 设置 style 属性的值
    重绘repaint
    repiant或者redraw遍历所有的节点检测各节点的可见性、颜色、轮廓等可见的样式属性,然后根据检测的结果更新页面的响应部分。
    当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘
    只触发重绘不触发重排的一些CSS属性:
    • color
    • border-style、border-radius
    • visibility
    • text-decoration
    • background、background-image、background-position、background-repeat、background-size
    • outline、outline-color、outline-style、outline-width
    • box-shadow
    当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。

    减少重绘与重排:
    重绘和回流在实际开发中是很难避免的,我们能做的就是尽量减少这种行为的发生。
    • js尽量少访问dom节点和css 属性,尽量不要过多的频繁的去增加,修改,删除元素,因为这可能会频繁的导致页面reflow,可以先把该dom节点抽离到内存中进行复杂的操作然后再display到页面上。(虚拟DOM)
    • 减少不必要的 DOM 层级(DOM depth)。改变 DOM 树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行 reflow 上面。
    • 不要通过父级来改变子元素样式,最好直接改变子元素样式,改变子元素样式尽可能不要影响父元素和兄弟元素的大小和尺寸
    • 尽量通过class来设计元素样式,切忌用style 多次操作单个属性
    • 尽可能的为产生动画的 HTML 元素使用 fixed 或 absolute 的 position ,那么修改他们的 CSS 是不会 Reflow 的。
    • img标签要设置高宽,以减少重绘重排
    • 把DOM离线后修改,如将一个dom脱离文档流,比如display:none ,再修改属性,这里只发生一次回流。
    • 尽量用 transform 来做形变和位移,不会造成回流
    • 权衡速度的平滑。比如实现一个动画,以1个像素为单位移动这样最平滑,但reflow就会过于频繁,CPU很快就会被完全占用。如果以3个像素为单位移动就会好很多。
    • 不要用tables布局的另一个原因就是tables中某个元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用table的场合,可以设置table-layout为auto或fixed,
    • 避免不必要的复杂的 CSS 选择器,尤其是后代选择器(descendant selectors),因为为了匹配选择器将耗费更多的 CPU。
    PS: display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。
    Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。
    优化工作清单
    当小伙伴们出现了页面性能问题时,或者在设计我们的应用之前,就可以参考如下的条目进行快速检查。
    • 页面内容
    o 减少 HTTP 请求数
    o 减少 DNS 查询
    o 避免重定向
    o 缓存 Ajax 请求
    o 延迟加载
    o 预先加载
    o 减少 DOM 元素数量
    o 划分内容到不同域名
    o 尽量减少 iframe 使用
    o 避免 404 错误
    • 服务器
    o 使用 CDN
    o 添加 Expires 或 Cache-Control 响应头
    o 启用 Gzip
    o 配置 Etag
    o 尽早输出缓冲
    o Ajax 请求使用 GET 方法
    o 避免图片 src 为空
    • Cookie
    o 减少 Cookie 大小
    o 静态资源使用无 Cookie 域名
    • CSS
    o 把样式表放在 中
    o 不要使用 CSS 表达式
    o 使用 link 替代 @import
    o 不要使用 filter
    • JavaScript
    o 把脚本放在页面底部
    o 使用外部 JavaScript 和 CSS
    o 压缩 JavaScript 和 CSS
    o 移除重复脚本
    o 减少 DOM 操作
    o 使用高效的事件处理
    • 图片
    o 优化图片
    o 优化 CSS Sprite
    o 不要在 HTML 中缩放图片
    o 使用体积小、可缓存的 favicon.ico
    • 移动端
    o 保持单个文件小于 25 KB
    o 打包内容为分段(multipart)文档

    浏览器部分

    • 网络层面

    1. 过多的HTTP请求
      打开一个网页的时候,后台程序的响应并不所需太多时间,等待的时间主要花费在下载网页元素上了,即HTML、CSS、JavaScript、Flash、图片等。据统计,每增加一个元素,网页载入的时间就会增加25-40毫秒(具体取决于用户的带宽情况)。
    2. 资源访问带宽小
      两方面,一方面是客户端的带宽,一方面是服务器端的带宽。
    3. 网页元素(图片、视频、样式)太大
      • 浏览器渲染层面
    4. 渲染阻塞:
      浏览器想要渲染一个页面就必须先构建出DOM树与CSSOM树,如果HTML与CSS文件结构非常庞大与复杂,这显然会给页面加载速度带来严重影响。
      所谓渲染阻塞资源,即是对该资源发送请求后还需要先构建对应的DOM树或CSSOM树,这种行为显然会延迟渲染操作的开始时间。
      JS阻塞与CSS阻塞:
      HTML、CSS、JavaScript都是会对渲染产生阻塞的资源,HTML是必需的(没有DOM还谈何渲染),但还可以从CSS与JavaScript着手优化,尽可能地减少阻塞的产生。
    5. 重复渲染
    6. DNS解析
      • 服务端层面
    7. 硬件配置低:这个是双向的
    8. 服务器软件,比如防火墙、内网策略等
    9. 未对Nginx这类web服务器进行配置优化
    10. CPU占满、数据库未优化
    11. 代码问题,代码效率,代码性能
    12. 包含了过多的分析类工具
      代码部分
      • 构建层面
      未对代码进行打包、压缩、兼容性优化。
      未合并重复的请求、代码。
      • 编码层面
      没有良好的编码习惯,错误的编排JS与CSS
      for循环、迭代、同步、重定向、阻塞请求
      未删除重复、无用的代码
      未对逻辑业务复杂的代码进行重构,了解设计模式,对业务进行疏理
      • 机制(SSR,英文Server Side Render:服务器端渲染)
      未加入Async异步机制
      未思考页面加载、用户体验
      • 规范
      CSS规范
      HTML规范/HTML5规范
      Airbnb代码规范等。

    • 使用css媒体类型

    如果可以让CSS资源只在特定条件下使用,这样这些资源就可以在首次加载时先不进行构建CSSOM树,只有在符合特定条件时,才会让浏览器进行阻塞渲染然后构建CSSOM树。
    CSS的媒体查询正是用来实现这个功能的,它由媒体类型以及零个或多个检查特定媒体特征状况的表达式组成。

    使用媒体查询可以让CSS资源不在首次加载中阻塞渲染,但不管是哪种CSS资源它们的下载请求都不会被忽略,浏览器仍然会先下载CSS文件。
  • 相关阅读:
    A方法调用B方法,JVM是怎么一步一步调用的
    java ImmutableMap使用
    使用 Spring 配置动态数据源实现读写分离
    spring-boot的三种启动方式
    使用 Spring 配置动态数据源实现读写分离
    Java改变生成随机数的平均值(改变生成随机数的概率)
    微信抢红包算法实现(JAVA)
    Redis分布式锁的实现原理
    Redis和队列
    springboot自定义配置文件
  • 原文地址:https://www.cnblogs.com/xm0328/p/13783011.html
Copyright © 2011-2022 走看看