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 })
在使用第三方函数时,除非是专门为当前场景所设计的,否则需要注意回调函数和选项对象的问题!