阅读 7149

调试第一步:让强大的console家族助你一臂之力

console相比大家一定不陌生,平时项目中用的最多的就是console.log()方法吧。但是console相关的方法有很多,涉及的调试面板的相关内容比较广泛,彻底弄清楚它们并在项目中合理使用,有助于我们更好的开发和调试。

下面我们在控制台打印一下console,看看它还有哪些神奇的方法:

如果没了解过console的,似不似惊呆了,console还有这么多方法?下面我们从最简单的console.log方法开始,逐个分析其他方法以及所涉及到的调试技巧。

1.console.log()打印内容。这个方法那是太熟悉不过了,平时也是用的最多的或许也是这一个吧!!!基本用法呢就不说了,主要聊一聊console.log()的占位符。其共有五种占位符,分别是:

  1. %s 字符串
  2. %d 或 %i 整数
  3. %f 浮点数
  4. %o 对象的链接
  5. %c CSS格式字符串

如果方法的第一个参数中使用了占位符,那么就依次使用后面的参数进行替换。

const name = 'chinaBerg';
const age = 88;
const money = 12.88;
const obj = {
    status: '很积极'
}

console.log('我叫%s,%d岁,有%f元,状态:%o', name, age, money, obj.status, '又打印一句话')复制代码

谷歌打印结果:


可以看到我们后面使用的参数对前面的占位符进行了替换,有点像我们字符串拼接的简化操作。比如我们es5中的字符串拼接:

console.log('我叫' +  name + ' ,' + age +'岁,有' + money + '元')复制代码

当然了,es6已经有了更强悍的字符串模板:

console.log(`我叫${name}${age}岁, 有${money}元`);复制代码

拼接的字符串中混着使用也是可以的:

//例如,这里演示的:第一参数是拼接的字符串,第二参数插入字符串的浮点数
console.log('我叫' +  name + ' ,' + age +'岁,有%f元',  money)复制代码

但es6的字符串模板中,只能使用%c占位符,其他占位符是没有效果的。

// 注意这里字符串模板的最后插入了%f
console.log(`我叫${name}${age}岁, 有%f元`, 12.88);复制代码


%c占位符还是略有趣味的:

const css1 = 'font-size: 22px;font-weight: bold';
const css2 = 'border: 1px solid green';
const css3 = 'color: #fff;background: #f00';

// 占位符填入				
console.log('%c %s + %s = %s', css1, 1, 2, 3);
// 字符串拼接形式中插入%c占位符
console.log('%c我的名字叫' + name + ', 今年' + age + '岁', css2);
// es6字符串模板中插入%c占位符
console.log(`%c我叫${name}${age}岁, 有%f元`, css3);
复制代码

谷歌打印效果:


可以看到这些打印的内容已经被添加了我们的样式。

---------------------------------------------------------------------

2.但是和console.log()很像的还有俩,一个是console.info(),一个是console.debug()。其实这个三个功能都是一样的,只不过有些区别,下面就具体介绍一下这三个方法。

先来看一下下面这三行代码,在谷歌、火狐、ie上的打印效果:

console.log('我是console.log()打印出来的');

console.info('我是console.info()打印出来的');

console.debug('console.debug()打印出来的')复制代码
谷歌浏览器控制台打印效果:

火狐控制台:


ie控制台:


从结果可以看出:

  • console.log()方法,无论哪个浏览器,打印出的效果都是一样的。
  • console.info()方法,ie没有打印出来,即不支持这个属性。但是在谷歌和火狐上又略有区别:打印的结果是一样的,但是火狐控制台上,会在打印的结果前面添加一个类似i的小符号。
  • console.debug()方法,谷歌和opera是不支持的,ie和火狐是支持的。

所以呢,既然三个方法功能是基本一样的,我们如果只想打印一些内容的话,还是老老实实的使用console.log()稳一点。当然了,这也抵不住你就使用火狐来debug呢!可是,考虑到如果你的代码库的某些打印信息需要给别的开发者看的话,还是用兼容性更好的稳一些。

---------------------------------------------------------------------

3.console.clear() 清除控制台打印的内容,并将光标回归到第一行。


这个属性没什么好说的,和我们点击控制台的这个清除按钮的效果一样。

---------------------------------------------------------------------

4. console.assert(表达式 [,arg1, arg2……argn])打印断言。

第一个参数是用来判断是否打印断言的表达式,只有当表达式的值为falsy的时候,才会打印后续的参数:

const arr = [1, 2, 3];

// 打印断言,如果arr[0]的值不等于2,则打印提示信息
console.assert(arr[0] === 2, 'arr[0]的值不等于2');复制代码

谷歌控制台打印如下:


如果没有参数,默认打印如下字符串:

