JavaScript中的13种循环1次讲清 | 干货收藏

54 阅读11分钟

前言

在编程中,循环是必不可少的方法,它可以让我们重复执行特定的次数来达到实现业务逻辑的目的,那么问题来了,JavaScript中那么多循环方法,应该什么场景用什么方法呢?这是一个值的我们深入研究的话题,也是我要写这篇文章的起因,希望这篇文章能给你在循环的选择上带来一些帮助,内容是我翻阅各种资料总结而来,如有遗漏、错误等问题,请大家及时指正。

JavaScript有哪些循环

名称作用
for对一段代码块重复执行特定次数
while条件为真时一直重复执行循环体内的代码
do-while先执行循环体内的代码,再判断条件是否为真,为真时继续循环,否则停止循环
for-in遍历对象的属性
for-of用于遍历可迭代对象,如数组、字符串
forEach遍历数组或类数组,每次循环会调用配置好的函数
map映射数组中的每个元素并返回一个新的数组
filter过滤数组中满足条件的元素,然后把这些元素返回到一个新数组中
some检查数组中是否存在至少一个满足条件的元素,返回的是布尔值
every检查数组中是否所有元素都满足条件,返回的是布尔值
reduce用于对数组进行相加、相乘等计算,最终返回计算结果
reduceRight从数组末尾开始向前进行遍历
function模拟循环

for循环

for循环基本语法

for (let i = 0; i < 5; i++) {
    // 循环体
    console.log(i);  // [0, 1, 2, 3, 4]
}

for循环有三个参数,分别是初始化表达式、条件表达式、迭代表达式,这三个参数都是非必填。

  • 初始化表达式:初始化一个变量,用来迭代
  • 条件表达式:如若条件为真,就继续执行循环操作
  • 迭代表达式:迭代初始化变量

for循环的原理分析

  1. 开始循环,执行第一个参数初始化表达式,确定本次循环的起点
  2. 执行第二个参数条件表达式,如若条件为false,就会终止循环,如果条件为true,就执行循环体内的代码
  3. 循环体执行完成后,执行第三个参数迭代表达式,在这一步通常用来更新迭代初始化时的那个变量
  4. 以此规则循环,直到条件表达式为false,循环结束

for循环的使用场景及案例

循环数组

// for循环过程中是可以随时终止的,可以配合if判断,使用return终止循环
let arr = [1, 3, 4, 8];
for (let i = 0; i < arr; i++) {
    console.log(arr[i])  // 1 3 4 8
}

终止循环

// for循环过程中是可以随时终止的,可以配合if判断,使用return终止循环
for (let i = 0; i < 5; i++) {
    if (i === 3) {
        return
    }
}

初始化表达式生成多个变量

for (let i = 0, k = 5; i < 5; i++, k--) {
    console.log(i, k)
}
输出:
0 5
1 4
2 3
3 2
4 1

for循环的注意事项

  1. for循环三个参数非必填,如都不传就是死循环,因为第二个参数条件表达式默认值是true。
  2. 条件表达式决定循环次数,需要注意条件的正确性,以免写成死循环或不循环。
  3. 如果使用var初始化变量,那么变量就是和for循环同一级,可以在循环体外进行访问,如果使用let初始化变量,该变量就变成了循环体内的私有变量,在循环体外是访问不到的,而且每循环一次,上次的变量都会被销毁,在新的循环之前重新初始化变量,例子如下:
function test(params) {
    for (var i = 0; i < 5; i++) {}
    console.log(i)
}
test()  // 输出5


function test(params) {
    for (let i = 0; i < 5; i++) {}
    console.log(i)
}
test()  // 报错:i is not defined

while

while循环基本语法

let i = 0;
while(i < 3) { 
    // 循环体
    i++
}

while循环的原理分析

while只有一个参数,这个参数要求返回值必须是布尔类型,当参数为true的时候,会一直重复执行循环体内的代码,只有参数变为false,循环才会结束。

while循环的使用场景及案例

终止循环

let i = 0;
while(true) {
    i++
    console.log(i)  // 1  2  3
    if (i === 3) {
        break  // 使用break终止循环
    }
}

do-while

do-while循环基本语法

let i = 0;
do { 
    // 循环体
    i++
} while(i < 3)

do-while循环的原理分析

