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/server
ADD locations.ini /var/www/html/settings/
locations.ini:
[locations]
1=Test_loc
[Test_loc]
1=Test
label=Test Location
group=Desktop
[Test]
browser=Chrome,Firefox
label="Test Location"
connectivity=LAN
本地build镜像
$ docker build -t local-wptserver .
Agent
创建一个agent文件夹,包含Dockerfile和script.sh文件。
Dockerfile
FROM webpagetest/agent
ADD script.sh /
ENTRYPOINT /script.sh
script.sh
#!/bin/bash
set -e
if [ -z "$SERVER_URL" ]; then
echo >&2 'SERVER_URL not set'
exit 1
fi
if [ -z "$LOCATION" ]; then
echo >&2 'LOCATION not set'
exit 1
fi
EXTRA_ARGS=""
if [ -n "$NAME" ]; then
EXTRA_ARGS="$EXTRA_ARGS --name $NAME"
fi
python /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-wptserver
docker 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 - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时 : responseEnd - responseStart
DOM 解析耗时 : domInteractive - responseEnd
资源加载耗时 : loadEventStart - domContentLoadedEventEnd
First Byte 时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间 TTI : domInteractive - fetchStart
DOM Ready 时间 DCL : domContentLoadEventEnd - fetchStart
页面完全加载时间 L: loadEventStart - fetchStart
http 头部大小 :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.js
const 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;
# 压缩级别官网建议是6
gzip_comp_level 6;
# 对哪些格式文件压缩
gzip_types text/plain application/javascript application/x-javascript text/css application/xml
text/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.js
import { 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 anonymous
icons: [
{
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 2048
openssl rsa -passin pass:shenshuai -in server.pass.key -out server.key
openssl req -new -key server.key -out server.csr
openssl 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 /dist
index 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