又是经典的背八股时,for in 遍历对象的可枚举属性,for of 遍历可迭代对象。那么,什么叫做枚举和迭代呢?什么又是可枚举属性和可迭代对象呢?
枚举和迭代分别是什么?
举个生活中的例子
假设你有一个书架,上面摆满了书。每本书都有一个编号(键名)和内容(值)。我们可以通过两种方式来查看这些书:
- 枚举(Enumeration) :关注的是书的编号。
- 迭代(Iteration) :关注的是书的内容。
1. 枚举
你想知道书架上有哪些书的编号(比如1、2、3...),而不是每本书的内容
这个时候就用到for...in了
const bookshelf = {
1: "JavaScript入门",
2: "算法导论",
3: "计算机网络"
};
// 枚举书架上的书编号
for (let number in bookshelf) {
console.log("书的编号:", number);
}
//输出:
//书的编号: 1
//书的编号: 2
//书的编号: 3
重点是获取“键”(书的编号),而不是“值”(书的内容)
2. 迭代
你想一本一本地阅读书架上的书,关注的是书的内容,而不是编号。
const books = ["JavaScript入门", "算法导论", "计算机网络"];
// 迭代书架上的书内容
for (let book of books) {
console.log("书的内容:", book);
}
//输出
//书的内容: JavaScript入门
//书的内容: 算法导论
//书的内容: 计算机网络
重点是获取“值”(书的内容),而不是“键”(书的编号)
现在是不是就对枚举和迭代有了基本的概念?那么接下来就用程序员的视角再来看一看枚举和迭代
程序员眼中的枚举和迭代
枚举
枚举指的是通过某种方式逐一访问一个对象的所有属性或键名。它通常用于描述对象的属性是否可以通过某些方法(如 for...in 或 Object.keys())被访问。
特点
- 枚举的对象通常是键值对 形式的数据结构,比如普通对象。
- 枚举的核心是访问对象的键名 (属性名),而不是直接访问值。
- 枚举的结果可能包括对象自身的属性以及从原型链继承的属性(需要手动过滤)。
例子
const obj = { a: 1, b: 2, c: 3 };
// 使用 for...in 枚举对象的键名
for (let key in obj) {
console.log(key); // 输出:a, b, c
}
// 使用 Object.keys() 获取对象的可枚举属性
console.log(Object.keys(obj)); // 输出:['a', 'b', 'c']
// 使用 Object.entries() 获取键值对
console.log(Object.entries(obj)); // 输出:[['a', 1], ['b', 2], ['c', 3]]
注意
枚举会包含从原型链继承的属性,因此需要结合hasOwnProperty()来过滤掉继承的属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key); // 只输出自身属性
}
}
迭代
迭代指的是通过某种协议逐一访问一个集合(如数组、字符串、Map、Set 等)中的元素。迭代强调的是对值 的访问,而不是键名。
特点
- 迭代的对象通常是有序集合 ,比如数组、字符串、
Map、Set等。 - 迭代的核心是访问集合中的值 ,而不是键名。
例子
// 数组迭代
const arr = [10, 20, 30];
for (let value of arr) {
console.log(value); // 输出:10, 20, 30
}
// 字符串迭代
const str = "hello";
for (let char of str) {
console.log(char); // 输出:h, e, l, l, o
}#
// Map 迭代
const map = new Map([
['a', 1],
['b', 2]
]);
for (let [key, value] of map) {
console.log(key, value); // 输出:a 1, b 2
}
// Set 迭代
const set = new Set([1, 2, 3]);
for (let value of set) {
console.log(value); // 输出:1, 2, 3
}
可枚举属性与可迭代对象
可枚举属性
可枚举属性是指那些在使用特定方法(如 for...in、Object.keys() 等)时可以被访问到的属性。
- 每个对象的属性都有一个内部特性
[[Enumerable]],它决定了该属性是否是可枚举的。 - 如果
[[Enumerable]]为true,则该属性是可枚举的;如果为false,则是不可枚举的。
如果你知道
defineProperty,那么你可以通过他来修改enumerable的值
注意
- 默认情况下,通过字面量或构造函数创建的对象属性是可枚举的。
- 内置对象(如
Array.prototype或Object.prototype)上的方法通常是不可枚举的。 - 使用
Object.defineProperty()创建的属性,默认是不可枚举的,除非显式设置enumerable: true。 - 可以使用
Object.getOwnPropertyDescriptor()来查看某个属性是否是可枚举的:
const obj = { a: 1 };
Object.defineProperty(obj, 'b', { value: 2, enumerable: false });
console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
// 输出:{ value: 1, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(obj, 'b'));
// 输出:{ value: 2, writable: false, enumerable: false, configurable: false }
如何遍历可枚举属性:
- 使用
for...in遍历对象的所有可枚举属性(包括继承的属性)。 - 使用
Object.keys()获取对象自身的所有可枚举属性。 - 使用
Object.values()和Object.entries()分别获取可枚举属性的值和键值对。
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'c', { value: 3, enumerable: false });
for (let key in obj) {
console.log(key); // 输出:a, b
}
console.log(Object.keys(obj)); // 输出:['a', 'b']
console.log(Object.values(obj)); // 输出:[1, 2]
可迭代对象
可迭代对象是指那些实现了迭代协议 (Iteration Protocol)的对象。迭代协议允许对象通过 for...of 循环或其他需要迭代的地方(如扩展运算符 ...、解构赋值等)进行遍历。
- 一个对象要成为可迭代对象,必须实现一个特殊的属性
[Symbol.iterator],并且这个属性的值是一个返回迭代器的方法。
注意
- 常见的内置可迭代对象包括:数组、字符串、
Map、Set、NodeList等。 - 普通对象默认不是可迭代对象,但可以通过自定义
[Symbol.iterator]方法使其变得可迭代。
const arr = [1, 2, 3];
console.log(typeof arr[Symbol.iterator]); // 输出:'function'
const obj = { a: 1, b: 2 };
console.log(typeof obj[Symbol.iterator]); // 输出:'undefined'
最后
for...in用于遍历对象的可枚举属性,它主要用于遍历对象的键,而不是值for...of用于遍历可迭代对象 的值。- 但一个数据类型并不是绝对的,例如,数组的索引是可枚举的,而数组本身是可迭代的