do-while循环参数规则和while的参数一样,do-while循环不管参数是否为true,都会先执行一遍循环体内的代码,可以理解成想做一件事,不管条件是否成立,都会尝试做一次。

for-in

for-in循环基本语法

// 循环对象
let obj = {
    name: "智豪",
    age: 25
};
for (key in obj) {
    // 循环体
    console.log(key);  // name  age
}

for-in循环的原理分析

for-in是一种可以遍历对象所有可枚举属性的循环结构,它会按照对象内属性的顺序,依次把属性名称赋值给循环变量,并且执行循环体内的代码。这里有一个需要注意的点,就是for_in只能循环可枚举属性,下面我将举例说明一些特殊场景下for_in的意外情况:

对象的symbol属性
let id = Symbol('id');
let user = {
  name: 'name',
  [id]: 'id'
};

for (let key in user) {
  console.log(user[key]);  // name
}

这段代码只输出了"name",是因为symbol类型的属性是不可枚举的,所以for-in循环时会自动将它过滤掉。如果想获取该对象中的所有symbol属性,可以使用Object.getOwnPropertySymbols(user)

for-in循环字符串或数组
// 循环字符串
for (index in "我是一个字符串") {
    // 循环体
    console.log(index);  // 0 1 2 3 4 5 6
}

// 循环数组
let arr = ['元素1', '元素1'];
arr.new_property = "新增属性"
for (index in arr) {
    // 循环体
    console.log(index);  // 0 1 'new_property'
}

对于数组和字符串,for-in循环会遍历它们的每个元素,并将元素的下标作为键返回。for-in在循环数组的时候,实际上会遍历数组的原型,这是因为数组是对象的特殊形式,它继承了对象的原型,所以新增到数组中的属性也会被for-in遍历到。

for-in循环的使用场景

  1. 查询对象中所有可枚举属性
  2. 检查对象中某些属性是否存在

for-in循环的注意事项

  1. for-in只能遍历可枚举的属性
  2. 会遍历到对象从原型继承的属性,需要特别注意

for-of

for-of循环基本语法

for (let item of ['小红', '小明', '小黑']) {
  // 循环体
  console.log(item);  // '小红' '小明' '小黑'
}

for-of只能循环可迭代(遍历)对象,比如数组、字符串、map、set等,与for-in循环不同,for-of循环直接遍历对象的元素,而不是属性名,在使用for-of循环时,要注意可迭代对象必须具备迭代器,否则无法进行遍历。

for-of循环注意事项

  1. 只能用于可迭代对象,确保要遍历的对象是可迭代的,否则会报错
  2. 无法中断循环:一旦开始循环,只能等到循环自然结束,不能中途退出
  3. 不能直接用于对象:如前面所说,对象本身不是可迭代对象,需要特殊处理才能用for-of遍历

forEach

forEach循环基本语法

let arr = ['元素1', '元素2', '元素3'];
arr.forEach((item, index, arr) => {
    console.log(item)  // '元素1' '元素2' '元素3'
    console.log(index)  // 0  1  2
    console.log(arr)  // ['元素1', '元素2', '元素3']
})

forEach循环的原理分析

forEach 循环的语法是array.forEach(callback),其中callback是一个函数,接收三个参数:当前元素、当前元素的索引和数组本身。在执行循环时,依次将每个元素传递给回调函数进行处理,且无法在循环中直接停止

map

map循环基本语法

let arr = ['f', 'z', 'h'];
let new_arr = arr.map((item, index, arr) => {
    return item.toUpperCase()
})
console.log(new_arr);  // ['F', 'Z', 'H']

map循环的原理分析

map 循环的语法和forEach一样,是array.map(callback),其中callback是一个函数,接收三个参数:当前元素、当前元素的索引和数组本身,循环时它会创建一个新的数组,其中每个元素都是原数组元素经过回调函数处理后的结果,map 循环的主要作用是对数组元素进行变换,生成一个新的数组。

filter

filter循环基本语法

let arr = ['f', 'z', 'h'];
let new_arr = arr.filter((item, index, arr) => {
    return index === 2
})
console.log(new_arr);  // ['h']

filter循环的原理分析

filter 循环用于从数组中筛选出符合条件的元素,filter 循环的语法是array.filter(callback),其中callback是一个函数,接收三个参数:当前元素、当前元素的索引和数组本身,它会遍历数组中的每个元素,将满足回调函数条件的元素保留在新的数组中。

