本文深入解析JavaScript中for...in与for...of循环的核心差异。for...in遍历对象的可枚举字符串属性(含继承),而for...of仅遍历实现了Iterator接口的可迭代对象(如数组、字符串)。文章详细对比了二者在遍历对象、遍历顺序、可迭代性要求及稀疏数组处理上的不同,并解析了迭代器协议中next()方法的返回值结构。
1. 迭代的对象不同
- for...in 循环主要用于遍历对象的可枚举字符串属性,这既包括对象自身的属性,也包括从原型链继承的属性。需要注意的是,它不会遍历非可枚举属性,也不会访问对象的方法。
由内置构造函数(如 Array 和 Object)创建的对象,会从 Array.prototype 和 Object.prototype 继承一些不可枚举的属性。例如 Array 的 indexOf() 方法或 Object 的 toString() 方法,这些方法在 for...in 循环中不会被遍历到。
- for...of 循环则用于遍历可迭代对象(Iterable)中定义的值。常见的可迭代对象包括数组、字符串、Set 和 Map,以及 arguments 对象。它遍历的是可迭代对象通过迭代器(Iterator)返回的值或键值对,因此不能直接用于普通的非可迭代对象。
当执行 for...of 循环时,JavaScript 引擎首先调用可迭代对象的 @@iterator 方法以获取一个迭代器。随后,引擎重复调用该迭代器的 next() 方法,从而生成分配给每次循环迭代的值序列。
需要注意的是,每次迭代都会创建一个新的变量。在循环体内部对变量重新赋值,不会影响可迭代对象本身的原始值。
一个对象要成为可迭代对象,必须拥有 @@iterator 方法。这意味着该对象必须包含一个键为 Symbol.iterator 的属性,且该属性指向一个能返回迭代器对象的函数。
迭代器对象必须包含一个 next() 方法。该方法不接受参数或接受一个参数,并返回符合 IteratorResult 接口的对象。
- value 属性表示当前迭代的值。如果迭代器已经到达末尾,value 的值通常为 undefined,但这并非绝对,具体取决于迭代器的具体实现。
- done 是一个布尔值,用于指示迭代是否已完成。当遍历结束时,done 为 true;否则为 false。
实际上,规范对返回值的属性并非严格要求;如果返回一个没有任何属性的对象,其效果等价于
{ done: false, value: undefined }。
const obj = {
a: 1,
b: { h:123 },
};
//定义在自身的属性
Object.defineProperty(obj, 'c', {
value: 3,
});
//定义在自身的属性
Object.defineProperty(obj, 'd', {
value: 4,
enumerable: true,//是否可枚举,默认false不可枚举
});
//定义在原型上的属性
Object.prototype.e=5;
for (let key in obj) {
console.log(key); // 输出 'a' 、 'b'、 'd'、'e',但不会输出 'c'、'h'
}
console.log(Object.keys(obj)); // 输出 ['a', 'b','d'],不包括 'c'、'e'、'h'
console.log(Object.getOwnPropertyNames(obj)); // 输出 ['a', 'b', 'c','d'],包括 'c', 但不包括'e'、'h'扩展:
Object.keys 会返回一个包含所有可枚举的自有字符串属性的数组,而 Object.getOwnPropertyNames 则会包含所有属性,包括不可枚举的属性。Object.getOwnPropertyDescriptor(obj, prop)静态方法返回一个对象,该对象描述给定对象上特定属性(即直接存在于对象上而不在对象的原型链中的属性)的配置。
obj:要查找其属性的对象。
prop:要检索其描述的属性的名称或 Symbol。
返回值:如果指定的属性存在于对象上,则返回其属性描述符;否则返回 undefined。
// 对象本身的属性的属性描述符
const desc = Object.getOwnPropertyDescriptor(obj, "c");
console.log(desc);
// {
// value: 3,
// writable: false,
// enumerable: false,
// configurable: false
// }
// 对象原型上的属性的属性描述符
const desc1 = Object.getOwnPropertyDescriptor(Object.prototype, "e");
console.log(desc1);
//{
// value: 5,
// writable: true,
// enumerable: true,
// configurable: true
// }属性描述符
- value
与属性关联的值(仅限数据描述符)。
- writable:是否可更改
当且仅当与属性关联的值可以更改时,为 true(仅限数据描述符)。
- enumerable:是否可枚举
当且仅当此属性在相应对象的属性枚举中出现时,为 true。
- configurable:是否可删除
当且仅当该属性描述符的类型可以更改且该属性可以从相应对象中删除时,为 true。
在迭代 Array 时,for...of 循环和 for...in 循环之间的区别。
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
const iterable = [3, 5, 7];
iterable.foo = "hello";
for (const i in iterable) {
console.log(i);
}
// "0"、"1"、"2"、"foo"、"arrCustom"、"objCustom"
for (const i in iterable) {
if (Object.hasOwn(iterable, i)) {
console.log(i);
}
}
// "0" "1" "2" "foo"
for (const i of iterable) {
console.log(i);
}
// 3 5 7使用 Object.hasOwn() 来检查找到的可枚举属性是否为对象的自有属性,即非继承属性。
const arr=['A','B', ,'D', ,'F'];
for(const key in arr){
console.log(key); //0,1,3,5
}
for(const item of arr){
console.log(item); // A,B,undefined,D,undefined,F
}for...in 使用属性枚举而不是数组的迭代器。在稀疏数组中,for...of 会访问空槽(生成 undefined),但 for...in 不会访问空槽。
2. 遍历顺序
- for...in 循环在遍历对象属性时,其遍历顺序是不确定的,因为普通对象属性没有固定的顺序。
根据现代 ECMAScript 规范的定义,遍历的顺序是一致且可预测的。在原型链的每个组件中,所有非负整数键(可以作为数组索引)将首先按值升序遍历,然后是其他字符串键按属性创建的先后顺序升序遍历。
- for...of 循环在遍历可迭代对象时,其遍历顺序是按照对象迭代器定义的顺序进行的。
3. 可迭代性要求
- for...in 循环不需要对象满足可迭代性要求。
- for...of 循环要求被遍历的对象必须是可迭代对象(实现了迭代器接口)。如果对象没有迭代器接口,尝试使用 for...of 循环会抛出错误。
总的来说:
- for...in 循环用于迭代对象的可枚举字符串属性,包括自身属性和继承的属性,
- for...of 循环用于迭代可迭代对象定义的要进行迭代的值。
到此这篇关于JS中for...in 和 for...of 的区别的文章就介绍到这了,更多相关js for...in 和 for...of 区别内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!