前端性能优化

897 阅读6分钟

  前端性能优化,总结为两大部分,一是文件获取优化,二是代码执行优化。

从输入URL到页面加载完成,发生了什么?

  1. 输入网址(域名)。
  2. 浏览器通过DNS服务器,把域名解析为IP地址。
  3. 和服务器的IP地址建立TCP链接,发送HTTP请求。
  4. 服务器接收请求,查数据库,读文件等,拼接好一条HTTP响应到客户端。
  5. 浏览器收到首屏html,开始渲染。
  6. 解析html为dom树,在这一步浏览器执行了所有加载解析逻辑,发出了页面渲染所需的各种外部资源。
  7. 解析CSS为CSS-tree。
  8. DOM+CSS生成渲染树,绘制图层,整合页面。
  9. 加载script中的js文件,执行js。
  10. Ps:以上是从前端的角度来说的。

   性能优化,就是上面的步骤加在一起,时间尽可能的短。

文件获取优化

  前三条中涉及到网络协议:一条http请求IP寻址到服务器的位置。通过网线,经过路由器、交换机和运营商这么一条漫长的路线,TCP来确保数据的完整有序,再到更上一层网络协议http,它作为网页传输协议,是客户端和浏览器聊天的载体。

  这个过程涉及到DNS的解析,TCP的三次握手、分包、重发,路由跳转等等的步骤,它是非常耗能的,在这个阶段我们需要考虑的就是如何优化整个文件获取的过程。

  1. 长连接:HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP链接不断开,等待同域名下继续用这个通道传输数据,节省了TCP三次握手的时间。
  2. DNS预加载:prefetch预获取当前页面所用到的所有域名,在解析DOM时进行DNS预解析。
  3. 减少文件体积:文件压缩打包(gzip)、引入模块的按需加载、webpack中的tree-shaking、去除无效代码、一些图片优化(jpg、png、svg)。
  4. 减少文件请求次数:合理利用缓存、文件的懒加载、文件合并。
  5. 减少用户和服务器的距离:CDN,将静态资源缓存到离用户很近的相同网络运营商的CDN节点上。
  6. 本地存储:cookie、localstorage和sessionStorage。

浏览器缓存机制


Ps:此图为网络上找的
  浏览器会主动帮我们缓存文件,尽量的不要发起一请求,浏览器会先根据服务器提供的过期时间进行强制缓存,过了这个时间,浏览器再进行对比缓存,根据对比文件是否相同来由服务端决定是否更新文件缓存。

  浏览器的缓存机制,通过JS是无法操控的,我们能够操控的是如何利用缓存,这就引出了一个问题,如何上线前端代码?缓存时间过长,发布上线一个新版本,用户可能还在用有bug的老版本。缓存时间过短,重复加载文件太多浪费带宽。

  前端需要加载的资源有:模板html,静态资源css、js、image、video、audio等。首先模板是不能被缓存的(no-cache),它是项目的入口文件,一旦被缓存,发布上线的新代码,就无法被加载了。

  文件加hash值(高效利用缓存):形如a.hash.js,hash是整个a.js文件的md5值,文件内容不变,hash值不变。文件内容变,产生一个新文件,缓存随即失效。这样的机制使我们能够将所有可以缓存的静态资源的缓存时间设置的很长。webpack中运用了这种机制,也是我们现在上线代码的机制。这既是高效利用缓存的一个实例。

代码执行优化

  1. 节流和防抖:避免函数执行次数太多。
  2. 回流和重绘:虚拟DOM,js模拟真实DOM。Vue和React。
  3. Lazy-load:首屏只加载第一眼所见。
  4. Vue:v-if和v-show、和渲染无关的数据,不要放在data上、nextTick等。
  5. React:只传需要的props、pureComponent、少在render中绑定事件。

节流和防抖

  节流:某段时间内只触发一次。

  使用场景:一些浏览器事件:scroll,mousemove等,触发的频率非常高,你需要通过它们的触发事件做一些事情时,如果不做处理,会造成浏览器的性能问题。


  防抖:一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。
  应用场景:向后台发送数据,用户频繁触发,会对服务器造成压力。比如每次文本输入需要后台验证时,只在用户输入完毕之后发送请求。


  其实看redux-saga的源码会发现再io-helper中提供了节流和防抖的函数,例如takeLatest,就有点防抖的意思,监听到action创建一个task,如果监听到新的,取消前一个task。


  所以说节流和防抖的用处还是很多且比较重要的。

回流和重绘

  为什么操作DOM会非常耗能,因为一个DOM有几百个属性。回流:对DOM的修改引发了几何尺寸的变化。重绘:对DOM的修改引发了样式的变化。

虚拟DOM

  虚拟DOM是JS按照DOM结构来实现的树形结构对象。它可以优化代码执行性能。

  1. 页面初次渲染时,用数据+模板生成虚拟DOM,再用虚拟DOM来生成真实DOM。
  2. State发生变化。
  3. 数据+模板生成新的虚拟DOM(这一步极大的提升了性能,就算用DocumentFragment也会生成真实DOM,只是不直接替换原始的DOM而已。)
  4. 比较新的虚拟DOM和原始虚拟DOM的区别(DOM diff),找出变化的节点。
  5. 直接操作真实DOM,只改变找出的节点。(patch打补丁)
Vue中的虚拟DOM

  其实在Vue1.0中,使用defineProperty进行了数据的双向绑定,给每个数据都绑定了一个监听器Watcher,实际上是可以知道具体哪个数据发生变化,然后直接去修改变化了的数据对应的真实DOM节点。

  但虚拟DOM是我们通过Diff算法才得知哪个数据修改了,再去修改真实DOM。

  既然Vue1.0已经可以用defineProperty来获取具体哪个数据变化的信息,为什么还要在Vue2.0中引入虚拟DOM呢?

  Vue1.0的问题是,每个数据都有监听器,使得在项目变大后,Wacther越来越多,性能越来越差。于是Vue2.0做出这种方案,只将Wacther绑定在组件层级上,我们通过defineProperty只会知道哪个组件变化了,具体到数据变化需要用虚拟DOM的diff算法来做。

Vue的DOM diff算法对比React的DOM diff算法
  1. Vue2.0的dom diff对比react的dom diff算法做了一些优化。
  2. Vue中的oldVnode和Vnode会有头尾两个指针,先做四种匹配,头头,尾尾,头尾和尾头。
  3. React只有oldVnode和Vnode只会有一个指针,从左向右依次进行对比。 像右边这种情况,Vue中oldVnode的尾指针和Vnode的头指针匹配,直接在真实DOM将最后一个节点移到第一个节点。
  4. React中则直接比较key,会把A,B,C也移动到后面。


Ps:图片来自网络

参考

开课吧课程,强烈推荐