for、for...in、for...for、forEach()详解


本文旨在简述四种遍历方式的机制和作用,并使用示例说明

for

介绍

用于创建一个循环,它包含了三个可选的表达式,这三个表达式被包围在圆括号之中,使用分号分隔,后跟一个用于在循环中执行的语句(通常是一个块语句)。

语法

for (初始化表达式; 条件表达式; 更新表达式) {
    	// 循环体内的代码
}
  • 初始化表达式(可选)在循环开始前初始化的表达式(包含赋值表达式)或者变量声明。通常用于初始化计数器变量。该表达式可以选择使用 varlet 关键字声明新的变量,使用 var 声明的变量不是该循环的局部变量,而是与 for 循环处在同样的作用域中。用 let 声明的变量是语句的局部变量。该表达式的结果会被丢弃
  • 条件表达式(可选)每次循环迭代之前要判定的表达式。如果该表达式的判定结果为真,循环体 将被执行。如果判定结果为假,那么执行流程将退出循环,并转到 for 结构后面的第一条语句。如果缺省,该条件总是计算为
  • 更新表达式(可选)每次循环迭代之后执行的表达式。执行时机是在下一次判定 条件表达式 之前。通常被用于更新或者递增(减)计数器变量
  • 循环体(可选)只要条件的判定结果为就会被执行的语句。可以使用块语句来执行多个语句。如果没有任何语句要执行,需要使用一个空语句(;)

示例

// 基础版
for (var i = 0; i < 9; i++) {
  console.log(i);
  // 更多语句
}
// 缺省版
let i = 0;
for (; i < 9; i++) {
  console.log(i);
  // 更多语句
}
// 如果省略条件判断语句,则必须确保在主体内打破循环,以免创建无限循环。
for (let i = 0; ; i++) {
  console.log(i);
  if (i > 3) break;
  // 更多语句
}
// 全部缺省,需要确保使用了 break 语句来结束循环并修改(递增)变量,使得中断语句的条件在某个时刻为真。
let i = 0;
for (;;) {
  if (i > 3) break;
  console.log(i);
  i++;
}

特点

  1. 灵活性:for 循环可以灵活地控制循环次数和循环变量的变化,适合于已知循环次数的情况或者通过循环条件动态控制循环结束的情况。
  2. 可中断性:在 for 循环内部可以通过 break 语句随时跳出循环,也可以使用 continue 语句跳过本次循环剩余部分,直接进入下一次循环的判断阶段。
  3. 广泛应用:可以遍历数组、字符串或其他任何可通过索引访问的数据结构,也可以用于进行一定次数的重复操作。
  4. 性能:相对于某些现代迭代器方法(如 .forEach()),传统的 for 循环在 JavaScript 中可能具有更好的性能,特别是在需要优化循环过程或需要在循环中执行条件判断提早退出循环时。不过具体性能表现还需根据实际应用情况来分析。
  5. 迭代范围可控:for 循环允许开发者精确地控制迭代变量的取值范围以及迭代步长,这是它相对于其他迭代机制(如 .forEach() 或 for…of)的一个显著优点。

for…in

介绍

迭代一个对象的所有可枚举字符串属性(除 Symbol 以外),包括继承的可枚举属性。

语法

for (variable in object)
  statement
  • variable:在每次迭代时接收一个字符串属性名。它可以通过使用 constletvar 进行声明,也可以是一个赋值目标(例如,先前声明的变量、对象属性或解构赋值模式)。使用 var 声明的变量不会局限于循环内部,即它们与 for...in 循环所在的作用域相同。
  • object:被迭代非符号可枚举属性的对象
  • statement:每次迭代后执行的语句。可以引用 variable。可以使用块语句执行多个语句。

示例

const obj = { a: 1, b: 2, c: 3 };
for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
}
// 输出:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

// 迭代自有属性
const triangle = { a: 1, b: 2, c: 3 };
function ColoredTriangle() {
  this.color = "红色";
}
ColoredTriangle.prototype = triangle;

const obj = new ColoredTriangle();
for (const prop in obj) {
  if (Object.hasOwn(obj, prop)) {
    console.log(`obj.${prop} = ${obj[prop]}`);
  }
}
// 输出:
// "obj.color = 红色"

// 在迭代期间添加到当前对象的属性永远不会被访问
const obj = { a: 1, b: 2 };
for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
  obj.c = 3;
}
// 输出:
// obj.a = 1
// obj.b = 2