some

some循环基本语法

let arr = [1, 4, 6, 2];
let f = arr.some((item, index, arr) => {
    return item === 4
});
console.log(f);  // true

some循环的原理分析

some循环用于检查数组中是否存在满足特定条件的元素,语法是array.some(callback),其中callback是一个函数,接收三个参数:当前元素、当前元素的索引和数组本身,它会依次对数组中的元素执行回调函数,只要有一个元素满足条件,就立即返回 true,否则返回 false

every

every循环基本语法

let arr = [1, 4, 6, 2];
let f = arr.every((item, index, arr) => {
    return item === 4
});
console.log(f);  // false

every循环和some唯一的区别就是只有当所有元素都满足条件时,才返回true,否则返回false。

reduce

reduce循环基本语法

let arr = [1, 5, 2, 9, 10];
let num = arr.reduce((accumulator, currentElement, index, arr) => {
    return accumulator + currentElement
}, 0)
console.log(num);  // 27

reduce循环的语法是array.reduce(callback, initialValue),其中callback是一个函数,接收四个参数:初始值(上一次回调的返回值或提供的初始值)、当前元素值、当前元素索引和调用reduce的数组。initialValue是初始化累计的值。

reduce循环的原理分析

reduce循环是一种对数组进行归约操作的方法,它接受一个函数作为累加器,并为数组中的每个元素依次执行该回调函数,在每次迭代中,回调函数的返回值将作为下一次迭代的初始值,如果没有提供初始值,则默认使用数组的第一个元素作为初始值,然后从第二个元素开始计算(示例如下),reduce 循环会遍历数组中的所有元素,包括那些可能被删除或从未被赋值的元素。

// 带有默认值
let arr = [1, 5, 2, 9, 10];
let num = arr.reduce((accumulator, currentElement, index, arr) => {
    console.log(accumulator + '------' + accumulator + currentElement)
    return accumulator + currentElement
}, 0)
输出结果:
0------01
1------15
6------62
8------89
17------1710


// 不带默认值
let arr = [1, 5, 2, 9, 10];
let num = arr.reduce((accumulator, currentElement, index, arr) => {
    console.log(accumulator + '------' + accumulator + currentElement)
    return accumulator + currentElement
})
输出结果:
1------15
6------62
8------89
17------1710

reduce循环的使用场景及案例

连接数组元素为一个字符串

let words = ['Hello', 'World', '!'];
let sentence = words.reduce((accumulator, currentValue) => accumulator + currentValue);
console.log(sentence);   // HelloWorld!

计算数组中所有偶数的平方和

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evenSquaresSum = numbers.reduce((accumulator, currentValue) => {
  if (currentValue % 2 === 0) {
    return accumulator + currentValue * currentValue;
  }
  return accumulator;
}, 0);
console.log(evenSquaresSum); 

reduceRight

reduceRight循环基本语法

let arr = [2, 5, 3, 9, 12];
let num = arr.reduceRight((accumulator, currentValue) => {
    return accumulator + currentValue
}, 0)
console.log(num);  // 31

reduceRight 循环与 reduce 循环类似,都是对数组进行归约操作的方法。不同的是,reduceRight 循环从数组的末尾开始,向前遍历数组元素,并将每个元素与上一次迭代的结果进行累积计算。

function模拟循环

模拟reduceRight计算数组中所有字符串连接起来

function reduceRight(arr, callback, initialValue) {
  for (let i = arr.length - 1; i >= 0; i--) {
    initialValue = callback(initialValue, arr[i], i, arr);
  }
  return initialValue;
}

let words = ['Hello', 'World', '!'];
let sentence = reduceRight(words, (accumulator, currentValue) => accumulator + currentValue, '');
console.log(sentence);   // !WorldHello

function模拟循环可操作性和灵活性是非常高的,但它也有缺点,比如可读性差,代码结构变得复杂,还是尽量使用JavaScript提供的循环方法。

写在最后

以上内容就是我了解到JavaScript所有的循环了,整体梳理下来感觉并没有特别复杂特别难懂的,语法都差不多,难的就是你能在合适的时候想到合适的循环方法,解决这个难题就要多实践多回顾,拜拜👋🏻。