Skip to content

在 ES 6 之前,我们要保存多个数据只能用数组或集合,这是很匮乏的,可选性太少了,于是在 ES 6 提出了 set 和 map 两个新的数据类型,这两个数据类型都可以用于保存多个数据。

set 集合可以用于保存多个数据,类似于数组,但是 set 集合有一个很大的特点,就是天然去重,也就是 set 集合中的数据是不可能重复的。

接下来使用代码来演示 set 集合的使用

js
new Set();//创建一个空的Set集合

const s = new Set();//这就是一个空的Set集合

const s1 = new Set([1,2,3,4]);//在创建Set的时候,可以传入一个可迭代对象,这里传入一个数组,此时Set集合就会根据可迭代对象的迭代结果来初始化内容。这里实际上就实现了可迭代对象向Set集合的转换。

const s2 = new Set([1,2,3,4,4]);//如果数组中有重复数据,会自动去重

s1.size;//只读属性,这个属性可以用于拿到Set集合的尺寸,相当于数组的length

s1.add(2);//添加一个数据在set集合的末位,注意,set集合是有序的,如果数据已存在,不进行任何操作

//注意,set集合使用Object.is()函数来判断两个数据是否相同,但是针对+0和-0,set认为是同一个数据。

s1.has(3);//可以判断Set中是否存在指定数据,判断方式同上,返回一个布尔值

s1.delete(3);//删除Set集合中指定的数据,注意,这里是按值删除,返回一个布尔值,表示是否删除成功。

s1.clear();//清空整个Set集合

const arr = [...s];//由于Set本身是一个可迭代对象,迭代的每一个结果为每一项的数据,所以我们可以使用展开运算符来将Set集合转换为数组。
//这里结合前面的数组转Set,可以很轻松的实现数组去重

[...new Set([1,2,3,4,5,1,2,4,6])];//这里就可以得到一个去重后的数组。

//由于Set本身是一个可迭代对象,所以我们可以使用for...of循环遍历

for(const item of s){
	console.log(item);
}

//使用Set中的forEach方法遍历
s.forEach(item => {//在Set集合中,不存在下标,所以在forEach的回调函数中,我们也获取不到他的下标,这里的forEach的回调函数也可以传入三个参数,第一个参数和第二个参数一模一样,都表示值,第三个参数为Set集合本身。
	console.log(item);
})

//Set集合很常见是做法是求两个数组的交集,并集,差集

const arr1 = [1,2,3,4];
const arr2 = [3,4,5,6];

function union(arr1,arr2){//并集
	const arr = arr1.concat(arr2);
	return [...new Set(arr)];
}

function intersetction(arr1,arr2){//交集
	const newArr1 = [...new Set(arr1)];
	arr2 = new Set(arr2);
	return newArr1.filter(item =>{
		return arr2.has(item);
	});
}

function difference(arr1,arr2){//arr1 - arr2
	arr1 = [...new Set(arr1)];
	arr2 = new Set(arr2);
	return arr1.filter(item => {
		return !arr2.has(item);
	});
}

接下来手写 Set 集合

js
/**
 * 类 MySet 实现一个简单的集合数据结构。
 */
class MySet {
	/**
	 * 构造函数,初始化集合。
	 * @param {Iterable} obj 可迭代对象,用于初始化集合。
	 */
	constructor(obj) {
		this.size = 0;
		// 如果传入对象可迭代,则遍历并添加到集合中
		if (obj[Symbol.iterator]) {
			for (const item of obj) {
				this.add(item);
			}
		}
	}

	/**
	 * 实现集合的迭代器方法,使其可遍历。
	 * @yields {*} 遍历集合中的每个元素。
	 */
	*[Symbol.iterator]() {
		for (let i = 0; i < this.size; i++) {
			yield this[i];
		}
	}

	/**
	 * 检查集合中是否存在指定元素。
	 * @param {*} item 要检查的元素。
	 * @returns {boolean} 元素存在返回 true,否则返回 false。
	 */
	has(item) {
		// 遍历集合,进行存在性检查
		for (const n of this) {
			if (item === n || (Number.isNaN(n) && Number.isNaN(item)))
				return true
		}
		return false;
	}

	/**
	 * 从集合中删除指定元素。
	 * @param {*} item 要删除的元素。
	 * @returns 无返回值。
	 */
	delete(item) {
		// 先检查元素是否存在
		if (!this.has(item)) {
			return;
		}
		// 标记找到要删除的元素,然后将其从集合中移除
		let index = 0;
		let flag = false;
		for (const n of this) {
			if (flag) {
				this[index - 1] = this[index];
			} else {
				if (item === n || (Number.isNaN(n) && Number.isNaN(item))) {
					flag = true;
				}
			}
			index++;
		}
		this.size--;
		delete this[this.size];
	}

	/**
	 * 向集合中添加一个元素。
	 * @param {*} item 要添加的元素。
	 * @returns 无返回值。
	 */
	add(item) {
		// 如果元素不存在于集合中,则添加
		if (!this.has(item)) {
			this[this.size] = item;
			this.size++;
		}
	}

	/**
	 * 清空集合,移除所有元素。
	 * @returns 无返回值。
	 */
	clear() {
		// 遍历集合,逐个删除元素
		for (let i = 0; i < this.size; i++) {
			delete this[i];
		}
		this.size = 0;
	}

	/**
	 * 对集合中每个元素执行给定的回调函数。
	 * @param {Function} fn 回调函数,接受三个参数:当前元素,当前元素,集合本身。
	 * @returns 无返回值。
	 */
	forEach(fn) {
		// 遍历集合,执行回调函数
		for (const item of this) {
			fn(item, item, this);
		}
	}
}

以笔记为镜,可以知得失