Chrome 开发者工具 | Web | Google Developers
Command Line API 参考 | 调试增强 | Google Developers

自适应&移动设备测试

蓝色:针对最大宽度的查询。
绿色:针对一定范围内宽度的查询。
橙色:针对最小宽度的查询。

e.g.:测试自适应和设备特定的视口 | Web | Google Developers
eg:模拟传感器:地理定位与加速度计 | Web | Google Developers

Elements

样式

查看本地更改



  1. 在 Styles 窗格中,点击您修改的文件。DevTools 会将您带到 Sources 面板。
  2. 右键点击文件。
  3. 选择 Local modifications。
    eg:检查和编辑页面与样式 | Web | Google Developers
    eg:使用 DevTools 的工作区设置持久化 | Web | Google Developers
    eg:编辑样式 | Web | Google Developers

检查动画

检查动画 | Web | Google Developers

Dom

快速定位到可视窗口

右键点击节点并选择 Scroll into View

元素侦听&断点


查看元素事件侦听器

  • handler 包含一个回调函数。右键点击函数并选择 Show Function Definition 可以查看函数的定义位置(如果源代码可用)。
  • useCapture 指示 addEventListener 上的 useCapture 标志是否设置的布尔值。
  1. Ancestors 除了当前选定节点的事件侦听器外,还会显示其祖先实体的事件侦听器
  2. Framework listeners 框架侦听器,DevTools 会自动解析事件代码的框架或内容库封装部分,然后告诉您实际将事件绑定到代码中的位置。例如:使用jQuery,可观察到不同之处。

eg:编辑 DOM | Web | Google Developers

Console



setInterval分析
eg:使用控制台 | Web | Google Developers

Sources

创建一个有条件的行号断点

右键单击一个还没有断点的行号,并按 Add conditional breakpoint 可创建一个有条件的断点。 如果您已在行代码中添加了一个断点,并想为该断点设置条件,则右键点击并按 Edit breakpoint。
ps:当条件满足时,断点才会生效;相反,会忽略此断点。

XHR断点

eg:如何设置断点 | Web | Google Developers

Event断点

eg:如何设置断点 | Web | Google Developers

异常断点

eg:如何设置断点 | Web | Google Developers

JavaScript Profiler

JavaScript Profiler性能分析、代码分析。


以上可以看出,都指向同一个地方存在性能问题,并导致了脚本Content Download时间变长


优化前


优化后

Network

Network 面板由五个窗格组成:

  1. Controls。使用这些选项可以控制 Network 面板的外观和功能。
  2. Filters。 使用这些选项可以控制在 Requests Table 中显示哪些资源。提示:按住 Cmd (Mac) 或 Ctrl (Windows/Linux) 并点击过滤器可以同时选择多个过滤器。
  3. Overview。 此图表显示了资源检索时间的时间线。如果您看到多条竖线堆叠在一起,则说明这些资源被同时检索。
  4. Requests Table。 此表格列出了检索的每一个资源。 默认情况下,此表格按时间顺序排序,最早的资源在顶部。点击资源的名称可以显示更多信息。 提示:右键点击 Timeline 以外的任何一个表格标题可以添加或移除信息列。
  5. Summary。 此窗格可以一目了然地告诉您请求总数、传输的数据量和加载时间。

ps:

Overview 窗格中的蓝色竖线表示事件。
在 Summary 窗格中,您可以看到事件的确切时间(底部蓝色字部分)。

Overview 窗格中的红色竖线表示事件。
Requests Table 中的红色竖线也表示事件。
在 Summary 窗格中,您可以看到事件的确切时间(红字)

  • Name。资源的名称。
  • Status。HTTP 状态代码。
  • Type。已请求资源的 MIME 类型。
  • Initiator。发起请求的对象或进程。值为以下选项之一:
  • Parser。Chrome 的 HTML 解析器发起请求。
  • Redirect。HTTP 重定向发起请求。
  • Script。脚本发起请求。
  • Other。某些其他进程或操作发起请求,例如用户通过链接或者在地址栏中输入网址导航到页面。
  • Size。响应标头(通常为数百字节)加响应正文(由服务器提供)的组合大小。
  • Time。从请求开始至在响应中接收到最终字节的总持续时间。
  • Timeline。Timeline 列可以显示所有网络请求的可视瀑布。 点击此列的标题可以显示一个包含更多排序字段的菜单。

单个资源信息

  1. Headers。与资源关联的 HTTP 标头。
  2. Preview。JSON、图像和文本资源的预览。
  3. Response。HTTP 响应数据(如果存在)。
  4. Timing。资源请求生命周期的精细分解。

