RAIL 评估标准
- 响应:处理事件应在 50ms 以内完成
- 动画:每 10ms 产生一帧
- 空闲:尽可能增加空闲时间
- 加载:在 5s 内完成内容加载并可以交互
性能测量工具
- Chrome DevTools 开发调试、性能评测
- Lighthouse 网站整体质量评估
- WebPageTest 多测试地点、全面性能报告
使用 Lighthouse 分析性能
- 本地 npm 安装: npm install -g lighthouse
- lighthouse http://www.bilibili.com
使用 Chrome DevTools 分析性能
性能测量 APIs
- 关键时间节点(Navigation Timing,Resource Timing)
- 网络状态(Network APIs)
- 客户端服务端协商(HTTP Client Hints)& 网页显示状态(UI APIs)
浏览器的渲染流程
浏览器构建对象模型
- 构建 DOM 对象
HTML->DOM - 构建 CSSOM 对象
CSS->CSSOM
浏览器构建渲染树
回流与重绘
复合线程(compositor thread)与图层(layers)
复合线程做什么
- 将页面拆分图层进行绘制再进行复合
- 利用 DevTools 了解网页的图层拆分情况
- 哪些样式仅影响复合
避免重绘
高频 事件处理函数 防抖
JS 开销和如何缩短解析时间
- Code splitting 代码拆分, 按需加载
- Tree shaking 代码减重
减少主线程工作量
- 避免长任务
- 避免超过 1kb 的行间脚本
- 使用 rAF 和 rIC 进行时间调度
Progressive Bootstrapping
- 可见不可交互 vs 最小可交互资源集
- 先加载首屏资源, 其余延时加载
V8 编译原理, 配合 V8 有效优化代码
抽象语法树
- 源码=>抽象语法树 =>字节码 Bytecode => 机器码
- 编译过程会进行优化
- 运行时可能发生反优化
V8 优化机制
- 脚本流
- 字节码缓存
- 懒解析
函数优化
- lazy parsing 懒解析 vs eager parsing 饥饿解析
- 利用 Optimize.js 优化初次加载时间
JS 对象优化
- 以相同顺序初始化对象成员,避免隐藏类的调整
- 实例化后避免添加新属性
- 尽量使用 Array 代替 array-like 对象
- 避免读取超过数组的长度
- 避免元素类型转换
//1
class RectArea {//HC0
constructor(l, w) {
this.l = l;//HC1
this.w = w;//HC2
}
}
const rec1 = new RectArea(3, 4)
const rec2 = new RectArea(5, 6)
const car1 = {color: 'red'}//HC0
car1.seats = 4;//HC1
const car2 = {seats: 2}//HC2
car2.color = 'blue'//HC3
//2
const car3 = {color: 'red'}//In-object 属性
car1.seats = 4;//Normal/Fast 属性,存储 property store 里,需要通过描述数组间接查找
//3
Array.prototype.forEach.call(arrObj, (value, index) => {//不如在真实数组上效率高
console.log(`${index}:${value}`);
})
//将类数组对象转为真实数组, 再遍历
const arr=Array.prototype.slice.call(arrObj,0)
arr.forEach((value, index) => {//转换的代价比优化影响小
console.log(`${index}:${value}`);
})
//4
function foo(array){
for (let i = 0; i < array.length; i++) {
if(array[i]>1000){//1.造成 undefined 跟数字进行比较 2.沿原型链的查找
console.log(array[i])//业务上无效,出错
}
}
}
//[10,100,1000]
//5
const array=[3,2,1];//PACKED_SMI_ELEMENTS
array.push(4.4) //对数组类型又一次更改 PACKED_DOUBLE_ELEMENTS
字体优化
什么是 FOIT 和 FOUT
- 字体未下载完成时,浏览器隐藏或自动降级,导致字体闪烁
- Flash Of Invisible Text
- Flash Of Unstyled Text
使用 font-display
- auto
- swap
- optional
- block
- fallback
使用 AJAX+Base64
- 解决兼容性问题
- 缺点:缓存问题
webpack 优化配置
Tree-shaking
- 上下文未用到的代码(dead code)
- 基于 ES6 import export
- package.json 中配置 sideEffects
- 注意 Babel 默认配置的影响
JS 压缩
- Webpack4 后引入 uglifyjs-webpack-plugin
- 支持 ES6 替换为 terser-webpack-plugin
- 减少 JS 文件体积
作用域提升
- 代码体积减小
- 提高执行效率
- 同样注意 Babel 的 modules 配置
Babel7 优化配置
- 在需要的地方引入 polyfill
- 辅助函数的按需引入
- 根据目标浏览器按需转换代码
启用压缩 Gzip
- 对传输资源进行体积压缩,可高达 90%
- 如何配置 Nginx 启用 Gzip
启用 Keep Alive
- 一个持久的 TCP 连接,节省了连接创建时间
- Nginx 默认开启 keep alive
HTTP 资源缓存
- 提高重复访问时资源加载的速度
服务端渲染 SSR
- 加速首屏加载
- 更好的 SEO
从 PNG 到 IconFont
- 多个图标->一套字体, 减少获取时的请求数量和体积
- 矢量图形, 可伸缩
- 直接通过 css 修改样式(颜色, 大小等)
从 iconfont 到 svg
- 保持了图片能力,支持多色彩
- 独立的矢量图形
- XML 语法搜索引擎 SEO 和无障碍读屏软件读取
使用 flexbox 优化布局
- flex 使用栅格布局
- 更高性能的实现方案
- 容器有能力决定子元素的大小, 顺序, 对齐, 间隔等
- 双向布局
//1
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.main{
width: 100%;
height: 1000px;
background-color: gray;
display: flex;
flex-flow: row wrap;
}
.box{
width: 100px;
height: 100px;
background-color: red;
margin: 5px;
/*1.float: left;*/
}
</style>
</head>
<body>
<div id="main" class="main"></div>
<script>
window.addEventListener('load',()=>{
let mainNode=document.getElementById('main');
for (let i = 0; i < 10000; i++) {
let divNode=document.createElement('div');
divNode.setAttribute('class','box')
mainNode.appendChild(divNode);
}
})
</script>
</body>
</html>
//2
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.flex-container {
display: flex;
flex-flow: row wrap;
justify-content: space-around;
}
.flex-card {
width: 220px;
height: 220px;
display: flex;
justify-content: center;
align-items: center;
}
.flex-card-number {
font-size: 128px;
color: white;
}
</style>
</head>
<body>
<div id="flex-container" class="flex-container"></div>
<script>
function generateRandomColor() {
let x = Math.floor(Math.random() * 256)
let y = Math.floor(Math.random() * 256)
let z = Math.floor(Math.random() * 256)
return 'rgb(' + x + ',' + y + ',' + z + ')'
}
window.addEventListener('load', () => {
let mainNode = document.getElementById('flex-container');
for (let i = 0; i < 10; i++) {
let divNode = document.createElement('div');
divNode.setAttribute('class', 'flex-card')
divNode.style.backgroundColor = generateRandomColor()
let spanNode = document.createElement('span');
spanNode.setAttribute('class', 'flex-card-number')
spanNode.appendChild(document.createTextNode((i + 1).toString()))
divNode.appendChild(spanNode);
mainNode.appendChild(divNode);
}
})
</script>
</body>
</html>
//3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Title</title>
<style>
.wrapper{
display: flex;
flex-flow: row wrap;
text-align: center;
}
.wrapper>*{
padding: 10px;
flex: 1 100%;/*等分, 宽度 100%*/
}
.header{
background-color: red;
}
.footer{
background-color: pink;
}
.main{
background-color: aqua;
}
.nav{
background-color: blue;
}
.ads{
background-color: lightcoral;
}
@media all and (min-width: 700px) {
.aside{
flex: 1 0 0;/* 占比权重 1, 不收缩,宽度 0,剩余空间按照权重均分*/
}
}
@media all and (min-width: 800px){
.main{
flex: 3 0 0;
order: 2;
}
.nav{
order: 1;
}
.ads{
order: 3;
}
.footer{
order: 4;
}
}
</style>
</head>
<body>
<div class="wrapper">
<header class="header">Header</header>
<article class="main">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam, assumenda, delectus doloribus
doloremque eaque enim eos expedita fuga harum ipsa iure labore magnam maiores natus nihil nulla
pariatur quae quasi quibusdam quidem quo repellat sapiente sed similique tempora
</article>
<aside class="aside nav">Navigation</aside>
<aside class="aside ads">ADs</aside>
<footer class="footer">Footer</footer>
</div>
</body>
</html>
- 不同窗口显示不同

