Тайная сила свойства Array.length

Каждый день разработчики имеют дело с массивами. Будучи упорядоченной коллекцией, важным свойством массива для создания запроса является Array.prototype.length.

В JavaScript length не всегда указывает на количество существующих элементов (для разреженных массивов). Поэтому давайте разберемся с этим свойством.

Определение

Длина массива представляется без знаковым 32-битным целым числом, значение которого больше максимального индекса. С определенными типами массивов это свойство ведет себя по-разному.

Давайте перечислим их: плотный массив — его элементы имеют смежные индексы, начиная с 0. Например: [1, 3, 4] –плотный массив, поскольку индексы являются смежными: 0, 1 и 2.

Разреженный массив — его элементы не имеют смежных индексов, начиная с 0. Например: [1, , 4, 6] – разреженный массив, так как индексы элементов не являются смежными: 0, 2 и 3.

Длина, как количество элементов в массиве

Обычно JavaScript array length используют для определения количества элементов. Это корректно для плотного типа коллекции:

var fruits = ['апельсин', 'яблоко', 'банан']; //fruits – плотный массив  
fruits.length // выводит 3, реальное количество элементов

fruits.push('манго');  
fruits.length // выводит 4, так как был добавлен еще один элемент

var empty = [];  
empty.length // выводит 0, пустой массив

Посмотреть пример

Плотный массив не имеет пробелов, и количество элементов соответствует максимальному индексу плюс единица. В [3, 5, 7, 8] максимальный индекс 3 у элемента «8», при этом размер массива 3 + 1 = 4.

Длина как число большее, чем индекс с максимальным значением

В разреженном массиве arr length JavaScript больше максимального индекса, но он не указывает на реальное количество элементов. При выполнении запроса length будет больше, чем количество элементов. Это происходит из-за пробелов в массиве:

var animals = ['кошка', 'собака', , 'обезьяна']; // animals  -разреженный массив  
animals.length // выводит 4, но реальное количество элементов 3

var words = ['hello'];  
words[6] = 'welcome'; //максимальный индекс 6. words – разреженный массив  
words.length //выводит 7, опирается на максимальный индекс

При добавлении или удалении элементов length изменяет только максимальный индекс. Любые изменения массива, которые не влияют на индекс с максимальным значением, не изменяют length. Например, при использовании delete:

var colors = ['синий', 'красный', 'желтый', 'белый', 'черный'];  
colors.length // выводит 5

delete colors[0]; // удаляем первый элемент 'синий'.  
                  // Массив становится разреженным

colors.length // по-прежнему выводит 5, потому что максимальный индекс 4  
              // не изменен

Посмотреть пример

Изменение длины

В предыдущих примерах свойство length предназначалось только для чтения. Но JavaScript length позволяет изменить и это свойство.

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

Когда новое значение length меньше или равно максимальному индексу, любые элементы, индекс которых больше или равен новому размеру, удаляются. Полезный сценарий для удаления элементов из конца массива:

var numbers = [1, 3, 5, 7, 8];
numbers.length = 3; // изменение длины массива  
numbers // выводит [1, 3, 5], элементы 7 и 8 удалены

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

var osTypes = ['OS X', 'Linux', 'Windows'];

osTypes.length = 5; // Создание разреженного массива. Элементы с индексами 3 и 4  
                    // отсутствуют

osTypes // выводит ['OS X', 'Linux', 'Windows', , , ]

Посмотреть пример

Length можно присвоить нечисловой тип данных. JavaScript преобразует примитив в число. Если результат преобразования равен NaN или числу меньше 0, то выдается ошибка Uncaught RangeError: Invalid array lengthнедопустимая длина массива»):

var numbers = [1, 4, 6, 7];  
numbers.length = '2'; // '2' преобразуется в число 2  
numbers.length = 'not-number'; // выдает ошибку Uncaught RangeError: Invalid array length  
numbers.length = -2; // выдает Uncaught RangeError: Invalid array length

Код безопасности

Изменение свойства JavaScript array length, удаление элементов с помощью delete, добавление элементов с новым индексом является источником проблем: так создаются разреженные массивы. И как результат получаем противоречивое значение length.

JavaScript предлагает более безопасные альтернативы.

Для добавления элементов в конец массива используют Array.prototype.push(), а для удаления последнего элемента — pop(). Чтобы вставить элемент в начало, используется unshift(). Для удаления первого элемента — shift().

Для осуществления более сложных вставок, удалений или замен используйте достаточно мощный метод splice():

var companies = ['Apple', 'Dell'];

companies.push('ASUS'); // Вставляет элемент в конец массива 
companies // prints ['Apple', 'Dell', 'ASUS']

companies.pop();  // Выводит"ASUS". Удаляет последний элемент массива
companies // выводит ['Apple', 'Dell']

companies.shift(); // Выводит "Apple". Удаляет первый элемент массива
companies // Выводит ["Dell"]

companies.splice(1, 0, "Microsoft", "HP"); // Добавляет 2 компании  
companies // Выводит ["Dell", "Microsoft", "HP"]

companies.length // Выводит 3. Массив  плотный

Посмотреть пример

Иногда массив может быть разреженным. Поэтому не безопасно полагаться на свойство JavaScript length для определения количества элементов. Лучше используйте вспомогательную функцию, которая обрабатывает недостающие элементы:

/**
 * Количество элементов в разреженном массиве
 * @param {Array} collection
 * @return {number}
 */
function count(collection) {  
  var totalCount = 0;
  for (var index = 0; index < collection.length; index++) {
    if (index in collection) {
      totalCount++;
    }
  }
  return totalCount;
}

Оператор in определяет, содержится ли свойство в указанном объекте. Он отлично подходит для проверки существования элемента по определенному индексу.

Вывод

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

В качестве альтернативы избегайте любого изменения этого свойства и используйте метод splice().

Перевод статьи “The magic behind array length property” был подготовлен дружной командой проекта Сайтостроение от А до Я.