JavaScript 循环结构注意事项

笔记哥 / 06-03 / 13点赞 / 0评论 / 837阅读
循环作为 `算法与数据结构` 中的基石,JS 与其他编程语言一样,都提供了多种循环结构用于处理数据。 ### for 循环 事物的开端往往都是从最常用的开始,循环结构咱们从 `for` 循环说起。 语法: ```js for (初始化; 条件; 增量) { // ... } ``` 示例: ```js // 增量每次 +1 for (let i = 0; i < 10; i++) { console.log(i); } // 增量每次 +2 for (let i = 0; i < 10; i += 2) { console.log(i); } ``` 性能优化: 在使用 for 循环遍历数组的时候,可以提前缓存数组长度,减少 `length` 的访问次数。 ```js const arr = ['前', '端', '路', '引']; // 提前使用 len 缓存数组长度 for (let i = 0, len = arr.length; i < len; i++) { console.log(arr[i]); } ``` 此示例中使用了 `let` 同时声明了多个变量,在常规的代码编写中,不建议这么使用,但在循环体这种特殊情况下,这么写也能接受。 ```js // 同时声明多个变量 let a = '前端路引', b = 2, c = true; ``` 增量不一定要使用 `i++` 自增,也可以使用 `i--` 递减,或者使用 `i += 2` 步进,甚至可以是 `i += 10`。 ### for in 循环 ES6 规范出现之前,只能使用 `for in` 循环遍历对象,但这哥们有个坑,不止会遍历对象自身属性,还能遍历原型链上可枚举属性。 ```js const obj1 = { name: '前端路引', age: 1, 'favorite-color': 'red', } for (let key in obj1) { console.log(key, obj1[key]); } /* // 输出结果 name 前端路引 age 1 favorite-color red */ ``` 看个遍历原型链例子: ```js // 如果有兄弟不小心给对象的原型链上填了一笔 Object.prototype.test = '我是原型链上的测试属性'; const obj1 = { name: '前端路引', age: 1, 'favorite-color': 'red', } for (let key in obj1) { console.log(key, obj1[key]); } /* // 输出结果 name 前端路引 age 1 favorite-color red test 我是原型链上的测试属性 */ for (const key in obj1) { if (obj.hasOwnProperty(key)) { // 过滤掉原型链属性 console.log(key, obj1[key]); } } /* // 输出结果 name 前端路引 age 1 favorite-color red */ ``` 如上所示,代码编写规范建议不要对 JS 自身的原型链做修改,扩展原型链虽然方便了一些对象操作,但实际上这是埋了雷的,不知道啥时候就会引爆!! 在使用 `for in` 循环也需要注意原型链的属性,必须使用 `hasOwnProperty` 方法来过滤掉原型链上的属性。 ### for of 循环 由于 `for in` 的各种弊端,后来定规范的大佬们,就新增了一个 `for of` 循环用于遍历可迭代对象,比如:数组、字符串、Set、Map 等等。 ```js const obj1 = { name: '前端路引', age: 1, 'favorite-color': 'red', } // for of 循环 for (let [key, value] of Object.entries(obj1)) { console.log(key, value); } /* // 以上 let [key, value] 使用了 解构赋值,其代码等于 for (let item of Object.entries(obj1)) { const [key, value] = item; console.log(key, value); } // 又等于 for (let item of Object.entries(obj1)) { const key = item[0]; const value = item[1]; console.log(key, value); } */ ``` `for of` 无法直接遍历对象,需要遍历对象时,需使用内置方法 `Object.entries` 将对象转为数组,再使用 `for of` 遍历,或者使用 `Object.keys`/`Object.values` 将对象转为键/值数组再遍历。 相比于 `for in` 循环,`for of` 循环性能更好,也不用考虑原型链问题。 ### while 循环 `while` 循环多用于不确定循环次数的应用场景,比如读取文件数据流,并不知道需要循环多少次才能读取完。 ```js let i = 0; while (i < 3) { console.log(i); i++; } ``` 一般能用 for 循环的场景,都能使用 while 循环替代。 ### do while 循环 这个循环可有意思了,不管条件是否满足,都会先跑一次循环体,再判断条件。 应用场景例子:必须让用户先输入,再判断条件,直到输入正确才继续。 ```js let userInput; do { userInput = prompt("请输入一个大于 10 的数字:"); } while (isNaN(userInput) || Number(userInput) <= 10); console.log("有效输入:", userInput); ``` ## 死循环 在使用循环遍历时候,需特别注意 `死循环` 问题,条件处理不好,就进入死循环,导致程序崩溃。 比如: ```js let i = 0; while (i < 3) { console.log(i); // i++; // 忘记修改 i 的值,导致进入死循环 } ``` ## 善用退出循环 `continue` / `break` / `return` 三个关键字都可以用来处理循环逻辑,不同的是: - `continue`:跳过当前循环,继续下一次循环。 - `break`:跳出当前循环,不再继续循环。 - `return`:跳出当前函数,不再继续执行。 continue 示例: ```js function loop1 () { for (let i = 0; i < 10; i++) { if (i % 2 === 0) { continue; // 跳过偶数次循环,只输出奇数次循环 } console.log(i); // 输出 1 3 5 7 9 } console.log('循环结束'); // 会执行 } loop1() ``` break 示例: ```js function loop2 () { for (let i = 0; i < 10; i++) { if (i === 5) { break; // 在第 6 次循环退出 } console.log(i); // 输出 0 1 2 3 4 } console.log('循环结束'); // 会执行 } loop2() ``` return 示例: ```js function loop3 () { for (let i = 0; i < 10; i++) { if (i === 5) { return; // 在第 6 次循环退出函数,不会执行循环体后面的代码 } console.log(i); // 输出 0 1 2 3 4 } console.log('循环结束'); // 此行代码不会执行 } loop3() ``` 三个退出循环关键字都可以用于所有的循环语句,不要局限于 for 循环~~ ## 写在最后 如果说算法是程序的灵魂,那么循环可以算是算法的基石,很多常见的算法都需要使用循环实现,比如各种数组排序算法、查找算法、最短路径算法等等。