JavaScript中的循环

一、循环的本质:JavaScript 的迭代哲学

循环是编程中处理重复逻辑的核心范式,而 JavaScript 的循环体系兼具灵活性与特殊性 ------ 它不仅继承了传统结构化编程的循环思想,更融入了函数式编程的迭代理念。在 JS 引擎中,循环的本质是通过条件判断控制代码块的重复执行,但不同循环的底层执行机制、适用场景和性能表现存在显著差异。

关键认知:JS 循环的性能瓶颈往往不在于循环本身,而在于循环体内部的操作(如 DOM 操作、冗余计算)和迭代器的效率。

二、JavaScript 循环家族全解析(附场景对比)

1. 基础循环:for /while/do...while

这三类是最经典的循环形式,基于 "初始化 - 条件判断 - 迭代" 的三段式逻辑,适用于大多数简单迭代场景。

for 循环:结构清晰,适合已知循环次数的场景

复制代码

// 高效遍历数组(缓存长度,避免重复计算)

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

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

console.log(arr[i]); // 直接访问索引,性能最优

}

while 循环:适合未知循环次数,依赖动态条件终止

复制代码

// 处理异步队列(模拟消息消费)

const queue = [/* 任务列表 */];

while (queue.length > 0) {

const task = queue.shift();

executeTask(task); // 任务执行完才继续循环

}

do...while 循环:至少执行一次,适合 "先执行后判断" 场景

复制代码

// 表单验证(确保用户至少输入一次)

let input;

do {

input = prompt("请输入用户名(不能为空)");

} while (!input?.trim());

2. 数组专用循环:forEach /map/filter /reduce

ES5 引入的数组迭代方法,基于函数式编程思想,代码更简洁,避免了索引操作的冗余,但需注意其特性差异:

|---------|------------|---------|---------------|----------|
| 方法 | 核心作用 | 是否改变原数组 | 返回值 | 中断支持 |
| forEach | 遍历数组执行回调 | 否(回调决定) | undefined | 不支持(需抛错) |
| map | 遍历数组返回新数组 | 否 | 新数组(长度与原数组一致) | 不支持 |
| filter | 筛选数组元素 | 否 | 符合条件的新数组 | 不支持 |
| reduce | 累加计算(聚合数组) | 否 | 最终累加结果 | 不支持 |

实战案例:数据处理流水线

复制代码

const users = [

{ id: 1, name: "张三", age: 25, isVip: true },

{ id: 2, name: "李四", age: 17, isVip: false },

{ id: 3, name: "王五", age: 32, isVip: true }

];

// 筛选成年VIP -> 提取姓名 -> 拼接欢迎语

const greetings = users

.filter(user => user.age >= 18 && user.isVip) // 筛选:[张三, 王五]

.map(user => user.name) // 提取:["张三", "王五"]

.reduce((acc, name) => {

acc.push(`欢迎VIP用户:${name}`);

return acc;

}, []); // 聚合:["欢迎VIP用户:张三", "欢迎VIP用户:王五"]

3. 高阶循环:for...in/for...of/ 迭代器

ES6 + 引入的现代化循环方式,解决了传统循环的痛点,支持更多数据类型:

for...in:遍历对象可枚举属性(含原型链)

复制代码

const obj = { a: 1, b: 2 };

// 安全遍历(排除原型链属性)

for (const key in obj) {

if (obj.hasOwnProperty(key)) {

console.log(`${key}: ${obj[key]}`);

}

}

⚠️ 避坑:不可用于数组遍历!会遍历数组的非数字属性(如arr.name),且索引为字符串类型。

for...of:遍历可迭代对象(数组、字符串、Map、Set 等)

复制代码

// 遍历数组(支持break/continue)

const fruits = ["苹果", "香蕉", "橙子"];

for (const fruit of fruits) {

if (fruit === "香蕉") break;

console.log(fruit); // 输出:苹果

}

// 遍历Map(键值对)

const map = new Map([["a", 1], ["b", 2]]);

for (const [key, value] of map) {

console.log(`${key}: ${value}`);

}

// 遍历字符串(Unicode字符)

for (const char of "𠮷abc") {

console.log(char); // 正确输出:𠮷、a、b、c(解决for循环UTF-16代理对问题)

}

迭代器(Iterator):自定义遍历逻辑

复制代码

// 生成1-5的迭代器

const createIterator = (start, end) => {

let current = start;

return {

[Symbol.iterator]() { return this; },

next() {

return current ? { value: current++, done: false }

: { done: true };

}

};

};