资源优先级
- 浏览器默认安排资源加载优先级
- 使用 preload, prefetch 调整优先级
preload, prefetch 适用场景
- preload: 提前加载较晚出现, 但对当前页面非常重要的资源
- prefetch: 提前加载后继路由需要的资源,优先级低
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="preload" href="img/product2.png" as="image">
<!-- 预加载字体文件-->
<link rel="prefetch" as="style" href="product.css">
<style>
.wrapper {
display: flex;
flex-flow: column;
}
.link {
width: 150px;
height: 150px;
}
.link-image {
width: 100px;
height: 100px;
margin: 5px;
}
</style>
</head>
<body>
<div id="wrapper" class="wrapper">
<a class="link" href="product1.html">
<img class="link-image" src="img/product1.png"/>
</a>
<a class="link" href="product2.html">
<img class="link-image" src="img/product2.png"/>
</a>
</div>
</body>
</html>
//product1.html
<link rel="stylesheet" href="product.css">
</head>
<body>
<p class="content">
最好的产品体验
</p>
//product2.html
<link rel="stylesheet" href="product.css">
<link rel="preload" href="https://fonts.gstatic.com/s/longcang/v17/LYjAdGP8kkgoTec8zkRQqnoq.woff2" as="font"
type="font/woff2" crossorigin="anonymous">
</head>
<body>
<p class="content">
最好的产品体验
</p>
- 动态 preload ,动态解析
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="preload" href="img/product2.png" as="image">
<!-- 预加载字体文件-->
<link rel="prefetch" as="style" href="product.css">
<style>
.wrapper {
display: flex;
flex-flow: column;
}
.link {
width: 150px;
height: 150px;
}
.link-image,.link-image-1 {
width: 100px;
height: 100px;
margin: 5px;
}
</style>
</head>
<body>
<div id="wrapper" class="wrapper">
<a class="link" href="product1.html">
<img class="link-image-1" src="img/product1.png"/>
</a>
<a class="link" href="product2.html">
<img class="link-image" src="img/product2.png"/>
</a>
</div>
<script>
function loadResource(url){
let link=document.createElement('link')
link.rel='reload'
link.href=url
link.as='image'
document.head.appendChild(link)
}
function execResource(url){
let image=document.getElementById('link-image-1')
image.src=url
}
let url='img/product1.png'
//预加载图片资源
loadResource(url)
setTimeout(()=>{
execResource(url)//为图片进行动态赋值
},2000)
</script>
</body>
</html>
预渲染的作用
- 大型单页应用的性能瓶颈: JS 下载+解析+执行
- SSR 的主要问题: 牺牲 TTFB 来补救 First Paint; 实现复杂
- Pre-rendering 打包时提前渲染页面, 没有服务端参与
Windowing(窗口化) 提高列表性能?
使用骨架组件减少布局移动?
Skeletonn/Placeholder 的作用?
- 占位
- 提升用户感知性能
问题: 从输入 URL 到页面加载显示完成都发生了什么?[重点答: 渲染]
问题: 什么是首屏加载? 怎么优化?
-
Web 增量加载的特点决定了首屏性能不会完美
-
过长的白屏影响用户体验和留存
-
首屏(above the fold)>初次印象
-
怎么优化?
javascript 内存管理
问题: JS 是怎样管理内存的?什么情况会造成内存泄漏?
-
内存泄漏严重影响性能
-
高级语言!=不需要管理内存
解答:
-
变量创建时自动分配内存, 不使用时"自动"释放内存--GC
-
内存释放的主要问题是如何确定不再需要使用的内存
-
所有 GC 都是近似实现, 只能通过判断变量是否还能再次访问到
作用域
- 局部变量, 函数执行完, 没有闭包引用, 就会被标记回收
- 全局变量, 直至浏览器卸载页面时释放
GC 的实现机制
避免内存泄漏
-
避免意外的全局变量产生
-
避免反复运行引发大量闭包
-
避免脱离的 DOM 元素



