查看网络耗时

  1. Queuing
  2. Stalled
  3. 如果适用:DNS lookup、initial connection、SSL handshake
  4. Request sent
  5. Waiting (TTFB)
  6. Content Download

eg:了解 Resource Timing | Web | Google Developers

查看 WebSocket 框架

点击 Frames 标签可以查看 WebSocket 连接信息。
只有选定资源发起 WebSocket 连接时,此标签才会显示。

  1. Data。消息负载。如果消息为纯文本,将在此处显示。 对于二进制操作码,此字段将显示操作码的名称和代码。 支持以下操作码:
  2. 延续框架
  3. 二进制框架
  4. 连接关闭框架
  5. Ping 框架
  6. Pong 框架
  7. Length。消息负载的长度(以字节为单位)。
  8. Time。消息创建时的时间戳。

消息根据其类型进行彩色编码:

  • 传出文本消息为浅绿色。
  • 传入文本消息为白色。
  • WebSocket 操作码为浅黄色。
  • 错误为浅红色。

有关当前实现的说明:

  • 要在每条新消息到达后刷新 Frames 表,请点击左侧的资源名称。
  • Frames 表格仅保留最后 100 条 WebSocket 消息。

查看资源发起者和依赖项

将鼠标悬停在资源上,可以查看其发起者和依赖项。

排序请求

Timeline 列与其他列不同。点击此列时,它将显示一个由多个排序字段组成的菜单:

  1. Timeline。按每个网络请求的开始时间排序。这是默认排序方式,与按 Start Time 选项排序相同。
  2. Start Time。按每个网络请求的开始时间排序(与按 Timeline 选项排序相同)。
  3. Response Time。按每个请求的响应时间排序。
  4. End Time。按每个请求完成的时间排序。
  5. Duration。按每个请求的总时间排序。选择此过滤器可以确定哪些资源的加载时间最长。
  6. Latency。按请求开始与响应开始之间的时间排序。 选择此过滤器可以确定哪些资源至第一字节 (TTFB) 的时间最长。

过滤请求

点击 Filter 按钮 可以隐藏或显示 Filters 窗格。
ps:按住 Cmd (Mac) 或 Ctrl (Windows/Linux) 并点击过滤器可以同时启用多个过滤器。

Filter 文本字段还支持各种关键词:

  1. domain。仅显示来自指定域的资源。您可以使用通配符字符 () 来包含多个域。 例如,.com 将显示来自以 .com 结尾的所有域名的资源。 DevTools 会使用它遇到的所有域填充自动填充下拉菜单。
  2. has-response-header。显示包含指定 HTTP 响应标头的资源。 DevTools 会使用它遇到的所有响应标头填充自动填充下拉菜单。
  3. is。使用 is:running 可以查找 WebSocket 资源。
  4. larger-than。显示大于指定大小的资源(以字节为单位)。 将值设为 1000 等同于设置为 1k。
  5. method。显示通过指定 HTTP 方法类型检索的资源。 DevTools 会使用它遇到的所有 HTTP 方法填充下拉菜单。
  6. mime-type。显示指定 MIME 类型的资源。DevTools 会使用它遇到的所有 MIME 类型填充下拉菜单。
  7. mixed-content。显示所有混合内容资源 (mixed-content:all),或者仅显示当前显示的资源 (mixed-content:displayed)。
  8. scheme。显示通过未保护 HTTP (scheme:http) 或受保护 HTTPS (scheme:https) 检索的资源。
  9. set-cookie-domain。显示具有 Set-Cookie 标头并带有与指定值匹配的 Domain 属性的资源。 DevTools 会使用它遇到的所有 Cookie 域填充自动填充下拉菜单。
  10. set-cookie-name。显示具有 Set-Cookie 标头并且名称与指定值匹配的资源。 DevTools 会使用它遇到的所有 Cookie 名称填充自动填充下拉菜单。
  11. set-cookie-value。显示具有 Set-Cookie 标头并且值与指定值匹配的资源。 DevTools 会使用它遇到的所有 Cookie 值填充自动填充下拉菜单。
  12. status-code。仅显示 HTTP 状态代码与指定代码匹配的资源。 DevTools 会使用它遇到的所有状态代码填充自动填充下拉菜单。

eg:Network栏目 | Web | Google Developers

Performance

