使用 Chrome DevTools 的 Performance 工具 进行性能分析
先简单的准备一段代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>worker performance optimization</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
xhr.send();
</script>
<script>
function a() {
b();
}
function b() {
let total = 0;
for (let i = 0; i < 10 * 10000 * 10000; i++) {
total += i;
}
console.log("b:", total);
}
a();
</script>
<script>
function c() {
d();
}
function d() {
let total = 0;
for (let i = 0; i < 10 * 10000 * 10000; i++) {
total += i;
}
console.log("c:", total);
}
c();
function e() {
d()
}
e();
</script>
</body>
</html>
- Main 就是主线程
- Frames、Network 等是浏览器的其他线程
HTML 中的script
标签内的脚本代码也是我们常说的宏任务 【常见的宏任务包括setTimeout
、setInterval
、requestAnimationFrame
、I/O
操作(如读取文件等)】
Performance 工具最重要的是分析主线程的 Event Loop,分析每个 Task 的耗时、调用栈等信息。
Task 浏览器执行的一个任务单元
浏览器在处理网页时,会将各种操作分解成一个个的任务来执行。这些任务包括解析 HTML、执行 JavaScript 代码、处理样式、进行网络请求等
这里还有个需要注意的地方 时间线上是连续执行的可能会被分到同一个task 下面!
性能分析的时间轴上,它们可能会被视为在同一任务(task)中执行
在性能分析图中,你可以看到不同类型的任务。例如,图中的 “Evaluate script” 就是一种任务类型,表示浏览器在执行 JavaScript 脚本。
还有像 “send” 这样的操作,它对应的是XMLHttpRequest
对象的send
方法,这也是一个任务。这个任务表示浏览器正在发送一个网络请求。
这些任务可能会被分组到一起,例如,在同一个函数调用或者同一段代码块中的操作可能会被归为一个任务,以便更好地理解代码的执行流程和性能瓶颈。例如函数a()
调用b()
,那么a()
和b()
的执行很可能被视为一个任务。这是因为从代码逻辑角度看,它们是连贯的操作。
如果在XHR 外面包一层setTimeout
那么 这个send
和 后面的a() b()
和 xhr
就不是连贯的了
现在 setTimeout
和 a() b()
是在一个任务单元了
XHR 的跑到这里来了
函数分析并优化
Summary 可以看到整个执行摘要
整个任务耗时 2.16s
主要是脚本耗时
function c() {
d();
}
function d() {
let total = 0;
for (let i = 0; i < 10 * 10000 * 10000; i++) {
total += i;
}
console.log("c:", total);
}
“Bottom - up”(自底向上)
- Self time(自耗时):指特定操作本身所花费的时间,不包括它调用的其他操作的时间。
- Total time(总耗时):指特定操作及其调用的所有子操作总共花费的时间。
- Activity(活动):描述了具体的操作或函数名称。可以展开开具体的信息
右侧有源码地址,点击就可以跳到 Sources
对应的代码
“Call tree”(调用树)
用于展示函数或操作的调用关系和时间消耗情况
“Bottom - up” 视图侧重于从耗时最长的操作开始展示
而 “Call tree” 视图则展示了完整的调用关系,帮助你理解函数是如何被调用和执行的。
“Event log” (事件日志)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>worker performance optimization</title>
</head>
<body>
<button id="myButton">点击</button>
<script>
// 获取按钮元素
const button = document.getElementById("myButton");
// 定义事件处理函数
function handleClick() {
alert("你点击了按钮");
}
// 为按钮添加点击事件监听器
button.onclick = handleClick;
</script>
</body>
</html>
它提供了一个按时间顺序排列的事件列表 StartTime
,一个宏观的时间线视角,帮助开发者了解在整个操作流程中各个事件是如何依次发生的。这个 StartTime 是页面渲染完成后时间操作的时间
比如你页面渲染完 1s 后点击的按钮 StartTime 就是1000 ms
上面那些用按钮点击的那个demo 比较易看
可以使用定时器来解决阻塞的问题,但是不能解决Long task 的问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>worker performance optimization</title>
</head>
<body>
<button id="myButton">点击</button>
<script>
function c() {
return d();
}
function d() {
return new Promise((resolve) => {
setTimeout(() => {
let total = 0;
for (let i = 0; i < 20 * 10000 * 10000; i++) {
total += i;
}
resolve(total);
}, 100);
});
}
c().then((r) => {
console.log(r);
});
</script>
</body>
</html>
因为不管是宏任务和微任务的回调都还是在js 的主线程执行的!
使用 web worker优化 long task
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>worker performance optimization</title>
</head>
<body>
<button id="myButton">点击</button>
<script>
function c() {
return d();
}
function runWorker(url, num) {
return new Promise((resolve, reject) => {
const worker = new Worker(url);
worker.postMessage(num);
worker.addEventListener("message", function (evt) {
resolve(evt.data);
});
worker.onerror = reject;
});
}
function d() {
return runWorker("./worker.js", 10 * 10000 * 10000);
}
c().then((r) => {
console.log(r);
});
</script>
</body>
</html>
worker.js
addEventListener('message', function(evt) {
let total = 0;
let num = evt.data;
for(let i = 0; i< num; i++) {
total += i;
}
postMessage(total);
});
主线程脚本执行了 380ms
大计算量费时的转到了 work 中!
虽然 Worker 还有 long task,但是不重要,毕竟计算量在那,只要主线程没有 long task 就行。
使用Performance 分析 Event Loop
<!DOCTYPE html>
<html lang="en">
<body>
<script>
function calc1() {
let product = 1;
for (let k = 1; k < 10000000; k++) {
product *= k;
}
}
function calc2() {
let product = 1;
for (let k = 1; k < 10000000; k++) {
product *= k;
}
}
function calc3() {
let product = 1;
for (let k = 1; k < 10000000; k++) {
product *= k;
}
}
function calc4() {
let product = 1;
for (let k = 1; k < 10000000; k++) {
product *= k;
}
}
function calc5() {
let product = 1;
for (let k = 1; k < 10000000; k++) {
product *= k;
}
}
const timer1 = setTimeout(() => {
console.log(3);
calc3();
Promise.resolve().then(() => {
console.log(4);
calc4();
});
}, 0);
Promise.resolve().then(() => {
console.log(1);
calc1();
const timer2 = setTimeout(() => {
console.log(2);
calc2();
}, 0);
});
console.log("5");
</script>
</body>
</html>
可以看到异步的执行顺序 1,3,4,2
- “Run microtasks”(运行微任务)
- “Timer fired”(定时器触发)
- “Function call”(函数调用)
评论区