Массивы в JavaScript

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

var fruits = ["Apple", "Orange", "Donkey"]

Чтобы извлечь элемент, поместите его индекс в квадратные скобки. Первый индекс 0:

var fruits = ["Apple", "Orange", "Donkey"]

alert(fruits[0])
alert(fruits[1])
alert(fruits[2])

Мы также можем получить длину массива JavaScript:

var fruits = ["Apple", "Orange", "Donkey"]

alert(fruits.length)

Упс! Мы создали массив с двумя фруктами и ослом. Теперь нам нужно удалить осла.

Содержание

Работа с массивами JS - методы pop и push

Метод pop в JavaScript удаляет элемент массива и возвращает его.

В следующем примере показано, как «Осел» извлекается из массива:

var fruits = ["Apple", "Orange", "Donkey"]
alert("Я удаляю "+fruits.pop())
// Теперь у нас только ["Apple","Orange"]
alert("Теперь размер массива: "+fruits.length) // осел удален

Обратите внимание, что pop изменяет сам массив.

Аналог pop - это метод push, который добавляет элемент в массив. Например, мы забыли добавить персик:

var fruits = ["Apple", "Orange"]
fruits.push("Peach");
// теперь у нас есть ["Apple", "Orange", "Peach"]
alert("Последний элемент:"+fruits[fruits.length-1])

Задание для самостоятельного выполнения

  1. Создайте массив styles с элементами “Jazz”, “Blues”;
  2. Добавьте значение "Rock'n'Roll";
  3. Замените второе значение с конца значением "Classic". У вас должен получиться массив: “Jazz”, ”Classic”, ”Rock’n’Roll”. Код должен работать для любой длины массива;
  4. Извлеките последнее значение из массива и выведите его через alert.

Решение

// 1
var styles = ["Jazz", "Bluez"]

// 2
styles.push("Rock'n'Roll") // или: styles[styles.length] = "Rock'n'Roll"

// 3 
styles[styles.length-2] = "Classic"

// 4
alert( styles.pop() )

Работа с массивами JS - методы shift/unshift

Методы shift/unshift работают с концом массива, но вы также можете использовать shift, чтобы сдвинуть элементы вверх (первое значение массива удаляется со сдвигом элементов). Метод unshift позволяет в JavaScript добавить элемент в массив с конца:

var fruits = ["Apple", "Orange"]
var apple = fruits.shift() // теперь у нас остался только ["Orange"]
fruits.unshift("Lemon") // теперь мы имеем ["Lemon", "Orange"]
alert(fruits.length) // 2

И shift, и unshift могут работать с несколькими элементами одновременно:

var fruits = ["Apple"]
fruits.push("Orange","Peach")
fruits.unshift("Pineapple","Lemon")
// теперь массив выглядит так: ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]

Задание для самостоятельного выполнения

Напишите код, чтобы вывести через alert случайное значение из массива arr:

var arr = ["Plum","Orange","Donkey","Carrot","JavaScript"]

Примечание: Код для получения случайного числа от минимального до максимального значения (включительно) следующий:

var rand = min + Math.floor(Math.random()*(max+1-min))

Решение

Нам нужно извлечь случайное число от 0 до arr.length-1 (включительно):

var arr = ["Plum","Orange","Donkey","Carrot","JavaScript"]

var rand = Math.floor(Math.random()*arr.length)

alert(arr[rand])

Работа с массивами JS - перебор массива

В JavaScript перебор массива осуществляется с помощью цикла for:

var fruits = ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
for(var i=0; i<fruits.length; i++) {
  alert(fruits[i])
}

Задание для самостоятельного выполнения

Создайте функцию find(arr,value), которая находит значение в заданном массиве и возвращает его индекс или -1, если значение не найдено.

Например:

arr = [ "test", 2, 1.5, false ]
find(arr, "test") // 0
find(arr, 2) // 1
find(arr, 1.5) // 2
find(arr, 0) // -1

Решение

Возможное решение может выглядеть так:

function find(array, value) {
  for(var i=0; i<array.length; i++) {
    if (array[i] == value) return i;
  }

  return -1;
}

Но это неверно, потому что == не определяет разницу между 0 и false.

Более корректно при работе с массивами в JavaScript использовать ===. Кроме того новейший стандарт ES5 содержит функцию Array#indexOf. С ее помощью мы можем определить функцию следующим образом:

function find(array, value) {
  if (array.indexOf) return array.indexOf(value) 
  for(var i=0; i<array.length; i++) {
    if (array[i] === value) return i;
  }

  return -1;
}
var arr = ["a", -1, 2, "b"];
var index = find(arr, 2);
alert(index);

Еще разумнее было бы определить find через условие, чтобы проверить, существует ли метод indexOf.

Задание для самостоятельного выполнения