// 被遮蔽(shadowed)的属性只会被访问一次
const proto = { a: 1 };
const obj = { __proto__: proto, a: 2 };

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
}
// 输出:
// obj.a = 2

Object.defineProperty(obj, "a", { enumerable: false });

for (const prop in obj) {
  console.log(`obj.${prop} = ${obj[prop]}`);
}
// 没有输出,因为第一个“a”属性已经被访问过,并且是不可枚举的,不输入,原型链上的“a”被遮蔽不访问。

特点

  1. 只遍历可枚举属性。像 ArrayObject 使用内置构造函数所创建的对象都会继承自Object.prototypeString.prototype的不可枚举属性,例如 String 的 indexOf() 方法或 Object的toString()方法。循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性。
  2. 虽然可是遍历数组,但是最好不要如此。因为for...in 使用属性枚举而不是数组的迭代器。在稀疏数组中 for…in 不会访问空槽
  3. 遍历顺序:顺序是一致且可预测的。在原型链的每个组件中,所有非负整数键(可以作为数组索引)将首先按值升序遍历,然后是其他字符串键按属性创建的先后顺序升序遍历。
  4. 属性访问原则:
    1.它首先获取当前对象的所有自有的字符串键,其方式与 Object.getOwnPropertyNames() 非常相似。
    2.对于每一个键,如果没有访问过具有相同值的字符串,则获取属性描述符并只访问可枚举的属性。但是,即使该属性不可枚举,也会标记为已访问
    3.然后,当前对象被替换为其原型并重复上述过程。
  5. 迭代中更改属性
    • 任何在迭代过程中被添加到当前访问对象中的属性都不会被访问到,因为当前对象的所有自有属性已经被保存了。
    • 如果原型链中多个对象具有相同名称的属性,则只会访问第一个,并且只有它是可枚举时才会被访问。如果它是不可枚举的,则不会访问具有相同名称的任何其他属性,即使它们也是可枚举的。

for…of

介绍

处理来自可迭代对象的值序列。可迭代对象包括内置对象的实例,例如 ArrayStringTypedArrayMapSetNodeList(以及其他 DOM 集合),还包括 arguments 对象、由生成器函数生成的生成器,以及用户定义的可迭代对象。

语法

for (variable of iterable)
  statement
  • variable:每次迭代时从序列接收一个值。可以是用 constletvar 声明的变量,也可以是赋值目标(例如之前声明的变量、对象属性或解构赋值模式)。使用 var 声明的变量不会局限于循环内部,即它们与 for…of 循环所在的作用域相同。
  • iterable:可迭代对象。循环操作的序列值的来源。
  • statement:每次迭代后执行的语句。可以引用 variable。可以使用块语句来执行多个语句。

示例

const iterable = [10, 20, 30];
for (const value of iterable) {
  console.log(value);
}
// 10
// 20
// 30

// 遍历得到的值可以随意更改
const iterable = [10, 20, 30];
for (let value of iterable) {
  value += 1;
  console.log(value);
}
// 11
// 21
// 31

// 字符串将会按 Unicode 码位迭代
const iterable = "boo";
for (const value of iterable) {
  console.log(value);
}
// "b"
// "o"
// "o"

// 迭代类型化数组
const iterable = new Uint8Array([0x00, 0xff]);
for (const value of iterable) {
  console.log(value);
}
// 0
// 255

// 迭代 Map
const iterable = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3],
]);
for (const entry of iterable) {
  console.log(entry);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]

for (const [key, value] of iterable) {
  console.log(value);
}
// 1
// 2
// 3

// 迭代 Set
const iterable = new Set([1, 1, 2, 2, 3, 3]);
for (const value of iterable) {
  console.log(value);
}
// 1
// 2
// 3

// 迭代 NodeList。通过迭代一个 NodeList DOM 集合,为直接位于 <article> 元素下的段落添加一个 read 类。
const articleParagraphs = document.querySelectorAll("article > p");
for (const paragraph of articleParagraphs) {
  paragraph.classList.add("read");
}

// 提前退出
const source = [1, 2, 3];
const iterator = source[Symbol.iterator]();
for (const value of iterator) {
  console.log(value);
  if (value === 1) {
    break;
  }
  console.log("这个字符串不会被输出。");
}
// 1

// 另一个使用相同迭代器的循环从上一个循环中断的地方继续。
for (const value of iterator) {
  console.log(value);
}
// 2
// 3

// 迭代器已用完。该循环不会执行任何迭代。
for (const value of iterator) {
  console.log(value);
}
// [没有输出]

