Клавиша / esc

Массив

Ни один язык программирования не обходится без хранения списков значений. JavaScript не исключение.

Время чтения: меньше 5 мин

Кратко

Скопировано

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

Массивы очень похожи на нумерованные списки.

Как пишется

Скопировано

Создадим массив с помощью квадратных скобок [].

К примеру, можно создать пустой массив:

        
          
          const guestList = [] // 😭 гостей нет
          const guestList = [] // 😭 гостей нет

        
        
          
        
      

А можно создать сразу с элементами внутри:

        
          
          const theGirlList = [  'Серсея', 'Илин Пейн', 'Меррин Трант', 'Дансен', 'Гора']
          const theGirlList = [
  'Серсея', 'Илин Пейн', 'Меррин Трант', 'Дансен', 'Гора'
]

        
        
          
        
      

Элементы могут быть разных типов:

        
          
          const infoArray = [  'Россия', 'Москва', 144.5, 'Russian ruble', true]
          const infoArray = [
  'Россия', 'Москва', 144.5, 'Russian ruble', true
]

        
        
          
        
      

Внутри массива могут быть другие массивы:

        
          
          const arrayOfArrays = [  'Россия',  ['Москва', 'Санкт-Петербург', 'Казань', 'Екатеринбург'],  [true, true, false, true]]
          const arrayOfArrays = [
  'Россия',
  ['Москва', 'Санкт-Петербург', 'Казань', 'Екатеринбург'],
  [true, true, false, true]
]

        
        
          
        
      

Как понять

Скопировано

Массивы хранят элементы в пронумерованных «ячейках». Нумерация начинается с нуля. Первый элемент массива будет иметь номер 0, второй — 1 и так далее. Номера называют индексами.

Количество доступных ячеек называют размером или длиной массива. В JavaScript длина массива обычно совпадает с количеством элементов в нем. Массивы хранят свой размер в свойстве length:

        
          
          const infoArray = [  'Россия', 'Москва', 144.5, 'Russian ruble', true]console.log(infoArray.length)// 5
          const infoArray = [
  'Россия', 'Москва', 144.5, 'Russian ruble', true
]
console.log(infoArray.length)
// 5

        
        
          
        
      

Чтение

Скопировано

Чтобы получить содержимое ячейки с этим номером, обратитесь к конкретному индексу. Если ячейка пустая или такой ячейки нет, то JavaScript вернёт undefined:

        
          
          const guestList = ['Маша', 'Леонард', 'Шелдон', 'Джон Сноу']const firstGuest = guestList[0]console.log(firstGuest)// Машаconsole.log(guestList[3])// Джон Сноуconsole.log(guestList[999])// undefined
          const guestList = ['Маша', 'Леонард', 'Шелдон', 'Джон Сноу']
const firstGuest = guestList[0]

console.log(firstGuest)
// Маша

console.log(guestList[3])
// Джон Сноу

console.log(guestList[999])
// undefined

        
        
          
        
      

Запись

Скопировано

Используйте комбинацию чтения и оператора присваивания:

        
          
          const episodesPerSeasons = [10, 10, 10, 10, 10, 9, 7, 6]console.log(episodesPerSeasons[5])// 9// Запись в ячейку с индексом 5episodesPerSeasons[5] = 10console.log(episodesPerSeasons[5])// 10
          const episodesPerSeasons = [10, 10, 10, 10, 10, 9, 7, 6]

console.log(episodesPerSeasons[5])
// 9

// Запись в ячейку с индексом 5
episodesPerSeasons[5] = 10
console.log(episodesPerSeasons[5])
// 10

        
        
          
        
      

Добавление элементов

Скопировано

Добавление элементов — это частая операция. Для добавления используйте методы:

  • push() — для добавления в конец массива.
  • unshift() — для добавления в начало массива.

Оба принимают произвольное количество аргументов. Все аргументы будут добавлены в массив. Лучше использовать push(), он работает быстрее. Методы возвращают размер массива после вставки:

        
          
          const watched = ['Властелин Колец', 'Гарри Поттер']watched.push('Зелёная Книга')console.log(watched)// ['Властелин Колец', 'Гарри Поттер', 'Зелёная книга']let newLength = watched.push('Мстители', 'Король Лев')console.log(newLength)// 5newLength = watched.unshift('Грязные танцы')console.log(newLength)// 6console.log(watched)// [//  'Грязные танцы', 'Властелин Колец', 'Гарри Поттер',//  'Зелёная книга', 'Мстители', 'Король Лев'// ]
          const watched = ['Властелин Колец', 'Гарри Поттер']

