1 Network
1.1waterfall

Queueing:将请求资源放入队列,按照优先级顺序依次下载
Stalled:因放入队列时,导致停滞的时间
DNS LookUP: DNS域名解析时间
Initial connection:开始建立网络连接,建立TCP连接的时间,包括TCP的三次握手
SSL:HTTPS加密,浏览器和服务器建立安全连接的时间
waiting TTFB:TTFB 是 Time to First Byte 的缩写,指的是浏览器开始收到服务器响应数据的时间(后台处理时间+重定向时间),是反映服务端响应速度的重要指标。TTFB 时间都在 50 ms 以下,这个时间就是我们优化时候可以追求的时间。TTFB 时间如果超过了 500 ms,用户在打开网页的时候就会感觉到明显的等待。
Content DownLoad :浏览器下载资源的时间
1.2lighthouse工具的使用
1.3frame帧频
ctrl+shift+p,输入frame可以显示页面刷新频率
2 RAIL
R:Response响应,处理事件应在50ms内完成
A:Animation动画, 每10ms产生一帧
I: Idle空闲,尽可能增加空闲时间,一次处理异步时间不应超过50ms
L: Load加载提示,在5s内完成加载并可以交互
3 性能测试工具
1.1 webpagetest
Details 详细的细节数据
Waterfall View


