目录
[3.script 标签的 defer 和 async](#3.script 标签的 defer 和 async)
1.为什么做动画时,改变left比改变transform属性更消耗性能
前言
往期回顾:
浏览器渲染篇
1.关键路径渲染
关键路径渲染指的是浏览器把代码变成屏幕上像素经过的六个阶段,任何一个阶段卡住,页面就会白屏或者卡顿,下面是六个阶段:
- 构建DOM树:解析HTML,生成DOM节点树
- 构建CSSOM树,解释CSS,生成CSS规则树
- 生成渲染树:(DOM + CSSOM)才是渲染树,display:none的节点不会出现在渲染树中,但是visibility:hidden的节点会出现在渲染树中(因为它占据空间)
- 回流:计算每个节点在屏幕上的位置和大小(即几何信息)
- 重绘(绘制):填充像素(颜色、背景、边框等等)
- 合成:浏览器将不同的层合并,最终显示在屏幕上
所以,根据上面的六个阶段,如果想要优化浏览器页面加载速度可以从以下几个方面入手:
- 减少关键资源请求数(少写几个CSS、JS文件)
- 减少关键资源体积(例如将png格式的图片改为webp格式的图片)
- 异步加载JS文件,只在需要的时候才动态import文件
2.回流和重绘(绘制)的区别
回流必定引起重绘,但是重绘不一定引起回流
这句话很好理解:"回流计算的是节点的几何信息(大小、位置),重绘计算的是节点的(颜色、背景色、阴影等),可以说,重绘是对回流计算的节点进行美化的一个过程"
因此,当某个节点大小改变时,那么颜色、背景色这些属性对应的也要进行重绘来填充
而某个节点的颜色改变时,只需要重绘填充颜色即可,至于节点的大小、位置并没有改变,所以回流不会发生
下面是对回流 和重绘的一个解释:
回流:
定义 :当元素的几何属性(宽、高、位置、显示隐藏)发生变化时,浏览器需要重新计算元素的几何信息
性能:消耗资源非常多,一个元素的大小变化在整个DOM树节点上的影响是全局性的
触发条件:
- 改变窗口大小
- 改变字体大小
- 添加/删除DOM节点
- 改变width、height、margin、padding
- 读取offsetWidth、offsetHeight、scrollTop等属性时
重绘:
定义:当元素的外观属性发生变化,但不影响布局时
性能:资源消耗较少
触发条件:
- color
- background-color
- visibility
- outline
3.script 标签的 defer 和 async
在HTML中,**<script>**标签默认会阻塞DOM的解析,当浏览器执行到<script>标签时,会从服务器下载并执行JS,执行完JS之后再解析HTML
为了不阻塞页面显示,<script> 引入了两个属性:"defer "和"async"
两个属性改变的是**<script>** 标签的下载时机 、执行时机 、执行顺序
| 特性 | <script> | <script async> | <script defer> |
|---|---|---|---|
| HTML解析 | 立即停止,等待JS代码下载和执行 | 不停止,JS并行下载 | 不停止,JS并行下载 |
| JS执行时机 | 下载完立即执行 | 下载完立即执行 | 等到HTML全部解析完 (DOMContentLoaded前)执行 |
| 执行顺序 (多个<script>标签) | 按代码书写顺序 | 谁先下完执行谁 | 严格按代码书写顺序 |
| 适用场景 | 关键且依赖顺序的脚本 | 独立的脚本 (埋点、广告) | 依赖DOM或其它脚本的方案 |
一句话概括:
<script async>:下载完立即中断HTML去执行,谁下载快谁先被执行,但如果a.js依赖于b.js,但是b.js大一点下得慢,a.js先执行就会报错
<script defer>:下载完成并等到HTML解析完成之后再执行,执行顺序严格按照代码顺序,并且可以保证DOM被渲染好了(不用写window.onload)
现在的工程项目中,通常把<script>放在<head>里并加入<defer>,这是最安全、性能最好的选择
在以前的HTML老代码中,可能会将JS代码写在<body>标签的最后,或者在JS里手动添加"window.onload"这很啰嗦,但在现代项目中,直接使用defer关键字即可
一个老项目代码:
html
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = function() {
const box = document.getElementById('box');
box.innerText = "DOM加载完成";
}
</script>
</head>
<body>
<div id="box">准备改变内容</div>
<script src="vue.js"></script>
<script src="plugin.js"></script>
<script src="app.js"></script>
</body>
</html>
经过现代化工程修改后的代码:
javascript
<!DOCTYPE html>
<html>
<head>
<title>现代化工程代码</title>
<script defer src="vue.js"></script>
<script defer src="plugin.js"></script>
<script defer src="app.js"></script>
</head>
<body>
<div id="box">准备改变内容</div>
</body>
</html>
代码篇
1.手写一个EventBus(订阅-发布模式)
javascript
class EventBus {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
emit(eventName, ...args) {
if (this.events[eventName]) {
const callbacks = [...this.events[eventName]];
callbacks.forEach(callback => {
callback(...args);
})
}
}
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => {
return cb !== callback && cb.originalCallback != callback;
})
}
}
once(eventName, callback) {
const onceWrapper = (...args) => {
this.off(eventName, onceWrapper);
callback(...args);
}
onceWrapper.originalCallback = callback;
this.on(eventName, onceWrapper);
}
}
const bus = new EventBus();
const fn1 = (name, age) => console.log(`fn1: ${name}, ${age}`);
const fn2 = (name, age) => console.log(`fn2: ${name}, ${age}`);
console.log("1.on和emit方法展示")
bus.on('sayHi', fn1);
bus.on('sayHi', fn2);
bus.emit('sayHi', 'Alice', 30);
console.log("2.off方法展示")
bus.off('sayHi', fn1);
bus.emit('sayHi', 'Bob', 25);
console.log("3.once方法展示")
const onceOn = (name, age) => console.log(`onceOn: ${name}, ${age}`);
bus.once('greet', onceOn);
bus.emit('greet', 'Charlie', 28);
bus.emit('greet', 'David', 35);
console.log("4.once还没被执行就被off掉")
const onceOff = (name, age) => console.log(`onceOff: ${name}, ${age}`);
bus.once('welcome', onceOff);
bus.off('welcome', onceOff);
bus.emit('welcome', 'Eve', 22);
运行结果:

回顾篇
1.为什么做动画时,改变left比改变transform属性更消耗性能
本问题用"回流/重绘"原理解释:
- 首先,改变left属性必然会引起回流,此时性能消耗巨大,此外,还会引起重绘,所以性能消耗更大了
- 其次,改变transform属性不会引起节点的几何属性变化,也不会引起节点的外观属性变化,故不会引起回流或者重绘
- 最后,使用transform,浏览器会使用GPU加速渲染,大大提升效率