关于 WEB 性能优化
本文讨论的是前后端分离模式下前端网页的性能优化,追求更快的响应速度,更少的网络资源,内存资源,算力资源的消耗。我将目前了解的相关内容整理至此,尚未完善,随时补充。
#
DNS 解析阶段dns-prefetch
可帮助开发人员降低 DNS 解析延迟。 HTML <link>
元素 通过 dns-prefetch
的 rel
属性值提供此功能。然后在 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
可以实现静态资源实时压缩,此模块是默认集成的。
gzip on # 开启 gzip
#
内容缓存proxy-cache
可以将用户的请缓存到本地一个目录,当下一个请求时可以直接调取缓存文件,就不用去后端服务器去取文件了。
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
(纯组件) 通过props
和states
的浅对比来自动实现shouldComponentUpate()
, 其他特征与React.Component
表现一致。此外, 可以通过高阶函数React.memo
(React16.6 新增) 将函数式组件包装为纯组件 - 由于 tree diff 算法特性, Virtual DOM 层级结构变化会重新创建对应节点, 所以保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是移除或添加 DOM 节点。
- 尽可能避免 DOM 类型变更, component diff 算法会将类型改变的 DOM 重新创建
- 为统一层级不同节点添加静态
key
以优化个别元素的添加/删除/移动操作
#
Vue 相关优化- 其 diff 算法与 React 类似,相关优化方案可以适用。
- TODO...