JavaScript中Set对象研究01:Set基本介绍、实例方法add、clear、delete、has、entries、keys、values、forEach、集合运算(union、difference)
在现代JavaScript开发中,Set
对象作为一种集合数据结构,提供了存储唯一值的方式,无论这些值是什么类型。
本文将深入研究Set
对象,包括其基本介绍、Set()
构造函数,以及一系列常用的实例方法,如add
、clear
、delete
、has
、entries
、keys
、values
和forEach
。此外,我们还将探讨如何实现集合的**并集(union)和差集(difference)**操作。
一、Set基本介绍
1. 什么是Set对象?
Set
对象是ES6(ECMAScript 2015)引入的一种新的集合数据结构,用于存储唯一值的有序列表。Set
中没有键值对,只有值(value),且每个值在集合中只能出现一次,这意味着Set
内部不会有重复的值。
特点:
- 值的唯一性:
Set
集合中的值都是唯一的,没有重复。 - 值的类型多样性:
Set
可以存储任何类型的值,包括基本类型和对象引用。 - 迭代特性:
Set
对象是可迭代的,支持for...of
循环等迭代方式。
2. 为什么使用Set?
在处理需要唯一性的数据集合时,Set
提供了比数组更方便和高效的方式。例如,从一个包含重复元素的数组中获取唯一值列表,或者在添加元素时自动去重。
优势:
- 自动去重:无需手动检查重复,
Set
会自动确保元素的唯一性。 - 高效的查找:
Set
提供了has
方法,可以快速判断某个值是否存在于集合中。 - 丰富的方法:
Set
提供了多种方法,方便对集合进行操作和遍历。
二、Set()构造函数
1. 创建Set对象
Set
对象可以通过Set()
构造函数创建。可以创建一个空的Set
,也可以使用可迭代对象(如数组)初始化Set
。
语法:
// 创建空的Set
const set = new Set();
// 使用可迭代对象初始化Set
const set = new Set(iterable);
iterable
:一个可迭代的对象,其所有元素将被添加到新的Set
中。
2. 示例
(1)创建空的Set
const mySet = new Set();
(2)使用数组初始化Set
const mySet = new Set([1, 2, 3, 4, 5]);
console.log(mySet.size); // 5
(3)自动去重功能
const mySet = new Set([1, 2, 2, 3, 4, 4, 5]);
console.log(mySet.size); // 5
console.log([...mySet]); // [1, 2, 3, 4, 5]
(4)使用字符串作为值
const mySet = new Set(['apple', 'banana', 'orange', 'apple']);
console.log(mySet.size); // 3
console.log([...mySet]); // ['apple', 'banana', 'orange']
3. 注意事项
- 如果
iterable
中存在重复的值,Set
会自动过滤,只保留一个。 - 值的比较采用“同值零等性”(
SameValueZero
),类似于严格相等===
,但NaN
被认为等于NaN
。
三、Set实例方法
Set
对象提供了一系列实例方法,用于操作集合。以下是常用的实例方法:
add(value)
clear()
delete(value)
has(value)
entries()
keys()
values()
forEach(callback[, thisArg])
1. add(value)
定义
add()
方法向Set
对象添加一个指定的值,如果该值已存在,则不会发生任何变化。
语法
set.add(value);
value
:要添加到Set
对象中的值。
返回值
- 返回
Set
对象本身,可以链式调用。
示例
const mySet = new Set();
mySet.add(1);
mySet.add(5).add(5).add(10); // 链式调用,重复的值5不会被添加多次
console.log(mySet.size); // 3
console.log([...mySet]); // [1, 5, 10]
注意事项
- 如果添加的值已存在于
Set
中,集合不会发生变化。
2. clear()
定义
clear()
方法移除Set
对象中的所有元素。
语法
set.clear();
示例
const mySet = new Set([1, 2, 3]);
console.log(mySet.size); // 3
mySet.clear();
console.log(mySet.size); // 0
注意事项
clear()
方法不返回值,执行后Set
对象将变为空。
3. delete(value)
定义
delete()
方法从Set
对象中移除指定的元素。
语法
set.delete(value);
value
:要移除的值。
返回值
true
:如果成功移除对应的值。false
:如果值不存在于Set
中。
示例
const mySet = new Set([1, 2, 3]);
console.log(mySet.size); // 3
mySet.delete(2); // 返回 true
console.log(mySet.size); // 2
console.log(mySet.has(2)); // false
mySet.delete(5); // 返回 false,因为值不存在
注意事项
- 如果值不存在,
delete()
方法返回false
,Set
对象不发生变化。
4. has(value)
定义
has()
方法返回一个布尔值,表示Set
对象中是否存在指定的值。
语法
set.has(value);
value
:要检查的值。
示例
const mySet = new Set([1, 2, 3]);
console.log(mySet.has(2)); // true
console.log(mySet.has(5)); // false
console.log(mySet.has(NaN)); // false
mySet.add(NaN);
console.log(mySet.has(NaN)); // true
5. entries()
定义
entries()
方法返回一个新的Iterator
对象,包含Set
对象中所有元素的键值对,键和值相同。
语法
set.entries();
示例
const mySet = new Set(['apple', 'banana', 'orange']);
for (const [key, value] of mySet.entries()) {
console.log(`${key} => ${value}`);
}
// 输出:
/*
apple => apple
banana => banana
orange => orange
*/
注意事项
- 由于
Set
没有键的概念,所以entries()
方法返回的键和值是相同的。
6. keys()
定义
keys()
方法返回一个新的Iterator
对象,包含Set
对象中的所有元素。
语法
set.keys();
示例
const mySet = new Set([1, 2, 3]);
for (const key of mySet.keys()) {
console.log(key);
}
// 输出:
/*
1
2
3
*/
注意事项
- 对于
Set
对象,keys()
方法与values()
方法功能相同,都是返回集合中的元素。
7. values()
定义
values()
方法返回一个新的Iterator
对象,包含Set
对象中的所有元素。
语法
set.values();
示例
const mySet = new Set(['a', 'b', 'c']);
for (const value of mySet.values()) {
console.log(value);
}
// 输出:
/*
a
b
c
*/
8. forEach(callback[, thisArg])
定义
forEach()
方法对Set
对象中的每个值,按照插入顺序调用一次callback
函数。
语法
set.forEach(callback[, thisArg]);
callback(value, key, set)
:为每个元素执行的函数。value
:当前元素的值。key
:对于Set
对象,key
与value
相同。set
:调用forEach()
的Set
对象本身。
thisArg
:可选,执行callback
函数时,用作this
的值。
示例
const mySet = new Set([1, 2, 3]);
mySet.forEach((value, key, set) => {
console.log(`Value: ${value}, Key: ${key}`);
});
// 输出:
/*
Value: 1, Key: 1
Value: 2, Key: 2
Value: 3, Key: 3
*/
使用thisArg
const mySet = new Set([1, 2, 3]);
const multiplier = {
factor: 2,
multiply(value) {
return value * this.factor;
}
};
mySet.forEach(function(value) {
console.log(`${value} * ${this.factor} = ${this.multiply(value)}`);
}, multiplier);
// 输出:
/*
1 * 2 = 2
2 * 2 = 4
3 * 2 = 6
*/
注意事项
forEach()
方法的回调函数的参数与Map
的forEach()
一致,但key
和value
相同。- 如果需要中途停止迭代,可以考虑使用
for...of
循环,配合break
或return
。
四、Set的遍历
除了上述的entries()
、keys()
、values()
和forEach()
方法,Set
对象还可以通过迭代器和解构赋值进行遍历。
1. 使用for...of
循环
const mySet = new Set(['x', 'y', 'z']);
for (const value of mySet) {
console.log(value);
}
// 输出:
/*
x
y
z
*/
2. 使用扩展运算符
const mySet = new Set([1, 2, 3]);
const setArray = [...mySet];
console.log(setArray); // [1, 2, 3]
3. 使用Array.from()
const mySet = new Set(['a', 'b', 'c']);
const arrayFromSet = Array.from(mySet);
console.log(arrayFromSet); // ['a', 'b', 'c']
五、集合运算
在2024年已经支持了如并集(union)和差集(difference)、判断子集等功能,但我们可以通过自定义函数来实现这些功能。
1. 并集(union)
并集是指两个集合的所有元素的集合。
实现并集
function union(setA, setB) {
const resultSet = new Set(setA);
for (const value of setB) {
resultSet.add(value);
}
return resultSet;
}
// 示例
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);
const unionSet = union(setA, setB);
console.log([...unionSet]); // [1, 2, 3, 4, 5]
2. 差集(difference)
差集是指属于第一个集合但不属于第二个集合的元素的集合。
实现差集
function difference(setA, setB) {
const resultSet = new Set(setA);
for (const value of setB) {
resultSet.delete(value);
}
return resultSet;
}
// 示例
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
const differenceSet = difference(setA, setB);
console.log([...differenceSet]); // [1]
3. 交集(intersection)
交集是指同时属于两个集合的元素的集合。
实现交集
function intersection(setA, setB) {
const resultSet = new Set();
for (const value of setA) {
if (setB.has(value)) {
resultSet.add(value);
}
}
return resultSet;
}
// 示例
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
const intersectionSet = intersection(setA, setB);
console.log([...intersectionSet]); // [2, 3]
4. 子集(subset)
判断一个集合是否是另一个集合的子集。
实现子集判断
function isSubset(setA, setB) {
for (const value of setA) {
if (!setB.has(value)) {
return false;
}
}
return true;
}
// 示例
const setA = new Set([1, 2]);
const setB = new Set([1, 2, 3]);
console.log(isSubset(setA, setB)); // true
console.log(isSubset(setB, setA)); // false
六、应用示例
1. 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
2. 字符串中独特字符的统计
function countUniqueChars(str) {
return new Set(str).size;
}
console.log(countUniqueChars('hello')); // 4 ('h', 'e', 'l', 'o')
console.log(countUniqueChars('world')); // 5
3. 过滤重复对象
const items = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 1, name: 'apple' },
];
const uniqueItems = Array.from(new Set(items.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
console.log(uniqueItems);
/*
[
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' }
]
*/
七、注意事项
-
值的相等性:
Set
使用“同值零等性”比较值,这意味着0
和-0
被认为是相同的值,而NaN
可以作为有效的值,并且NaN === NaN
在Set
中为true
。const mySet = new Set(); mySet.add(NaN); console.log(mySet.has(NaN)); // true
-
不可重复性:
Set
中不会有重复的值,如果尝试添加重复的值,Set
会忽略它。 -
遍历顺序:
Set
会按照元素添加的顺序进行遍历,因此可以预测遍历的顺序。 -
Set与数组的转换:可以使用扩展运算符或
Array.from()
在Set
和数组之间进行转换。const mySet = new Set([1, 2, 3]); const myArray = [...mySet]; // [1, 2, 3]
-
Set的性能:对于频繁的增删查操作,
Set
的性能通常优于数组,因为Set
的has
方法在平均情况下的时间复杂度为O(1)。
参考资料
- MDN Web Docs - Set
- ECMAScript® 2015 Language Specification - Set Objects
- JavaScript高级程序设计(第4版)
- Understanding ECMAScript 6 - Set and WeakSet