其他注意点:
  • 客户端的console.assert()打印断言,并不会阻塞后续代码的执行,只是在断言的表达式为false的时候,向控制台打印你的内容。
  • 而在node.js中,值为假的断言将会导致一个AssertionError被抛出,使得代码执行被打断。这两者是有区别的。

---------------------------------------------------------------------

5.console.count() 打印计数。输出他被调用了多少次。

传递一个参数作为计数提示:

for (let i = 0; i < 10; i++) {
    console.count('我被调用了');
}复制代码

谷歌控制台:


简单修改一下:

for (let i = 0; i < 10; i++) {
    console.count(`我是${i}我被调用了`);
}复制代码

打印效果:


这个方法意思就是:向控制台写入在同一行使用相同标签调用 count() 的次数。

就是如果你给count()传递的参数值不一样,那么是分开计数的。

再看一个简单的示例:

function fun (name) {
    console.count(name)
}
fun('小米');
fun('小刚');
fun('小米');复制代码

效果:


所以,即便同一个函数,我们可以清晰的知道小米同学被调用了2次,小刚同学被调用了1次。

如果不传递参数,默认的计数提示标签是default字符串:

for (let i = 0; i < 10; i++) {
    // count()没传递提示标签
    console.count();
}复制代码

效果如下:


一般在某些循环中,如果我们想知道一个函数或者变量被执行或者调用了多少次的时候,可以使用console.count()方法,而通过传递提示标签,我们更可以清晰的知道一个函数分别被不同的情况调用了几次,从而帮助我们定位错误信息。

---------------------------------------------------------------------

6.console.time()和console.timeEnd()打印计时。用来跟踪某一个操作的占用时长。每一个计时器必须拥有唯一的名字,time()的参数名和timeEnd()的参数名要一样。可以没有参数,默认计时提示为default:

// 立即启动计时器
console.time()

// 某些操作
for (let i = 0; i < 10000; i++) {
    // 某些操作				
}

// 立即结束计时器,并输出计时结果
console.timeEnd()复制代码

控制台打印效果如下:


传递计时器提示:

// 立即启动计时器
console.time('time')

// 某些操作
for (let i = 0; i < 10000; i++) {
    // 某些操作				
}

// 立即结束计时器,并输出计时结果
console.timeEnd('time')复制代码

控制台打印效果如下:


注意:

  • 页面中最多能同时运行10,000个计时器
  • 该方法并不会将结算结果返回到js中,而只是能打印在控制台上。所以不能使用此方法在js中来作为普通计时器使用或者进行性能收集器的一部分。


7.console.dir() 输出以 JavaScript 形式表示的指定对象。如果正在记录的对象是 HTML 元素,将输出其以 DOM 形式表示的属性。

打印一个对象:

// 一个对象
const obj = {
    name: '某某渣',
    age: 22,
    sex: '男'
}

// dir打印
console.dir(obj);

// log打印
console.log(obj);复制代码

谷歌控制台效果:


对于对象或者json等,console.log()和console.dir()效果基本一样。

但是如果打印的是一个dom元素:

// dir打印
console.dir(document.body);

// log打印
console.log(document.body)复制代码
  • console.dir()会将dom的所有属性和事件都被打印出来:
  • console.log()打印的就是dom:

如果哪天你coding的时候突然想不起来dom的某个方法了,你完全可以console.dir()一下,而不必去翻阅资料了。

---------------------------------------------------------------------

8.console.dirxml(object) 如果可以,输出 object 子级元素的 XML 表示形式,否则输出其 JavaScript 表示形式。 在 HTML 和 XML 元素上调用 console.dirxml() 等同于调用 console.log()。

---------------------------------------------------------------------

9.console.group() + console.groupEnd()将控制台输出的内容进行分组。

将打印的信息归类分组打印,并且可以展开、折叠。这在输出大量数据的或许有用。

// console.groupCollapsed() + console.groupEnd()的形式,默认是折叠的
console.group('分第一组');
console.log('html')
console.dir({ type: '前端'}),
console.groupEnd('分第一组')

// console.group() + console.groupEnd() 默认是展开的
console.group('分第2组');
console.log('php')
console.dir({ type: '后台'}),
console.groupEnd('分第2组')复制代码

谷歌打印效果:


---------------------------------------------------------------------

10.console.table()可以将数组、对象等复杂类型的数据打印成表格的形式。

打印简单的数组:

const arr = ['a', 'b'];
			
console.table(arr)复制代码

打印复杂的数组:

const arr = [
    {
        name: '小明',
        age: 22,
        likes: ['跳舞', '上网']
    },
    {
        name: '小刚',
        age: 23,
        likes: ['撸码', '计算机']
    }
];
				
console.table(arr)复制代码


打印对象:

const obj = {
    name: '小明',
    age: 22,
    likes: [
        {
            a: 1,
            b: 2
        },
        {
            a: 3,
            b: 4
        },
    ]
}
				
console.table(obj)复制代码


