Как карты, но другие

Как бы я ни любил коллекции похожих объектов, этот пост в блоге не об этом.

Что такое набор?

Набор — это краткий способ создания индексированной (технически ключевой) коллекции значений, которая содержит только уникальные значения. Так что это похоже на Map в том смысле, что это нечто среднее между массивом и простым объектом. Наборы также могут устанавливать любой тип значения в качестве ключа, индексируются в порядке вставки и используют аналогичный синтаксис с важным дополнением: значение может встречаться только один раз. В то время как карты больше похожи на простые объекты, наборы больше похожи на массивы.

Как они работают?

Конструктор: const mySet = new Set();

Вы можете создать новый набор с любым итерируемым объектом, включая другие наборы.

const mySetFromArray = new Set([1, 2, 3]);
const mySetClone = new Set(mySet);
const mySetMix = new Set([...mySet, ...mySetFromArray, 4]);

Также легко сделать массив из набора.

const myArray = Array.from(mySetMix);
const mySpreadArray = [...mySetMix];
console.log(mySetMix);
    //--> {1, 2, 3, 4}
console.log(myArray);
    //--> [1, 2, 3, 4]
console.log(mySpreadArray);
    //--> [1, 2, 3, 4]

И объект.

const myObj = Object.fromEntries(mySet.entries());

Разница между Map и Set здесь заключается в том, что в Set ключи и значения одинаковы.

const myObj2 = Object.fromEntries(mySetMix.entries())
console.log(myObj2);
    //--> {1: 1, 2: 2, 3: 3, 4: 4}

Set имеет аналогичное свойствоsize, но использует несколько иной синтаксис, чем Map.

mySet.add('value') создает новое значение, а mySet.has('value') возвращает логическое значение, указывающее, содержит ли набор это значение. Помните, что значение в наборе может относиться к любому типу данных, включая объекты.

Добавление значения к уже содержащемуся набору ничего не делает, но и не вызывает ошибки.

mySet.delete('value') удаляет значение, а mySet.clear() удаляет все записи в наборе.

Как и в обычном объекте, набор имеет mySet.keys(), mySet.values() и mySet.entries(), которые являются парами ключ-значение. Имейте в виду, что возвращаемое значение этих методов является итератором, а не массивом, но вы можете легко использовать Array.from() для создания массива, если хотите. keys() и values() функционально идентичны.

Перебирать карту очень просто. for (let item of mySet) { }. Вы также можете использовать этот шаблон с keys(), values() и entries(). Вы также можете использовать метод mySet.forEach() с обратным вызовом.

Одна из ловушек заключается в том, что, как и Map, Set не использует обычный синтаксис присваивания свойств объекта, и если вы это сделаете, вы все испортите.

mySet['a'] = 1;
mySet.has('a');     //--> false
mySet.delete('a');  //--> false
console.log(mySet['a']);  //--> 1

Некоторые базовые операции над множествами из MDN упрощаются тем, что записи множеств всегда будут уникальными без какого-либо специального участия с вашей стороны.

Союз, все setA и все setB:

function union(setA, setB) {
    let _union = new Set(setA)
    for (let elem of setB) {
        _union.add(elem)
    }
    return _union
}

Пересечение, общие элементы setA и setB:

function intersection(setA, setB) {
    let _intersection = new Set()
    for (let elem of setB) {
        if (setA.has(elem)) {
            _intersection.add(elem)
        }
    }
    return _intersection
}

Разница, элементы, уникальные для setA и уникальные для setB:

function difference(setA, setB) {
    let _difference = new Set(setA)
    for (let elem of setB) {
        _difference.delete(elem)
    }
    return _difference
}

Каков вариант использования?

Наборы полезны для быстрого удаления дубликатов из массива или строки:

const numbers = [1, 1, 1, 2, 2, 3, 4, 4, 4, 4, 5, 5, 6, 6, 7];
const uniques = [...new Set(numbers)];
console.log(uniques);  //--> [1, 2, 3, 4, 5, 6, 7]

Строка будет рассматриваться как итерируемая:

const text = 'antidisestablishmentarianism';
const uniques = [...new Set(text)];
console.log(uniques);  
  //--> ['a', 'n', 't', 'i', 'd', 's', 'e', 'b', 'l', 'h', 'm', 'r']

Опять же, как и в случае с Map, для WeakMap есть объект, аналогичный WeakSet. В WeakSet все значения (которые по-прежнему должны быть уникальными) также должны быть объектами. Хорошее применение — обнаружение циклических ссылок в рекурсивных функциях. От МДН:

function execRecursively(fn, subject, _refs = null){
	if(!_refs)
		_refs = new WeakSet();
	
	// Avoid infinite recursion
	if(_refs.has(subject))
		return;

	fn(subject);
	if("object" === typeof subject){
		_refs.add(subject);
		for(let key in subject)
			execRecursively(fn, subject[key], _refs);
	}
}

const foo = {
	foo: "Foo",
	bar: {
		bar: "Bar"
	}
};

foo.bar.baz = foo; // Circular reference!
execRecursively(obj => console.log(obj), foo);

Здесь WeakSet создается при первом запуске и передается вместе с каждым последующим вызовом функции (с использованием внутреннего параметра _refs). Количество объектов или порядок их обхода не имеют значения, поэтому WeakSet более подходит (и эффективнее), чем Set для отслеживания ссылок на объекты, особенно если задействовано очень большое количество объектов.

Аккуратный!

TL;DR

Набор — это объект, похожий на массив, который содержит только уникальные значения и имеет несколько удобных объектно-подобных методов.