логотип PurpleSchool
  • Бесплатно
      Карта развитияОсновы разработкиCSS Flexbox
    • Новостные рассылкиИконка стрелки
    • База знанийИконка стрелки
    • Карьерные пути
      • Frontend React разработчик
      • Frontend Vue разработчик
      • Backend разработчик Node.js
      • Fullstack разработчик React / Node.js
      • Mobile разработчик React Native
      • Backend разработчик Golang
      • Devops инженер
    • О нас
      • Отзывы
      • Реферальная программа
      • О компании
      • Контакты
    • Иконка открытия меню
      • Сообщество
      • PurpleПлюс
      • AI тренажёр
      • Проекты
    логотип PurpleSchool
    ютуб иконка
    Telegram иконка
    VK иконка
    Курсы
    ГлавнаяКаталог курсовFrontendBackendFullstack
    Практика
    КарьераПроектыPurpleПлюс
    Материалы
    БлогБаза знаний
    Документы
    Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
    Реквизиты
    ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

    PurpleSchool © 2020 -2025 Все права защищены

  • Курсы
    Иконка слояПерейти в каталог курсов
    • FrontendИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    • Картинка группы Общее

      Общее


      • Основы разработки
      • Основы Git
      • HTML и CSS
      • CSS Flexbox
      • Основы JavaScript
      • Продвинутый JavaScript
      • TypeScript с нуля
      • Neovim
    • Картинка группы React

      React


      • React и Redux Toolkit
      • Zustand
      • Next.js - с нуля
      • Feature-Sliced Design
    • Картинка группы Vue.js

      Vue.js


      • Vue 3 и Pinia
      • Nuxt
      • Feature-Sliced Design
    • Картинка группы Angular

      Angular


      • Angular 19 Иконка курсаСкоро!
    • Картинка группы Node.js

      Node.js


      • Основы Git
      • Основы JavaScript
      • Продвинутый JavaScript
      • Telegraf.js Иконка курсаСкоро!
      • TypeScript с нуля
      • Node.js с нуля
      • Nest.js с нуля
    • Картинка группы Golang

      Golang


      • Основы Git
      • Основы Golang
      • Продвинутый Golang
      • Golang - Templ Fiber HTMX
    • Картинка группы C#

      C#


      • Основы C#
    • Картинка группы PHP

      PHP


      • Основы PHP Иконка курсаСкоро!
    • Картинка группы Python

      Python


      • Основы Python
      • Продвинутый Python
    • Картинка группы Общее

      Общее


      • Основы разработки
      • Docker и Ansible
      • Kubernetes и Helm
      • Микросервисы
      • Neovim
    • Картинка группы Общее

      Общее


      • Основы разработки
      • Основы Git
      • Основы Linux
      • Bash скрипты
      • Docker и Ansible
      • Kubernetes и Helm
      • Микросервисы
      • Neovim
    • Картинка группы Общее

      Общее


      • Основы разработки
      • Основы Git
      • Neovim
    • Картинка группы React Native

      React Native


      • HTML и CSS
      • Основы JavaScript
      • Продвинутый JavaScript
      • TypeScript с нуля
      • React и Redux Toolkit
      • React Native и Expo Router
    • Картинка группы Swift

      Swift


      • Основы Swift и iOS
    • Картинка группы Общее

      Общее


      • Продвинутое тестирование Иконка курсаСкоро!
      • Основы тестирования ПО
    • Картинка группы Общее

      Общее


      • Собеседование
      • Современный Agile
    • Картинка группы Figma

      Figma


      • Основы дизайна
  • логотип PurpleSchool
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    Массивы в JavaScript не то, чем кажутся

    Массивы в JavaScript не то, чем кажутся

    Аватар автора Массивы в JavaScript не то, чем кажутся

    Валерий Шестернин

    Иконка календаря10 августа 2023

    Разработка любого программного обеспечения это в первую очередь работа с данными. Вне зависимости от того хотим мы обрабатывать эти данные с помощью сложных алгоритмов, представлять в виде каких-либо интерфейсов или делать с ними что-то еще, сами данные - основа, вокруг и для которой строится приложение. Поэтому крайне важно знать типы данных используемого языка программирования. Сегодня мы подробно рассмотрим массивы в JavaScript, их малоизвестные особенности и специфику работы с ними, что будет полезно как новичкам в языке, так и опытным разработчикам.

    Что такое массивы в JavaScript

    Массивы в JavaScript являются особым подвидом объектов, в котором ключи это цифры, начиная с нуля. Такие “ключи” обычно называют индексами и они присваиваются элементом массива автоматически при добавлении. Мы можем обратиться к любому элементу массива почти так же как обращаемся к свойствам объекта, но в случае с массивами, индекс элемента нужно указывать в квадратных скобках или с помощью нового метода at, который в отличии от первого способа, может принимать и отрицательные значения. Это удобно, если нужно найти последний элемент массива, для чего нужно передать в метод at значение -1. Раньше для такой операции приходилось вычислять индекс последнего элемента отнимая единицу от длинны массива: const lastItem = arr[arr.length -1].

    const myArr = [1, 2, 3, 4, 5];
    console.log(myArr[1]); // 2
    console.log(myArr[-2]); // undefined
    console.log(myArr.at(1)); // 2
    console.log(myArr.at(-2)); // 4
    const myObj = { ...myArr };
    console.log(myObj); // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 }
    

    Способы создания

    Массив можно объявить двумя способами: с помощью конструктора (const myArr = new Array(<содержимое массива или количество элементов цифрой>)) или просто обернув нужные значения в квадратные скобки (const myArr = [<элементы массива через запятую>]). Первый способ практически не используется в реальной жизни, а глобальный объект Array в основном полезен благодаря своим методам isArray, который проверяет является ли переданный параметр массивом и возвращает результат проверки в виде булева значения, а так же from, который, в отличии от конструктора, не просто принимает набор значений, а создает новый массив из итерируемого или псевдомассива (например, строки или объекта). При этом метод from может принимать необязательными параметрами функцию, которая будет вызвана для каждого элемента нового массива перед добавлением и аргумент, который будет использоваться в качестве this при выполнении такой функции:

    const myArr = [1, 2, 3, 4, 5];
    console.log(Array.isArray(myArr)); //true
    console.log(Array.from("string")); //[ 's', 't', 'r', 'i', 'n', 'g' ]
    function multiply(value) {
      return value * this.multiplier;
    }
    console.log(Array.from(myArr, multiply, { multiplier: 3 })); // [ 3, 6, 9, 12, 15 ]
    

    Вложенность и клонирование

    Массивы - ссылочный тип данных JavaScript т.е. они могут содержать любые типы данных включая ссылки на другие массивы или объекты. Такие массивы называются вложенными и глубина вложенности не ограничена. Механизм ссылок позволяет экономить память, но если нам нужна не просто ссылка, а независимая копия (чаще называется клоном), мы должны убедиться, что вложенные массивы также будут скопированы и будут являться независимыми копиями. Для этого нужно использовать соответствующий подход в клонировании массива:

    const nestedArr = [1, 2];
    const nestedObg = { name: "John" };
    const arr = ["a", nestedArr, nestedObg];
    console.log(arr[1][0], arr[2].name); //1 John
    const clone1 = arr; //простое присвоение значения по ссылке
    const clone2 = JSON.parse(JSON.stringify(arr)); //а здесь созлается полностью новый массив
    nestedArr[0] = 3;
    nestedObg.name = "Stan";
    console.log(arr, clone1);
    // Оригинал: [ 'a', [ 3, 2 ], { name: 'Stan' } ]
    // Клон:     [ 'a', [ 3, 2 ], { name: 'Stan' } ]
    //после изменения вложенного массива эти изменения затронут и клон
    console.log(clone2); //[ 'a', [ 1, 2 ], { name: 'John' } ]
    //здесь клон не зависит от оригинала
    

    Важные особенности

    Существуют разные движки JavaScript и все они имеют внутренние механизмы для оптимизации работы с массивами такие как использование специализированных инструкций процессора, хранение массивов в последовательном блоке памяти, использование в некоторых случаях специализированных структур данных (трансформирующие и константные массивы) и многое другое. Хотя методы оптимизации могут отличаться, все они делает работу массивов быстрой. Однако что бы не потерять эту скорость, разработчику нужно знать как наиболее оптимально использовать массивы. Далее мы разберем оптимальные приемы работы с массивами.

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

    Производительность сильно зависит от того с какой частью массива мы работает. Если воспользоваться методами, которые изменяют первый элемент (с индексом 0), а именно unshift для добавления элемента в начало массива или shift для удаления первого элемента - индексы всех элементов массива будут перезаписаны на новые (сдвинутся на 1). Такая операция займет гораздо больше времени и ресурсов, чем добавление элемента в конец массива методом push или удаление последнего элемента с помощью метода pop. Поэтому при работе с массивами крайне желательно использовать именно push и pop.

    const myArr = new Array(10000000).fill(1);
    for (let i in myArr) {
      myArr[i] = myArr[i] * 2;
    } //займет 1151 мс
    for (let i of myArr) {
      i = i * 2;
    } //займет 141 мс
    

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

    Разряженные и плотные массивы

    В примере с добавлением и удалением элементов мы создали огромный массив с помощью конструктора и заполнили его через метод fill, но что будет если не заполнять такой массив? В таком случае он будет наполнен пустыми элементами. Массивы содержащие пустые элементы называются разряженными, а те что не имеют таковых - плотными. Разряженный массив так же можно создать прямо указав длину (arr.length = <значение целым числом>), если присвоенное значение длинны больше количества элементов в массиве. Или даже случайно, просто поставив две запятые после очередного элемента при описании массива вручную. Ни одна IDE не подсветит этот участок кода поскольку такое описание не нарушает синтаксис.

    const myArr = new Array(5);
    console.log(myArr); //[ <5 empty items> ]
    const myArr2 = [];
    myArr2.length = 5;
    console.log(myArr2); //[ <5 empty items> ]
    const myArr3 = [, , , , ,];
    console.log(myArr3); //[ <5 empty items> ]
    console.log(myArr[0], myArr2[1], myArr3[2]);
    //undefined undefined undefined
    myArr[0] = "value";
    console.log(myArr); //[ 'value', <4 empty items> ]
    

    Разреженные массивы имеют свое применение в некоторых случаях, однако, следует помнить, что пустые элементы все равно получают индекс, который занимает место в памяти и итерируется при переборе. Поэтому разряженные массивы могут быть менее эффективными в использовании памяти и в производительности по сравнению с обычными массивами в JavaScript. Следовательно лучше избегать прямого указания длинны массива и быть внимательным к количеству запятых.

    Перебор массивов

    Вы же помните что массивы это особый подвид объектов в JavaScript? Это значит что мы можем итерировать их с помощью цикла for…in…, но для массивов существует специальный цикл for…of… . Разница между ними не только в синтаксисе (в for…in… переменная это ключ объекта, а в for…of… - элемент массива ), но и в скорости работы с массивами:

    const myArr = new Array(10000000).fill(1);
    for (let i in myArr) {
      myArr[i] = myArr[i] * 2;
    } //займет 1151 мс
    for (let i of myArr) {
      i = i * 2;
    } //займет 141 мс
    

    Нечисловые свойства

    Мы уже знаем, что кроме обращения к элементам массива по индексу можно напрямую обратиться к его длине и даже изменять ее, а что будет, если прямо задать нашему массиву ключ и присвоить этому ключу значение?

    const myArr = [1, 2, 3];
    myArr.type = "numbers";
    console.log(myArr.length); // длинна равна 3
    for (let i of myArr) {
      console.log(i);
    } // в консоли будут только числовые свойства 1 2 3
    for (let i in myArr) {
      console.log(myArr[i]);
    } // в консоль выведется и нечисловое свойство numbers
    console.log(myArr.type); // numbers
    myArr.push(4);
    //элемент с нечисловым ключем не повлияет на индексы
    //новых элементов массива
    console.log(myArr[3]); //4
    

    Как видно из примера, не произойдет ни какой ошибки, специфичные для массивов методы и способы перебора будут просто игнорировать присвоенную нами пару ключ-значение. У массива не изменится длинна, а for...in... не проитерирует новый элемент. Казалось бы мы получили объект со всеми преимуществами массива, но без ограничений по виду ключей! Такие структуры данных называются псевдомассивами. Но на самом деле даже одно кастомное свойство ломает всю “магию” оптимизации массивов:

    const myArr = new Array(10000000).fill("some value");
    myArr.someKey = "some value";
    myArr.push("new value");//выполнится за 28 мс
    

    Операция добавления нового элемента в конец псевдомассива занимает больше времени, чем добавление элемента в начало настоящего массива. Интересно что цикл for…of… перебирает псевдомассивы так же быстро, как и обычные. Псевдомассивы могут найти свое применение, но только если вы точно знаете что в данной ситуации вам нужно именно такое решение, в противном случае вы получите просадку производительности.

    Заключение

    Давайте резюмируем что нам нужно помнить при работе с массивами JavaScript:

    • Массивы - специальный вид объектов со всеми вытекающими особенностями.
    • При копировании массива - его значение передается по ссылке, то же происходит и со вложенными в него массивами и ссылочными типами данных, поэтому для создания независимого массива его нужно клонировать специальными способами.
    • Значительно производительнее добавлять и удалять элементы в конец массива, чем в его начало или в любой другой участок.
    • Прямое указание длинны массива или лишняя запятая при перечислении элементов создадут разряженный массив, который не будет оптимален по расходу памяти и производительности.
    • Перебирать массивы быстрее с помощью цикла for…of…, чем любым другим способом.
    • Массиву можно добавить кастомное свойство, но тогда он превратится в псевдомассив и движок JavaScript не сможет так хорошо оптимизировать работу с ним.
    Иконка глаза4 966

    Комментарии

    0

    Постройте личный план изучения Основы разработки до уровня Middle — бесплатно!

    Основы разработки — часть карты развития Frontend, Backend, Mobile

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    Основы Git

    Антон Ларичев
    AI-тренажеры
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    HTML и CSS

    Антон Ларичев
    AI-тренажеры
    Практика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    CSS Flexbox

    Антон Ларичев
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    бесплатно
    Подробнее
    Иконка чипа0