JavaScript可迭代性与枚举性
梗概
JavaScript中的迭代性和枚举性是两个相关但不同的概念:
- 可迭代性(Iterability): 决定对象是否可以使用
for...of、展开运算符、解构赋值等特性 - 可枚举性(Enumerability): 决定对象属性是否可以被
for...in、Object.keys()等方法枚举
1. 可迭代性 (Iterability)
1.1 什么是可迭代对象
具有迭代器的对象称为可迭代对象(Iterable)。一个对象要成为可迭代对象,必须实现@@iterator方法,即对象的Symbol.iterator属性必须是一个返回迭代器的函数。
1.2 内置可迭代对象
JavaScript中以下内置类型都是可迭代的:
- Array
- String
- Map
- Set
- TypedArray
- Arguments
- NodeList
1.3 与可迭代性相关的API
1.3.1 for…of 循环
// 遍历数组
const array = [1, 2, 3];
for (const item of array) {
console.log(item); // 1, 2, 3
}
// 遍历字符串
const str = "hello";
for (const char of str) {
console.log(char); // "h", "e", "l", "l", "o"
}for...of循环本质上是使用目标对象所提供的迭代器进行迭代,因此遍历的顺序和内容是由目标对象内部决定的。
1.3.2 展开运算符 (…)
展开运算符可以将可迭代对象展开为单独的元素:
// 展开数组
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const combined = [...array1, ...array2]; // [1, 2, 3, 4, 5, 6]
// 展开字符串
const chars = [...'hello']; // ['h', 'e', 'l', 'l', 'o']
// 转换可迭代对象为数组
const set = new Set([1, 2, 3]);
const arrayFromSet = [...set]; // [1, 2, 3]展开运算符是浅拷贝的一种实现方式。
1.3.3 解构赋值
解构赋值可以从可迭代对象中提取值并赋给变量:
// 数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1, 2, 3
// 忽略某些值
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1, 3
// 结合展开运算符
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1, [2, 3, 4]
// 解构字符串
const [x, y, ...rest] = 'hello';
console.log(x, y, rest); // 'h', 'e', ['l', 'l', 'o']任何具有迭代器的对象都能被当成数组解构。可以给解构时声明的变量赋予默认值,未匹配到的值将是undefined。
1.4 自定义可迭代对象
可以为对象定义迭代器使其成为可迭代对象:
const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
for (const item of myIterable) {
console.log(item); // 1, 2, 3
}1.5 判断对象是否为可迭代
可以通过检查对象是否具有Symbol.iterator方法来判断对象是否可迭代:
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
console.log(isIterable([])); // true
console.log(isIterable('hello')); // true
console.log(isIterable(new Map())); // true
console.log(isIterable({})); // false2. 可枚举性 (Enumerability)
可枚举性是对象属性的一个特性,它决定了该属性是否会在某些操作中被包含或排除。
2.1 与可枚举性相关的API
2.1.1 for…in 循环
遍历对象自身和原型链上的所有可枚举属性:
const obj = { a: 1, b: 2 };
Object.prototype.c = 3;
for (const key in obj) {
console.log(key); // 'a', 'b', 'c'(包含继承的属性)
if (obj.hasOwnProperty(key)) {
console.log(`自身属性: ${key}`); // 'a', 'b'(不包含继承的属性)
}
}2.1.2 Object.keys(obj)
返回对象自身的可枚举属性名组成的数组:
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'c', {
value: 3,
enumerable: false // 不可枚举
});
Object.prototype.d = 4;
console.log(Object.keys(obj)); // ['a', 'b'](不包括不可枚举属性和继承属性)2.1.3 JSON.stringify(obj)
序列化对象时,只会包含对象自身的可枚举属性:
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'c', {
value: 3,
enumerable: false
});
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'(不包含不可枚举属性)2.2 设置属性的可枚举性
可以使用Object.defineProperty()和Object.defineProperties()设置属性的可枚举性:
const obj = { a: 1 }; // 默认可枚举
// 添加不可枚举属性
Object.defineProperty(obj, 'b', {
value: 2,
enumerable: false,
writable: true,
configurable: true
});
console.log(obj.b); // 2
console.log(Object.keys(obj)); // ['a']
// 批量设置属性
Object.defineProperties(obj, {
c: {
value: 3,
enumerable: true
},
d: {
value: 4,
enumerable: false
}
});
console.log(Object.keys(obj)); // ['a', 'c']2.3 检查属性的可枚举性
使用propertyIsEnumerable()方法检查属性是否可枚举:
const obj = { a: 1 };
Object.defineProperty(obj, 'b', {
value: 2,
enumerable: false
});
console.log(obj.propertyIsEnumerable('a')); // true
console.log(obj.propertyIsEnumerable('b')); // false3. 可迭代性与可枚举性的区别
-
适用对象不同:
- 可迭代性适用于整个对象
- 可枚举性适用于对象的属性
-
相关API不同:
- 可迭代对象可用于:
for...of、展开运算符、解构赋值 - 可枚举属性可用于:
for...in、Object.keys()、JSON.stringify()
- 可迭代对象可用于:
-
实现方式不同:
- 可迭代通过实现
Symbol.iterator方法 - 可枚举通过设置属性的
enumerable特性
- 可迭代通过实现
4. 常见误解澄清
- 对象本身并不可迭代:标准对象(
{})默认不可迭代,除非显式实现迭代器 - 每个JS对象属性都有”可枚举性”:这是正确的,属性可以是可枚举的或不可枚举的
- 每个JS对象可定义迭代器:这是正确的,任何对象都可以通过定义
Symbol.iterator方法变为可迭代对象
5. 最佳实践
- 遍历自身属性:使用
Object.keys()或Object.getOwnPropertyNames()而非for...in循环 - 检查属性存在:使用
obj.hasOwnProperty(key)而非简单的key in obj - 库开发:将内部属性设为不可枚举,避免影响用户代码
- 序列化控制:利用不可枚举属性控制JSON序列化输出
- 性能考虑:
Object.keys()通常比for...in加hasOwnProperty组合更快