watched.push('Зелёная Книга')
console.log(watched)
// ['Властелин Колец', 'Гарри Поттер', 'Зелёная книга']

let newLength = watched.push('Мстители', 'Король Лев')
console.log(newLength)
// 5

newLength = watched.unshift('Грязные танцы')
console.log(newLength)
// 6

console.log(watched)
// [
//  'Грязные танцы', 'Властелин Колец', 'Гарри Поттер',
//  'Зелёная книга', 'Мстители', 'Король Лев'
// ]

        
        
          
        
      

Создать большой массив из чисел

Скопировано

С помощью цикла и метода push() можно быстро создать большой массив с числами.

Создадим массив чисел от 1 до 1000:

        
          
          const numbers = []for (let i = 1; i <= 1000; ++i) {  numbers.push(i)}
          const numbers = []
for (let i = 1; i <= 1000; ++i) {
  numbers.push(i)
}

        
        
          
        
      

Создадим массив чётных чисел от 0 до 1000:

        
          
          const evenNumbers = []for (let i = 0; i <= 1000; i += 2) {  evenNumbers.push(i)}
          const evenNumbers = []
for (let i = 0; i <= 1000; i += 2) {
  evenNumbers.push(i)
}

        
        
          
        
      

💡 Поиск по массиву

Скопировано

Используйте indexOf(), чтобы найти, под каким индексом хранится элемент.

Используйте includes(), чтобы проверить, что элемент есть в массиве:

        
          
          const episodesPerSeasons = [10, 10, 10, 10, 10, 9, 7, 6]console.log(episodesPerSeasons.includes(8))// falseconsole.log(episodesPerSeasons.includes(6))// true
          const episodesPerSeasons = [10, 10, 10, 10, 10, 9, 7, 6]

console.log(episodesPerSeasons.includes(8))
// false

console.log(episodesPerSeasons.includes(6))
// true

        
        
          
        
      

Интересно, что если в массиве будут индексы с пропусками, то можно получить разрежённый массив. Предположим, у нас есть набор элементов:

        
          
          const arr = ['d', 'o', 'k', 'a']
          const arr = ['d', 'o', 'k', 'a']

        
        
          
        
      

Добавим к нему ещё один элемент, так, чтобы его индекс был больше длины всего набора элементов. Мы получим массив с незаполненным элементом (empty slot). Если обратимся к нему по индексу, получим undefined:

        
          
          arr[5] = '!'console.log(arr)// Выведет в Firefox ['d', 'o', 'k', 'a', <1 empty slot>, '!']console.log(arr[4])// Выведет undefined
          arr[5] = '!'

console.log(arr)
// Выведет в Firefox ['d', 'o', 'k', 'a', <1 empty slot>, '!']

console.log(arr[4])
// Выведет undefined

        
        
          
        
      

Длина массива будет включать в себя все элементы, включая незаполненные, то есть в нашем случае не 5 элементов, а 6:

        
          
          console.log(arr.length)// Выведет 6
          console.log(arr.length)
// Выведет 6

        
        
          
        
      

Или мы можем взять другой пример:

        
          
          // Зрители, которые заняли три места в ряду. Индексы их мест: 0, 1, 2const audience = ['🐸',  '🐶',  '🐱']// Опоздавший зритель занял место в темноте и не по порядку!audience[5] = '🐌'// Места с индексами 3 и 4 всё ещё свободны, и это логично!console.log(audience)// Array(6) [ '🐸', '🐶', '🐱', <2 empty slots>, '🐌' ]
          // Зрители, которые заняли три места в ряду. Индексы их мест: 0, 1, 2
const audience = ['🐸',  '🐶',  '🐱']

// Опоздавший зритель занял место в темноте и не по порядку!
audience[5] = '🐌'

// Места с индексами 3 и 4 всё ещё свободны, и это логично!
console.log(audience)
// Array(6) [ '🐸', '🐶', '🐱', <2 empty slots>, '🐌' ]

        
        
          
        
      

