JavaScript 的 foreach 用不了 break/continue?同样写法下 for 循环也不行

JavaScript 的 foreach 用不了 break/continue?同样写法下 for 循环也不行

JavaScript 的 foreach 用不了 break/continue?同样写法下 for 循环也不行

今天在群里和群友一起探讨一个 JavaScript 异步问题的时候,就 foreach/map 函数进行了一番学习和讨论。当然,群友的侧重点还是在异步的实现方面。

对于我而言,更感兴趣的反而是数组这边为什么不能够使用 break/continue/return。这边这里就结合一下自己的理解,以及 MDN 上实现的 polyfill 去分析一下其中详情。

for循环 vs forEach

这里会简单的就 for循环 和 forEach 进行一下对比,输入和条件都是一样的。

for循环

正常情况下,大多数人是这样使用 for循环 的:

const arr = [1, 2, 3, 4, 5];

for (let i = 0; i < arr.length; i++) {

console.log(arr[i]);

}

以上面的数组为例,它是一个升序数组,假设需求是只打印出 3 以下(包括 3)的所有数字,那么可以使用 break 关键词去跳出循环,达到提升性能的效果:

const arr = [1, 2, 3, 4, 5];

for (let i = 0; i < arr.length; i++) {

console.log(arr[i]);

if (arr[i] >= 3) break;

}

这样,控制台就只会输出 1,2,3,而不会输出 3 以上的数字。对于传统的 for循环 来说,还要自己控制 下标(index),需要开发自己去实现增长,使用起来却是有一点的麻烦。

forEach

也因此,除了传统的 for循环 之外,ES5 也提供了诸如 forEach() 和 map() 这样使用起来更加方便的迭代函数。

以 forEach() 为例,完成对数组所有数值的输出可以这样实现:

const arr = [1, 2, 3, 4, 5];

arr.forEach((el, index) => {

console.log(el, index);

});

比起传统的 for循环 而言,显然 forEach() 的代码量更少,写起来更加的简洁干净。只是,如果有同样的需求,即只打印出 3 以下(包括 3)的所有数字,使用 forEach() 就没有办法像 for循环 那样终止迭代了:

const arr = [1, 2, 3, 4, 5];

arr.forEach((el, index) => {

console.log(arr[i]);

// 会报错

if (el >= 3) break; // SyntaxError: Illegal break statement

});

在这个情况下,使用 return 关键字是不会产生任何效果的,使用 break 以及 continue 会出现 SyntaxError 的报错。

也因此,之前也看到过有一些文章会推荐用抛出异常的方式去强行终止程序,再在外侧使用 try/catch 去接住抛出的异常,使得程序不至于被终止——这其实不是一个好习惯,这个代码这样也存在逻辑上的问题。

而且,其实在实现同样写法的情况下,使用 for循环 也会造成同样的后果。

回调函数是问题的根本

forEach 的完整语法其实是这样的:

arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

用中文解释一下是这样的:

forEach 接受一个 回调函数(callback) 作为必要的参数

而 回调函数 又会接受以下三个参数:

currentValue

当前被操作的值

index

当前被操作的值的索引,可选

array

forEach() 方法正在操作的数组,可选

forEach 接受一个 thisArg 作为可选参数

thisArg 可是做回调函数中的 this

也就是说,当调用 forEach 的时候,其实是回调函数在进行真正的操作。所以 forEach 又可以被重写成这样:

function cb(currentValue, index, array) {

console.log(currentValue);

// break & continue 当然不会工作

// 因为 cb 都不在循环体内

// return 只会起到中止 cb 的作用

// 所以对于 forEach 来说,它会结束当前迭代,进入下一个循环

return;

}

arr.forEach(cb);

而 forEach 的 polyfill 也是这么实现的:

// Production steps of ECMA-262, Edition 5, 15.4.4.18

// Reference: https://es5.github.io/#x15.4.4.18

if (!Array.prototype['forEach']) {

Array.prototype.forEach = function (callback, thisArg) {

// 省略一些异常的处理,英文的备注,只留下了处理 callback 这一部分的代码

var T, k;

var O = Object(this);

var len = O.length >>> 0;

if (arguments.length > 1) {

T = thisArg;

}

k = 0;

while (k < len) {

var kValue;

if (k in O) {

kValue = O[k];

// T 就是 this,有参数的情况下就是 thisArg

// kValue 即 currentValue

// k 即 index

// O 即 array

callback.call(T, kValue, k, O);

}

k++;

}

};

}

使用 cb,for循环 也会报错

在 for循环 内写回调函数的效果也是一样的:

const arr = [1, 2, 3, 4, 5];

function cb(currentValue, index, array) {

console.log(currentValue);

// 这里用 break/continue 同样会报错

// return 提前终止函数,不会进行其他的操作

// 相当于直接在 for循环 内使用 continue

if (index >= 3) {

return;

}

console.log('永远<3', index);

}

for (let i = 0; i < arr.length; i++) {

cb(arr[i], i);

}

输出结果为:

notes>node test.js

1

永远<3 0

2

永远<3 1

3

永远<3 2

4

5

关于 map

map 和 forEach 同样都是 ES5 的产物,实现方式也颇为相似,所以出现异常的原理也是一样的。

唯一的不同就在于,map 在实现内加入了一个数组,最后结果也返回了一个数组,简化版约为:

// 这里就不重写 Array.prototype.map 了,直接接受一个数组作为参数

const map = (arr, cb) => {

const returnVal = [];

for (let i = 0; i < arr.length; i++) {

const el = arr[i];

returnVal.push(cb(el));

}

return returnVal;

};

// 测试代码

const arr = [1, 2, 3, 4, 5];

// 主要内容的实现依旧在回调函数之中,所以同样,使用 break/continue 没有任何作用

// 提前 reeturn 只是向 map 中的返回数组中推进一个 undefined

console.log(map(arr, (val) => val ** 2)); // [ 1, 4, 9, 16, 25 ]

forEach 的代替品

ES6 推出了一个 for...of 的语法,内部的实现原理是通过实现 可迭代(iterable) 去实现的,因此内部可以使用 continue, break,return,yield,效果如下:

for (const val of arr) {

if (val % 2 === 0) continue;

console.log(val ** 2);

}

console.log('-----------');

for (const val of arr) {

if (val > 2) break;

console.log(val);

}

console.log('-----------');

for (const val of arr) {

if (val === 1) return;

console.log(val); // 不会有任何输出

}

输出结果为:

1

9

25

-----------

1

2

-----------

相关作品

闻的成语
365bet如何提款

闻的成语

📅 07-06 👁️ 6834
记忆力训练软件
365bet正网盘口

记忆力训练软件

📅 07-13 👁️ 9699
新乡市牧野区小马体育舞蹈用品商店
365bet如何提款

新乡市牧野区小马体育舞蹈用品商店

📅 07-09 👁️ 4672