面板包含以下四个窗格:

  1. Controls。开始记录,停止记录和配置记录期间捕获的信息。
  2. Overview。 页面性能的高级汇总。更多内容请参见下文。
  3. 火焰图。 CPU 堆叠追踪的可视化。
  4. 火焰图记录详细信息表

Performance分析内存泄露

  1. 开启Performance项的记录
  2. 执行一次CG,创建基准参考线
  3. 连续单击【click】按钮1-3次,新建三个Leaker对象
  4. 执行一次CG
  5. 停止记录

分析代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Leaker</title>
</head>
<body>
<input type="button" value="click">
<script>
!(function() {
function Leaker() {
this.init();
}
Leaker.prototype = {
init: function() {
this.name = Array(100000).join('*');
// console.log("Leaking an object %o: %o", (new Date()), this);// this对象不能被回收
},
destroy: function() {
// do something....
}
};
document.querySelector('input').addEventListener(
'click',
function() {
new Leaker();
},
false
);
})();
</script>
</body>
</html>

Performance分析DOM泄露

分析代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Dom-Leakage</title>
</head>
<body>
<input type="button" value="remove" class="remove">
<input type="button" value="add" class="add">
<div class="container">
<pre class="wrapper"></pre>
</div>
<script>
// 因为要多次用到<pre>节点,将其缓存到本地变量wrapper中,
var wrapper = document.querySelector('.wrapper');
var counter = 0;
document.querySelector('.remove').addEventListener('click', function () {
document.querySelector('.container').removeChild(wrapper);
}, false);
document.querySelector('.add').addEventListener('click', function () {
wrapper.appendChild(document.createTextNode('\t' + ++counter + ':a new line text\n'));
}, false);
</script>
</body>
</html>

  1. 开启Performance项的记录
  2. 执行一次CG,创建基准参考线
  3. 连续单击【add】按钮6次,增加6个文本节点到pre元素中
  4. 单击【remove】按钮,删除刚增加6个文本节点和pre元元素
  5. 执行一次CG
  6. 停止记录堆分析

从分析结果图可知,虽然6次add操作增加6个Node,但是remove操作并没有让Nodes节点数下降,即remove操作失败。尽管还主动执行了一次CG操作,Nodes曲线也没有下降。因此可以断定内存泄露了!那问题来了,如何去查找问题的原因呢?这里可以通过Chrome浏览器的Devtools–>Memory进行诊断分析,执行如下操作步骤:Memory分DOM泄漏

Performance分析EventListener

分析代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>callbacks</title>
</head>
<body>
<div class="container"></div>
<script>
var container = document.querySelector('.container');
var counter = 0;
var createHtml = function (n, counter) {
var template = `${(new Array(n)).join(`<div>${counter}: this is a new data <input type="button" value="remove"></div>`)}`
container.innerHTML = template;
}
// var clickCallback = function (event) {
// var target = event.target;
// if (target.tagName === 'INPUT') {
// container.removeChild(target.parentElement)
// }
// }
var resizeCallback = function (init) {
createHtml(10, ++counter);
// 事件委托
container.addEventListener('click', function (event){
var target = event.target;
if(target.tagName === 'INPUT'){
container.removeChild(target.parentElement)
}
}, false);
// container.addEventListener('click', clickCallback, false);
}
window.addEventListener('resize', resizeCallback, false);
resizeCallback(true);
</script>
</body>
</html>

  1. 开启Performance项的记录
  2. 执行一次CG,创建基准参考线
  3. 对窗口大小进行调整
  4. 执行一次CG
  5. 停止记录

如分析结果所示,在窗口大小变化时,会不断地对container添加代理事件。

同一个元素节点注册了多个相同的EventListener,那么重复的实例会被抛弃。这么做不会让得EventListener被重复调用,也不需要用removeEventListener手动清除多余的EventListener,因为重复的都被自动抛弃了。而这条规则只是针对于命名函数。对于匿名函数,浏览器会将其看做不同的EventListener,所以只要将匿名的EventListener,命名一下就可以解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var container = document.querySelector('.container');
var counter = 0;
var createHtml = function (n, counter) {
var template = `${(new Array(n)).join(`<div>${counter}: this is a new data <input type="button" value="remove"></div>`)}`
container.innerHTML = template;
}
//
var clickCallback = function (event) {
var target = event.target;
if (target.tagName === 'INPUT') {
container.removeChild(target.parentElement)
}
}
var resizeCallback = function (init) {
createHtml(10, ++counter);
// 事件委托
container.addEventListener('click', clickCallback, false);
}
window.addEventListener('resize', resizeCallback, false);
resizeCallback(true);