Создайте функцию filterNumeric(arr), которая принимает массив и возвращает новый массив, содержащий только числовые значения из arr.

Пример того, как это должно работать:

arr = ["a", 1, "b", 2];
arr = filterNumeric(arr);
// теперь arr = [1,2]

Решение

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

Работа с массивами JS - join и split

Иногда нужен быстрый способ преобразовать JavaScript массив в строку. Именно для этого предназначен метод join.

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

var fruits = ["Lemon","Apple","Orange","Peach"];
var str = fruits.join(', ');
alert(str);

Обратное преобразование легко выполняется с помощью метода split:

var fruits = "Apple,Orange,Peach";
var arr = fruits.split(',');
// arr содержит теперь ["Apple", "Orange", "Peach"]
alert(arr[0]);

Задание для самостоятельного выполнения

Объект включает в себя свойство className, которое содержит имена классов, разделенные пробелами:

var obj = {
	className: 'open menu' 
}

Напишите функцию addClass(obj, cls), которая добавляет класс cls, но только если он не существует:

ddClass(obj, 'new') // obj.className='open menu new'
addClass(obj, 'open')  // без изменений (class already exists)
addClass(obj, 'me') // obj.className='open menu new me'
alert(obj.className)  //

Решение

Нужно разделить className и цикл на части. Если класс не найден, тогда он добавляется.

Цикл немного оптимизирован для увеличения производительности:

function addClass(elem, cls) {
  for(var c = elem.className.split(' '), i=c.length-1; i>=0; i--) {
    if (c[i] == cls) return
  }
	
  elem.className += ' '+cls
}
var obj = { className: 'open menu' }
addClass(obj, 'new')
addClass(obj, 'open') 
alert(obj.className)   // open menu new

В приведенном выше примере переменная c определяется в начале цикла, и для ее последнего индекса задается значение i.

Сам цикл обрабатывается в обратном направлении, заканчиваясь условием i>=0. Потому что i>=0 проверить быстрее, чем i. Что в JavaScript ускоряет поиск в массиве.

Работа с массивами JS - использование length для обрезки массива

С помощью свойства length можно обрезать массив следующим образом:

var arr = [0, 1, 2, 3] 
alert(arr[2]); // элемент все еще здесь
arr.length = 2; // обрезаем массив до двух элементов (то есть остаются: [0,1])
alert(arr[2]); // нет, элемент уже был обрезан

Вы задаете длину, и браузер обрезает массив.

Работа с массивами JS - array представляет собой объект, что из этого следует

На самом деле в JavaScript Array - это Object, дополненный автоматической установкой длины и специальными методами.

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

Работа с массивами JS - нечисловые ключи массива

Ключи - это числа, но они могут иметь любые имена:

arr = [] 
arr[0] = 5
arr.prop = 10 // не делайте так

Но делать этого не рекомендуется. Числовые массивы подходят для числовых ключей, а JavaScript ассоциативный массив - для связанных пар ключ-значение. И смешивать их не стоит.

Массивы в JavaScript представляют собой хэш-таблицы с их преимуществами в плане производительности, но и с определенными недостатками.

Например, push/pop работают только с крайними элементами массива, поэтому они невероятно быстры.

push работает только с концом:

var arr = ["My", "array"]
arr.push("something")
alert(arr[1]) // строка "array"

Методы shift/unshift медленные, потому что им нужно изменить нумерацию всего массива. Метод splice также может привести к изменению нумерации:

Работа с массивами JS - нечисловые ключи массива

Таким образом, shift/unshift работают медленнее, чем push/pop. Чем больше массив, тем больше времени занимает в JavaScript сортировка массива.

Задание для самостоятельного выполнения

Какой получится результат? Почему?

arr = ["a", "b"]
arr.push( function() { alert(this) } )
arr[arr.length-1]()  // ?

Решение

Поскольку массивы являются объектами, arr<a href="/..">..</a> фактически является вызовом метода объекта, таким как obj<a href="/method">method</a>:

arr[arr.length-1]() 
// то же самое что
arr[2]()
// синтаксически это неправильно, но концептуально то же самое:
arr.2() 
// переписанное в том же стиле, что и obj.method()
this = arr в таком случае передается функции, поэтому выводится содержимое arr.
arr = ["a", "b"]

arr.push( function() { alert(this) } )

arr[arr.length-1]() // "a","b",функция

Работа с массивами JS - разреженные массивы, описание length

Свойство length позволяет получить не размер массива в JavaScript, а последний индекс + 1. Это важно, если речь идет о разреженных массивах, с "промежутками" в индексах.

В следующем примере мы добавим два элемента в пустые fruits, но значение length останется 100:

var fruits = [] // пустой массив
fruits[1] = 'Peach'
fruits[99] = 'Apple'
alert(fruits.length)  // 100 (но элементов в массиве всего 2)

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

