前端优化
前端性能衡量指标
- 白屏时间 该时间点表示浏览器开始绘制页面,在此之前页面都是白屏,也称为开始渲染时间
- 首屏时间 该时间点表示用户看到第一屏页面的时间
- 用户可交互时间 也叫 DOM Ready,该时间点表示 DOM 解析完成,资源还没有完成,这个时候用户与页面可以交互了
- 完全加载时间 该时间点是 window.onload 时间触发的时间,表示原始文档和所用引用的内容已经加载完成,用户最明显的感觉就是浏览器 tab 上 loading 状态结束
- 首字节时间(TTFB) 第一字节响应时间(TTFB)=发送请求到 WEB 服务器的时间+WEB 服务器处理请求并生成响应花费的时间+WEB 服务器生成响应到浏览器花费的时间
- DNS 解析时间
- TCP 连接时间
- HTTP 请求时间
- HTTP 响应时间
优化原则
雅虎 14 条优化原则:
尽可能的减少 HTTP 的请求数 content
使用 CDN(Content Delivery Network) server
添加 Expires 头(或者 Cache-control ) server
Gzip 组件 server
将 CSS 样式放在页面的上方 css
将脚本移动到底部(包括内联的) javascript
避免使用 CSS 中的 Expressions css
将 JavaScript 和 CSS 独立成外部文件 javascript css
减少 DNS 查询 content
压缩 JavaScript 和 CSS (包括内联的) javascript css
避免重定向 server
移除重复的脚本 javascript
配置实体标签(ETags) css
使 AJAX 缓存
优化方向 | 优化手段 |
---|---|
请求数量 | 合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域,字体图标,雪碧图片等 |
请求带宽 | 开启服务器 GZip,精简 JavaScript,移除重复脚本,图像优化(包括图片大小 kb) |
缓存利用 | 使用 CDN,使用外部 JavaScript 和 CSS,添加 Expires 头,减少 DNS 查找,配置 ETag,使 AjaX 可缓存 |
页面结构 | 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 |
代码校验 | 避免 CSS 表达式,避免重定向 |
网络相关
DNS 预解析
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
1 | <link rel="dns-prefetch" href="//yuchengkai.cn" /> |
利用缓存
缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度。
通常浏览器缓存策略分为两种:强缓存和协商缓存
选择合适的缓存策略
对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略
- 对于某些不需要缓存的资源,可以使用
Cache-control: no-store
,表示该资源不需要缓存 - 对于频繁变动的资源,可以使用
Cache-Control: no-cache
并配合ETag
使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。 - 对于代码文件来说,通常使用
Cache-Control: max-age=31536000
并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件。
使用 HTTP / 2.0
因为浏览器会有并发请求限制,在 HTTP / 1.1 时代,每个请求都需要建立和断开,消耗了好几个 RTT 时间,并且由于 TCP 慢启动的原因,加载体积大的文件会需要更多的时间。
在 HTTP / 2.0 中引入了多路复用,能够让多个请求使用同一个 TCP 链接,极大的加快了网页的加载速度。并且还支持 Header 压缩,进一步的减少了请求的数据大小。
预加载&懒加载
预加载
在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。
预加载其实是声明式的 fetch
,强制浏览器请求资源,并且不会阻塞 onload
事件,可以使用以下代码开启预加载
1 | <link rel="preload" href="http://example.com" /> |
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。
预渲染
可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
1 | <link rel="prerender" href="http://example.com" /> |
预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染。
懒执行
懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。
懒加载
懒加载就是将不关键的资源延后加载。
懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src
属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src
属性,这样图片就会去下载资源,实现了图片懒加载。
懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。
文件优化
图片优化
图片大小计算
在实际项目中,一张图片可能并不需要使用那么多颜色去显示,我们可以通过减少每个像素的调色板来相应缩小图片的大小。
了解了如何计算图片大小的知识,那么对于如何优化图片,想必大家已经有 2 个思路了:
- 减少像素点
- 减少每个像素点能够显示的颜色
图片加载优化
- 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
- 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
- 小图使用 base64 格式
- 将多个图标文件整合到一张图片中(雪碧图)
- 选择正确的图片格式:
- 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
- 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
- 照片使用 JPEG
其他文件优化
- CSS 文件放在
head
中 - 服务端开启文件压缩功能
- 将
script
标签放在body
底部,因为 JS 文件执行会阻塞渲染。当然也可以把script
标签放在任意位置然后加上defer
,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。对于没有任何依赖的 JS 文件可以加上async
,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。 - 执行 JS 代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑使用
Webworker
。Webworker
可以让我们另开一个线程执行脚本而不影响渲染。
CDN
CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。—— 科学百科
CDN 存在的意义
为了不让网络拥塞成为互联网发展的障碍。
CDN 的优势
- CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源站的负载;
- 降低“广播风暴”的影响,提高网络访问的稳定性;节省骨干网带宽,减少带宽需求量。
CDN 的核心点
缓存:将从根服务器请求来的资源按要求缓存。
回源:当有用户访问某个资源的时候,如果被解析到的那个 CDN 节点没有缓存响应的内容,或者是缓存已经到期,就会回源站去获取。没有人访问,CDN 节点不会主动去源站请求资源。
关键技术
- 内容发布:它借助于建立索引、缓存、流分裂、组播(Multicast)等技术,将内容发布或投递到距离用户最近的远程服务点(POP)处;
- 内容路由:它是整体性的网络负载均衡技术,通过内容路由器中的重定向(DNS)机制,在多个远程 POP 上均衡用户的请求,以使用户请求得到最近内容源的响应;
- 内容交换:它根据内容的可用性、服务器的可用性以及用户的背景,在 POP 的缓存服务器上,利用应用层交换、流分裂、重定向(ICP、WCCP)等技术,智能地平衡负载流量;
- 性能管理:它通过内部和外部监控系统,获取网络部件的状况信息,测量内容发布的端到端性能(如包丢失、延时、平均带宽、启动时间、帧速率等),保证网络处于最佳的运行状态。
CDN & 静态资源
静态资源本身具有访问频率高、承接流量大的特点,因此静态资源加载速度始终是前端性能的一个非常关键的指标。CDN 是静态资源提速的重要手段。
静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。
其他优化
使用 Webpack 优化项目
- 对于 Webpack4,打包项目使用 production 模式,这样会自动开启代码压缩
- 使用 ES6 模块来开启 tree shaking,这个技术可以移除没有使用的代码
- 优化图片,对于小图可以使用 base64 的方式写入文件中
- 按照路由拆分代码,实现按需加载
- 给打包出来的文件名添加哈希,实现浏览器缓存文件
监控
对于代码运行错误,通常的办法是使用 window.onerror
拦截报错。该方法能拦截到大部分的详细报错信息,但是也有例外
- 对于跨域的代码运行错误会显示
Script error.
对于这种情况我们需要给script
标签添加crossorigin
属性 - 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过
arguments.callee.caller
来做栈递归
对于异步代码来说,可以使用 catch
的方式捕获错误。比如 Promise
可以直接使用 catch
函数,async await
可以使用 try catch
但是要注意线上运行的代码都是压缩过的,需要在打包时生成 sourceMap 文件便于 debug。
对于捕获的错误需要上传给服务器,通常可以通过 img
标签的 src
发起一个请求。
如何优化第二次加载速度?(增量加载)
- 降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。
- 加快请求速度:预解析 DNS,减少域名数,并行加载,CDN 分发。
- 缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。
- 渲染:JS/CSS 优化,加载顺序,服务端渲染,pipeline。
其中对首屏启动速度影响最大的就是网络请求,所以优化的重点就是缓存
增量式更新”的解决方案,简单地说就是在版本更新的时候不需要重新加载资源,只需要加载一段很小的 diff 信息,然后合并到当前资源上,类似 git merge 的效果。
增量加载的一种思路:
1、用户端使用 LocalStorage 或者其它储存方案,存储一份原始代码+时间戳:
1 | { |
2、每次加载资源的时候向服务器发送这个时间戳;
3、服务器从接受到时间戳中识别出客户端的版本,和最新的版本做一次 diff,返回两者的 diff 信息:
1 | diff("aaabbbccc", "aaagggccc"); |
4、客户端接收到这个 diff 信息之后,把本地资源和时间戳更新到最新,实现一次增量更新:
1 | mergeDiff("aaabbbccc", [3, "-3", "+ggg", 3]); |
面试题(如何渲染几万条数据并不卡住界)
这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame
来每 16 ms 刷新一次。
1 |
|
拓展知识之 Gzip
基本介绍
gzip 是 GNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE 等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持 gzip。
gzip 压缩比率在 3 到 10 倍左右,可以大大节省服务器的网络带宽。而在实际应用中,并不是对所有文件进行压缩,通常只是压缩静态文件。
gzip 工作原理:
- 浏览器请求 url,并在 request header 中设置属性 accept-encoding:gzip。表明浏览器支持 gzip。
- 服务器收到浏览器发送的请求之后,判断浏览器是否支持 gzip,如果支持 gzip,则向浏览器传送压缩过的内容,不支持则向浏览器发送未经压缩的内容。一般情况下,浏览器和服务器都支持 gzip,response headers 返回包含 content-encoding:gzip。
- 浏览器接收到服务器的响应之后判断内容是否被压缩,如果被压缩则解压缩显示页面内容。
Nginx 中开启 gzip
如果服务端接口使用 nodejs 和 express,那么开启 nginx 非常简单。启用 compress() 中间件即可并在 nginx.conf 中添加 gzip 配置项即可,express.compress() gzip 压缩中间件,通过 filter 函数设置需要压缩的文件类型。压缩算法为 gzip/deflate。这个中间件应该放置在所有的中间件最前面以保证所有的返回都是被压缩的。如果使用 java 开发,需要配置 filter。
添加完参数后,运行 nginx –t 检查一下语法,若语法检测通过,则开始访问 url 检测 gzip 是否添加成功。
- gzip on:开启 gzip。
- gzip_comp_level:gzip 压缩比。
- gzip_min_length:允许被压缩的页面最小字节数。
- gzip_types:匹配 MIME 类型进行压缩,text/html 默认被压缩。
检测 gzip 是否开启
如果没有现成的项目代码,这里提供一个比较简单的检测方式。首先在本地安装 nginx,在 nginx 默认目录下面添加了两个静态文件 bootstrap.css、bootstrap.js。
OS X 系统的默认路径为:/usr/local/Cellar/nginx/1.10.2_1/html,Windows 系统直接复制文件到文件夹下面。
拷贝文件指令可参考:cp -r bootstrap.js /usr/local/Cellar/nginx/1.10.2_1/html,在 nginx 的默认成功跳转页面 index.html 引入这两个静态文件。index.html 页面内容如图所示。
做好这一切的准备工作之后,浏览器输入http://localhost:8080/。出现如图所示页面表明 nginx 启动成功。
nginx 启动成功界面。
此时打开 Chrome 控制台,可以看到 network 信息,response headers 中返回了 content-encoding:gzip,表明 gzip 开启成功。
gzip 未开启前 network 信息如图所示:
开启后返回 network 信息如图所示:
url 请求的 headers 报文如图所示:
对比以上三图可以看出 gzip 压缩效率非常高,且经过压缩后静态文件大小不到原来的五分之一。这里值得一提的是静态资源文件越大,gzip 的压缩效率越高。所以对于静态资源量非常大的网站,开启 gzip 可节省大量流量,而同时 gzip 的应用远不止提高 web 性能,Android,IOS 底层网络请求同样可用。