前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 函数回调风险

JavaScript 函数回调风险

作者头像
奋飛
发布2021-08-31 17:00:38
4510
发布2021-08-31 17:00:38
举报
文章被收录于专栏:Super 前端Super 前端

Thinking系列,旨在利用10分钟的时间传达一种可落地的编程思想。

引用某第三方模块某函数:

代码语言:javascript
复制
// 将一些数字转换为可读的字符串
import { toReadableNumber } from 'some-library'
const readableNumbers = someNumbers.map(toReadableNumber)

toReadableNumber 的实现:

代码语言:javascript
复制
export function toReadableNumber(num) {
  // 例如 10000000 转换成 '10,000,000'
}

上述的示例运行良好,直到 some-library 更新。但并不是 some-library 库导致的 – 因为其从未将 toReadableNumber 设计为 array.map 的回调。

代码语言:javascript
复制
// 我们认为:
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 只有一个参数,但在新版本中:

代码语言:javascript
复制
export function toReadableNumber(num, base = 10) {
  // 默认基数为10,但可修改
}

toReadableNumber 的开发人员正在进行向后兼容的更改,添加了一个新参数,并给它一个默认值。==> 这是兼容处理,非破坏性的。 但是,他们没想到某些代码已经使用三个参数调用了该函数。

问题的根源: toReadableNumber 不是为了作为 array.map 的回调而设计的,所以安全的做法是创建你自己的用于与 array.map 一起使用的函数:

代码语言:javascript
复制
const readableNumbers = someNumbers.map((n) => toReadableNumber(n))

就是这样! toReadableNumber 的开发人员现在可以在不破坏我们的代码的情况下添加参数。

同样问题,web functions

代码语言:javascript
复制
const nextFrame = () => new Promise(requestAnimationFrame)
// 等价于
const nextFrame = () => new Promise((resolve, reject) => requestAnimationFrame(resolve, reject))

这在今天有效,因为 requestAnimationFrame 只对第一个参数执行某些操作,但这可能永远不会正确。可能会添加一个额外的参数,并且上面的代码可能会在任何浏览器提供更新的 requestAnimationFrame 时中断。

window.requestAnimationFrame(callback) 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

更容易发现问题的例子:

代码语言:javascript
复制
const parsedInts = ['-10', '0', '10', '20', '30'].map(parseInt)

parseInt 有两个参数。

parseInt(string, radix) 解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。

针对上面问题,好的写法:

代码语言:javascript
复制
const nextFrame = () => new Promise((resolve, reject) => requestAnimationFrame(resolve))
const parsedInts = ['-10', '0', '10', '20', '30'].map((n) => parseInt(n))

同样问题,option objects

Chrome 90 将允许您使用 AbortSignal 删除事件侦听器,这意味着单个 AbortSignal 可用于删除事件侦听器、取消获取以及任何其他支持信号的内容:

代码语言:javascript
复制
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();

坏的例子:

代码语言:javascript
复制
const controller = new AbortController()
el.addEventListener(name, callback, controller)

与回调示例一样,这在今天有效(因为 AbortControlleraddEventListener 选项唯一的共同点是 signal 属性),但将来可能会中断。

target.addEventListener(type, listener, options); options:

  • capture:该类型的事件捕获阶段触发;
  • once:添加之后最多只调用一次(自动移除);
  • passive:true 时,表示 listener 永远不会调;
  • signal:该 AbortSignalabort() 方法被调用时,监听器会被移除。

所以,这里最好创建 addEventListener option 的对象:

代码语言:javascript
复制
const controller = new AbortController()
const options = { signal: controller.signal }
el.addEventListener(name, callback, options)
// 或者
const { signal } = controller
el.addEventListener(name, callback, { signal })

总结

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-08-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 同样问题,web functions
  • 同样问题,option objects
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档