На практике

Скопировано

Николай Лопин советует

Скопировано

🛠 Копирование массива

С копированием есть хитрость. Массив — большая структура, и она не вмещается в одну переменную. Переменная хранит адрес, где находится массив. Если этого не знать, то результат такого кода будет выглядеть странно:

        
          
          const iWatched = ['GameOfThrones', 'Breaking Bad']const vitalikWatched = iWatchedvitalikWatched.push('American Gods')console.log(iWatched)// ['GameOfThrones', 'Breaking Bad', 'American Gods'] 🤷‍♂️
          const iWatched = ['GameOfThrones', 'Breaking Bad']
const vitalikWatched = iWatched

vitalikWatched.push('American Gods')
console.log(iWatched)
// ['GameOfThrones', 'Breaking Bad', 'American Gods'] 🤷‍♂️

        
        
          
        
      

Хитрость в том, что во второй строке происходит копирование адреса, где находится массив, а не самого массива. В итоге получаем ситуацию, когда две переменные iWatched и vitalikWatched работают с одним массивом, так как хранят один адрес. Это особенность работы со значениями, которые хранятся по ссылке.

Копия массива создаётся с помощью метода slice(). Нужно вызвать его без аргументов и сохранить результат в новую переменную:

        
          
          const iWatched = ['GameOfThrones', 'Breaking Bad']const vitalikWatched = iWatched.slice()vitalikWatched.push('American Gods')console.log(iWatched)// ['GameOfThrones', 'Breaking Bad'] 👍console.log(vitalikWatched)// ['GameOfThrones', 'Breaking Bad', 'American Gods'] 💪
          const iWatched = ['GameOfThrones', 'Breaking Bad']
const vitalikWatched = iWatched.slice()

vitalikWatched.push('American Gods')
console.log(iWatched)
// ['GameOfThrones', 'Breaking Bad'] 👍
console.log(vitalikWatched)
// ['GameOfThrones', 'Breaking Bad', 'American Gods'] 💪

        
        
          
        
      

🛠 Деструктуризация массива

В современном JavaScript очень популярна деструктуризация массивов. Этот подход позволяет создавать переменные из элементов массива в одну строку:

        
          
          const catProfile = [  'Maru',  'Scottish Fold',  true,  'https://youtu.be/ChignoxJHXc']
          const catProfile = [
  'Maru',
  'Scottish Fold',
  true,
  'https://youtu.be/ChignoxJHXc'
]

        
        
          
        
      

В старом подходе, если из массива нужна пара значений, то их читают и сохраняют в переменные:

        
          
          const catName = catProfile[0]const catBreed = catProfile[1]
          const catName = catProfile[0]
const catBreed = catProfile[1]

        
        
          
        
      

Новый подход делает то же самое, но короче:

        
          
          const [name, breed] = catProfileconsole.log(name)// Maru
          const [name, breed] = catProfile
console.log(name)
// Maru

        
        
          
        
      

На собеседовании

Скопировано
Задать вопрос в рубрику
🤚 Я знаю ответ

Артур Бэйлис Ли  отвечает

Скопировано

В JavaScript массивы — это не отдельный тип данных, а просто объекты. Заполненные слоты массива хранятся под числовыми ключами, а пустые слоты вообще не существуют.

В консоли Chrome Dev Tools пустые слоты отображаются как empty, а в Node.js — empty items. Но это лишь абстрактное представление ситуации, когда поле .length массива не совпадает с ожидаемыми заполненными слотами.

Пример отображение empty slots

Чтобы проверить наличие значения по ключу, можно использовать оператор in или метод .hasOwnProperty():

        
          
          const test = new Array(5)test[2] = 422 in test // truetest.hasOwnProperty(2) // true0 in test // falsetest.hasOwnProperty(0) // false
          const test = new Array(5)
test[2] = 42

2 in test // true
test.hasOwnProperty(2) // true

0 in test // false
test.hasOwnProperty(0) // false

        
        
          
        
      

То есть, мы можем пройтись по массиву от 0 до length и проверить отсутвие текущего индекса в качестве ключа. Однако есть способ ещё проще: итератор массива и методы, поверх него, используют только существующие ключи:

        
          
          const test = new Array(5)test[2] = 42test.forEach((value, key) => console.log(`значение по ключу ${key}: ${value}`))// значение по ключу 2: 42// консоль вывела значение только один раз
          const test = new Array(5)