// 遍历自定义迭代器

for (const num of createIterator(1, 5)) {

console.log(num); // 1,2,3,4,5

}

三、95 分必备:循环性能优化实战

要达到高分水准,必须掌握 "场景匹配 + 性能优化" 的核心逻辑,以下是关键技巧:

1. 循环类型选择原则
  • 数组遍历优先用for(已知长度)或for...of(需中断 / 遍历复杂类型)
  • 数据转换 / 筛选用map/filter/reduce(代码简洁,可读性高)
  • 对象遍历用for...in(需过滤原型链)或Object.keys()+for
  • 大数据量遍历避免forEach(无法中断,性能略逊)
2. 性能优化技巧
  • 缓存长度:数组遍历前缓存length,避免每次循环重新计算(尤其对于 DOM 集合)
复制代码

// 低效:const lis = document.getElementsByTagName("li");(实时集合,每次访问length都重新查询DOM)

const lis = Array.from(document.getElementsByTagName("li"));

for (let i = 0, len = lis.length; i ; i++) { // 缓存len

// 操作lis[i]

}

  • 减少循环体操作:将循环体内的常量、函数调用移到外部
复制代码

// 低效

for (let i = 0; i 00; i++) {

console.log(Math.random() * 10); // Math.random()每次循环都调用

}

// 高效

const random = Math.random;

for (let i = 0; i < 1000; i++) {

console.log(random() * 10); // 缓存函数引用

}

  • 避免不必要的 DOM 操作:循环中 DOM 操作会导致浏览器频繁重排重绘,应批量处理
复制代码

// 低效:每次循环都修改DOM

const list = document.getElementById("list");

for (let i = 0; i 00; i++) {

list.innerHTML += `<li>${i};

}

// 高效:先拼接字符串,再一次性插入

let html = "";

for (let i = 0; i < 100; i++) {

html += `<li>${i}>`;

}

list.innerHTML = html;

3. 循环中断的正确方式

|------------|------------------------|------------------|
| 循环类型 | 中断方式 | 注意事项 |
| for/while | break(终止)/continue(跳过) | 直接使用,无副作用 |
| for...of | break/continue | 支持,且不影响迭代器状态 |
| forEach | 抛出异常(不推荐) | 会终止循环,但代码不优雅 |
| map/filter | 无法中断 | 需改用 for/for...of |

四、高频面试题:循环进阶考点

  1. forEach 与 for 循环的性能差异
    • for 循环直接访问索引,无函数调用开销,性能更优;
    • forEach 需执行回调函数,且无法中断,大数据量下性能略逊。
  1. for...in 遍历数组的问题
    • 索引为字符串类型,可能导致i + 1等运算错误;
    • 会遍历数组的非数字属性(如arr.prototype.method);
    • 遍历顺序不固定(依赖浏览器实现)。
  1. 异步循环的陷阱(经典面试题)
复制代码

// 期望:1秒后输出0-4,实际输出5个5

for (var i = 0; i 5; i++) {

setTimeout(() => console.log(i), 1000);

}

// 解决方案1:使用let(块级作用域)

for (let i = 0; i ++) {

setTimeout(() => console.log(i), 1000);

}

// 解决方案2:闭包保存变量

for (var i = 0; i {

(function(j) {

setTimeout(() => console.log(j), 1000);

})(i);

}

相关推荐
小皮虾2 小时前
搞全栈还在纠结 POST、GET、RESTful?试试这个,像调用本地函数一样写接口
前端·node.js·全栈
掘金安东尼2 小时前
⏰前端周刊第445期(2025年12月15日–12月21日)
前端
AAA阿giao2 小时前
JavaScript 中 this 的终极解析:从 call、bind 到箭头函数的深度探索
前端·javascript·ecmascript 6
404NotFound3052 小时前
利用 WebMKS 和 Java 实现前端访问虚拟机网页
前端
文心快码BaiduComate2 小时前
插件开发实录:我用Comate在VS Code里造了一场“能被代码融化”的初雪
前端·后端·前端框架
嘻哈baby2 小时前
搞了三年运维,这些脚本我天天在用
前端
inCBle2 小时前
vue2 封装一个自动校验是否溢出的 tooltip 自定义指令
前端·javascript·vue.js
掘金安东尼2 小时前
⏰前端周刊第444期(2025年12月8日–12月14日)
前端
weixin_448119942 小时前
Datawhale Hello-Agents入门篇202512第2次作业
java·前端·javascript