19 KiB
Arrays with numeric indexes
Array -- is a special kind of objects, suited to store ordered, numbered collections of values. It provides additional methods to manipulate the collection.
For instance, we can use arrays to keep a list of students in the group, a list of goods in the catalog etc.
[cut]
Definition
There are two syntaxes for creating an empty array:
let arr = new Array();
let arr = [];
Almost all the time, the second syntax is used. We can also list elements in the brackets:
let fruits = ["Apple", "Orange", "Plum"];
Array elements are numbered, starting with zero.
We can get an element by its number in square brackets:
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits[0] ); // Apple
alert( fruits[1] ); // Orange
alert( fruits[2] ); // Plum
We can replace an element:
fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
...Or add to the array:
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Plum", "Lemon"]
The total count of the elements in the array is its length
:
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits.length ); // 3
We can also use alert
to show the whole array.
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits ); // Apple,Orange,Plum
An array can store elements of any type.
For instance:
// mix of values
let arr = [ 1, 'Apple', { name: 'John' }, true, function() {} ];
// get the object at index 2 and then its name
alert( arr[2].name ); // John
Методы pop/push, shift/unshift
Одно из применений массива -- это очередь. В классическом программировании так называют упорядоченную коллекцию элементов, такую что элементы добавляются в конец, а обрабатываются -- с начала.
В реальной жизни эта структура данных встречается очень часто. Например, очередь сообщений, которые надо показать на экране.
Очень близка к очереди еще одна структура данных: стек. Это такая коллекция элементов, в которой новые элементы добавляются в конец и берутся с конца.
Например, стеком является колода карт, в которую новые карты кладутся сверху, и берутся -- тоже сверху.
Для того, чтобы реализовывать эти структуры данных, и просто для более удобной работы с началом и концом массива существуют специальные методы.
Конец массива
pop
- Удаляет последний элемент из массива и возвращает его:
var fruits = ["Яблоко", "Апельсин", "Груша"]; alert( fruits.pop() ); // удалили "Груша" alert( fruits ); // Яблоко, Апельсин
push
- Добавляет элемент в конец массива:
var fruits = ["Яблоко", "Апельсин"]; fruits.push("Груша"); alert( fruits ); // Яблоко, Апельсин, Груша
Вызов
fruits.push(...)
равнозначенfruits[fruits.length] = ...
.
Начало массива
shift
- Удаляет из массива первый элемент и возвращает его:
var fruits = ["Яблоко", "Апельсин", "Груша"]; alert( fruits.shift() ); // удалили Яблоко alert( fruits ); // Апельсин, Груша
unshift
- Добавляет элемент в начало массива:
var fruits = ["Апельсин", "Груша"]; fruits.unshift('Яблоко'); alert( fruits ); // Яблоко, Апельсин, Груша
Методы push
и unshift
могут добавлять сразу по несколько элементов:
var fruits = ["Яблоко"];
fruits.push("Апельсин", "Персик");
fruits.unshift("Ананас", "Лимон");
// результат: ["Ананас", "Лимон", "Яблоко", "Апельсин", "Персик"]
alert( fruits );
Внутреннее устройство массива
Массив -- это объект, где в качестве ключей выбраны цифры, с дополнительными методами и свойством length
.
Так как это объект, то в функцию он передаётся по ссылке:
function eat(arr) {
arr.pop();
}
var arr = ["нам", "не", "страшен", "серый", "волк"]
alert( arr.length ); // 5
eat(arr);
eat(arr);
alert( arr.length ); // 3, в функцию массив не скопирован, а передана ссылка
Ещё одно следствие -- можно присваивать в массив любые свойства.
Например:
var fruits = []; // создать массив
fruits[99999] = 5; // присвоить свойство с любым номером
fruits.age = 25; // назначить свойство со строковым именем
.. Но массивы для того и придуманы в JavaScript, чтобы удобно работать именно с упорядоченными, нумерованными данными. Для этого в них существуют специальные методы и свойство length
.
Как правило, нет причин использовать массив как обычный объект, хотя технически это и возможно.
Если в массиве есть пропущенные индексы, то при выводе в большинстве браузеров появляются "лишние" запятые, например:
```js run
var a = [];
a[0] = 0;
a[5] = 5;
alert( a ); // 0,,,,,5
```
Эти запятые появляются потому, что алгоритм вывода массива идёт от `0` до `arr.length` и выводит всё через запятую. Отсутствие значений даёт несколько запятых подряд.
Влияние на быстродействие
Методы push/pop
выполняются быстро, а shift/unshift
-- медленно.
Чтобы понять, почему работать с концом массива -- быстрее, чем с его началом, разберём подробнее происходящее при операции:
fruits.shift(); // убрать 1 элемент с начала
При этом, так как все элементы находятся в своих ячейках, просто удалить элемент с номером 0
недостаточно. Нужно еще и переместить остальные элементы на их новые индексы.
Операция shift
должна выполнить целых три действия:
- Удалить нулевой элемент.
- Переместить все свойства влево, с индекса
1
на0
, с2
на1
и так далее. - Обновить свойство
length
.
Чем больше элементов в массиве, тем дольше их перемещать, это много операций с памятью.
Аналогично работает unshift
: чтобы добавить элемент в начало массива, нужно сначала перенести вправо, в увеличенные индексы, все существующие.
А что же с push/pop
? Им как раз перемещать ничего не надо. Для того, чтобы удалить элемент, метод pop
очищает ячейку и укорачивает length
.
Действия при операции:
fruits.pop(); // убрать 1 элемент с конца
Перемещать при pop
не требуется, так как прочие элементы после этой операции остаются на тех же индексах.
Аналогично работает push
.
Перебор элементов
Для перебора элементов обычно используется цикл:
var arr = ["Яблоко", "Апельсин", "Груша"];
*!*
for (var i = 0; i < arr.length; i++) {
alert( arr[i] );
}
*/!*
````warn header="Не используйте for..in
для массивов"
Так как массив является объектом, то возможен и вариант for..in
:
var arr = ["Яблоко", "Апельсин", "Груша"];
*!*
for (var key in arr) {
*/!*
alert( arr[key] ); // Яблоко, Апельсин, Груша
}
Недостатки этого способа:
-
Цикл
for..in
выведет все свойства объекта, а не только цифровые.В браузере, при работе с объектами страницы, встречаются коллекции элементов, которые по виду как массивы, но имеют дополнительные нецифровые свойства. При переборе таких "похожих на массив" коллекций через
for..in
эти свойства будут выведены, а они как раз не нужны.Бывают и библиотеки, которые предоставляют такие коллекции. Классический
for
надёжно выведет только цифровые свойства, что обычно и требуется. -
Цикл
for (var i=0; i<arr.length; i++)
в современных браузерах выполняется в 10-100 раз быстрее. Казалось бы, по виду он сложнее, но браузер особым образом оптимизирует такие циклы.
Если коротко: цикл for(var i=0; i<arr.length...)
надёжнее и быстрее.
## Особенности работы length
Встроенные методы для работы с массивом автоматически обновляют его длину `length`.
**Длина `length` -- не количество элементов массива, а `последний индекс + 1`**.
Так уж оно устроено.
Это легко увидеть на следующем примере:
```js run
var arr = [];
arr[1000] = true;
alert(arr.length); // *!*1001*/!*
```
Кстати, если у вас элементы массива нумеруются случайно или с большими пропусками, то стоит подумать о том, чтобы использовать обычный объект. Массивы предназначены именно для работы с непрерывной упорядоченной коллекцией элементов.
### Используем length для укорачивания массива
Обычно нам не нужно самостоятельно менять `length`... Но есть один фокус, который можно провернуть.
**При уменьшении `length` массив укорачивается.**
Причем этот процесс необратимый, т.е. даже если потом вернуть `length` обратно -- значения не восстановятся:
```js run
var arr = [1, 2, 3, 4, 5];
arr.length = 2; // укоротить до 2 элементов
alert( arr ); // [1, 2]
arr.length = 5; // вернуть length обратно, как было
alert( arr[3] ); // undefined: значения не вернулись
```
Самый простой способ очистить массив -- это `arr.length=0`.
## Создание вызовом new Array [#new-array]
### new Array()
Существует еще один синтаксис для создания массива:
```js
var arr = *!*new Array*/!*("Яблоко", "Груша", "и т.п.");
```
Он редко используется, т.к. квадратные скобки `[]` короче.
Кроме того, у него есть одна особенность. Обычно `new Array(элементы, ...)` создаёт массив из данных элементов, но если у него один аргумент-число `new Array(число)`, то он создает массив *без элементов, но с заданной длиной*.
Проверим это:
```js run
var arr = new Array(2, 3);
alert( arr[0] ); // 2, создан массив [2, 3], всё ок
*!*
arr = new Array(2); // создаст массив [2] ?
alert( arr[0] ); // undefined! у нас массив без элементов, длины 2
*/!*
```
Что же такое этот "массив без элементов, но с длиной"? Как такое возможно?
Оказывается, очень даже возможно и соответствует объекту `{length: 2}`. Получившийся массив ведёт себя так, как будто его элементы равны `undefined`.
Это может быть неожиданным сюрпризом, поэтому обычно используют квадратные скобки.
### Многомерные массивы
Массивы в JavaScript могут содержать в качестве элементов другие массивы. Это можно использовать для создания многомерных массивов, например матриц:
```js run
var matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // центральный элемент
```
## Внутреннее представление массивов
```warn header="Hardcore coders only"
Эта секция относится ко внутреннему устройству структуры данных и требует специальных знаний. Она не обязательна к прочтению.
```
Числовые массивы, согласно спецификации, являются объектами, в которые добавили ряд свойств, методов и автоматическую длину `length`. Но внутри они, как правило, устроены по-другому.
**Современные интерпретаторы стараются оптимизировать их и хранить в памяти не в виде хэш-таблицы, а в виде непрерывной области памяти, по которой легко пробежаться от начала до конца.**
Операции с массивами также оптимизируются, особенно если массив хранит только один тип данных, например только числа. Порождаемый набор инструкций для процессора получается очень эффективным.
Чтобы у интерпретатора получались эти оптимизации, программист не должен мешать.
В частности:
- Не ставить массиву произвольные свойства, такие как `arr.test = 5`. То есть, работать именно как с массивом, а не как с объектом.
- Заполнять массив непрерывно и по возрастающей. Как только браузер встречает необычное поведение массива, например устанавливается значение `arr[0]`, а потом сразу `arr[1000]`, то он начинает работать с ним, как с обычным объектом. Как правило, это влечёт преобразование его в хэш-таблицу.
Если следовать этим принципам, то массивы будут занимать меньше памяти и быстрее работать.
## Итого
Массивы существуют для работы с упорядоченным набором элементов.
**Объявление:**
```js
// предпочтительное
var arr = [элемент1, элемент2...];
// new Array
var arr = new Array(элемент1, элемент2...);
```
При этом `new Array(число)` создаёт массив заданной длины, *без элементов*. Чтобы избежать ошибок, предпочтителен первый синтаксис.
**Свойство `length`** -- длина массива. Если точнее, то последний индекс массива плюс `1`. Если её уменьшить вручную, то массив укоротится. Если `length` больше реального количества элементов, то отсутствующие элементы равны `undefined`.
Массив можно использовать как очередь или стек.
**Операции с концом массива:**
- `arr.push(элемент1, элемент2...)` добавляет элементы в конец.
- `var elem = arr.pop()` удаляет и возвращает последний элемент.
**Операции с началом массива:**
- `arr.unshift(элемент1, элемент2...)` добавляет элементы в начало.
- `var elem = arr.shift()` удаляет и возвращает первый элемент.
Эти операции перенумеровывают все элементы, поэтому работают медленно.
В следующей главе мы рассмотрим другие методы для работы с массивами.