Chrome控制台的调试技巧
Chrome控制台有些较为常用的开发技巧,能熟练的掌握可以提高你的工作效率。
在控制台的Element版块中,用鼠标选中一个DOM节点,此时右侧会出现 == $0,这个时候就可以在Console板块输入$0对当前选中的DOM节点进行引用。
在调试样式的过程中,有的DOM节点嵌套太多,依次点击展开较为耗时。可以通过Alt + 鼠标左键,来快速展开多层DOM,也可以进行收起。
也可以通过右键完成这个操作,在控制台中的对象和数组也可以。
在接口的调试过程中,右键选择Store object as global variable,Console面板会自动生成一个temp全局变量
可以再对temp1进行table打印或其他操作
同第一条,Alt + 鼠标左键可以快速展开对象或数组,同理也可以在Network调试接口界面直接展开对象或数组
在调试接口界面或Console板块,对象层级较多时对接字段容易看错字段从属关系。右键点击选择 Copy property path 将对象路径复制到粘贴板里
控制台的Console板块,左上角有个小眼睛的图标(live expression),通过这个图标可以创建表达式。并能随着表达式的计算结果变化而变化,动态刷新表达式的计算结果。
例如添加一个localStorage.uuid的表达式,当运行代码并操作UI,或在控制台改变变量,让它动态刷新表达式的结果
也可以这样
当c的值发生变化时,表达式的结果也会发生变化
Console Importer: 能让你在 Chrome 控制台中调试你的插件
开发过程中,难免会用到一些库和插件,插件的调试一般都是在项目代码中完成的。保存代码刷新页面,项目大时,可能要等待个几秒,进入相应的页面,过程耗时繁琐。有没有更快速便捷的方法?这里推荐一个Chrome插件,名字叫 Console Importer
使用方法也很简单,安装完成之后直接在控制台中输入 $i 来安装你想要调试的库/插件
调试lodash
安装 React 也可以
使用 Array.prototype.at() 方法获取数组最后一个元素
// standard
const list = [1, 2, 3, 4]
const lastItem = list[list.length - 1] // 4
// good
const list = [1, 2, 3, 4]
const lastItem = list.at(-1) // 4
获取数组中元素的下标并删除,使用位运算简化过程
const removeItem = '未知'
const list = [1, 2, 3, 4]
// standard
const targetIndex = list.indexOf(removeItem)
if (targetIndex !== -1) {
list.splice(targetIndex, 1)
}
// standard
const targetIndex = list.indexOf(removeItem)
if (~targetIndex) {
list.splice(targetIndex, 1)
}
// 或 ~targetIndex && list.splice(targetIndex, 1)
// better
list.splice(list.indexOf(removeItem) >>> 0, 1)
注:当数组中不存在removeItem元素时,list.indexOf(removeItem)会返回-1,而splice中的-1为数组的最后一个元素。为了避免最后一个元素被删除,可以使用位运算将 -1 变为数组中不存在的下标(4294967295),splice不会对数组进行任何操作。
使用位运算将小数/数字字符串转化为整数
位运算不能替代parseInt,仅限于小数和数字字符串
console.log(parseInt(2.555)) // 2
console.log('2.555' | 0) // 2
console.log(2.555 | 0) // 2
console.log(~~2.555) // 2
console.log(parseInt('abc')) // NaN
console.log('abc' | 0) // 0
使用Array.prototype.flatMap() 简化代码逻辑
获取 active 为 true 的 userId
const list = [
{ active: true, userId: 'dwP3GcHl' },
{ active: true, userId: 'HSIY5B9D' },
{ active: false, userId: '4Etk68NG' },
{ active: true, userId: 'sfMzhk1N' },
{ active: false, userId: '0dAAGUXS' },
{ active: true, userId: 'WXtpAbSE' },
{ active: false, userId: 'LtuUHahp' },
{ active: false, userId: 'GV71ZIW7' },
{ active: true, userId: 'WAta7pNI' }
]
// standard
// 使用了两次循环
const ret = list
.filter(item => item.active)
.map(item => item.userId)
// good
const ret = []
for (const { active, userId } of list) {
if (active) {
ret.push(userId)
}
// 或 active && ret.push(userId)
}
// better
const ret = list.flatMap(item => item.active ? item.userId : [])
当一个多次运行的函数里,某个代码块只需运行一次时
// bad
let flag = true
const func = () => {
if (flag) {
console.log(1)
console.log(2)
flag = false
} else {
console.log(3)
console.log(4)
}
}
func() // 1 2
func() // 3 4
func() // 3 4
func() // 3 4
// better
let func = () => {
console.log(1)
console.log(2)
func = () => {
console.log(3)
console.log(4)
}
}
func() // 1 2
func() // 3 4
func() // 3 4
func() // 3 4
给对象添加一个迭代器[Symbol.iterator],并使用 for ... of
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function () {
const keyList = Object.keys(this)
const { length } = keyList
let index = 0
return {
next: () => {
return {
value: `${keyList[index]}的值为${this[keyList[index]]}`,
done: index++ === length
}
}
}
}
for (const i of obj) console.log(i)
// a的值为1
// b的值为2
用原生JS向前/后推导日期
这个也是无意中发现。在不用插件的情况下,JavaScript中的 new Date()在某些方面还是蛮好用的,除了过程繁琐一点。Date的入参对负数也做过处理,会自动向前推导,同理也可以向后推导。
// 设定今天是2022年7月13日,第二个参数月数是从0开始。
//向前100天
const date = new Date(2022, 6, 13 - 100)
console.log(date.toLocaleDateString())
// 2022/4/4
// 向后100天
const date = new Date(2022, 6, 13 + 100)
console.log(date.toLocaleDateString())
// 2022/10/21
// 向前50个月
const date = new Date(2022, 6 - 50, 13)
console.log(date.toLocaleDateString())
// 2018/5/13
CSS中的 all 属性和 display: revert;
先上一段代码看下效果
<style>
.wrapper {
width: 500px;
height: 100px;
background: yellow;
border: 1px solid #ccc;
color: red;
font-size: 20px;
font-weight: bolder;
}
</style>
<div class="wrapper">
This is a content
</div>
给他一个all: unset的CSS属性
能看到上述代码的所有样式都会失效
官方是这么对 all 属性解释的:
all 属性会重置除unicode bidi, direction和CSS自定义属性外的所有元素属性。它可以将属性设置为其初始值 。
可以理解为,等同于它的所有已被设置的属性(包括默认自带属性),均被设为了unset;
.wrapper {
width: unset;
height: unset;
background: unset;
border: unset;
color: unset;
font-size: unset;
font-weight: unset;
/* div 有个默认 display: block; 属性 */
display: unset;
}
这里有个隐藏点,在控制台中可以看到。一个 <div> 他有个默认的 display: block; 属性,所以在设置了all: unset;之后,它的 display 属性也会被 unset 掉;大家都知道,块级元素默认独占一行,此时的 <div> 已经失去了块级元素的特征。此时它的宽度是由它的内容决定的。
但我的目的是为了清除掉所有“已经设置的CSS样式”,而 <div> 失去了块级元素的特征,这样显然不符合我的要求,这个时候display: revert; 就派上了用场。display: revert; 会让浏览器按照它默认的 display 值来计算,也就是 display: block;
.wrapper {
all: unset;
display: revert;
}
display: revert; 也有其他用法。
例如一个需求,通过JavaScript代码来隐藏掉一个 <li> 元素。
li.style.display = 'none'
如果要再次把它显示出来,应该怎么写?
li.style.display = 'block'
这样看似没有问题,实际上会发现 <li> 前面的黑点没有了。
从 Chrome 控制台中可以发现,<li> 原本的 display 属性并不是 block,而是list-item。前面的黑点是 list-item 属性实现的。所以这里就不能给他赋 block 这个值。
那么这里应该给 display 设置 revert,这样浏览器会把它的 display 按照它默认的 display: list-item; 来计算。
最后分享一个前段时间项目中遇到的一个BUG
BUG是前端需要把后端返回的一个HTML模板展示在页面上。大家可以把下面的代码复制到控制台中试一下。
// template 是后端返回的一个模板
const template = '<span class="content">This is span tag</span>'
document.body.innerHTML = template
然后奇怪的事情就出现了,这个标签无法通过 class 给标签设置样式。从Chrome控制台能看到这个标签变成了这个样子。
排查了很久发现,是后端返回的HTML模板有问题。span 和 class 中间穿插了一个不间断空格,长得和大家常用的空格一模一样。
// 大家可以复制下来试一试
console.log(' ' === ' ') // false
在写HTML时,标签中的属性会用空格来做分离,使解析出标签的各个属性。这个地方不是空格的话,浏览器会把它当做一个整体“标签名”来解析,就变成了你自定义的标签。中间的“不间断空格”相当于任何一个字母,任何一个数字。
既然class和标签名变成了一个整体,那么通过class给它设置样式,当然就不会生效了。
这里通过 charCodeAt() 查看它的Unicode码的位置,来看清他们的真面目。
而大家常用的空格是
两个不同的字符相比较,当然不会相等了。
console.log('\u00a0' === '\u0020') // false
所以解决方案是把里面的“不间断空格”的Unicode码通过 replace() 替换成常规空格。
不要把空格复制粘贴到replace里面,因为其他同事看到了可能会疑惑。
// bad
// 不要这么写
const template = '<span class="content">This is span tag</span>'
document.body.innerHTML = template.replace(' ', ' ')
正确做法是:
// better
const template = '<span class="content">This is span tag</span>'
// 接口有坑,返回字段里面包含其他 Unicode 字符
document.body.innerHTML = template.replace(/\u00a0/g, ' ')