使用 Array.prototype.some 代替 Array.prototype.forEach

首先,Array.prototype.someArray.prototype.forEach 都是 ES5 进入标准的,因此不存在值得考虑的兼容性问题。

事实上,Array.prototype.forEach 被使用时,通常在回调函数中是不会包含 return 语句的。但一种常见的特例是:

function visitor(array) {
  return array.forEach(value => {
    if (value.visited) {
      return
    }
    value.counter += 1
    value.visited = true
  })
}

在这种情况下,return 的语义实际上是 continue。由于语言特性需要保证可靠的流程控制,原型方法的遍历实际上不可能直接支持 breakcontinue,只能通过类似的方式来实现相同语义。

但是, forEach 并不支持等价于 break(语义上来说,break 意味着并不是 each 的)的操作,除非使用 throw 来模拟。**在这种情况下可以用 Array.prototype.some 来代替 forEach**。

some 的本意是回调函数版本的 includes(正如 findIndex 之于 indexOf),当回调函数对任一元素返回 truthy 时函数返回 true。因此,可以实现这样的语义:

function visitor(array) {
  return array.some(value => {
    if (value.wanted) {
      return true // <-- as `break`
    }
    if (value.visited) {
      return // <-- as `continue`
    }
    value.counter += 1
    value.visited = true
  })
}

和常用的范式相比:

  • 相比较于 forEach,实际代码并无明显差别,但增加了 break 语义
  • 相比较于 for of 循环,原型方法遍历有返回值的优势。可以借此实现类似于 Python 的 for-else 从句:
    const breakpoint = array.some(() => { /* loop function */ })
    if (breakpoint) {} else { /* else block */ }
    这里还可以使用 findfindIndex 代替 some,以使变量 breakpoint 取得 “break” 时的元素或下标
  • 比起 forEachsome 本身的语义性不佳;但另一方面,return true 在上面的示例中较好的表示了 wanted 的意图

补充:

  1. 某些规范在 Babel 环境下不建议使用 for of,因为可能会引入 regenerator-runtime。目前测试下来似乎没有此表现,不确定是否有其他问题
  2. forEach 的另一个好处是不止对数组有效,例如 NodeList.prototype.forEach。这种情况通常来说转化为数组是更合理的应用,但也可能存在不适用的场景。

Powered by Sairin