通过console.table()打印出的结果,我们可以很直观的看到数据的组成。

---------------------------------------------------------------------

11.console.trace()堆栈中调用此方法的路径。

如果想要清楚地知道一个函数的调用轨迹,可以将此方法写在函数内部,便可以跟踪函数的调用轨迹,代码实现如下:

function test(name) {
    console.trace(`此处调用了${name}`)
}
				
function doSome (name) {
    test(name);
}
				
doSome('翠花');复制代码

谷歌控制台打印如下:


此处打印出了js中调用test()的所有堆栈位置。从上到下依次为最里层的调用一直到最外层调用。平时我们使用第三方库的时候,如果写法不对,经常可以在控制台看到我们的报错信息,并且像这样打印出了错误位置的堆栈信息。

---------------------------------------------------------------------

12.console.warn()打印一条警告信息

console.warn('我是一条警告')复制代码

谷歌打印结果如下:


打印结果会有一条黄色背景,前面附加一个感叹号的图标。

默认是收起的,点击可以展开,列出了警告位置的堆栈信息,点击堆栈位置可以对应打开警告位置代码。这个的使用没什么好说的,如果你需要打印一条警告信息,用这个方法很合适。

---------------------------------------------------------------------

13.console.error()打印错误。

console.error('我这里出现了错误,我来告知用户')
复制代码

谷歌打印结果如下:

该方法主要用来打印错误,打印结果的样式如上图。也没什么好说的,不过如果你开发第三方库的时候,可以用到。但是throw抛出错误的方式也会用到不少。

---------------------------------------------------------------------

14.console.profile() 和 console.profileEnd() 新建一个性能分析器(基于cpu的使用情况)。用于函数性能分析的利器。

我们已经知道通过console.time()和console.timeEnd()我们可以知道一段代码的运行时间。但是,如果我们需要分析较为复杂的js逻辑代码,继而从中找出程序运行的性能瓶颈的话,如果继续使用console.time()方法的话,意味着我们要插入大量的该方法,这显然是笨拙的,也是让我们不可接受的。

相对于复杂逻辑的JavaScript程序调优,console.profile() 和 console.profileEnd()新建性能分析器便派上用场了。

用法和time的一样,console.profile()开始,console.profileEnd()结束,需要传递一个参数作为标签使用,说俗了点就是为这个性能分析器起个名字。看下如下代码,我们测试几种不同for循环书写方式的耗时情况:

// 简单新建一个数组吧,新建一个一千万个成员为1的数组
let arr = new Array(10000000).fill(1);
				
// 第一种for循环书写方式				
function fun1 () {
    for (let i = 0, len = arr.length; i < len; i++) {}
}

// 第二种for循环书写方式				
function fun2 () {
    for (let i = arr.length; i --; ) {}
    fun1();
}

// 第三种for循环书写方式		
function fun3 () {
    for (let i = 0, item; item = arr[i++]; ) {}
}

// 执行三个函数		
function fun () {
    fun1();
    fun2();
    fun3();
}

// 立即开始一个性能分析器
console.profile('测试for循环');
fun();
//
console.profileEnd('测试for循环');
复制代码

运行如上程序,打开谷歌控制台一看:

嗯,没错,打印了两句话,性能分析器开启和结束。纳尼~说的性能分析器呢???小拳拳要捶你胸口了!!!

别急,性能分析器不在这里,在javascript Profiler面板中。


点击javascript Profiler面板,便可以看到性能分析器。如果你没有上面红框标识的面板,那么点击右边的三个点,在下拉菜单中依次选择More tools -> JavaScript Profiler选项,就可以将该选项添加到上面的红框位置。然后点击该面板,进入对应内容:

可以看到,已经有了刚才的性能分析情况,这里清晰展示了每一个函数执行过程所耗时间。然后我们点开每一个函数看下具体的情况:

图上我进行了标注:

  • 1处,Self Time表示当前函数自身运行耗时,什么意思?就是说当前函数自身执行耗时,不包括当前函数中调用的其他函数运行耗时。
  • 2处,Total Time表示当前函数运行总耗时,包括了自身运行耗时+函数内部调用的其他函数的执行耗时。
  • Function那一列,我们通过上图打开的fun1那一栏说明,fun1展开后的结果包括funfun2,这指的是函数fun1在函数funfun2中被调用执行的耗时。通过代码我们知道,fun1函数确实在fun函数和fun2个被调用过1次,所以这里展示了fun在这两处被调用执行的耗时时间。
  • 每个函数行最右边还有会堆栈位置,点击即可进入resouce面板中该函数所在的文件位置。