重要指标:主线程的空闲时间(Brower Main Thread)和页面可交互时间(Page is Interactive)
1.2本地搭建webpagetest
1.2.1安装 WPT Agent 和 WPT Server
Docker基本环境配置好后,接下来需要安装 WPT 的包了。WPT 的软件包分为 Agent 和 Server 两个部分,对应:
- https://hub.docker.com/r/webpagetest/agent/
- https://hub.docker.com/r/webpagetest/server/
运行 WPT Server$ docker pull webpagetest/server$ docker pull webpagetest/agent
运行 WPT Agent$ docker run -d -p 4000:80 --rm webpagetest/server
运行上述步骤后,直接访问 http://localhost:4000 即可看到docker run -d -p 4001:80 --network="host" -e "SERVER_URL=http://localhost:4000/work/" -e "LOCATION=Test" webpagetest/agent
依赖检查
WPT 已经安装好了,WPT的对应配置检查可以通过:http://localhost:4000/install 来看它的依赖是否都安装。
Mac 下 Traffic Shaping问题
OSX 下会遇到Error configuring traffic-shaping报错,这是因为OSX下还没有实现 traffic-shaping
可以去掉traffic shaping特性通过在settings/locations.ini设置一个假的connectivity值,
并且在agent运行的时候增加—shaper参数。
更好的办法是基于原有的WPT agent/server镜像制作新的Docker镜像,方便后续搭建Docker集群。
1.2.2创建自己的webpagetest镜像
Server
创建一个server文件夹,包含Dockerfile和locations.ini文件。
Dockerfile:
FROM webpagetest/serverADD locations.ini /var/www/html/settings/
locations.ini:
[locations]1=Test_loc[Test_loc]1=Testlabel=Test Locationgroup=Desktop[Test]browser=Chrome,Firefoxlabel="Test Location"connectivity=LAN
本地build镜像
$ docker build -t local-wptserver .
Agent
创建一个agent文件夹,包含Dockerfile和script.sh文件。
Dockerfile
FROM webpagetest/agentADD script.sh /ENTRYPOINT /script.sh
script.sh
#!/bin/bashset -eif [ -z "$SERVER_URL" ]; thenecho >&2 'SERVER_URL not set'exit 1fiif [ -z "$LOCATION" ]; thenecho >&2 'LOCATION not set'exit 1fiEXTRA_ARGS=""if [ -n "$NAME" ]; thenEXTRA_ARGS="$EXTRA_ARGS --name $NAME"fipython /wptagent/wptagent.py --server $SERVER_URL --location $LOCATION $EXTRA_ARGS --xvfb --dockerized -vvvvv --shaper none
让 script.sh 可执行
chmod u+x script.sh
制作Agent镜像
$ docker build -t local-wptagent .
开始运行一个Webpagetest Docker实例
docker run -d -p 4000:80 local-wptserverdocker run -d -p 4001:80 --network="host" -e "SERVER_URL=http://localhost:4000/work/" -e "LOCATION=Test" local-wptagent
最后访问 http://127.0.0.1:4000 即可查看到效果。
1.3lighthouse
使用npm本地安装
npm install -g lighthouse
或者在Chrome中直接使用
会生成一份网站的各项性能报告,可以根据报告对网站进行优化。
1.4 Chrome Devtools
1.4.1 查看文件的size,是否开启压缩
1.4.2 performance
根据生成线程执行报告,查看主线程中执行的任务
| 关键时间节点 | 描述 | 含义 |
|---|---|---|
| TTFB | time to first byte(首字节的时间) | 从请求到数据返回第一个字节所消耗的时间 |
| TTI | Time to Interactive(可交互时间) | DOM树构建完成,可以执行事件 |
| DCL | DOMContentLoaded(事件消耗时间) | 当HTML文档被完全加载和解析完成后,DOMContentLoaded事件被触发 |
| L | onLoad事件耗时 | 当依赖资源全部加载完毕之后才触发 |
| FP | First Paint 首次绘制 | 第一个像素点绘制到屏幕的时间 |
| FCP | First Contentful Paint 首次内容绘制 | 首次绘制文本,图片,非空白节点的时间 |
| FMP | First Meaningful paint首次有意义绘制 | 首次有意义绘制,页面可用性的度量标准 |
| LCP | Largest Contentful Paint最大内容绘制 | 在viewport中页面最大内容元素加载时间 |
| FID | First Input Delay 首次输入延迟 | 用户首次和页面交互(点击链接或者点击按钮)到页面响应交互的时间 |
4 WEB API
4.1计算 DOMContentLoaded 时间
window.addEventListener('DOMContentLoaded', (event) => {let timing = performance.getEntriesByType('navigation')[0];console.log(timing.domInteractive);console.log(timing.fetchStart);let diff = timing.domInteractive - timing.fetchStart;console.log("TTI: " + diff);})
4.2观察长任务(performance 中 Task)
const observer = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {console.log(entry)}})observer.observe({entryTypes: ['longtask']})
4.3监听视窗激活状态
// 窗口激活状态监听let vEvent = 'visibilitychange';if (document.webkitHidden != undefined) {vEvent = 'webkitvisibilitychange';}function visibilityChanged() {if (document.hidden || document.webkitHidden) {document.title = '客官,别走啊~'console.log("Web page is hidden.")} else {document.title = '客官,你又回来了呢~'console.log("Web page is visible.")}}document.addEventListener(vEvent, visibilityChanged, false);
4.4监听网络变化
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;var type = connection.effectiveType;function updateConnectionStatus() {console.log("Connection type changed from " + type + " to " + connection.effectiveType);type = connection.effectiveType;}connection.addEventListener('change', updateConnectionStatus);
4.5更多计算规则
通过在控制台输入performance.timing,可以查看到左右的时间节点
DNS 解析耗时: domainLookupEnd - domainLookupStartTCP 连接耗时: connectEnd - connectStartSSL 安全连接耗时: connectEnd - secureConnectionStart网络请求耗时 (TTFB): responseStart - requestStart数据传输耗时 : responseEnd - responseStartDOM 解析耗时 : domInteractive - responseEnd资源加载耗时 : loadEventStart - domContentLoadedEventEndFirst Byte 时间: responseStart - domainLookupStart白屏时间: responseEnd - fetchStart首次可交互时间 TTI : domInteractive - fetchStartDOM Ready 时间 DCL : domContentLoadEventEnd - fetchStart页面完全加载时间 L: loadEventStart - fetchStarthttp 头部大小 :transferSize - encodedBodySize重定向次数 :performance.navigation.redirectCount重定向耗时 : redirectEnd - redirectStart
5 浏览器渲染路径
浏览器接收到服务器返回的数据,关键渲染页面经历的过程
- JavaScript:脚本及事件处理
- Style:样式合成计算
- Layout:布局,再次布局(回流reflow)只关心元素的大小和位置
- Paint:重绘,颜色、阴影、文字等
- Composite:合成层
Layout和Paint处理不当会消耗大量时间,占用主线程,造成页面卡顿。
5.1减少Layout和Paint
5.1.1影响Layout的因素
- 添加、删除元素
- 操作styles
- 设置display:none
- 设置样式的offsetLeft、offsetTop、scrollTop、scrollLeft、clientWidth、clientHeight等
- 移动元素位置
-
5.1.2避免layout thrashing
let cards = document.getElementsByClassName("MuiPaper-rounded");const update = (timestamp) => {for (let i = 0; i <cards.length; i++) {let top = cards[i].offsetTop;cards[i].style.width = ((Math.sin(cards[i].offsetTop + timestamp / 100 + 1) * 500) + 'px')}window.requestAnimationFrame(update)}update(1000);
以上代码会造成页面多次layout,动画非常卡顿

