?2022?|?2022? JavaScript超详细循环总结

前言 这是我梳理JavaScript基础系列文章中的一篇,这里没有谈及各类循环的效率,主要是总结一下使用方式和了解一些注意事项,点击二三级标题可以打开MDN相应文档,日常博客记录在语雀,欢迎关注 语雀文档。
如果文章对你有帮助的话,欢迎点赞评论收藏加转发。有问题和疑惑的话也可以在评论区留言,我会第一时间回复大家,如果觉得我的文章哪里有知识点错误的话,也恳请能够告知,把错的东西理解成对的,无论在什么行业,都是致命的。
JavaScript中循环

  1. for
  2. for...of
  3. for...in
  4. for await...of
  5. forEach 这个其实不能划为此类
  6. while
  7. do...while
先创建基础数据,全文通用。
const string='12345'const array = [1, 2, 3, 4, 5]const uint8Array=new Uint8Array([0, 1, 2, 3])const searchParams = new URLSearchParams("key1=value1&key2=value2"); const obj = { 'obj0': 1, 'obj1': 2, 'obj2': 3, 'obj3': 4, 'obj4': 5 }const obj2 = Object.defineProperties({ 'obj0': 1 }, { 'obj1': { value: 2, enumerable: true }, 'obj2': { value: 3, enumerable: true }, 'obj3': { value: 4, enumerable: false }, }); obj2.__proto__.obj4=5const map = new Map([[0,1],[1,2],[2,3],[3,4],[4,5]]) const set = new Set([1,2,3,4,5])

