前端性能优化不完全手册

标签: javascript node.js typescript css html5 | 发表时间:2019-04-11 00:06 | 作者:Jerry谭金杰
出处:https://segmentfault.com/blogs

性能优化是一门大学问,本文仅对个人一些积累知识的阐述,欢迎下面补充。

抛出一个问题,从输入 url地址栏到所有内容显示到界面上做了哪些事?
  • 1.浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  • 2.建立 TCP连接(三次握手);
  • 3.浏览器发出读取文件( URL 中域名后面部分对应的文件)的 HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
  • 4.服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
  • 5.浏览器将该 html 文本并显示内容;
  • 6.释放 TCP连接(四次挥手);
上面这个问题是一个面试官非常喜欢问的问题,我们下面把这6个步骤分解,逐步细谈优化。

一、 DNS 解析

  • DNS`解析:将域名解析为ip地址 ,由上往下匹配,只要命中便停止

    • 走缓存
    • 浏览器DNS缓存
    • 本机DNS缓存
    • 路由器DNS缓存
    • 网络运营商服务器DNS缓存 (80%的DNS解析在这完成的)
    • 递归查询
优化策略:尽量允许使用浏览器的缓存,能给我们节省大量时间。

二、 TCP的三次握手

  • SYN (同步序列编号)ACK(确认字符)

    • 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等 待Server确认。
    • 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
    • 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

三、浏览器发送请求

优化策略:
    • 1. HTTP协议通信最耗费时间的是建立 TCP连接的过程,那我们就可以使用 HTTP Keep-Alive,在 HTTP 早期,每个 HTTP 请求都要求打开一个 TCP socket连接,并且使用一次之后就断开这个 TCP连接。 使用 keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用 keep-alive机制,可以减少 TCP连接建立次数,也意味着可以减少 TIME_WAIT状态连接,以此提高性能和提高 http服务器的吞吐率(更少的 tcp连接意味着更少的系统内核调用
    • 2.但是, keep-alive并不是免费的午餐,长时间的 TCP连接容易导致系统资源无效占用。配置不当的 keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置 keep-alive timeout时间非常重要。(这个 keep-alive_timout时间值意味着:一个 http产生的 tcp连接在传送完最后一个响应后,还需要 holdkeepalive_timeout秒后,才开始关闭这个连接),如果想更详细了解可以看这篇文章 keep-alve性能优化的测试结果
    • 3.使用 webScoket通信协议,仅一次 TCP握手就一直保持连接,而且他对二进制数据的传输有更好的支持,可以应用于即时通信,海量高并发场景。 webSocket的原理以及详解
    • 4.减少 HTTP请求次数,每次 HTTP请求都会有请求头,返回响应都会有响应头,多次请求不仅浪费时间而且会让网络传输很多无效的资源,使用前端模块化技术 AMD CMD commonJS ES6等模块化方案将多个文件压缩打包成一个,当然也不能都放在一个文件中,因为这样传输起来可能会很慢,权衡取一个中间值
    • 5.配置使用懒加载,对于一些用户不立刻使用到的文件到特定的事件触发再请求,也许用户只是想看到你首页上半屏的内容,但是你却请求了整个页面的所有图片,如果用户量很大,那么这是一种极大的浪费
    • 6.服务器资源的部署尽量使用同源策略

    四、服务器返回响应,浏览器接受到响应数据

    五、浏览器解析数据,绘制渲染页面的过程

    • 先预解析(将需要发送请求的标签的请求发出去)
    • 从上到下解析 html文件
    • 遇到HTML标签,调用html解析器将其解析 DOM
    • 遇到 css标记,调用css解析器将其解析 CSSOM
    • link 阻塞 - 为了解决闪屏,所有解决闪屏的样式
    • style 非阻塞,与闪屏的样式不相关的
    • DOM树和 CSSOM树结合在一起,形成 render
    • layout布局 render渲染
    • 遇到 script标签,阻塞,调用 js解析器解析 js代码,可能会修改 DOM树,也可能会修改 CSSOM
    • DOM树和 CSSOM树结合在一起,形成 render
    • layout布局 render渲染(重排重绘)
    • script标签的属性

      • async 异步 谁先回来谁就先解析,不阻塞
      • defer 异步 按照先后顺序(defer)解析,不阻塞
      • script标签放在body下,放置多次重排重绘,能够操作dom
    性能优化策略:
    • 需要阻塞的样式使用 link引入,不需要的使用 style标签(具体是否需要阻塞看业务场景)
    • 图片比较多的时候,一定要使用懒加载,图片是最需要优化的, webpack4中也要配置图片压缩,能极大压缩图片大小,对于新版本浏览器可以使用 webp格式图片 webP详解,图片优化对性能提升最大。
    • webpack4配置 代码分割,提取公共代码成单独模块。方便缓存
          /*
        runtimeChunk 设置为 true, webpack 就会把 chunk 文件名全部存到一个单独的 chunk 中,
        这样更新一个文件只会影响到它所在的 chunk 和 runtimeChunk,避免了引用这个 chunk 的文件也发生改变。
        */
        runtimeChunk: true, 
        splitChunks: {
          chunks: 'all'  // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了
        }
      }
        //因为是单入口文件配置,所以没有考虑多入口的情况,多入口是应该分别进行处理。
    • 对于需要事件驱动的 webpack4配置懒加载的,可以看这篇 webpack4优化教程,写得非常全面
    • 一些原生 javaScriptDOM操作等优化会在下面总结

    六、 TCP的四次挥手,断开连接


    终结篇:性能只是 load 时间或者 DOMContentLoaded 时间的问题吗?

    • RAIL

      • Responce 响应,研究表明,100ms内对用户的输入操作进行响应,通常会被人类认为是立即响应。时间再长,操作与反应之间的连接就会中断,人们就会觉得它的操作有延迟。例如:当用户点击一个按钮,如果100ms内给出响应,那么用户就会觉得响应很及时,不会察觉到丝毫延迟感。
      • Animaton 现如今大多数设备的屏幕刷新频率是60Hz,也就是每秒钟屏幕刷新60次;因此网页动画的运行速度只要达到60FPS,我们就会觉得动画很流畅。
      • Idle RAIL规定,空闲周期内运行的任务不得超过50ms,当然不止RAIL规定,W3C性能工作组的Longtasks标准也规定了超过50毫秒的任务属于长任务,那么50ms这个数字是怎么得来的呢?浏览器是单线程的,这意味着同一时间主线程只能处理一个任务,如果一个任务执行时间过长,浏览器则无法执行其他任务,用户会感觉到浏览器被卡死了,因为他的输入得不到任何响应。为了达到100ms内给出响应,将空闲周期执行的任务限制为50ms意味着,即使用户的输入行为发生在空闲任务刚开始执行,浏览器仍有剩余的50ms时间用来响应用户输入,而不会产生用户可察觉的延迟。
      • Load如果不能在1秒钟内加载网页并让用户看到内容,用户的注意力就会分散。用户会觉得他要做的事情被打断,如果10秒钟还打不开网页,用户会感到失望,会放弃他们想做的事,以后他们或许都不会再回来。
      如何使网页更丝滑?
      • 使用requestAnimationFrame

        • 即便你能保证每一帧的总耗时都小于16ms,也无法保证一定不会出现丢帧的情况,这取决于触发JS执行的方式。假设使用 setTimeout 或 setInterval 来触发JS执行并修改样式从而导致视觉变化;那么会有这样一种情况,因为setTimeout 或 setInterval没有办法保证回调函数什么时候执行,它可能在每一帧的中间执行,也可能在每一帧的最后执行。所以会导致即便我们能保障每一帧的总耗时小于16ms,但是执行的时机如果在每一帧的中间或最后,最后的结果依然是没有办法每隔16ms让屏幕产生一次变化,也就是说,即便我们能保证每一帧总体时间小于16ms,但如果使用定时器触发动画,那么由于定时器的触发时机不确定,所以还是会导致动画丢帧。现在整个Web只有一个API可以解决这个问题,那就是requestAnimationFrame,它可以保证回调函数稳定的在每一帧最开始触发。
      • 避免 FSL

        • 先执行 JS,然后在 JS中修改了样式从而导致样式计算,然后样式的改动触发了布局、绘制、合成。但 JavaScript可以强制浏览器将布局提前执行,这就叫 强制同步布局 FSL

                    //读取offsetWidth的值会导致重绘
           const newWidth = container.offsetWidth;
             
            //设置width的值会导致重排,但是for循环内部
            代码执行速度极快,当上面的查询操作导致的重绘
            还没有完成,下面的代码又会导致重排,而且这个重
            排会强制结束上面的重绘,直接重排,这样对性能影响
            非常大。所以我们一般会在循环外部定义一个变量,这里
            面使用变量代替container.offsetWidth;
           boxes[i].style.width = newWidth + 'px';
          }
      • 使用 transform属性去操作动画,这个属性是由合成器单独处理的,所以使用这个属性可以避免布局与绘制。
      • 使用 translateZ(0)开启图层,减少重绘重排。特别在移动端,尽量使用 transform代替 absolute。创建图层的最佳方式是使用will-change,但某些不支持这个属性的浏览器可以使用3D 变形(transform: translateZ(0))来强制创建一个新层。
      • 有兴趣的可以看看这篇文字 前端页面优化
      • 样式的切换最好提前定义好 class,通过 class的切换批量修改样式,避免多次重绘重排
      • 可以先切换 display:none再修改样式
      • 多次的 append 操作可以先插入到一个新生成的元素中,再一次性插入到页面中。
      • 代码复用,函数柯里化,封装高阶函数,将多次复用代码封装成普通函数(俗称方法), React中封装成高阶组件, ES6中可以使用继承, TypeScript中接口继承,类继承,接口合并,类合并。
      • 强力推荐阅读: 阮一峰ES6教程
      • 以及 什么是TypeScript以及入门
    以上都是根据本人的知识点总结得出,后期还会有 React的性能优化方案等出来,路过点个赞收藏收藏~,欢迎提出问题补充~

    相关 [前端 性能优化 完全] 推荐:

    前端性能优化不完全手册

    - - SegmentFault 最新的文章
    性能优化是一门大学问,本文仅对个人一些积累知识的阐述,欢迎下面补充. 抛出一个问题,从输入 url地址栏到所有内容显示到界面上做了哪些事. DNS 服务器请求解析该 URL 中的域名所对应的. 2.建立 TCP连接(三次握手);. 3.浏览器发出读取文件( URL 中域名后面部分对应的文件)的 HTTP 请求,该请求报文作为.

    前端性能优化

    - - JavaScript - Web前端 - ITeye博客
      在浏览器(客户端)和服务器发生通信时,就已经消耗了大量的时间,尤其是在网络情况比较糟糕的时候,这个问题尤其的突出.   一个正常HTTP请求的流程简述:如在浏览器中输入" www.xxxxxx.com"并按下回车,浏览器再与这个URL指向的服务器建立连接,然后浏览器才能向服务器发送请求信息,服务器在接受到请求的信息后再返回相应的信息,浏览器接收到来自服务器的应答信息后,对这些数据解释执行.

    前端工程与性能优化

    - - FEX 百度 Web 前端研发部
    每个参与过开发企业级 web 应用的前端工程师或许都曾思考过前端性能优化方面的问题. 我们有雅虎 14 条性能优化原则,还有两本很经典的性能优化指导书:《高性能网站建设指南》、《高性能网站建设进阶指南》. 经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来. 这些性能优化原则大概是在 7 年前提出的,对于 web 性能优化至今都有非常重要的指导意义.

    web前端性能优化进阶路

    - - 阿里巴巴(中国站)用户体验设计部博客
    Web前端性能优化WPO,相信大多数前端同学都不会陌生,在各自所负责的站点页面中,也都会或多或少的有过一定的技术实践. 可以说,这个领域并不缺乏成熟技术理论和技术牛人:例如Yahoo的web站点性能优化 黄金法则,以及大名鼎鼎的优化大师 Steve Souders. 本文并非一篇讨论性能优化技术方法的文章,而更多的是对中文站 搜索List页面持续两年多的前端性能优化实践的 思路总结.

    前端性能优化最佳实践

    - - Web前端 - ITeye博客
    如今浏览器能够实现的特性越来越多,并且网络逐渐向移动设备转移,使我们的前端代码更加紧凑,如何优化,就变得越来越重要了. 开发人员普遍会将他们的代码习惯优先于用户体验. 但是很多很小的改变可以让用户体验有个飞跃提升,所以任何一点儿小小的优化都会提升你网站的性能. 前端给力的地方是可以有许多种简单的策略和代码习惯让我们可以保证最理想的前端性能.

    前端性能优化指南

    - - SegmentFault 最新的文章
    XMLHttpRequest时,而URL长度不到. GET类型请求只需要发送一个. CSS等静态文件放在静态资源服务器上并配置单独域名,客户端请求静态文件的时候,减少. COOKIE反复传输时对主域名的影响. cloneNode在外部更新节点然后再通过. replace与原始节点互换. >多个节点插入操作,即使在外面设置节点的元素和风格再插入,由于多个节点还是会引发多次reflow.

    前端性能优化小纪 -

    - - IT瘾-geek
    天下武功,无坚不破,唯快不破. 对前端而言,快意味着要求资源体量更小、数量更精简、内容更早呈现、交互更加人性化. 当项目做到一定程度,就应该考虑性能的问题,前端的性能优化有诸多有迹可循的理论和方法,比如 Yahoo. 性能军规、Google PageSpeed Insights Rules. 我们团队一个比较老的项目首屏加载大概需要20多秒,这严重影响了用户体验,于是进行了一次首屏加载的性能优化.

    前端重构实践(一) —— 性能优化

    - - 搜索研发部官方博客
           最近一直在做性能优化和模块化改造的工作,并完成了一次前端重构. 在这里总结出一些经验和得失来帮助大家思考. 共两篇文章,第一篇讨论性能优化,第二篇讨论模块化框架. 而之所以把这两个话题放到一起,是因为这两项工作都涉及到对前端代码进行不同程度的重构,而且模块化改造其实是我们在对性能优化做到一定程度之后发现必须要做的一件事情.

    前端性能优化的14个规则

    - - Web前端 - ITeye博客
    作为一个半前端工程师,而且只会写点HTML5和CSS3的“假”前端工程师,为了能更好地理解一下前端的花花世界,最近拜读了《高性能网站建设指南》一书,对作者提出的前端性能优化的14个规则获益匪浅,为了让自己印象更深刻点,决定作此文,当做学习笔记也好,知识总结也罢,总归看过的东西要让自己很好地掌握很好地运用起来才是王道.

    移动H5前端性能优化指南

    - - 腾讯ISUX - 社交用户体验设计 - Better Experience Through Design
    移动H5前端性能优化指南[托尼托尼研究所]. PC优化手段在Mobile侧同样适用. 在Mobile侧我们提出三秒种渲染完成首屏指标. 基于第二点,首屏加载3秒完成或使用Loading. 基于联通3G网络平均338KB/s(2.71Mb/s),所以首屏资源不应超过1014KB. Mobile侧因手机配置原因,除加载外渲染速度也是优化重点.