前端实用技术分享

505 阅读6分钟

Chrome控制台的调试技巧

Chrome控制台有些较为常用的开发技巧,能熟练的掌握可以提高你的工作效率。

在控制台的Element版块中,用鼠标选中一个DOM节点,此时右侧会出现 == $0,这个时候就可以在Console板块输入$0对当前选中的DOM节点进行引用。

image.png

image.png

在调试样式的过程中,有的DOM节点嵌套太多,依次点击展开较为耗时。可以通过Alt + 鼠标左键,来快速展开多层DOM,也可以进行收起。

image.png

也可以通过右键完成这个操作,在控制台中的对象和数组也可以。

image.png

在接口的调试过程中,右键选择Store object as global variable,Console面板会自动生成一个temp全局变量

image.png

image.png

可以再对temp1进行table打印或其他操作

image.png

同第一条,Alt + 鼠标左键可以快速展开对象或数组,同理也可以在Network调试接口界面直接展开对象或数组

image.png

在调试接口界面或Console板块,对象层级较多时对接字段容易看错字段从属关系。右键点击选择 Copy property path 将对象路径复制到粘贴板里

image.png

控制台的Console板块,左上角有个小眼睛的图标(live expression),通过这个图标可以创建表达式。并能随着表达式的计算结果变化而变化,动态刷新表达式的计算结果。

image.png

例如添加一个localStorage.uuid的表达式,当运行代码并操作UI,或在控制台改变变量,让它动态刷新表达式的结果

image.png

也可以这样

image.png

当c的值发生变化时,表达式的结果也会发生变化

image.png

Console Importer: 能让你在 Chrome 控制台中调试你的插件

开发过程中,难免会用到一些库和插件,插件的调试一般都是在项目代码中完成的。保存代码刷新页面,项目大时,可能要等待个几秒,进入相应的页面,过程耗时繁琐。有没有更快速便捷的方法?这里推荐一个Chrome插件,名字叫 Console Importer

image.png

使用方法也很简单,安装完成之后直接在控制台中输入 $i 来安装你想要调试的库/插件

image.png

调试lodash

image.png

安装 React 也可以

image.png

使用 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>

image.png

给他一个all: unset的CSS属性

image.png

能看到上述代码的所有样式都会失效
官方是这么对 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> 已经失去了块级元素的特征。此时它的宽度是由它的内容决定的。

image.png

image.png

但我的目的是为了清除掉所有“已经设置的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> 前面的黑点没有了。

image.png

从 Chrome 控制台中可以发现,<li> 原本的 display 属性并不是 block,而是list-item。前面的黑点是 list-item 属性实现的。所以这里就不能给他赋 block 这个值。

image.png

那么这里应该给 display 设置 revert,这样浏览器会把它的 display 按照它默认的 display: list-item; 来计算。

image.png

最后分享一个前段时间项目中遇到的一个BUG

BUG是前端需要把后端返回的一个HTML模板展示在页面上。大家可以把下面的代码复制到控制台中试一下。

// template 是后端返回的一个模板
const template = '<span class="content">This is span tag</span>'
document.body.innerHTML = template

然后奇怪的事情就出现了,这个标签无法通过 class 给标签设置样式。从Chrome控制台能看到这个标签变成了这个样子。

image.png

排查了很久发现,是后端返回的HTML模板有问题。span 和 class 中间穿插了一个不间断空格,长得和大家常用的空格一模一样。

image.png

// 大家可以复制下来试一试
console.log(' ' === ' ') // false

image.png

image.png

在写HTML时,标签中的属性会用空格来做分离,使解析出标签的各个属性。这个地方不是空格的话,浏览器会把它当做一个整体“标签名”来解析,就变成了你自定义的标签。中间的“不间断空格”相当于任何一个字母,任何一个数字。

image.png

既然class和标签名变成了一个整体,那么通过class给它设置样式,当然就不会生效了。
这里通过 charCodeAt() 查看它的Unicode码的位置,来看清他们的真面目。

image.png

而大家常用的空格是

image.png

两个不同的字符相比较,当然不会相等了。

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, ' ')