⌈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=5

const 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
}
复制代码

对于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(){
   return  fetch('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=0

do {
    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 === 1) {
        //已经存在的值被改变,则传递给 callback 的值是 forEach() 遍历到他们那一刻的值
        array[4] = 666  
        //不会遍历出来,forEach遍历的范围在第一次调用 callback 前就会确定
        array.push(777) 
    }
    console.log(currentValue)
    // 1 2 3 4 666
});

array.forEach((currentValue) => {
    if (currentValue === 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,continuereturn中断遍历。
  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=0

while (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=0

while (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=0

while (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到底怎么用?

猜你喜欢

转载自juejin.im/post/7049919311804104717