test[2] = 42

test.forEach((value, key) => console.log(`значение по ключу ${key}: ${value}`))
// значение по ключу 2: 42

// консоль вывела значение только один раз

        
        
          
        
      

Таким образом, чтобы найти количество пустых слотов, достаточно от длины массива отнять число заполненных значений:

        
          
          const calcEmpty = items => items.reduce(amount => --amount, items.length)const test = new Array(5)test[2] = 42calcEmpty(test) // 4test[10] = 2calcEmpty(test) // 9
          const calcEmpty = items => items.reduce(amount => --amount, items.length)

const test = new Array(5)

test[2] = 42
calcEmpty(test) // 4

test[10] = 2
calcEmpty(test) // 9

        
        
          
        
      

Ярослав  отвечает

Скопировано

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

Как это выглядит:

        
          
          const arr = [1, 2]
          const arr = [1, 2]

        
        
          
        
      

Для движка, который обрабатывает JavaScript, массив arr выглядит примерно так:

        
          
          const arr = {  '0': 1,  '1': 2,  'length': 2}
          const arr = {
  '0': 1,
  '1': 2,
  'length': 2
}

        
        
          
        
      

Ключи «0» и «1» — это строки, соответствующие индексам массива.

Свойство length указывает на наибольший индекс + 1.

Как мы знаем, если обратиться к несуществующему ключу в объекте, результатом будет undefined. Например:

        
          
          const obj = {}console.log(obj['nonexistentKey']) // undefined
          const obj = {}
console.log(obj['nonexistentKey']) // undefined

        
        
          
        
      

Аналогично для массивов:

        
          
          const arr = [1, 2]console.log(arr[10]) // undefined
          const arr = [1, 2]
console.log(arr[10]) // undefined

        
        
          
        
      

В случае массивов, если между существующими индексами есть разрывы, например, [1, , 3], JavaScript не создаёт ключ для пропущенного индекса. Это означает, что такие слоты считаются «пустыми».

        
          
          const sparseArr = [1, , 3]console.log(sparseArr.length)// 3console.log(sparseArr)// [1, empty, 3]console.log(sparseArr[1])// undefined
          const sparseArr = [1, , 3]

console.log(sparseArr.length)
// 3

console.log(sparseArr)
// [1, empty, 3]

console.log(sparseArr[1])
// undefined

        
        
          
        
      

Для движка массив sparseArr будет выглядеть так:

        
          
          {  '0': 1,  '2': 3,  'length': 3}
          {
  '0': 1,
  '2': 3,
  'length': 3
}

        
        
          
        
      

Важно: empty — это не отдельный тип данных. Это просто обозначение отсутствия ключа для индекса в массиве.

Некоторые методы массива умеют отличать empty от хранящегося в массиве undefined. Например:

        
          
          const sparseArr = [1, , 3]sparseArr.forEach((element) => {  console.log(element)})// 1// 3
          const sparseArr = [1, , 3]
sparseArr.forEach((element) => {
  console.log(element)
})
// 1
// 3

        
        
          
        
      

На примере методов sort() и toSorted(), можно увидеть разницу в обработке empty.

        
          
          const colors = ['red', 'yellow', 'blue', undefined]colors[6] = 'purple'colors.toSorted()// ['blue', purple, 'red', 'yellow', undefined, undefined, undefined]colors.sort()// ['blue', purple, 'red', 'yellow', undefined, empty x 2]
          const colors = ['red', 'yellow', 'blue', undefined]
colors[6] = 'purple'

colors.toSorted()
// ['blue', purple, 'red', 'yellow', undefined, undefined, undefined]

colors.sort()
// ['blue', purple, 'red', 'yellow', undefined, empty x 2]

        
        
          
        
      

toSorted() преобразовал empty в undefined, а sort() сохранил их свойства, при этом переместив все empty в конец массива.

