forEach使用之Illegal continue/break statement

本文探讨了在JavaScript中使用forEach时,为何break和continue会引发错误,以及return关键字的效果。通过手动实现forEach,揭示了其内部作用域原理。文章指出,由于作用域限制,break和continue不能在forEach中直接使用,而return只会终止当前回调函数,不影响后续元素的执行。若要中断forEach,需借助异常处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

不知道大家在平时写代码的过程中是否遇到以下的报错信息
SyntaxError: Illegal break statement
SyntaxError: Illegal continue statement: no surrounding iteration statement
以上的报错原因就是非法使用break/continue关键字,具体点说就是这两个关键字应该在循环体里面使用。乍一看,感觉没毛病很正常,哪个憨憨会把这两个关键字放到循环体外面使用而产生报错呢?
那么,show you the issue code:

function main() {
    const nums = [1, 2, 3, 4];
    nums.forEach(num => {
        if (num === 2) {
            break;
        }
        console.log(num);
    })
}

是不是以为运行结果只有1?没这么简单,结果如下:
在这里插入图片描述
对的,没看错,看似是在终止循环,结果却是把break放到循环体外面了。
为什么呢?下面我们一起来看看这个forEach里面到底干了些什么。

再现forEach

我们知道,forEach是js数组原生提供的一个API函数,函数的传参是一个回调函数,forEach函数内部就是将遍历数组并将数组的每个元素都执行一次此回调函数。
我们先将上面的代码换一个形式:

function main() {
    const nums = [1, 2, 3, 4];
    nums.forEach(f)
}

function f(num) {
    if (num === 2) {
        break;
    }
    console.log(num);
}

此代码和原始代码是一模一样的效果,只不过这里将匿名的回调函数抽出来变成一个有名字的函数。
下一步,我们尝试自己手动实现一个forEach函数:

function main() {
    const nums = [1, 2, 3, 4];
    // nums.forEach(f);
    forEach(nums, f);
}

function f(num) {
    if (num === 2) {
        break;
    }
    console.log(num);
}

function forEach(nums, callback) {
    for (let num of nums) {
        callback(num);
    }
}

main();

到这里,想必大家都明白了为什么在forEach里面使用break/continue关键字会报错了。
我们知道在js里,每一组{}都是一个块级作用域,在这里for循环体内是一个块级作用域,callback函数内部是自己的一个函数作用域,并且我们知道js原生只有全局作用域和函数作用域,块级作用域也是基于函数作用域和词法环境而实现的。
正因为如此,在callback函数的函数作用域里没有循环体,所以会报错。
但是如果将代码改成如下的常见形式即不会出现错误:

function main() {
    const nums = [1, 2, 3, 4];
    // nums.forEach(f);
    forEach(nums, f);
}

function forEach(nums, callback) {
    for (let num of nums) {
        if (num === 2) {
            break;
        }
        console.log(num);
    }
}

main();

原因很简单,因为此时break和for循环体是在同一个函数作用域内,并且break所处在的块级作用域也是在for循环体的块级作用域内。

扩展(return和异常)

既然知道了forEach的实现原理,那么我们再看看在forEach中使用return会是什么效果:

function main() {
    const nums = [1, 2, 3, 4];
    nums.forEach(num => {
        if (num === 2) {
            return;
        }
        console.log(num);
    });
}

结果是1,3,4。
咦,为什么3,4还在呢?请看下面这段代码:

function main() {
    const nums = [1, 2, 3, 4];
    forEach(nums, f);
}

function f(num) {
    if (num === 2) {
        return;
    }
    console.log(num);
}

function forEach(nums, callback) {
    for (let num of nums) {
        callback(num);
    }
}

main();

此时结果就很显然了,因为这个return只是把当前回调函数给终止了,但是它并不会影响后续元素继续执行回调函数。
如MDN文档所介绍,如果想中止forEach循环,只能通过抛异常并将其捕获来处理:

function main() {
    const nums = [1, 2, 3, 4];
    try {
        forEach(nums, f);
    } catch (e) {
        console.log(e)
    }
}

function f(num) {
    if (num === 2) {
        throw "终止循环";
    }
    console.log(num);
}

function forEach(nums, callback) {
    for (let num of nums) {
        callback(num);
    }
}

main();

结语

这篇文章通过自己手动模拟一个forEach函数来展示forEach的底层实现原理,并结合break/continue、return关键字以及异常来演示其运行效果,其中还涉及到js的作用域和词法环境的相关概念,如果有理解不正确的地方,欢迎大家进行指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值