// for...of 不能直接访遍历普通对象
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
// edition
// committee
// standard
for (let e of es6) {
console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function

//使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}

特点

  1. 当循环迭代一个可迭代对象时,它首先调用可迭代对象的 Symbol.iterator() 方法,该方法返回一个迭代器,然后重复调用生成器的 next() 方法,以生成要分配给 variable 的值的序列。
  2. 在迭代器完成时退出(即迭代器的 next() 方法返回一个包含 done: true 的对象)。也可以使用控制流语句来改变正常的控制流程。break 语句退出循环并跳转到循环体后的第一条语句,而 continue 语句跳过当前迭代的剩余语句,继续进行下一次迭代。
  3. variable 部分可以接受任何在 = 运算符之前的内容。只要在循环体内部不重新赋值可以在迭代之间更改,因为它们是两个独立的变量),你可以使用 const 来声明变量。否则,你可以使用 let
  4. iterator的状态会被保存,在第一个循环中执行 break 语句会导致循环提前退出。迭代器尚未完成,因此第二个循环将从第一个循环停止的地方继续执行。

forEach()

介绍

一个迭代方法,对数组的每个元素执行一次给定的函数。总是返回 undefined,而且不能继续链式调用。其典型的用法是在链式调用的末尾执行某些操作。

语法

forEach(callbackFn)
forEach(callbackFn, thisArg)

callbackFn :为数组中每个元素执行的函数。并会丢弃它的返回值。该函数被调用时将传入以下参数:

  • element:数组中正在处理的当前元素.
  • index:数组中正在处理的当前元素的索引。
  • array:调用了 ·forEach()· 的数组本身。

thisArg可选。执行 callbackFn 时用作 this 的值。

示例

const array1 = ['a', 'b', 'c'];
array1.forEach((element) => console.log(element));

// Expected output: "a"
// Expected output: "b"
// Expected output: "c"

// 回调函数不建议使用功异步函数
const ratings = [5, 4, 5];
let sum = 0;
const sumFunction = async (a, b) => a + b;
ratings.forEach(async (rating) => {
  sum = await sumFunction(sum, rating);
});
console.log(sum);
// 期望的输出:14
// 实际的输出:0

// 稀疏数组空槽不访问
const arraySparse = [1, 3, /* empty */, 7];
let numCallbackRuns = 0;
arraySparse.forEach((element) => {
  console.log({ element });
  numCallbackRuns++;
});
console.log({ numCallbackRuns });
// { element: 1 }
// { element: 3 }
// { element: 7 }
// { numCallbackRuns: 3 }

// 在迭代期间修改数组
const words = ["one", "two", "three", "four"];
words.forEach((word) => {
  console.log(word);
  if (word === "two") {
    words.shift(); //'one' 将从数组中删除
  }
}); // one // two // four
// 当到达包含值 two 的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 four 正位于在数组更前的位置,所以 three 会被跳过
console.log(words); // ['two', 'three', 'four']

特点

  1. 仅对已赋值的数组索引调用。对于稀疏数组中的空槽,它不会被调用。
  2. forEach() 不会改变其调用的数组,但是,作为 callbackFn 的函数可以更改数组。请注意,在第一次调用 callbackFn 之前,数组的长度已经被保存
  3. 除非抛出异常,否则没有办法停止或中断 forEach() 循环。如果有这样的需求,则不应该使用 forEach() 方法。
  4. forEach() 期望的是一个同步函数,它不会等待 Promise 兑现。在使用 Promise(或异步函数)作为 forEach 回调时,请确保你意识到这一点可能带来的影响。

总结

  1. for...infor...of 它们之间的主要区别在于它们的迭代方式
    • for...in 语句以任意顺序迭代对象的可枚举属性,会遍历手动添加的其他,甚至包括原型链上的,只能获得对象的键名,不能直接获取键值,为遍历对象而设计。
    • for...of 语句遍历可迭代对象定义要迭代的数据,允许遍历获得键值数组的遍历器接口只返回具有数字索引的属性,不同于forEach方法,它可以与break、continue和return配合使用。提供了遍历所有数据结构的统一操作接口。
  2. for 简单宜用、随时可停、性能好、写起来麻烦。
  3. for...in 用于可枚举对象获取
  4. for...of 用于可迭代序列,获取值。
  5. forEach() 用于数组,简单已读,不要在迭代过程中删减替换元素

🎉完结撒花🎉 😁有用吗😁 👍点赞呀👍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sKK07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值