var fruits = [] // пустой массив
fruits[2] = 'Peach'
fruits[5] = 'Apple'
alert(fruits)  // ,Peach,,,Apple (или что-то вроде этого)

Но массив - это объект с двумя ключами. Недостающие значения не занимают места.

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

var fruits = [ ]
fruits[1] = 'Peach'
fruits[9] = 'Apple'
alert( fruits.pop() ) // выталкиваем 'Apple' (на индекс 9)
alert( fruits.pop() )  // выталкиваем не заданный элемент (на индекс 8)

Старайтесь избегать разреженных массивов. Во всяком случае, их методы не будут работать нормально. Вместо этого используйте Object.

Работа с массивами JS - удаление из массива

Как мы знаем, массивы - это объекты, поэтому мы могли бы использовать delete, чтобы удалить значение:

var arr = ["Go", "to", "home"]
delete arr[1]
// теперь arr = ["Go", undefined, "home"]
alert(arr[1]) // не задано

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

Оператор delete удаляет пару ключ-значение, и это все. Естественно, так как массив - это только хэш, позиция удаленного элемента становится undefined.

Чаще всего нам нужно удалить элемент, не оставляя «дыр» между индексами. Существует еще один метод, который поможет нам в этом.

Метод splice

Метод splice может удалять элементы и заменять их в JavaScript многомерных массивах. Его синтаксис:

arr.splice(index, deleteCount[, elem1, ..., elemN])

Удаляет элемент deleteCount, начиная с index, а затем вставляет на его место elem1, ..., elemN.

Давайте рассмотрим несколько примеров:

var arr = ["Go", "to", "home"]
arr.splice(1, 1)  // удалить 1 элемент, начиная с индекса 1
alert( arr.join(',') ) // ["Go", "home"] (1 элемент удален)

Таким образом, вы можете использовать splice, чтобы удалить один элемент из массива. Номера элементов массива сдвигаются, чтобы заполнить пробел:

var arr = ["Go", "to", "home"]
arr.splice(0, 1)  // удаляем 1 элемент, начиная с индекса 0
alert( arr[0] ) // "to" стал первым элементом

В следующем примере показано, как заменять элементы:

var arr = ["Go", "to", "home", "now"];
// remove 3 first elements and add two
arr.splice(0, 3, "Come", "here") 
alert( arr ) // ["Come", "here", "now"]

Метод splice возвращает массив удаленных элементов:

var arr = ["Go", "to", "home", "now"];
// удаляем 2 первых элемента 
var removed = arr.splice(0, 2) 
alert( removed ) // "Go", "to" <-- массив удаленных элементов
splice может вставлять элементы, задайте 0 для deleteCount.
var arr = ["Go", "to", "home"];
// со второй позиции 
// удаляем 0 
// и вставляем "my", "sweet"
arr.splice(2, 0, "my", "sweet") 
alert( arr) // "Go", "to", "my", "sweet", "home"

Данный метод также может использовать отрицательный индекс, который отсчитывается с конца массива:

var arr = [1, 2, 5]
// для элемента -1 (предпоследнего)
// удаляем 0 элементов, 
// и вставляем 3 и 4
arr.splice(-1, 0, 3, 4)
alert(arr)  // 1,2,3,4,5

Задание для самостоятельного выполнения

Объект содержит свойство className, в котором содержатся имена классов, разделенные пробелами:

var obj = {
	className: 'open menu'
}

Напишите функцию removeClass(obj, cls), которая удаляет класс cls, если он задан:

removeClass(obj, 'open') // obj.className='menu'

removeClass(obj, 'blabla')  // без изменений (класса для удаления не существует)

Решение

Нужно разделить className на части и перебрать эти части через цикл. Если найдено совпадение, оно удаляется из JavaScript массива объектов, а затем добавляется обратно в конец.

Немного оптимизируем это:

function removeClass(elem, cls) {
  for(var c = elem.className.split(' '), i=c.length-1; i>=0; i--) {
    if (c[i] == cls) c.splice(i,1)
  }
	
  elem.className = c.join(' ')
}
var obj = { className: 'open menu' }
removeClass(obj, 'open')
removeClass(obj, 'blabla')
alert(obj.className)   // menu

В приведенном выше примере переменная c задана в начале цикла, и для i задан ее последний индекс.

Сам цикл выполняется в обратном направлении, заканчиваясь условием i>=0. Это сделано потому, что i>=0 проверяется быстрее, чем i. Что ускоряет поиск свойства в c.

Работа с массивами JS - метод slice

Можно извлечь часть массива с помощью метода slice(begin[, end]):
var arr = ["Why", "learn", "JavaScript"];
var arr2 = arr.slice(0,2) // принимает 2 элемента, начиная с 0
alert(arr2.join(', ')) // "Why, learn"