在开发中,开发者很少关注事件解绑,因为浏览器已经为我们处理得很好了。不过在使用第三方库时,需要特别注意,因为一般第三方库都实现了自己的事件绑定,如果在使用过程中,在需要销毁事件绑定时,没有调用所解绑方法,就可能造成事件绑定数量的不断增加。如下链接是我在项目中使用jquery,遇见到类似问题:jQuery中忘记解绑注册的事件,造成内存泄露➹猛击😊

GitHub - zhansingsong/js-leakage-patterns: 本文主要介绍了JavaScript几种常见的内存泄露,相信你读完本文会有所收获的。

查看重绘区

ps:

Controls

功能栏

Overview

  1. FPS。每秒帧数。绿色竖线越高,FPS 越高。 FPS 图表上的红色块表示长时间帧,很可能会出现卡顿。
  2. CPU。 CPU 资源。此面积图指示消耗 CPU 资源的事件类型。
  3. NET。每条彩色横杠表示一种资源。横杠越长,检索资源所需的时间越长。 每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间)。

Overview 窗格包含以下三个图表:

  1. FPS。每秒帧数。绿色竖线越高,FPS 越高。 FPS 图表上的红色块表示长时间帧,很可能会出现卡顿。
  2. CPU。 CPU 资源。此面积图指示消耗 CPU 资源的事件类型。
  3. NET。每条彩色横杠表示一种资源。横杠越长,检索资源所需的时间越长。 每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间)。

ps:深色部分表示传输时间(下载第一个和最后一个字节之间的时间)。

横杠按照以下方式进行彩色编码:

  • HTML 文件为蓝色。
  • 脚本为黄色。
  • 样式表为紫色。
  • 媒体文件为绿色。
  • 其他资源为灰色。

火焰图

处于焦点时,按 Cmd+F (Mac) 或 Ctrl+F (Windows / Linux) 以打开一个查找工具栏。键入您想要检查的事件类型的名称,如 Event、html。
向上和向下箭头等同于在火焰图中点击事件。

分析 JavaScript

如何使用 Timeline 工具 | Web | Google Developers

分析绘制

如何使用 Timeline 工具 | Web | Google Developers

记录详细信息

选择事件后,此窗格会显示与该事件有关的更多信息。 未选择事件时,此窗格会显示选定时间范围的相关信息。
时间线事件参考 | Web | Google Developers

渲染设置

如何使用 Timeline 工具 | Web | Google Developers

Memory

Memory分析closures(闭包)

  1. 选中【Record allocation timeline】选项
  2. 执行一次CG
  3. 单击【start】按钮开始记录堆分析
  4. 连续单击【click】按钮十多次
  5. 停止记录堆分析

分析代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p>不断单击【click】按钮</p>
<button id="click_button">Click</button>
<script>
function f() {
var str = Array(10000).join('#');
var foo = {
name: 'foo'
};
function unused() {
var message = 'it is only a test message';
str = 'unused: ' + str;
}
function getData() {
return 'data';
}
return getData;
}
var list = [];
document.querySelector('#click_button').addEventListener(
'click',
function() {
list.push(f());
},
false
);
</script>
</body>
</html>


上图中蓝色柱形条表示随着时间新分配的内存。选中其中某条蓝色柱形条,过滤出对应新分配的对象:

查看对象的详细信息:

从图可知,在返回的闭包作用链(Scopes)中携带有它所在函数的作用域,作用域中还包含一个str字段。而str字段并没有在返回getData()中使用过。为什么会存在在作用域中,按理应该被GC回收掉的?

::原因::是在相同作用域内创建的多个内部函数对象是共享同一个变量对象(variable object)。如果创建的内部函数没有被其他对象引用,不管内部函数是否引用外部函数的变量和函数,在外部函数执行完,对应变量对象便会被销毁。反之,如果内部函数中存在有对外部函数变量或函数的访问(可以不是被引用的内部函数),并且存在某个或多个内部函数被其他对象引用,那么就会形成闭包,外部函数的变量对象就会存在于闭包函数的作用域链中。这样确保了闭包函数有权访问外部函数的所有变量和函数。了解了问题产生的原因,便可以对症下药了。对代码做如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function f() {
var str = Array(10000).join('#');
var foo = {
name: 'foo'
}
function unused() {
var message = 'it is only a test message';
// str = 'unused: ' + str; //删除该条语句
}
function getData() {
return 'data';
}
return getData;
}
var list = [];
document.querySelector('#click_button').addEventListener('click', function () {
list.push(f());
}, false);

