Статьи

Взгляд на наборы ES6

Эта статья была взята из Тайны JavaScript ниндзя , 2-е изд. 

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

Перечисление 1, Подражающее наборам с объектами
function Set(){
  this.data = {};//Use an object to store items
  this.length = 0;
}

Set.prototype.has = function(item){
  return typeof this.data[item] !== "undefined";
   }

Set.prototype.add = function(item){
  if(!this.has(item)){      //Add an item only if it isn’t already
    this.data[item] = true;//contained within the set
    this.length++;
  }
};

Set.prototype.remove = function(item){
  if(this.has(item)){  
    delete this.data[item];
    this.length--;
  }
}

var ninjas = new Set();
ninjas.add("Hattori");//Try to add Hattori 
ninjas.add("Hattori");//twice

assert(ninjas.has("Hattori") && ninjas.length == 1, //Check that Hattori is added only once
       "Our set contains only one Hattori");

ninjas.remove("Hattori");
assert(!ninjas.has("Hattori") && ninjas.length == 0, 
       "Our set is now empty");

В листинге 1 приведен очень простой пример того, как наборы можно имитировать объектами. Мы используем данные объекта хранения данных для отслеживания наших элементов набора, и мы представили три метода: метод has , который проверяет, содержится ли элемент в нашем наборе; дополнительный метод, который добавляет элемент, только если тот же элемент не содержится уже и удалить метод, который удаляет уже существующий элемент из набора.

Однако это плохой двойник, так как с картами вы не можете хранить объекты, только строки и числа, и всегда существует риск доступа к объектам-прототипам. По этим причинам комитет ECMAScript решил представить совершенно новый тип наборов наборов .

Обратите внимание, что наборы являются частью стандарта ES6. Для текущей совместимости браузера см .: https://kangax.github.io/compat-table/es6/#test-Set .

Создание нашего первого набора

Краеугольным камнем создания множеств является недавно представленная функция конструктора, которая называется « Set» . Давайте посмотрим на пример.

Перечисление 2, Создающее наборы
//The Set constructor can take in an array of items with which the set will be initialized
var ninjas = new Set(["Kuma", "Hattori", "Yagyu", "Hattori"]);

//Any duplicate items are simply discarded
assert(ninjas.has("Hattori"), "Hattori is in our set");
assert(ninjas.size == 3, "There are only three ninjas in our set!");

//We can add new items, that aren’t already contained within the set
assert(!ninjas.has("Yoshi"), "Yoshi is not in, yet..");
ninjas.add("Yoshi");
assert(ninjas.has("Yoshi"), "Yoshi is added");
assert(ninjas.size == 4, "There are four ninjas in our set!");

//Adding existing items will have no effect
assert(ninjas.has("Kuma"), "Kuma is already added");
ninjas.add("Kuma");
assert(ninjas.size == 4, "Adding Kuma again has no effect");

//We can iterate through sets with the for…of loop
for(var ninja of ninjas) {
  assert(ninja, ninja);
}

В листинге 2 мы используем встроенный конструктор Set для создания нового набора ниндзя, который будет содержать различные ниндзя. Если мы не передадим никаких аргументов, будет создан пустой набор. Мы также можем передать массив, как мы это сделали в этом примере, который будет предварительно заполнять набор для нас.

new Set(["Kuma", "Hattori", "Yagyu", "Hattori"]);

Как мы уже упоминали, наборы являются коллекциями уникальных предметов, и их основная цель состоит в том, чтобы помешать нам хранить несколько экземпляров одного и того же объекта. В нашем случае это означает, что «Хаттори», который мы пытались добавить дважды, будет добавлен только один раз.

Каждый набор имеет ряд доступных методов. Например, метод has проверяет, содержится ли элемент в наборе

ninjas.has("Hattori")

и метод add используется для добавления уникальных элементов в набор:

ninjas.add("Yoshi");

Если нам интересно, сколько элементов в наборе, мы всегда можем использовать свойство size .

Подобно картам и массивам, поскольку наборы также являются коллекциями, поэтому ничто не мешает нам перебирать их с помощью цикла for…. Как видно на следующем рисунке, элементы всегда перебираются в том порядке, в котором они были вставлены.

Рисунок 1 Запуск кода из листинга 2 приводит к следующему выводу. Элементы в наборе всегда перебираются в том порядке, в котором они были вставлены.

Теперь, когда мы ознакомились с основами множеств, давайте рассмотрим некоторые общие операции над множествами: объединения, пересечения и различия.

Союз Сетов

Первая операция, которую мы собираемся изучить, это объединение . Проще говоря, объединение двух наборов A и B создает новый набор, который содержит все элементы из A и B. Естественно, каждый элемент не может встречаться более одного раза в новом наборе.

Перечисление 3 Используя наборы, чтобы выполнить объединение коллекций


var ninjas = ["Kuma", "Hattori", "Yagyu"]; //Create an array of ninjas and samurai. 
var samurai = ["Hattori", "Oda", "Tomoe"]; //Notice how Hattori is both a ninja and a samurai

var warriors = new Set([...ninjas, ...samurai]);//Create a new set of warriors by de-structuring ninjas and samurai

//All our ninjas and samurai are included in the new warriors set
assert(warriors.has("Kuma"), "Kuma is here"); 
assert(warriors.has("Hattori"), "And Hattori");
assert(warriors.has("Yagyu"), "And Yagyu");
assert(warriors.has("Oda"), "And Oda");
assert(warriors.has("Tomoe"), "Tomoe, last but not least");

//There are no duplicates in the new set, 
//Hattori even though he is contained in 
//both the ninjas and the samurai sets is included only once!
assert(warriors.size === 5, "There are 5 warriors in total");

В листинге 3 мы сначала создаем массив ниндзя и массив самураев. Обратите внимание, как Хаттори ведет очень занятую жизнь; днем самурай, а ночью ниндзя. Теперь представьте, что нам нужно создать группу людей, которых мы можем призвать к оружию, если соседний даймё решит, что его провинция немного тесновата. Мы создадим новый набор воинов, который будет включать всех ниндзя и всех самураев. Однако, поскольку Хаттори находится в обеих коллекциях, мы хотим включить его только один раз, и совсем не похоже, что два Хаттори ответят на наш звонок.

В этом случае наборы идеальны! Нам не нужно вручную отслеживать, был ли элемент уже включен, набор позаботится об этом сам, автоматически.

При создании этого нового набора мы использовали оператор распространения: [… ниндзя, … самурай], чтобы создать новый массив, который будет содержать всех ниндзя и всех самураев. Если вам интересно, Хаттори будет присутствовать дважды в этом новом массиве. Однако когда мы наконец передадим этот массив конструктору Set, Hattori будет включен только один раз, см. Следующий рисунок.

Рис. 2. Объединение двух наборов позволяет хранить элементы из обеих коллекций (конечно, без дубликатов)

Пересечение множеств

Вторая операция, которую мы рассмотрим, — это пересечение двух наборов A и B, которое создает набор, содержащий элементы A, которые также находятся в B. В нашем примере это может быть поиск ниндзя, которые также являются самураями, см. следующий пример.

Перечисление 4 Пересечение Наборов
var ninjas = new Set(["Kuma", "Hattori", "Yagyu"]); 
var samurai = new Set(["Hattori", "Oda", "Tomoe"]);   

var ninjaSamurais = new Set(
  //Use the spread operator to turn our set into an array, 
  //so that we can use the array’s filter method to keep 
  //only ninjas that are contained in the samurai set
  [...ninjas].filter(function(ninja){
    return samurai.has(ninja);
  })
);

assert(ninjaSamurais.size == 1, "There’s only one ninja samurai");
assert(ninjaSamurais.has("Hattori") == "Hattori is his name");

Идея листинга 4 заключается в создании нового набора, который будет содержать только ниндзя, которые также являются самураями. Мы сделаем это, используя метод фильтра массива, который, как вы помните, создает новый массив, который содержит только элементы, которые соответствуют определенному критерию. В нашем случае этот критерий заключается в том, что ниндзя также является самураем (что он содержится в наборе самураев). Так как метод фильтра может использоваться только для массивов, мы должны превратить наш набор ниндзя в массив, используя оператор распространения:

[...ninjas]

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

Разница множеств

Последняя операция набора, которую мы собираемся изучить, — это разность двух наборов A и B, которая содержит все элементы, которые находятся в наборе A, но не находятся в наборе B. Как вы можете догадаться, это очень похоже на пересечение множеств, с одним небольшим, но существенным отличием. Давайте посмотрим на следующий листинг, в котором мы хотим найти только настоящих ниндзя (а не тех, которые также являются дневным светом? Как самураи):

Различие перечисления 5 наборов
var ninjas = new Set(["Kuma", "Hattori", "Yagyu"]); 
var samurai = new Set(["Hattori", "Oda", "Tomoe"]);   

var pureNinjas = new Set(
  [...ninjas].filter(function(ninja){
    //With set difference, we care only about ninjas that are NOT samurai!
    return !samurai.has(ninja);
  })
);

assert(pureNinjas.size == 2, "There’s only one ninja samurai");
assert(pureNinjas.has("Kuma"), "Kuma is a true ninja");
assert(pureNinjas.has("Yagyu"), "Yagyu is a true ninja");

Единственное изменение, которое мы внесли, заключается в том, что мы заботимся только о ниндзя, которые НЕ являются также самураями, просто поставив восклицательный знак (!) Перед выражением samurai.has (ниндзя).

Чтобы получить более подробную информацию о других функциях JavaScript (как старых, так и новых), см. Секреты ниндзя JavaScript  (используйте код dzmaras для 39% скидки).