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);

}

相关推荐
w***95491 分钟前
spring-boot-starter和spring-boot-starter-web的关联
前端
Moment5 分钟前
富文本编辑器技术选型,到底是 Prosemirror 还是 Tiptap 好 ❓❓❓
前端·javascript·面试
xkxnq10 分钟前
第二阶段:Vue 组件化开发(第 18天)
前端·javascript·vue.js
晓得迷路了11 分钟前
栗子前端技术周刊第 112 期 - Rspack 1.7、2025 JS 新星榜单、HTML 状态调查...
前端·javascript·html
怕浪猫14 分钟前
React从入门到出门 第五章 React Router 配置与原理初探
前端·javascript·react.js
jinmo_C++14 分钟前
从零开始学前端 · HTML 基础篇(一):认识 HTML 与页面结构
前端·html·状态模式
鹏多多20 分钟前
前端2025年终总结:借着AI做大做强再创辉煌
前端·javascript
小Tomkk29 分钟前
⭐️ StarRocks Web 使用介绍与实战指南
前端·ffmpeg
不一样的少年_33 分钟前
产品催: 1 天优化 Vue 官网 SEO?我用这个插件半天搞定(不重构 Nuxt)
前端·javascript·vue.js
-dcr34 分钟前
50.智能体
前端·javascript·人工智能·ai·easyui