41

不再写 break 和 continue 了

 4 years ago
source link: https://juejin.im/post/5d08a565e51d45773d468614
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
2019年06月18日 阅读 5678

不再写 break 和 continue 了

break 与 some、every

个人平时喜欢用 forEach 来代替 for 循环。但有时发现实现过程中,需要使用 break。这时,一般又得切换回 for 循环。譬如遇见如下的逻辑:

let arr = [1, 2, 3, 4, 5]
let text = ''
for (let v of arr) {
  if (v === 3) { 
    break
  }
  text += v + ','
}
console.log(text) // "1,2,"
复制代码

今天看文章时想到,既然 some 的实现逻辑本身就是短路的,即一旦返回 true 后续迭代就不再执行了,那么为啥不可以用 some 替换 forEach 呢?

let arr = [1, 2, 3, 4, 5]
let text = ''
arr.some(v => {
  if (v === 3) { 
    return true
  }
  text += v + ','
})
console.log(text) // "1,2,"
复制代码

一般情况下,我们用 some 都是要用它返回结果的。而这种没有拿其返回值做文章的做法,算是代码阅读的一个信号:原来只是简简单单利用了其循环罢了。当然,这么写代码可读性不是很高。但确实是替换掉 for 的一种方式。

类似地,every 也是短路的,当然也可以替代 break。不过要保证 break 之前的迭代返回 true

let arr = [1, 2, 3, 4, 5]
let text = ''
arr.every(v => {
  if (v === 3) { 
    return
  }
  text += v + ','
  return true
})
console.log(text) // "1,2,"
复制代码

本文发到朋友圈后,朋友说这样看起来不那么“pure”。建议用 reduce 实现,不考虑性能,毕竟空迭代不浪费多少性能。如果要替代 break 的逻辑,那么必须得用一些 flag 来实现,比如:

let arr = [1, 2, 3, 4, 5]
let text = arr.reduce(function(p, c) {
  if (this.break) {
    return p
  }
  // ...
  if (c === 3) {
    this.break = true
    return p
  }
  return p + c + ','
}.bind({}), '')
console.log(text) // "1,2,"
复制代码

另外关于“pure”,如果封装成函数后,是纯的了:

function formatter(arr) {
  let text = ''
  arr.some(v => {
    if (v === 3) { 
      return true
    }
    text += v + ','
  })
  return text
}
let arr = [1, 2, 3, 4, 5]
console.log(formatter(arr)) // "1,2,"
复制代码

也有网友留言,可以用递归方式来做。for 循环本身都是可以用递归来替换的。而一般 break 的条件,正好可以是一个递归出口。例如本例子的递归实现:

function formatter(arr, text = '', i = 0) {
  if (arr.length == 0 || arr[i] == '3') {
    return text
  }
  text += arr[i] + ','
  return formatter(arr, text, ++i)
}
let arr = [1, 2, 3, 4, 5]
console.log(formatter(arr)) // "1,2,"
复制代码

continue 与 return

至于 continue 嘛。。。

let arr = [1, 2, 3, 4, 5]
let text = ''
for (let v of arr) {
  if (v === 3) { 
    continue
  }
  text += v + ','
}
console.log(text) // "1,2,4,5,"
复制代码

答案意外简单,forEach 直接 return 就好了:

let arr = [1, 2, 3, 4, 5]
let text = ''
arr.forEach(v => {
  if (v === 3) { 
    return
  }
  text += v + ','
})
console.log(text) // "1,2,4,5,"
复制代码

如果 continuebreak 同时存在呢?譬如:

let arr = [1, 2, 3, 4, 5]
let text = ''
for (let v of arr) {
  if (v === 2) {
    continue
  }
  if (v === 4) { 
    break
  }
  text += v + ','
}
console.log(text) // "1,3,"
复制代码

只用 some 就好了,反正数组那几个 API,本质上都是 for 循环。

let arr = [1, 2, 3, 4, 5]
let text = ''
arr.some(v => {
  if (v === 2) {
    return
  }
  if (v === 4) { 
    return true
  }
  text += v + ','
})
console.log(text) // "1,3,"
复制代码

some 和 every 需要注意的地方

some 函数用来判断数组中,是否至少有一个元素满足回调函数的条件。此时回调函数可以称为谓词函数(即判断是不是的意思)。规范文档some 是这么实现的:

点击展开

用 JS 模拟,其核心逻辑如下:

Array.prototype.some = function(callbackfn, thisArg) {
  let len = Number(this.length)
  let k = 0;
  while(k < len) {
    let Pk = String(k)
    if (Pk in this) {
      let kValue = this[Pk]
      if (callbackfn.call(thisArg, kValue, k, this)) {
        return true
      }
    }
    k++
  }
  return false
}
复制代码

可以看出,遇到回调返回值是 true 的话,函数就直接返回、结束了。这是种短路算法,并不是所有回调都执行一遍,然后再最后求所有与值。every 也类似,不过与之相反,遇到回调返回值是 false 时,整体就直接返回 false 了。

从实现上表达出的语义来讲,some 是在说:有一个成功,我就成功,而 every 是在说:有一个失败,我就失败

另外要强调一点,对于稀疏数组,不存在的索引值时,回调函数是不执行的。例如下例子中回调函数只执行了 3 遍(其他 API 也类似)。

let arr = [1, 2, 3]
delete arr[1]
arr[5] = 6
console.log("1" in arr) // false
console.log(arr) // [1, empty, 3, empty × 2, 6]
arr.some(v => {
  console.log(v) // 1 3 6
})
复制代码

因此空数组,不管回调函数如何写,其结果仍是 false

[].some(_ => true) // false
复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK