Статьи

Подготовка к ECMAScript 6: Карта и слабая карта

Если вы читаете эту серию о ECMAScript 6, вы узнали о некоторых новых методах, доступных для типов String и Array . Новая версия JavaScript также вводит несколько новых типов данных. В этой статье мы обсудим Map и ее слабый аналог WeakMap .

Помните, что если вы хотите заполнить то, что мы рассмотрим в этом уроке, вы можете использовать es6-shim Пола Миллера .

Map

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

 ID 2 -> Колин Ихриг, США
 ID 3 -> Джон Доу, США

В таких языках, как Java и C #, у вас есть класс, который позволяет создавать экземпляры карт. В других языках, таких как PHP, вы можете создать карту, используя ассоциативный массив. До ECMAScript 6 JavaScript был одним из языков, в которых отсутствовала эта структура данных. Теперь этот тип данных существует, и он называется Map .

Карты JavaScript действительно мощные, потому что они позволяют использовать любое значение (как объекты, так и примитивные значения) в качестве ключа или значения. Это одно из самых важных отличий по сравнению с картами, созданными с использованием типа Object . Фактически, карты, созданные с использованием литерала объекта, допускают только строки в качестве ключей. Кроме того, как мы увидим через мгновение, у типа Map есть метод, позволяющий легко получить количество элементов, содержащихся в нем, в то время как с объектами вы должны перебирать их вручную, проверяя, что элемент принадлежит самому объекту, и он не наследуется (используя старый добрый hasOwnProperty() ).

Теперь, когда я познакомил вас с этим новым типом данных, давайте выясним, какие свойства и методы доступны.

Map.prototype.size

Свойство size возвращает количество элементов в объекте Map . Это хорошее дополнение, о котором я упоминал в предыдущем разделе, потому что благодаря этому вам не нужно считать элементы самостоятельно.

Map.prototype.constructor()

Конструктор объекта Map используется для создания новых объектов и принимает необязательный аргумент с именем iterable . Последний представляет собой массив или итеративный объект, элементами которого являются пары ключ / значение (двухэлементные массивы). Каждый из этих элементов будет добавлен на новую карту. Например, вы можете написать:

 var array = [['key1', 'value1'], ['key2', 100]]; var map = new Map(array); 

Map.prototype.set()

Метод set() используется для добавления нового элемента (пара ключ / значение) на карту. Если используемый ключ уже существует, соответствующее значение заменяется новым. Его подпись следующая:

 Map.prototype.set(key, value) 

где key — это ключ, который вы хотите использовать, а value — это значение для хранения. Этот метод изменяет карту, к которой он вызван, но также возвращает новую карту.

Этот метод в настоящее время реализован в Firefox, Internet Explorer 11, а также в Chrome и Opera за флагом («Включить экспериментальный JavaScript»).

Map.prototype.get()

Метод get() возвращает значение, связанное с предоставленным ключом. Если ключ не найден, метод возвращает значение undefined . Подпись метода показана ниже, где key — это ключ, который вы хотите использовать.

 Map.prototype.get(key) 

Этот метод в настоящее время реализован в Firefox, Internet Explorer 11, а также в Chrome и Opera за флагом («Включить экспериментальный JavaScript»).

Map.prototype.delete()

Метод delete() удаляет элемент, связанный с предоставленным ключом, с карты. Возвращает true если элемент успешно удален, или false противном случае. Подпись этого метода показана ниже:

 Map.prototype.delete(key) 

key представляет собой ключ элемента, который вы хотите удалить.

Этот метод в настоящее время реализован в Firefox, Internet Explorer 11, а также в Chrome и Opera (необходимо активировать обычный флаг).

Map.prototype.has()

has() — это метод проверки, существует ли элемент с данным ключом или нет. Возвращает true если ключ найден, или false противном случае. Подпись этого метода показана ниже:

 Map.prototype.has(key) 

где key — это ключ, который вы хотите найти.

Этот метод в настоящее время реализован в Firefox, Internet Explorer 11, а также в Chrome и Opera за флагом («Включить экспериментальный JavaScript»).

Map.prototype.clear()

Метод clear() — это удобный способ удалить все элементы из объекта Map . У метода нет возвращаемого значения (что означает, что он возвращает undefined ). Подпись clear() показана ниже:

 Map.prototype.clear() 

clear() в настоящее время реализована в Firefox, Internet Explorer 11, а также в Chrome и Opera за обычным флагом.

Map.prototype.forEach()

Так же, как мы можем зацикливать массивы, выполняя функцию обратного вызова с использованием метода forEach() , то же самое возможно с картами. Подпись forEach() показана ниже:

 Map.prototype.forEach(callback[, thisArg]) 

callback — это функция обратного вызова, которая выполняется для каждого из элементов на карте, и thisArg используется для установки контекста ( this ) обратного вызова. У метода нет возвращаемого значения (что означает, что он возвращает undefined ). callback получает три параметра:

  • value : значение обработанного элемента
  • key : ключ обработанного элемента
  • map : объект Map обрабатывается

Этот метод поддерживается Firefox, Internet Explorer 11, а также Chrome и Opera за флагом.

Map.prototype.entries()

entries() — это метод получения объекта Iterator для итерации по элементам карты. Я уже упоминал этот тип объекта, когда говорил о новом методе keys() типа Array . Сигнатура этого метода:

 Map.prototype.entries() 

Этот метод в настоящее время поддерживается Firefox, а Chrome и Opera — за флагом.

Map.prototype.keys()

Метод keys() очень похож на entries() но возвращает только ключи элементов. Его подпись следующая:

 Map.prototype.keys() 