避免重绘和回流 使用transform、opacity、filters开启硬件加速渲染,
- 使用虚拟dom框架,react、vue、angular
- 动画效果应用到position属性为absolute或fixed的元素(脱离文档流)
- 分离读写,使用fastdom库
- 使用will-change:transform 提取到单独图层

读写分离
使用fastDom进行优化,将对 dom 的读和写分离
let cards = document.getElementsByClassName("MuiPaper-rounded");const update = (timestamp) => {for (let i = 0; i < cards.length; i++) {fastdom.measure(() => {let top = cards[i].offsetTop;fastdom.mutate(() => {cards[i].style.width =Math.sin(top + timestamp / 100 + 1) * 500 + "px";});});}window.requestAnimationFrame(update)}update(1000);
fastdom在线预览:fastdom demo (http://wilsonpage.github.io/fastdom/examples/animation.html)
5.1.3 减少Paint
尽可能通过transform以及opacity设置动画
注意:将需要做动画的层可设置willchange:”transform”,可以创建新的图层。
5.2Composite复合线程
5.3浏览器一帧的生命周期
高频事件处理函数,防抖,一帧内多次触发InputEvents,会造成性能的浪费
5.4 react优化一帧内完成的事件
6 资源压缩优化
- 可以减少http请求次数
- 减少请求资源的大小
6.1 代码优化
6.1.1 html优化
使用html-minifier工具6.1.2 js优化
webpack中使用插件,uglifyjs-webpack-plugin,支持es6替换terser-webpack-plugin6.1.3 css优化
clean-css压缩工具6.2 图片优化
6.2.1 图片格式选择
压缩jpg:imagemin/imagemin
压缩png:imagemin/pngquant6.2.2 图片懒加载
verlok/lazyload
yall.js
原生的图片懒加载6.2.3 响应式、或者渐进式加载图片
给img标签设置sizes、srcset的属性6.3 字体优化
通过font-face引入字体,并设置font-display的属性
7 webpack工具
7.1 tree-shaking
代码要基于es6模块化,才能使用tree-shaking。
babel的配置文件中,要把modules设置为false,才能使用tree-shaking。
把mode设置为production,自动开启tree-shaking功能,内部主要使用terser-webpack-plugin插件。
取消对某些文件设置tree-shaking:在package.json中配置sideEffects。
7.2 依赖优化
7.2.1 noparse不解析一些库
7.2.2 DLLPlugin动态链接库
把经常使用的一些库,不变的库,减少重复打包,直接使用动态链接库。
通过DllPlugin插件,将一些比较大的,基本很少升级的包拆分出来,生成xx.dll.js文件,通过manifest.json引用。
// webpack.dll.config.jsconst path = require("path");const webpack = require("webpack");module.exports = {mode: "production",entry: {react: ["react", "react-dom"],},output: {filename: "[name].dll.js",path: path.resolve(__dirname, "dll"),library: "[name]"},plugins: [new webpack.DllPlugin({name: "[name]",path: path.resolve(__dirname, "dll/[name].manifest.json")})]};
在script中添加执行
"scripts": {"dll-build": "NODE_ENV=production webpack --config webpack.dll.config.js",},
7.2.3 code spliting代码拆分
第一种:多入口拆分
第二种:splitChunks提取公共代码,拆分业务代码和第三方库代码
optimization: {splitChunks: {cacheGroups: {vendor: {name: 'vendor',test: /[\\/]node_modules[\\/]/,minSize: 0,minChunks: 1,priority: 10,chunks: 'initial'},common: {name: 'common',test: /[\\/]src[\\/]/,chunks: 'all',minSize: 0,minChunks: 2}}}},
7.3 资源缓存持久化
- 每次打包后文件有唯一的hash
- 文件修改后,更新hash值
-
7.4 webpack监测与分析工具
stats分析与可视化图
- webpack-bundle-analyzer进行体积分析
speed-measure-webpack-plugin速度分析
01 webpack chart插件
webpack --profile --json > stats.json
02 source-map-explorer
03 bundle-analyzer
8 资源传输过程中优化
开启Gzip压缩
通过nginx开启Gzip压缩
nginx.confgzip on;# 字节多大进行压缩gzip_min_length 1K;# 压缩级别官网建议是6gzip_comp_level 6;# 对哪些格式文件压缩gzip_types text/plain application/javascript application/x-javascript text/css application/xmltext/xml text/javascript application/json;gzip_static on;# 添加到header中gzip_vary on;gzip_buffers 4 16k;gzip_http_version 1.1;
开启keep-alive
可以和服务器的tcp连接进行复用,能够减少连接性能的开销
在nginx中进行设置# 超时时间,超过80秒,断开tcp连接keepalive_timeout 80;# 建立tcp连接后,可开启100次请求keepalive_requests 100;
HTTP缓存
Cache-Control(1.1版本)/Expires(1.0版)
- Etag + If-None-Match
- Last-Modified(只能精确到秒,不准确) + If-Modified-Since


Etag 和 If-None-Match相等,直接拿缓存的资源,如果不相等,重新请求服务器资源。
Service Workers
javaScript 是单线程的,随着web业务的复杂化,开发者逐渐在js中做了许多耗费资源的运算过程,这使得单线程的弊端更加凹显。web worker正是基于此被创造出来,它是脱离在主线程之外的,我们可以将复杂耗费时间的事情交给web worker来做。但是web worker作为一个独立的线程,他的功能应当不仅于此。sw便是在web worker的基础上增加了离线缓存的能力。
- 离线支持访问
- 加速重复访问
- 独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context;
- 能向客户端推送消息;
- 不能直接操作 DOM;
- 出于安全的考虑,必须在 HTTPS 环境下才能工作;
Vue框架下添加service workers
// serviceWorker.jsimport { register } from 'register-service-worker'if (process.env.NODE_ENV === 'production') {register('service-worker.js', {ready () {console.log('App is being served from cache by a service worker.')},registered () {console.log('Service worker has been registered.')},cached () {console.log('Content has been cached for offline use.')},updatefound () {console.log('New content is downloading.')},updated () {console.log('New content is available; please refresh.')window.location.reload(true) // 这里需要刷新页面},offline () {console.log('No internet connection found. App is running in offline mode.')},error (error) {console.error('Error during service worker registration:', error)}})}
在 webpack.config.js的plugins 加入
plugins: [new SWPrecacheWebpackPlugin({cacheId: 'my-project-name',filename: 'service-worker.js',staticFileGlobs: ['dist/**/*.{js,html,css}'],minify: true,stripPrefix: 'dist/'}),new WebpackPwaManifest({name: 'My Progressive Web App',short_name: 'MyPWA',description: 'My awesome Progressive Web App!',background_color: '#ffffff',crossorigin: 'use-credentials', //can be null, use-credentials or anonymousicons: [{src: path.resolve('src/assets/icon.png'),sizes: [96, 128, 192, 256, 384, 512] // multiple sizes},{src: path.resolve('src/assets/large-icon.png'),size: '1024x1024' // you can also use the specifications pattern}]}),// ...]
打包出来的代码根目录里面多了个 service-worker.js ,html文件里面 pwa 相关元素也加上了。
在入口 main.js 引入该文件
import './serviceWorker'
http2
- 二进制分帧
- 首部压缩
- 服务器推送
- 流量控制
- 多路复用
- 请求优先级
- 服务器推送http2_push: ‘xxx.jpg’具体升级方式也很简单,修改一下 nginx 配置
使用http2必须开启https安全认证,需要生成ssl证书
生成ssl证书命令
openssl genrsa -des3 -passout pass:shenshuai -out server.pass.key 2048openssl rsa -passin pass:shenshuai -in server.pass.key -out server.keyopenssl req -new -key server.key -out server.csropenssl x509 -req -sha256 -days 3650 -in server.csr -signkey server.key -out server.crt
设置了本地正式,谷歌浏览器会禁止访问。此时可以直接在浏览器输入thisisunsafe
开启了http2之后,接口请求的多路复用效果
nginx开启HTTP2
在nginx.conf中设置
server {listen 443 ssl http2;root /var/www/html;location / {}}
nginx开启服务端推送资源
开启主动推送后,会减少TTFB请求的时间
location / {root /distindex index.html index.htm;http2_push /img/img1.jpg;http2_push /img/img2.jpg;}
9资源加载优先级preload和prefetch
preload
可以改变资源加载的优先级顺序
<link rel="prefetch" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.113.woff2" as="font"/>
prefetch
10 windowing窗口化
- 加载大列表,大列表的每一行会严重影响性能
- 即使使用了Lazy loading仍然会让DOM过大
- windowing只渲染可见的行,渲染和滚动的性能可以大幅提升
11 骨架组件
用 css 提前占好位置,当资源加载完成即可填充,减少页面的回流与重绘,同时还能给用户最直接的反馈。
react-placeholder react框架实现骨架屏
vue-skeleton-webpack-plugin 自动生成并自动插入静态骨架屏
12 常见面试题
12.1从输入url到页面显示经历了什么
Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。
Chrome 的主要进程:
- Browser Process:
- 负责地址栏书签栏,以及前进后退按钮的工作
- 处理浏览器的网络请求,文件访问
- Render Process:负责一个 tab 内关于网页呈现的所有事情
- Plugin Process:控制网页中用到的所有插件
- GPU Process:处理GPU图形相关的事情

Browser Process进程
该进程下包括其他一些进程
- UI thread: 控制浏览器上的按钮和输入框
- network thread: 处理网络请求,资源下载等
- storage thread: 控制文件的访问
UI线程
网络线程
UI线程和网络线程,都是在浏览器browser process进程中完成、
渲染进程
该进程下包含多个线程,主要有:
- GUI thread
- Js main thread
- 事件处理thread
- 定时器事件回调线程
- ajax异步网络请求线程
浏览器渲染原理
之后进入渲染进程,渲染进程中包含有主线程。
绘制与合成阶段
12.2 优化首屏加载

量化标准
First Contentful Paint (FCP) 首次绘制内容
Largest Contentful Paint (LCP) 最大内容的绘制
Time to interactive (TTI) 首次可交互时间
首屏优化方法:
- 解决资源体积过大:资源压缩、传输过程中压缩、代码拆分、Tree shaking、http2、缓存
- 首页内容过多:路由、懒加载、预渲染ssr、骨架屏inline css
- 加载顺序调整: preload、prefetch