getData()和unused()内部函数共享f函数对应的变量对象,因为unused()内部函数访问了f作用域内str变量,所以str字段存在于f变量对象中。加上getData()内部函数被返回,被其他对象引用,形成了闭包,因此对应的f变量对象存在于闭包函数的作用域链中。这里只要将函数unused中str = ‘unused: ‘ + str;语句删除便可解决问题。


Memory分DOM泄漏

Performance分析DOM泄露例子
分析代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Dom-Leakage</title>
</head>
<body>
<input type="button" value="remove" class="remove">
<input type="button" value="add" class="add">
<div class="container">
<pre class="wrapper"></pre>
</div>
<script>
// 因为要多次用到<pre>节点,将其缓存到本地变量wrapper中,
var wrapper = document.querySelector('.wrapper');
var counter = 0;
document.querySelector('.remove').addEventListener('click', function () {
document.querySelector('.container').removeChild(wrapper);
}, false);
document.querySelector('.add').addEventListener('click', function () {
wrapper.appendChild(document.createTextNode('\t' + ++counter + ':a new line text\n'));
}, false);
</script>
</body>
</html>

  1. 选中【Take heap snapshot】选项
  2. 连续单击【add】按钮6次,增加6个文本节点到pre元素中
  3. 单击【Take snapshot】按钮,执行一次堆快照
  4. 单击【remove】按钮,删除刚增加6个文本节点和pre元元素
  5. 单击【Take snapshot】按钮,执行一次堆快照
  6. 选中生成的第二个快照报告,并将视图由”Summary”切换到”Comparison”对比模式,在[class filter]过滤输入框中输入关键字Detached

从分析结果图可知,导致整个pre元素和6个文本节点无法别回收的原因是:代码中存在全局变量wrapper对pre元素的引用。知道了产生的问题原因,便可对症下药了。对代码做如下就修改:

1
2
3
4
5
6
7
8
9
10
11
12
// 因为要多次用到<pre>节点,将其缓存到本地变量wrapper中,
var wrapper = document.querySelector('.wrapper');
var counter = 0;
document.querySelector('.remove').addEventListener('click', function () {
document.querySelector('.container').removeChild(wrapper);
wrapper = null;//在执行删除操作时,将wrapper对pre节点的引用释放掉
}, false);
document.querySelector('.add').addEventListener('click', function () {
wrapper.appendChild(document.createTextNode('\t' + ++counter + ':a new line text\n'));
}, false);

在执行删除操作时,将wrapper对pre节点的引用释放掉,即在删除逻辑中增加wrapper = null;语句。再次在Devtools–>Performance中重复上述操作:

例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Practice</title>
</head>
<body>
<div id="refA"><ul><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#" id="refB"></a></li></ul></div>
<div></div>
<div></div>
<script>
var refA = document.getElementById('refA');
var refB = document.getElementById('refB');
document.body.removeChild(refA);
// #refA不能GC回收,因为存在变量refA对它的引用。将其对#refA引用释放,但还是无法回收#refA。
refA = null;
// 还存在变量refB对#refB的间接引用(refB引用了#refB,而#refB属于#refA)。将变量refB对#refB的引用释放,refA就可以被GC回收。
refB = null;
</script>
</body>
</html>

整个过程如下图所演示gif:


CPU 分析器
加速执行 JavaScript | Web | Google Developers
解决内存问题 | Web | Google Developers
如何使用分配分析器工具 | Web | Google Developers
如何记录堆快照 | Web | Google Developers

Application

检查和管理存储、数据库与缓存检查和管理存储、数据库与缓存 | Web | Google Developers

  1. 查看和修改本地存储与会话存储。
  2. 检查和修改 IndexedDB 数据库。
  3. 对 Web SQL 数据库执行语句。
  4. 查看应用缓存和服务工作线程缓存。
  5. 点击一次按钮即可清除所有存储、数据库、缓存和服务工作线程。
  1. Name。Cookie 的名称。
  2. Value。Cookie 的值。
  3. Domain。Cookie 所属的域。
  4. Path。Cookie 来源的网址路径。
  5. Expires / Max-Age。Cookie 的 expires 或 max-age 属性的值。
  6. Size。Cookie 的大小(以字节为单位)。
  7. HTTP。指示 Cookie 应仅由浏览器在 HTTP 请求中设置,而无法通过 JavaScript 访问。
  8. Secure。如果存在此属性,则指示 Cookie 应仅通过安全连接传输。

Security

调试安全问题,查看证书,检测https是否非安全源。了解安全问题 | Web | Google Developers

#chrome