Этот метод в настоящее время поддерживается Firefox, а Chrome и Opera — за флагом.

Map.prototype.values()

Подобно keys() у нас есть values() . Возвращает объект Iterator содержащий значения элементов карты. Его подпись следующая:

 Map.prototype.values() 

Этот метод в настоящее время поддерживается Firefox, а Chrome и Opera — за флагом.

WeakMap

WeakMap очень похож на Map но имеет несколько важных отличий. Во-первых, WeakMap принимает объекты только в качестве ключей. Это означает, что {} , function(){} (помните, что функции наследуются от Object ), и экземпляры ваших собственных классов разрешены, но 'key' , 10 и другие примитивные типы данных — нет.

Другое важное отличие состоит в том, что объекты WeakMap не предотвращают сборку мусора, если нет никаких других ссылок на объект, который действует как ключ (ссылка слабая ). Из-за этой разницы нет способа извлечь ключи (например, метод Map.prototype.keys() для Map ) или более одного элемента одновременно (например, Map.prototype.values() и Map.prototype.entries() ). Причина хорошо объяснена сетью разработчиков Mozilla (MDN):

Ключи WeakMap не перечисляются (т.е. нет метода, предоставляющего вам список ключей). Если бы они были, список зависел бы от состояния сборки мусора, вводящего недетерминизм.

Как дальнейшее следствие предыдущего пункта, свойство size недоступно.

Стоит также отметить, что Chrome 37 и Opera 24 (последние WeakMap на момент написания статьи) поддерживают WeakMap и его методы без флага, хотя это не относится к Map .

Собираем все вместе

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

В первой демонстрации мы увидим объект Map и его методы в действии.

 // Creates a new Map object var mapObj = new Map(); // Defines an object that will be used a key in the map var objKey = {third: 'c'}; // Adds a new element having a String as its key and a String as its value mapObj.set('first', 'a'); // Adds a new element having a Number as its key and an Array as its value mapObj.set(2, ['b']); // Adds a new element having an Object as its key and a Number as its value mapObj.set(objKey, 3); // Adds a new element having an Array as its key and a String as its value mapObj.set(['crazy', 'stuff'], 'd'); // Checks whether an element having a key of "2" exists in the map. Prints "true" console.log(mapObj.has(2)); // Checks whether an element having a key of "test" exists in the map. Prints "false" console.log(mapObj.has('test')); // Retrieves the element having key of "first". Prints "a" console.log(mapObj.get('first')); // Retrieves the element having key of "['crazy', 'stuff']". Prints "undefined" because even if the value of this array are identical to the one used to set a value, they are not the same array console.log(mapObj.get(['crazy', 'stuff'])); // Retrieves the element having as a key the value of objKey. Prints "3" because it's exactly the same object using to set the element console.log(mapObj.get(objKey)); // Retrieves the element having key of "empty". Prints "undefined" console.log(mapObj.get('empty')); // Retrieves the map size. Prints "4" console.log(mapObj.size); // Deletes the element having key of "first". Prints "true" console.log(mapObj.delete('first')); // Retrieves the map size. Prints "3" console.log(mapObj.size); // Loops over each element of the map mapObj.forEach(function(value, key, map) { // Prints both the value and the key console.log('Value ' + value + ' is associated to key ' + key); }); var entries = mapObj.entries(); var entry = entries.next(); // Loops over each element of the map while(!entry.done) { // Prints both the value and the key console.log('Value ' + entry.value[1] + ' is associated to key ' + entry.value[0]); entry = entries.next(); } var values = mapObj.values(); var value = values.next(); // Loops over each value of the map while(!value.done) { // Prints the value console.log('Value: ' + value.value); value = values.next(); } var keys = mapObj.keys(); var key = keys.next(); // Loops over each key of the map while(!key.done) { // Prints the key console.log('Key: ' + key.value); key = keys.next(); } // Deletes all the elements of the map mapObj.clear(); // Retrieves the map size. Prints "0" console.log(mapObj.size); 

Демонстрационная версия предыдущего кода показана ниже и также доступна в виде JSFiddle .

Во второй демонстрации мы увидим, как мы можем работать с объектом WeakMap .

 // Creates a new WeakMap object var weakMapObj = new WeakMap(); // Defines an object that will be used a key in the map var objKey1 = {a: 1}; // Defines another object that will be used a key in the map var objKey2 = {b: 2}; // Adds a new element having an Object as its key and a String as its value weakMapObj.set(objKey1, 'first'); // Adds a new element having an Object as its key and a String as its value weakMapObj.set(objKey2, 'second'); // Adds a new element having a Function as its key and a Number as its value weakMapObj.set(function(){}, 3); // Checks whether an element having as its key the value of objKey1 exists in the weak map. Prints "true" console.log(weakMapObj.has(objKey1)); // Retrieve the value of element associated with the key having the value of objKey1. Prints "first" console.log(weakMapObj.get(objKey1)); // Deletes the element having key of objKey1. Prints "true" console.log(weakMapObj.delete(objKey1)); // Deletes all the elements of the weak map weakMapObj.clear(); 

Демонстрационная версия предыдущего кода показана ниже и также доступна в виде JSFiddle .

Вывод

В этом уроке я рассмотрел новые WeakMap данных Map и WeakMap . Первый — хорошее дополнение к языку, потому что большинство разработчиков долгое время имитировали карты. Его слабый аналог на самом деле не то, что вы часто используете в своей повседневной работе, но, безусловно, существуют ситуации, когда он может подойти. Чтобы подкрепить обсуждаемые концепции, я настоятельно рекомендую вам поиграть с предоставленными демонстрациями. Веселиться!