关于 WEB 性能优化

· 2 分钟阅读

本文讨论的是前后端分离模式下前端网页的性能优化,追求更快的响应速度,更少的网络资源,内存资源,算力资源的消耗。我将目前了解的相关内容整理至此,尚未完善,随时补充。

DNS 解析阶段#

dns-prefetch 可帮助开发人员降低 DNS 解析延迟。 HTML <link> 元素 通过 dns-prefetchrel 属性值提供此功能。然后在 href 属性中指要跨域的域名, 例子:

<html>  <head>    <link rel="dns-prefetch" href="https://fonts.gstatic.com/" />    <!-- and all other head elements -->  </head>  <body>    <!-- your page content -->  </body></html>
注意

虽然使用 dns-prefetch 能够加快页面的解析速度,但是也不能滥用,因为这会导致 DNS 解析次数飙升,对于按量计费的 DNS 服务来说,这意味着更高的经济成本。可以使用 <meta http-equiv="x-dns-prefetch-control" content="off"> 来禁止隐式的 dns-prefetch

TCP、HTTP 阶段#

可以通过在服务端调整 TCP 相关报文参数来优化连接速度。此外 TLS1.3 可以简化握手流程,提升安全性的同时优化了响应速度。

静态资源服务器#

实时无损压缩#

Nginx 模块 ngx_http_gzip_module 可以实现静态资源实时压缩,此模块是默认集成的。

nginx.conf
gzip on  # 开启 gzip

内容缓存#

proxy-cache 可以将用户的请缓存到本地一个目录,当下一个请求时可以直接调取缓存文件,就不用去后端服务器去取文件了。

nginx.conf
location~ .*\.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*) {    proxy_pass http://appserver ;    proxy_redirect off;    proxy_set_header Host $host;    proxy_cache cache_one;    proxy_cache_valid 200 302 1h;    proxy_cache_valid 301 1d;    proxy_cache_valid any 1m;    expires 30d;}

浏览器渲染阶段#

浏览器在获得各种静态资源后,解析代码并渲染 DOM、运行 V8 进程。

资源加载顺序#

通常情况下,将 css 资源放在 html 头部 head,优先加载样式,可以避免页面错乱。将 js 资源放在 body 最底部,以保证 js 文件过大时优先渲染出基本内容。

这是 jquery 时代的技巧,在工程化模式下,依然适用。

按需加载#

V8 引擎在解析 AST 抽象语法树时需要消耗大量时间,所以对于首屏加载速度来说,减少代码量是最有效的性能提升方法。

借助 webpack 等打包工具,将各路由下独立不共享的代码拆分打包到不同的文件中,在路由激活时请求加载当前路由需要用到的资源,是为按需加载,又称懒加载。

如果是外部资源,可以通过动态插入 script 标签的方式按需加载。查看示例代码(TS)

预加载#

预加载是一个声明性提取请求,要求浏览器尽快请求资源。通过在 HTML 的 head 中添加标记 <link> rel="preload" 来预加载关键资源:

<head>  <link rel="preload" href="style.css" as="style" />  <link rel="preload" href="main.js" as="script" /></head>

预加载的 href 可以是 js , 文件在随后的页面渲染中,一旦需要使用它们,它们就会立即可用。
结合 按需加载,可以将与当前页面有关联的路由的相关联的资源放入预加载队列中。

JS 代码层面#

算法#

没有什么比高效能的算法更能优化性能的了。

事件委托#

又称事件代理,指通过事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

示例
element.addEventListener(  'click',  (e) => {    if (e.target.tagName === 'P') {      alert('ok')    }  },  true)

以上代码实现在 element 的所有子元素中的 p 标签上点击,都会弹出 ok

  • 事件委托本身在运行时性能消耗上无影响 怎么说
  • 在一些 MVC 框架中涉及到频繁的重渲染时,事件委托具有很大优势
  • 在数据量较大时,使用事件委托可以节约内存占用
  • 对于 Chromium 内核的现代浏览器来说,在数据量小(小于 1000 条)时没有必要使用事件委托
  • 缺点:层级复杂的元素代理繁琐,容易误判

立即执行函数#

对于立即执行函数来说, 括号方案比其他方案具有更好的性能优势

;(function () {  console.log('Hello, World!')})()

以上代码会在 AST 分析阶段立即解析
使用括号封装函数。对于解析器来说,这几乎总是一个积极的信号,即函数需要立即执行。如果解析器看到一个左括号,紧接着是一个函数声明,它将立即解析这个函数

// 这个函数完全可以优化成立即执行函数的形式。function foo() {  console.log('Hello, World!')}
foo()

以上代码不会在 AST 分析阶段立即解析, 先执行懒解析, 然后在执行立即解析, 这和立即解析相比,运行速度会慢 50%。

节流与防抖#

React 相关优化#

  • 使用 shouldComponentUpdate 通知 React 当前组件是否需要参与 Diff (component diff 环节)
  • 继承 React.PureComponent (纯组件) 通过 propsstates 的浅对比来自动实现 shouldComponentUpate(), 其他特征与 React.Component 表现一致。此外, 可以通过高阶函数 React.memo (React16.6 新增) 将函数式组件包装为纯组件
  • 由于 tree diff 算法特性, Virtual DOM 层级结构变化会重新创建对应节点, 所以保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是移除或添加 DOM 节点。
  • 尽可能避免 DOM 类型变更, component diff 算法会将类型改变的 DOM 重新创建
  • 为统一层级不同节点添加静态 key 以优化个别元素的添加/删除/移动操作

Vue 相关优化#

  • 其 diff 算法与 React 类似,相关优化方案可以适用。
  • TODO...

参考#