Обратите внимание, что этот метод не изменяет в JavaScript количество элементов в массиве, а копирует его часть.

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

var arr = ["Why", "learn", "JavaScript"];
var arr2 = arr.slice(1) // принимает все элементы, начиная с 1
alert(arr2.join(', ')) // "learn, JavaScript"

Метод поддерживает отрицательные индексы, так же, как String#slice.

Работа с массивами JS - метод reverse

Еще один полезный метод - reverse. Предположим, я хочу получить последнюю часть домена, например, “com” от “my.site.com”. Вот как это можно сделать:

var domain = "my.site.com"
var last = domain.split('.').reverse()[0]
alert(last)

Обратите внимание, что JavaScript массивы поддерживают сложный синтаксис (reverse()[0]) для вызова метода, а затем извлечения элемента из полученного массива.

Вы можете создавать более длинные вызовы, например, reverse()<a href="/0%5D%5B1">0][1</a>[5]..., синтаксис языка позволяет это.

Работа с массивами JS- сортировка, метод sort(fn)

Метод sort() сортирует массив, не изменяя количество элементов:

var arr = [ 1, 2, 15 ]
arr.sort()
alert( arr )   // 1, 15, 2

Запустите приведенный выше код. Вы получите порядок 1, 15, 2. Это потому, что метод преобразует все в строку и использует по умолчанию лексикографический порядок.

Чтобы сделать метод «умнее», нам нужно передать в него пользовательскую функцию сравнения. Она должна принимать два аргумента и возвращать 1, 0 или -1:

function compare(a, b) {
  if (a > b) return 1
  else if (a < b) return -1
  else return 0
}
var arr = [ 1, 2, 15 ]
arr.sort(compare)
alert( arr )   // 1, 2, 15

Теперь все работает правильно.

Задание для самостоятельного выполнения

Создайте функцию ageSort(people) для сортировки массива объектов людей по возрасту:

var john = { name: "John Smith", age: 23 }
var mary = { name: "Mary Key", age: 18 }
var bob = { name: "Bob-small", age: 6 }
var people = [ john, mary, bob ]
ageSort(people) // теперь люди должны быть отсортированы в таком порядке [ bob, mary, john ]

Выведите имена людей после сортировки JavaScript двумерного массива.

Решение

Нужно использовать Array#sort и пользовательское сравнение:

function ageCompare(a, b) {
  if (a.age > b.age) return 1
  else if (a.age < b.age) return -1
  return 0
}
function ageSort(people) {
  people.sort(ageCompare)
}
// проверьте это
var john = { name: "John Smith", age: 23 }
var mary = { name: "Mary Key", age: 18 }
var bob = { name: "Bob-small", age: 6 }
var people = [ john, mary, bob ]
ageSort(people)
// проверьте порядок
for(var i=0; i<people.length; i++) {
  alert(people[i].name)
}

Более короткий вариант

Функция сравнения может быть короче. Альтернативное решение:

people.sort(function(a,b) { return a.age - b.age })

Оно работает, так как нам не нужно возвращать 1 / -1 / 0, будут работать положительные или отрицательные числа.

Работа с массивами JS - подробнее об определении массива

new Array()

В JavaScript объявление массива можно осуществить с помощью другого синтаксиса:

var arr = Array("Apple", "Peach", "etc")

Он используется редко только потому, что квадратные скобки [] короче.

Также существует вероятность того, что new Array, вызываемый с одним числовым аргументом, создаст массив заданной длины с неопределенными элементами:

var arr = new Array(2,3) // Ок, мы имеем [2, 3]
arr = new Array(2) // получили ли мы [2] ?
alert(arr[0]) // нет! мы получили [undefined, undefined]

В приведенном выше примере мы получили undefined, потому что new Array(number) создает пустой массив с параметром length равным number.

Это может быть весьма неожиданно. Но если вы знаете об этой особенности, вы можете использовать new Array(number), например, так:

var indent = new Array(5).join('a') // aaaa (4 элемента)

Это оптимизированный способ повторить строку.

Многомерный массив JS

Массивы в JavaScript могут хранить любой тип данных:

var arr = ["My", "Small array", true, {name:'John'}, 345]
alert(arr[1]) // Small array

Это можно использовать для создания многомерных массивов:

var matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]
alert(matrix[1][1]) // центральный элемент

Заключение

Это все, что касается углубленного определения JavaScript массивов.

Мы рассмотрели:

  • Как объявляется массив, два различных синтаксиса;
  • Как добавлять, заменять, удалять элементы массива;
  • Как перебрать массив;
  • Как разбить строку на массив, а затем собрать ее снова;
  • Сравнение Array и Object в JavaScript.

Этого должно быть достаточно в 95% случаев. Чтобы узнать о других методах, обратитесь к Руководству по массиву на Mozilla.

Вадим Дворниковавтор-переводчик статьи «Array»