Thinking系列,旨在利用10分钟的时间传达一种可落地的编程思想。
引用某第三方模块某函数:
// 将一些数字转换为可读的字符串
import { toReadableNumber } from 'some-library'
const readableNumbers = someNumbers.map(toReadableNumber)toReadableNumber 的实现:
export function toReadableNumber(num) {
// 例如 10000000 转换成 '10,000,000'
}上述的示例运行良好,直到 some-library 更新。但并不是 some-library 库导致的 – 因为其从未将 toReadableNumber 设计为 array.map 的回调。
// 我们认为:
const readableNumbers = someNumbers.map(toReadableNumber)
// 或者是:
const readableNumbers = someNumbers.map((n) => toReadableNumber(n))
// 更甚至:
const readableNumbers = someNumbers.map((item, index, arr) =>
toReadableNumber(item, index, arr)
)我们将数组中项目的索引和数组本身传递给 toReadableNumber。起初这很好用,因为 toReadableNumber 只有一个参数,但在新版本中:
export function toReadableNumber(num, base = 10) {
// 默认基数为10,但可修改
}toReadableNumber 的开发人员正在进行向后兼容的更改,添加了一个新参数,并给它一个默认值。==> 这是兼容处理,非破坏性的。 但是,他们没想到某些代码已经使用三个参数调用了该函数。
问题的根源: toReadableNumber 不是为了作为 array.map 的回调而设计的,所以安全的做法是创建你自己的用于与 array.map 一起使用的函数:
const readableNumbers = someNumbers.map((n) => toReadableNumber(n))就是这样! toReadableNumber 的开发人员现在可以在不破坏我们的代码的情况下添加参数。
const nextFrame = () => new Promise(requestAnimationFrame)
// 等价于
const nextFrame = () => new Promise((resolve, reject) => requestAnimationFrame(resolve, reject))这在今天有效,因为 requestAnimationFrame 只对第一个参数执行某些操作,但这可能永远不会正确。可能会添加一个额外的参数,并且上面的代码可能会在任何浏览器提供更新的 requestAnimationFrame 时中断。
window.requestAnimationFrame(callback) 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
更容易发现问题的例子:
const parsedInts = ['-10', '0', '10', '20', '30'].map(parseInt)parseInt 有两个参数。
parseInt(string, radix) 解析一个字符串并返回指定基数的十进制整数,
radix是2-36之间的整数,表示被解析字符串的基数。
针对上面问题,好的写法:
const nextFrame = () => new Promise((resolve, reject) => requestAnimationFrame(resolve))
const parsedInts = ['-10', '0', '10', '20', '30'].map((n) => parseInt(n))Chrome 90 将允许您使用 AbortSignal 删除事件侦听器,这意味着单个 AbortSignal 可用于删除事件侦听器、取消获取以及任何其他支持信号的内容:
const controller = new AbortController();
const { signal } = controller;
el.addEventListener('mousemove', callback, { signal });
el.addEventListener('pointermove', callback, { signal });
el.addEventListener('touchmove', callback, { signal });
// Later, remove all three listeners:
controller.abort();坏的例子:
const controller = new AbortController()
el.addEventListener(name, callback, controller)与回调示例一样,这在今天有效(因为 AbortController 和 addEventListener 选项唯一的共同点是 signal 属性),但将来可能会中断。
target.addEventListener(type, listener, options); options:
listener 永远不会调;AbortSignal 的 abort() 方法被调用时,监听器会被移除。所以,这里最好创建 addEventListener option 的对象:
const controller = new AbortController()
const options = { signal: controller.signal }
el.addEventListener(name, callback, options)
// 或者
const { signal } = controller
el.addEventListener(name, callback, { signal })在使用第三方函数时,除非是专门为当前场景所设计的,否则需要注意回调函数和选项对象的问题!