如果你关注fun1函数的执行时间,你可以点击选中fun1这一行,然后点击上面的眼睛图标,将自动只为你展现fun1函数的信息:


  • 选中函数行,点击眼睛即针对性的展示当前函数。
  • 选中函数和,如果点击×号,将会删除当前函数行。
  • 选中函数行点击眼睛进入后,如果想返回到上述全部函数行的面板,可以点击上图刷新按钮。或者删除了函数行后也可以恢复如上图。
  • 上图三个按钮只有在颜色变深的时候才可点击,眼睛和×号只有在选中函数行的情况下可点,刷新按钮在进入或者删除函数行之后可以点。

还有一点没介绍,就是该这种数据展示方式,是默认的方式:Heavy(Bottom Up),即将所有执行的函数,按照耗时长度,从上到下降序排列,耗时的在最上面,不耗时的在最下面。但是他还有另外两种方式(Chart 和 Tree),如下图:


我们先来说说Tree型数据分析,下面我们先切换到Tree型看下图:

将每个函数行打开后,显示了该函数所调用的函数。这种数据分析的展示方式其实是,先展示最外层的函数,展开后,显示该函数所调用的所有函数,依次往里类推。每一行都展示该函数执行的耗时。其他操作同上。

最后说一下Chart的方式,Chart是以图片给我们展示函数运行后性能,可以看到每个函数开始运行的时间节点,如下图:

大致分为上下两部分,上部分蓝色区域为cpu占用的大体走势图,可以清晰地看到每个时间节点的cpu占用情况,是高是滴一目了然。下半部分为每个函数开始运行的时间节点。

如果点击上部分蓝色区域,还可以更细粒度查看当前事件的函数运行情况(在当前时间节点划分为更细的力度),如下图:


鼠标移入某个函数,还可以看到当前函数所运行的耗时情况,如下图:


console.profile() 和 console.profileEnd()函数性能分析器的建立,给我们分析函数性能带来的非常大的便利,这对于我们检测程序运行瓶颈非常有帮助。 谷歌为我们开发这么好使的调试工具,一定要在需要的时候好好利用。

---------------------------------------------------------------------

15.console.timeStamp('事件信息'),在Performance(以前叫Timeline)性能面板中的会话录制期间插入一条添加一个事件。

说到这个console.timeStamp()方法,这个方法在我们进行性能调试的时候会用到。说到这个方法首先要提到Performance性能面板,因为该方法打印出来的结果需要在这个调试面板中查看,准确的来说,该方法是配合性能面板来调试的:


如上图,在Perdormance面板中,我们可以分析当前页面的性能,可以得知页面加载和用户交互相关的事件分析结果。关于Performance这块的内容,如果仔细说起来,内容是比较多的。这里暂且只介绍和console.timeStamp方法相关的内容。以后可以单独把这块拿出来细细分析和记录。

回归正题,console.timeStamp可以在时间轴上写入一个事件:

// 一些其他操作
for (let i = 0; i < 10000; i ++) {}

// 在录制会话期间插入的第一个事件		
console.timeStamp('第一个循环完了')
				
// 一些其他操作
for (let i = 0; i < 10000; i ++) {}
				
// 在录制会话起价插入的第二个事件
console.timeStamp('第2个循环完了')复制代码

录制完会话后,我们输入移入下图红框左上方的黄色竖线上可以看到弹出一个提示框,上面标注了Timestamp提示:‘第一个循环完了’,并且还有该事件插入时的时间节点。


---------------------------------------------------------------------

16.console.markTimeline()方法效果等同于console.timeStamp(),是console.timeStamp()以前的写法,已经淘汰了。不多说。

---------------------------------------------------------------------

17.console.timeLine('标签参数')配合 console.timeLineEnd('标签参数')录制一段时间的时间轴。

我们在上面的console.timeStamp方法中了解到,在Performance面板中,我们可以录制当前页面的会话信息,而通过console.timeline和console.timelineEnd可以只录制某一段时间的会话信息。

// 录制第一段时间的会话信息
console.timeline('测试循环100万相关的性能分析')
for (let i = 0; i < 1000000; i ++) {}
console.timelineEnd('测试循环100万相关的性能分析')


// 录制第二段时间的会话信息				
console.timeline('测试循环1000万相关的性能分析')
for (let i = 0; i < 10000000; i ++) {}
console.timelineEnd('测试循环1000万相关的性能分析')复制代码

在我们的Performance面板中,点击开始录制当前页面,然后查看录制后的结果:


console.timeline('参数标签')console.timelineEnd('参数标签'),两个方法需要接收相同的一个参数标签,就是一个标识而已。

这里会了这个用法之后,更多的是怎样在Performance中进行性能的分析,然后找出影响程序性能的瓶颈,这才是重要的。


弄清楚了强大了console家族,可以在开发调试过程中祝我们一臂之力。简单的运用console相关内容,是调试入门的第一步,更强大还在于谷歌的调试面板的灵活合理使用,更高级的还有自动化测试。


如果觉得喜欢就❤❤❤一下吧!!!