Решение задачи:

        
          
          function countEmptySpacesInSparseArray(arr) {  let count = 0  for (let i = 0; i < arr.length; i++) {    // Проходясь по всей длине массива проверяем,    // отсутствует ли у него ключ, равный индексу    const isEmptySpace = !arr.hasOwnProperty(i)    if (isEmptySpace) {      // В случае отсутствия ключа, увеличиваем значение счётчика      count++    }  }  return count}
          function countEmptySpacesInSparseArray(arr) {
  let count = 0
  for (let i = 0; i < arr.length; i++) {
    // Проходясь по всей длине массива проверяем,
    // отсутствует ли у него ключ, равный индексу
    const isEmptySpace = !arr.hasOwnProperty(i)

    if (isEmptySpace) {
      // В случае отсутствия ключа, увеличиваем значение счётчика
      count++
    }
  }
  return count
}

        
        
          
        
      

Так как мы знаем об особенностях методов массивов, можно переписать код так:

        
          
          function countEmptySpacesInSparseArray(arr) {  let count = 0  arr.forEach(element => {    count++  })  return arr.length - count}
          function countEmptySpacesInSparseArray(arr) {
  let count = 0
  arr.forEach(element => {
    count++
  })
  return arr.length - count
}

        
        
          
        
      

Примечание: у решения алгоритмическая сложность O(n).

🤚 Я знаю ответ

Viktar Nezhbart  отвечает

Скопировано

Основной сложностью данной задачи является определение уникальности элементов коллекции.

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

        
          
          function getUnique (array) {  // Если это не массив, возвращаем пустой массив  if (Array.isArray(array) === false) {    return []  }  const uniqueArray = []  array.forEach(item => {    if (uniqueArray.includes(item)) {      return    }    uniqueArray.push(item)  })  return uniqueArray}
          function getUnique (array) {
  // Если это не массив, возвращаем пустой массив
  if (Array.isArray(array) === false) {
    return []
  }

  const uniqueArray = []

  array.forEach(item => {
    if (uniqueArray.includes(item)) {
      return
    }
    uniqueArray.push(item)
  })

  return uniqueArray
}

        
        
          
        
      

Сначала проверяем, что аргументом функции является массив, а затем перебираем коллекцию и накапливаем в новом массиве uniqueArray только уникальные элементы. Для проверки уникальности используем метод массивов .includes().

Проверим работу нашей функции:

        
          
          const result = getUnique([1,2,2,1,3,0,2])console.log(result)// [ 1, 2, 3, 0 ]
          const result = getUnique([1,2,2,1,3,0,2])
console.log(result)
// [ 1, 2, 3, 0 ]

        
        
          
        
      

Функция работает как ожидалось, но такое решение не самое оптимальное. Если оценить сложность алгоритма, то окажется что она равна O(n^2). Для каждого элемента исходного массива необходимо осуществлять поиск в создаваемом массиве уникальных элементов.

JavaScript имеет специальный тип для создания уникальных коллекций — Set. С его помощью решение будет намного проще:

        
          
          function getUnique (array) {  // Если это не массив, возвращаем пустой массив  if (Array.isArray(array) === false) {      return []  }  return [ ...new Set(array) ]}
          function getUnique (array) {
  // Если это не массив, возвращаем пустой массив
  if (Array.isArray(array) === false) {
      return []
  }

  return [ ...new Set(array) ]
}

        
        
          
        
      

Это решение не требует перебора коллекции вручную (это произойдёт "под капотом"), так как при создании объекта Set в него попадут только уникальные элементы переданого массива. Затем, используя деструктуризацию, мы преобразуем Set обратно в массив.
По сравнению с предыдущим вариантом, это улучшает производительность, так как поиск в коллекции Set гарантировано выполняется быстрее чем при использовании .includes().

А как быть с объектами в качестве элементов коллекции? С точки зрения JavaScript следующие элементы являются уникальными:

        
          
          console.log(new Set([{},{},{},{}]))// Set(4) { {}, {}, {}, {} }
          console.log(new Set([{},{},{},{}]))
// Set(4) { {}, {}, {}, {} }

        
        
          
        
      

Если определяете «равенство» объектов, можно воспользоваться библиотекой Lodash. В библиотеку входит метод .isEqual(), позволяющий сравнивать объекты:

        
          
          function getUnique (array) {  // Если это не массив, возвращаем пустой массив  if (Array.isArray(array) === false) {    return []  }  const uniqueArray = []  array.forEach(item => {    if (uniqueArray.some(uniqueItem => _.isEqual(item, uniqueItem))) {      return    }    uniqueArray.push(item)  })  return uniqueArray}
          function getUnique (array) {
  // Если это не массив, возвращаем пустой массив
  if (Array.isArray(array) === false) {
    return []
  }

  const uniqueArray = []

  array.forEach(item => {
    if (uniqueArray.some(uniqueItem => _.isEqual(item, uniqueItem))) {
      return
    }
    uniqueArray.push(item)
  })

  return uniqueArray
}

        
        
          
        
      

Теперь перебираем элементы коллекции и накапливаем уникальные элементы в новом массиве uniqueArray, используя для поиска повторений метод массивов .some() и метод библиотеки Lodash .isEqual(). Сложность этого алгоритма равна O(n^2), так как требуется внутренний цикл для тестирования уникальности каждого элемента.

Можно улучшить гибкость нашего решения:

  • адаптировать для использования не только массивов, но и других коллекций;
  • устранить зависимость от библиотеки Lodash и дать возможность использовать собственную функцию для проверки уникальности элемента;
  • снизить сложность алгоритма до O(n).

Для этого проверим что аргументом функции является итерируемый объект, а обход будем осуществлять с использованием for..of. Коллекцию уникальных элементов будем накапливать используя Map. Это позволит сохранять ключи значений для ускорения определения уникальности значений. Кроме этого, добавим возможность передавать в качестве второго аргумента функцию-компаратор. Функция должна вернуть пару ключ/значение, которые будут добавлены в коллекцию уникальных элементов.
Если функция не передана, вычисляем ключ, приводя значение к строке.

        
          
          function getUnique (collection, comparator) {  // Если это не итерируемая коллекция, возвращаем пустой массив  if (collection === null || typeof collection[Symbol.iterator] !== 'function') {    return []  }  const unique = new Map()  // Функция для получения пары ключ/значение  const getKeyAndValue = typeof comparator === 'function'    ? comparator    : (item) => {      const key = `${item}`      return [key, item]    }  for (const item of collection) {    const [key, value] = getKeyAndValue(item, unique)    unique.set(key, value)  }  return Array.from(unique.values())}
          function getUnique (collection, comparator) {
  // Если это не итерируемая коллекция, возвращаем пустой массив
  if (collection === null || typeof collection[Symbol.iterator] !== 'function') {
    return []
  }

  const unique = new Map()
  // Функция для получения пары ключ/значение
  const getKeyAndValue = typeof comparator === 'function'
    ? comparator
    : (item) => {
      const key = `${item}`
      return [key, item]
    }

  for (const item of collection) {
    const [key, value] = getKeyAndValue(item, unique)
    unique.set(key, value)
  }

  return Array.from(unique.values())
}

        
        
          
        
      

Протестируем это решение без передачи компаратора:

        
          
          console.log(  getUnique([1, null, {}, [], {}, null]))// [ 1, null, {}, [] ]
          console.log(
  getUnique([1, null, {}, [], {}, null])
)

// [ 1, null, {}, [] ]

        
        
          
        
      

Функция-компаратор пригодится для кастомизации логики, например если перед нами стоит задача объединить объекты на основе значения поля id:

        
          
          console.log(  getUnique(    [      { id: 2, type: 'd' },      { id: 3, category: null },      { name: 'Q', value: 42, id: 2 },      { width: 1024, size: 'big', id: 0 }    ],    (item, obj) => {      const key = item.id      const value = {        ...obj.get(key),        ...item      }      return [key, value]    }  ))// [//   { id: 2, type: 'd', name: 'Q', value: 42 },//   { id: 3, category: null },//   { width: 1024, size: 'big', id: 0 }// ]
          console.log(
  getUnique(
    [
      { id: 2, type: 'd' },
      { id: 3, category: null },
      { name: 'Q', value: 42, id: 2 },
      { width: 1024, size: 'big', id: 0 }
    ],
    (item, obj) => {
      const key = item.id
      const value = {
        ...obj.get(key),
        ...item
      }
      return [key, value]
    }
  )
)

// [
//   { id: 2, type: 'd', name: 'Q', value: 42 },
//   { id: 3, category: null },
//   { width: 1024, size: 'big', id: 0 }
// ]