0%

你可能不知道的 console

浏览器控制台是前端调试中使用最频繁的调试工具,没有之一。但它实际上还有很多不为人所知的功能~

console

console 是浏览器提供的接口。它是一个对象,在控制台中可以查看当前浏览器支持哪一些方法。下面介绍一些比较实用的 api:

log

console.log 这可能是学习前端调试技术最基础的第一步了吧,你还记得最开始是从什么地方学习到使用这个方法来调试吗?

1
2
console.log(obj1 [, obj2, ..., objN);
console.log(msg [, subst1, ..., substN);

console.log 的功能就是向控制台输出一条信息。它可以传入多个参数,输出的行为取决于第一个参数是否是一个代替字符串。

如果是替换字符串,则后面的参数会依次替换掉字符串里的占位符; 如果不是替换字符串,则输出每一个参数的值:

1
2
3
4
5
6
7
8
9
10
var name = 'anran758';

// 常规使用方式,输出原始值
console.log('Hello'); // Hello
console.log(name); // anran758
console.log(`Hello, ${name}`); // Hello, anran758

// 第一个参数是替换字符串,后面是替换的规则
console.log('Hi, %s. what are you doing', name); // Hi, anran758. what are you doing
console.log('%c I am some great text', 'font-size: 50px;'); // 假装 50px: Hi, anran758. what are you doing

下面是替换字符串所支持的参数, 学习过 C 语言的同学是不是有种熟悉的感觉呀~

占位符描述
%s字符串
%d or %i整数
%o or %O对象
%f浮点数
%c样式代码

在使用时需要注意的一点是:不同占位符在传入值时会预先针对类型的不同而进行格式化,比如:

1
2
3
4
5
6
// 我们在使用了数字占位符,却传入字符串. 内部进行格式化时进行了类似 parseInt('anran758', 10) 的数据转换
// 因此替换结果为 NaN
console.log('result: %d', 'anran758'); // result: NaN

// 再比如,我们传入一个浮点数,经过数据格式化为 3。这一种是符合预期,因为我们要的就是整数
console.log('result: %d', 3.25); // result: 3

了解了这些扩展方式后,我们可以来做一些有意思的事~

比如,如果你打开控制台去访问知乎、百度等网站时,可能会发现控制台有一些开发者留下的彩蛋~ 如招聘信息或者自家公司的立体 logo:

不过值得一提的是,js 规范中并没有规定 console 该如何实现,不同的浏览器调用 console 后的表现也会不一致。

这导致有一个常见的问题发生: 在调用接口时,明明请求的数据还没回来,打印出来却是有值的。实际上原因可能就是你所使用的浏览器,在实现时该 API 是异步的,它提前给你显示了出来。这个问题我在 segmentfault 的问答区中看到有很多朋友问了这样的问题,特意提一提这事。

解决的方法也很简单,如果你觉得这个值不对劲的话,你可以先将对象转为 json 字符串,然后再解析为对象, 这样就能将原始值输出。

1
onsole.log(JSON.parse(JSON.stringify(obj)));

info, warn, error

如果你使用过一些脚手架来搭建项目或者是使用过 SDK 的话,那么在控制台中你能看到以下相关的使用信息。

比如在载入程序时会发出一些运行信息会通过 log 或者 info 来输出信息。

或者你使用的框架中检测到你使用了一些将来可能会被废弃的 API,这时它可能会在控制抛出一个 warn 来警告你。

再或者你使用人家 SDK 的姿势不对,程序内部检测到压根不是这么用的,那么 SDK 内部会抛出错误信息,我们可以通过错误信息来进行排查错误。

这三个 API 更多的是给开发人员提供额外的信息,来查看页面的运行情况,更多用于被封装过后的组件或框架中。

1
2
3
4
5
6
7
8
// Info
console.info('Hi, This is message')

// warning 警告
console.warn('On, Your operation may cause a security breach!')

// Error 报错
console.error('Shit! Variable does not exist!')

table

console.table只接受一个数组或者对象, 可以接受一个额外的参数来描述表格的列数。它会把数据通过表格的形式打印出来, 这样我们看数据的时候就能直观了很多:

1
2
3
4
5
6
7
var array = [
{ name: 'Jack', age: 12 },
{ name: 'Tome', age: 18 },
{ name: 'baka', age: 15 }
];

console.table(array);

group

console.groupconsole.groupEnd 是成对出现的,就像我们使用的标签一样。group在控制台创建一个新的分组, 输出到控制台上的内容都会被添加一个缩进, 表示该内容属于当前分组, 直到调用 console.groupEnd() 之后, 当前分组才结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
var boys = [
{ name: 'Jack', age: 12 },
{ name: 'Tome', age: 18 },
{ name: 'baka', age: 15 }
]

boys.forEach(item => {
console.group(`${item.name}`)
console.log(`This is ${item.name}`);
console.log(`${item.name} is ${item.age} years old`);
console.log(`${item.name} is ${item.age * 7} years old`);
console.groupEnd(`${item.name}`)
})

dir

console.dir,在控制台中显示指定 JavaScript 对象的属性,并通过类似文件树样式的交互列表

我们知道console.log实际上是可以输出 DOM 节点的, 但很多时候我们想查看的不是 DOM 节点,而是该 DOM 节点下的属性,这里就可以使用 dir 来代替 log 来输出 DOM 节点对象.

1
2
3
4
var head = document.getElementById('head');

console.log(head);
console.dir(head);

但值得注意的是,这个特性是非标准,尽量不要在生产模式下使用。

count

count, 如同字面意思一样. count()会输出每一次被调用的次数. 该方法的兼容性也需要注意, 不适用于生产模式.

1
2
3
4
5
6
7
8
console.count('anran758')
console.count('anran758')
console.count('zero')
console.count('anran758')
console.count('zero')
console.count('anran758')
console.count('zero')
console.count('anran758')

clear

clear可以清楚调用这个 API 之前的所有 log 信息,这在一个 log 很多很乱的情况下进行某个功能调试会很有作用。

1
2
// something info
console.clear();

time

启动一个计时器(timer)来跟踪某一个操作的占用时长。每一个计时器必须拥有唯一的名字。页面中最多能同时运行 10,000 个计时器。跟 group 一样, time 也是配套的。

当以此计时器名字为参数调用 console.timeEnd() 时,浏览器将以毫秒为单位,输出对应计时器所经过的时间.

比如我们统计一下请求接口的时间:

1
2
3
4
5
6
7
console.time('fetching data');
fetch('https://api.github.com/users/anran758')
.then(data => data.json())
.then(data => {
console.timeEnd('fetching data');
console.log(data);
});

这样我们就轻易的知道了这次我们请求花费了多少时间啦~ 但值得注意的是,该方法的统计时间在微观下不够精准,更多时候我们还是需要使用更专业的测试工具后者网站来完成测试性能的工作。

assert

console.assert()还是蛮有意思的. 它第一个参数接受一个断言(声明), 第二个参数是一个message. 如果断言为 false,则将一个错误消息写入控制台。如果断言是 true,就当做没发生。语法如下:

1
console.assert(assertion, message [, subst1, ..., substN]);

这里的断言不一定是false才会触发错误. 我特意去测试了一下, 触发的规则也跟if的判断里的逻辑相反. 只要是断言是0, NaN, undefined, false, null, 空字符串''就会激活报错.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Assertion failed: Here is the "name" can not be empty
var str = '';
console.assert(str, 'Here is the "str" can not be empty');

// Assertion failed: 0 is not allowed!
var num = 0;
console.assert(0, '0 is not allowed!');

// Assertion failed: That is wrong!
console.assert(1 === 2, 'That is wrong!');

// 什么都不会发生
console.assert(1 === 1, 'That is wrong!');

以上是 18.01.30 分享出来的内容,分享至今又发现了一些好用的功能,如果你跟笔者使用的都是 chrome 浏览器的话,可以继续往下看,笔者将逐一进行介绍:

chrome devtools

下面介绍 chrome devtools 在控制台提供的一些语法糖,这些方法只能控制台使用,而不能直接在代码里使用。区分是浏览器提供的还是我们自己代码中定义的方法可以使用console.log(method),如果打印出来的是 [Command Line API] 就是内置方法。

$(selector, [startNode])

我相信做过前端的朋友都了解 jqeury$ 选择器,乃至于我们看到这个符号就能联想到 DOM 相关的东西。

IE9+ 引入了 document.querySelector(),可以让我们很方便找到对应的 DOM 元素,但该 API 拼写起来确实有点麻烦,并且该方法的使用频率还不低。因此 chrome 开发工具提供一个$的别名给我们使用。

$ 接受两个参数,第一个参数接受一个选择器,第二个参数接受一个 DOM 元素表示从该元素下开始搜索,不传默认为 document.

我们可以简单做一个 demo 实验一下:

1
2
3
4
5
6
<!-- 一个简单的嵌套结构 -->
<div class="main main.container">
<div class="start"></div>
<div class="main content"></div>
<div class="end"></div>
</div>

将上面的 demo 保存至 html 中后,在 chrome 浏览器中打开该文件。输入以下代码查看实际效果:

1
2
3
4
5
6
7
8
9
10
11
// 查看是否是 Command Line API,如果不是的话那就意味着你在全局中引入了第三方库,它会覆盖浏览器默认的 API
$;

// 从 document 中获取第一个 main 元素,
var container = $('.main');

// 我们将上面获取到的节点作为 startNode, 从该节点往下找可以找到 content
var content = $('.main', container);

console.log('container: ', container, '\n\n');
console.log('content: ', content, '\n\n');

$$(selector, [startNode])

$$ 相当于 document.querySelectorAll,但细节上看会有些不同。$$ 调用后会返回一个数组,而后者调用后返回的是一个 NodeList,缺少一些数组的方法。

$$$ 接受的参数的功能是一致的,就不过多介绍:

$_

说这个方法之前需要先科普一下返回值这个概念:js 每一个表达式都会有返回值,比如你定义一个函数,但是没有 return 任何东西,那么调用这个函数后的返回值就是 undefined。这个知识点可以在 你不知道的 JavaScript 系列中了解更详细的解析。

1
2
3
4
5
6
function fn() {} // 返回值是 undefined
fn(); // 返回值是 undefined

var n = 0; // 返回值是 undefined
n += 1; // 返回值是 1
[1, 2, 3].map(n => n * 2); // [2, 4, 6]

好啦,话归正题~有时候为了方便,我们会直接在控制台进行运算,此时我们可以使用 $_ 获取上一个表达式的值。在某些时候可以提供便利性。

cleardir 分别是 console.clearconsole.clear、dir 的别名。

copy(obj)

该方法可以将数据 copy 至粘贴板。

当初发现这个 API 时还是挺意外的。当时需要将城市映射表数据改造为另一种结构,就直接在控制台中处理好了。接着就在想如何将数据弄出来,数据量很大,不好拷贝下来。然后突发奇想,看看控制台有没有支持复制数据的功能,随便尝试了以下,然后就成功了。

这就意味着以后我们做一些数据处理可以直接在控制台中进行处理,在某些方面上会挺方便的,比如快速生成一个长度为 20 的数组,数组内有 0 ~ 19 的数字:

1
2
3
4
5
6
7
8
9
10
11
12
// 要检查一下有没有全局变量覆盖了默认的 API
// ƒ copy(value) { [Command Line API] }
copy;

var arr = [];
for (let i = 0; i < 20; i++) {
arr.push(i);
}

// 此时可以检查是否复制成功
console.log(arr);
copy(arr);

inspect(object/function)

inspect 接受一个对象或者函数。

如果这个参数是一个 DOM 元素的话,并且它是存在 DOM 树中的,那么它会切换到 Elements 这个 tab。如果这个 DOM 元素没有被插入 DOM 树种的话,那仅仅会切换 tab。

如果传入的是一个函数,会将这个函数传入 source 面板,用以检查函数(比起查看 DOM 的功能, 传递函数后的功能除了格式代码外并没有想到好的用处)

getEventListeners(obj)

getEventListeners(obj) 返回指定 DOM 绑定的事件监听器,有时候调试 DOM 时会挺方便的。以知乎首页为例,这样可以很清晰的看到 document.body 绑定的事件:

monitor/unmonitor(function)

调用指定的函数时,会向控制台记录一条消息,指示函数名称以及调用时传递给函数的参数。使用unmonitor(function)停止监控。

相当于在这个函数的头部添加了一个 console.info 的信息,算是语法糖吧~ 主要是调试时使用。当

monitorEvents/unmonitorEvents(object[, events])

monitorEvents 监控指定 DOM 的事件,如果不传第二个参数,就监听所有事件的发生。

相对应的是使用unmonitorEvents(object[, events])停止监控事件。

1
2
3
4
5
6
7
8
9
10
11
// 监控/停止监控 window 中触发的所有事件
monitorEvents(window);
unmonitorEvents(window);

// or 监控/停止监控 window 中触发的 mouseover 事件
monitorEvents(window, 'mouseover');
unmonitorEvents(window, 'mouseover');

// or 监控/停止监控 window 中触发的指定多个事件
monitorEvents(window, ['mouseover', 'mouseout']);
unmonitorEvents(window, ['mouseover', 'mouseout']);

别名

对于一些常用的方法, chrome tools 提供了别名以供我们快速使用:

缩短前缩短后
console.clearclear
console.dirdir
console.tabletable
Object.keyskeys
Object.valuesvalues

参考资料:

「请笔者喝杯奶茶鼓励一下」

欢迎关注我的其它发布渠道