for 纯粹的forJavaScript中最基础的循环语句
for (let i = 0; i < array.length; i++) { console.log(map.get(i)); //循环输出1 2 3 4 5 }

需要知道的是,for的三个表达式都是可选的,也就是上面的let i = 0; i < array.length; i++这部分,但是,虽然表达式是可选的,但三个; 是强制要求要有的。
for (let i = 0,j=0; ; i++,j++) { //这里不给他中止,会gg的 if (i > array.length) break; console.log(i,j); } // 0 0 // 1 1 // 2 2 // 3 3 // 4 4 // 5 5

如果你有需要的话,你甚至可以声明一个无语句的for循环,这样也是可以的。
let j=[] for (let i = 0; i < array.length; i++,j.push(i)); //这个分号不能少 console.log(j) //[ 1, 2, 3, 4, 5 ]

for...of for...of语句循环可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等),循环出来的的对象的值。
for (const element of array) { console.log(element); //循环输出1 2 3 4 5 }

这里要注意,普通的object对象不是可迭代对象,无法使用for...of
for (let element of obj) { console.log(element); // TypeError: obj is not iterable }

甚至可以循环字符串
for (let element of string) { console.log(element); //循环输出1 2 3 4 5 }

也能循环Map和Set
for (let element of map) { //是个数组哟 console.log(element[0],element[1]); // 0 1 // 1 2 // 2 3 // 3 4 // 4 5 }for (let element of set) { console.log(element); //循环输出1 2 3 4 5 }

【?2022?|?2022? JavaScript超详细循环总结】对于for...of的循环,可以由break、throw、continue、return终止。
for (let element of set) { if(element>3) break console.log(element); //循环输出1 2 3 }

对于不支持的普通object,我们可以为他创建一个迭代器,这样也能实现for...of循环。
obj[Symbol.iterator] = function*(){ var keys = Object.keys(obj); for(var key of keys){ yield [key,obj[key]] } }; for(var [key,value] of obj){ console.log(key,value); // obj0 1 // obj1 2 // obj2 3 // obj3 4 // obj4 5 }

也能借助Object.entries 实现遍历,Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组。
for (const [key, value] of Object.entries(obj2)) { console.log(`${key}: ${value}`); //obj0: 1 //obj1: 2 //obj2: 3 }

还有一些其他可迭代对象可以使用for...of,这里就不一一举例了。
for...in for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性,注意:语句无法保证遍历顺序(各浏览器有点区别,也可以去了解下常规属性和排序属性)。
for (let prop in obj) { console.log(prop + " = " + obj[prop]); // obj0 = 1 // obj1 = 2 // obj2 = 3 // obj3 = 4 // obj4 = 5 }

只会遍历出可迭代的属性,obj2['obj3']enumerablefalse所有没有被遍历出来,obj2['obj4']是继承过来的属性(__proto__上面的),也是能遍历出来的。
for (let prop in obj2) { console.log(prop + " = " + obj2[prop]); // obj0 = 1 // obj1 = 2 // obj2 = 3 // obj4 = 5 }

如果仅需自身属性,可以使用hasOwnProperty判断
for (let prop in obj2) { if (obj2.hasOwnProperty(prop)) { console.log(prop + " = " + obj[prop]); } // obj0 = 1 // obj1 = 2 // obj2 = 3 }

for await...of 和for...fo一样,但它只适用于异步可迭代的异步迭代器。
function getRes(){ returnfetch('http://whois.pconline.com.cn/ip.jsp') .then(response => 'res') }async function test(){ let i=0 let arr = [getRes(),getRes(),getRes()] for await (let x of arr){ i++ console.log(x+'---'+i); //按顺序循环出 res---1 res---2 res---3 } }test()

异步生成器已经实现了异步迭代器协议, 所以可以用 for await...of循环。
async function* asyncGenerator() { let i = 0; while (i < 3) { yield i++; } }async function test(){ for await (num of asyncGenerator()) { if (num>1) break; //也能使用break中止 ,完全和for...of一样 console.log(num); //按顺序循环出 0 0 } }test()

while while语句可以在某个条件表达式为真的前提下,循环执行指定的一段代码,直到那个表达式不为真时结束循环。
let n=0 while (n < array.length) { console.log(array[n]); //循环输出1 2 3 4 5 n++; }

do...while do...while 语句创建一个执行指定语句的循环,直到条件值为 false。在执行后do内的代码后,检测while内的条件,所以do内代码至少会执行一次。
let n=0do { console.log(array[n]); //循环输出1 2 3 4 5 n++; }while (n < array.length)do { console.log(array[n]); //1至少会循环一次 n++; }while (n < 0)

forEach JavaScriptArray、Map、Set、NodeList、TypedArray、URLSearchParams都存在forEach方法,对上述类型的每个元素执行一次给定的函数,无返回值。除URLSearchParams外,其他都有第二个thisArg可选参数,在Map.prototype.forEach中有示例。
Map.prototype.forEach
map.forEach((value,key,myMap)=>{ console.log(`${key}-${value}-${myMap.get(key)}`) // 0-1-1 // 1-2-2 // 2-3-3 // 3-4-4 // 4-5-5 })//第二个参数,本文后续类型的不再举例,这里不能使用箭头函数 //如果使用箭头函数表达式来传入函数参数, thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。 map.forEach(function(value,key,myMap){ console.log(`${key}-${value}-${myMap.get(key)}-${this.exp}`) // 0-1-1-exp // 1-2-2-exp // 2-3-3-exp // 3-4-4-exp // 4-5-5-exp },{exp:'exp'})

Array.prototype.forEach
  1. forEach方法按升序为数组中含有效值的每一项执行一次 callback 函数;
  2. forEach不会改变原数组,但是可以被callback 修改;
  3. forEach不可链式调用;
  4. forEach遍历的范围在第一次调用 callback 前就会确定;
  5. 除了抛出异常以外,没有办法中止或跳出 forEach循环;
  6. 如果已经存在的值被改变, callback 拿到的是最新的值;
  7. 调用 forEach 后添加到数组中的项不会被 callback 访问到;
  8. 已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift),之后的元素将被跳过。
array.forEach((currentValue, index ,myArray)=> { console.log(`${currentValue}-${index}-${myArray[index]}`); // 1-0-1 // 2-1-2 // 3-2-3 // 4-3-4 // 5-4-5 });

forEach遍历的范围在第一次调用 callback 前就会确定。调用 forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach(遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift),之后的元素将被跳过。
array.forEach((currentValue) => { if (currentValue =https://www.it610.com/article/== 1) { //已经存在的值被改变,则传递给 callback 的值是 forEach() 遍历到他们那一刻的值 array[4] = 666 //不会遍历出来,forEach遍历的范围在第一次调用 callback 前就会确定 array.push(777) } console.log(currentValue) // 1 2 3 4 666 }); array.forEach((currentValue) => { if (currentValue =https://www.it610.com/article/== 1) { array.shift(); } //执行到1的时候,此时currentValue已经是1了,所以可以打印出来,把数组第一个删除,导致 //所有数组向前后退了一步[2,3,4,5],但是此时,索引已经是1了,所以跳过了2 打印3 4 5 console.log(currentValue) // 1 // 3 // 4 // 5 });

除了抛出异常以外,没有办法中止或跳出 forEach() 循环
array.forEach((currentValue)=> { if(currentValue>3) break; // Uncaught SyntaxError: Illegal break statement console.log(currentValue) });

扁平化数组可以看看我的这篇文章
Set.prototype.forEach
set.forEach((value,mySet)=>{ console.log(`${value}-${mySet}`) // 1-1 // 2-2 // 3-3 // 4-4 // 5-5 })

NodeList.prototype.forEach
let node = document.createElement("div"); let kid1 = document.createElement("p"); let kid2 = document.createTextNode("hey"); let kid3 = document.createElement("span"); node.appendChild(kid1); node.appendChild(kid2); node.appendChild(kid3); let list = node.childNodes; list.forEach((currentValue, currentIndex, listObj) => { console.log(`${currentValue}-${currentIndex}-${listObj[currentIndex]}`) // [object HTMLParagraphElement]-0-[object HTMLParagraphElement] // [object Text]-1- [object Text] // [object HTMLSpanElement]-2- [object HTMLSpanElement] } );

TypedArray.prototype.forEach
TypedArray一个类型化数组
uint8Array.forEach((element, index, array)=>{ console.log(`${element}-${index}-${array[index]}`) // 0-0-0 // 1-1-1 // 2-2-2 // 3-3-3 });

URLSearchParams.forEach
searchParams.forEach((value, key,searchParams)=> { console.log(value, key,searchParams.get(key)); // value1 key1 value1 // value2 key2 value2 });

for...of和for...in异同
  1. 相同点
    • for...infor...of语句都是循环(迭代)一些东西;
    • 都可以 break,continue return中断遍历。
  2. 不同点
    • for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性,注意:语句无法保证遍历顺序;
    • for...in迭代的是对象的键,而for...of则迭代对象的键对应的值;
    • for...in可以迭代任何对象,但for...of则需要对象是可迭代对象;;
do...while和while异同 相同点:都是循环语句
不同点:do...while至少会循环一次
JavaScript中的break,continue和return return return语句终止函数执行( return 是函数返回语句,但是返回的同时也将函数停止),并返回一个指定的值给函数调用者,如果忽略,则返回 undefined
array.forEachf()内部的一个方法,在array.forEachreturn和在f()方法内return是不一样的。
// 在函数中 function f() { array.forEach((item)=>{ console.log(item) // return无效 连续打印 1 2 3 4 5 return item+1//array.forEach是无返回值的 }) console.log(2) //打印2 }console.log(f()) //undefined//在全局作用域 array.forEach((item)=>{ console.log(item) return item+1// return无效 连续打印 1 2 3 4 5 }) console.log(3) //打印3

在函数内部或全局作用域使用for...inreturn能正常中断for...in语句执行
// 在函数中 function f() { for (const objElement in array) { console.log(array[objElement]) //1 return array[objElement] } console.log(2) //无法打印,已经中止函数执行 }console.log(f()) //1// 在全局作用域 for (const objElement in array) { console.log(array[objElement]) //1 return array[objElement] } console.log(3) //无法打印,已经中止

在函数内部或全局作用域使用for...ofreturn能正常中断for...of语句执行
// 在函数中 function f() { for (const objElement of array) { console.log(objElement) //1 return objElement } console.log(2) //无法打印,已经中止函数执行 }console.log(f()) //1// 在全局作用域 for (const objElement of array) { console.log(objElement) //1 return objElement } console.log(3) //无法打印,已经中止

在函数内部或全局作用域使用forreturn能正常中断for语句执行
// 在函数中 function f() { for (let i = 0; i < array.length; i++) { console.log(array[i]) // 1 return array[i] } console.log(2) //无法打印,已经中止函数执行 }console.log(f()) //1// 在全局作用域 for (let i = 0; i < array.length; i++) { console.log(array[i]) // node环境和v8 表现不一致 Illegal return statement return array[i] } console.log(3) //无法打印,已经中止执行

在函数内部或全局作用域使用whiledo...whilereturn能正常中断whiledo...while语句执行
// 在函数中 function f() { let n=0 while (n < array.length) { n++; if (n === 3) { return 'f'; } console.log(n) // 1 2 } console.log(2) //2 }console.log(f()) //f//在全局作用域 let n=0while (n < array.length) { n++; if (n === 3) { return 'f'; // Illegal return statement } console.log(n) } console.log(3)

小结:
  1. return会将此时进行的语句停止,但是return更加适用于函数语句;
  2. 对于forEach方法,除抛出异常外无法中断,本节后面不再对forEach进行举例。
break break语句中止当前循环、switch语句或label 语句( label 语句,后面专门写一篇文章),并把程序控制流转到紧接着被中止语句后面的语句。
在函数内部或全局作用域使用for...inbreak能正常中断当前循环,但不会阻止后续程序执行。
// 在函数中 function f() { for (const objElement in array) { console.log(array[objElement]) //1 break } console.log(2) //2 } // console.log(f()) //undefined// // 在全局作用域 for (const objElement in array) { console.log(array[objElement]) //1 break } console.log(3) //3

在函数内部或全局作用域使用for...ofbreak能正常中断当前循环,但不会阻止后续程序执行。
// 在函数中 function f() { for (const objElement of array) { console.log(objElement) //1 break } console.log(2) //2 } // console.log(f()) //undefined// // 在全局作用域 for (const objElement of array) { console.log(objElement) //1 break } console.log(3) //3

在函数内部或全局作用域使用forbreak能正常中断当前循环,但不会阻止后续程序执行。
// 在函数中 function f() { for (let i = 0; i < array.length; i++) { console.log(array[i]) // 1 break; } console.log(2) //2 } // console.log(f()) //undefined// // 在全局作用域 for (let i = 0; i < array.length; i++) { console.log(array[i]) // 1 break; } console.log(3) //3

在函数内部或全局作用域使用whiledo...whilebreak能正常中断当前循环,但不会阻止后续程序执行。
// 在函数中 function f() { let n=0 while (n < array.length) { n++; if (n === 3) { break; } console.log(n) // 1 2 } console.log(2) //2 }console.log(f()) //undefined//在全局作用域 let n=0while (n < array.length) { n++; if (n === 3) { break; } console.log(n) // 1 2 } console.log(3) //3

小结:break终止当前循环,但不会阻止后续程序执行;
continue continue跳过本次循环,继续下一次循环。
// 在函数中 function f() { for (let i = 0; i < array.length; i++) { if(array[i] === 1) continue; console.log(array[i]) // 2 3 4 5 } console.log(2) //2 }console.log(f()) //undefined//在全局作用域 for (let i = 0; i < array.length; i++) { if(array[i] === 1) continue; console.log(array[i]) // 2 3 4 5} console.log(3) //3

在函数内部或全局作用域使用for...incontinue能正常跳过本次循环,继续下一次循环。
// 在函数中 function f() { for (const objElement in array) { if(array[objElement] === 1) continue; console.log(array[objElement]) // 2 3 4 5 } console.log(2) //2 }console.log(f()) //undefined//在全局作用域 for (const objElement in array) { if(array[objElement] === 1) continue; console.log(array[objElement]) // 2 3 4 5 } console.log(3) //3

在函数内部或全局作用域使用for...ofcontinue正常跳过本次循环,继续下一次循环。
// 在函数中 function f() { for (const objElement of array) { if(objElement === 1) continue; console.log(objElement) // 2 3 4 5 } console.log(2) //2 }console.log(f()) //undefined//在全局作用域 for (const objElement of array) { if(objElement === 1) continue; console.log(objElement) // 2 3 4 5 } console.log(3) //3

在函数内部或全局作用域使用whiledo...whilecontinue跳过本次循环,继续下一次循环。
// 在函数中 function f() { let n=0 while (n < array.length) { n++; if (n === 3) { continue; } console.log(n) // 1 2 4 5 } console.log(2) //2 }console.log(f()) //undefined//在全局作用域 let n=0while (n < array.length) { n++; if (n === 3) { continue; } console.log(n) // 1 2 4 5 } console.log(3) //3

小结:continue用于跳过本次循环,继续下一次循环。
总结
  1. forEach除了抛出异常,否则无法中断;
  2. 三者都会将此时进行的语句停止/跳过;
  3. break中断当前循环,但不会阻止后续程序执行;
  4. continue跳过本次循环,继续下一次循环;
  5. return是函数返回语句,但是返回的同时也将函数停止;
  6. 使用的语句环境不一样,breakcontinue是用在循环或switch语句中,return是用在函数语句中。
引用
首发于语雀文档@is_tao
MDN
js中的break,continue和return到底怎么用?

    推荐阅读