Всё, что вы хотели знать о массивах в JavaScript 3

Массив в JavaScript является глобальным объектом, приправленным определёнными методами и свойствами для работы с массивами, прототипом которому служит Object. По своей сути массивы в JS представляют коллекцию данных с числовым целым положительным индексом (неформально могут применяться строковые, отрицательные и дробные «индексы», см. ниже). Таким образом, от стандартных объектов в JS массивы отличает специальное оперирование свойствами с числовыми индексами и наличием свойства length.

Общая информация.

Именно за счёт того, что объекты в JavaScript’е, состоящие из пар ключ-значение являются хэш-таблицами, а плотные (dense) или не разреженные массивы (подробнее об этом речь пойдет ниже) хранятся в виде C-array, мы получаем оптимизацию от JS движков и увеличение скорости обработки данных.

Максимальная длина массива (он же максимальный индекс) равна положительному 32-ух битному целому (4,294,967,295). Свойство массива length возвращает номер последнего индекса + 1, а не количество элементов массива, как многие ожидают. 

Для поддержки работы со списком по принципу FIFO можно использовать методы unshift и pop. Отмечу, что методы push/pop выполняются быстрее, чем а shift/unshift. Это связано с тем, что во втором случае приходится полностью перестраивать (индексировать) массив. Рекомендуется использовать эти четыре метода вместо ручного добавления\удаления элементов (a[2] = ‘test’, delete a[2] и пр.) для гарантии отсутствия «дырок» в массиве и установки корректного значения для свойства length.

Перебор элементов массива.

Самое частое использование — это обычный цикл for:


var myArr = [1, 2, 3, 4, 5, 6, 7];

for (var i = 0, len = myArr.length; i < len; i++) {
  //
}

или более читаемый подход, правда пересчёт начинается с конца массива


var myArr = [1, 2, 3, 4, 5, 6, 7];
var i = myArr.length;

while (i--) {
  //
}

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


if (typeof myArr[i] !== 'undefined') {
  //
}

Применять для обхода массива цикл for in считается плохой практикой, поскольку данный цикл предназначен для обхода объектов.


var myArr = [1, 2, 3, 4, 5, 6, 7];

for (var i in myArr) {
  //
}

Чем же плохи эти подходы? Это трудно-читаемый код и нужно заводить дополнительную переменную (-ые). Также в первом случае для цикла for мы делаем очень много лишних «телодвижений»: присвоение переменной начального значения, проверка на окончание цикла, изменение переменной. А нашей задачей является лишь доступ к элементам массива. Всё это не в духе функционального программирования.

Сравните два подхода.


var myArr = [1, 2, 3];

for (var i = 0, len = myArr.length; i < len; i++) {
  alert(myArr[i]);
}

и тоже самое через функцию forEach, появившейся в ECMAScript5:


[1, 2, 3].forEach(alert);

Очевидно, что второй вариант гораздо лаконичнее и понятнее. Поэтому старайтесь использовать методы forEach, some, every и т.д. Хотя, здесь стоит отметить один немаловажный факт, эти методы не поддерживаются IE версией 8 и ниже. Для старых браузеров можно использовать полифилы: forEach, some, every.

Совместимость данных функций и браузеров:

  • Opera 11+
  • Firefox 3.6+
  • Safari 5+
  • Chrome 8+
  • Internet Explorer 9+

Сортировка.

Для сортировки массивов в JS есть специальный метод sort. По умолчанию сортировка осуществляется по номеру символа в Unicode.


var fruit = ["apples", "bananas", "Cherries"];
fruit.sort(); // ["Cherries", "apples", "bananas"];

var scores = [1, 2, 10, 21];
scores.sort(); // [1, 10, 2, 21]

var things = ["word", "Word", "1 Word", "2 Words"];
things.sort(); // ["1 Word", "2 Words", "Word", "word"]

В Unicode числа идут перед буквами в верхнем регистре, которые, в свою очередь, перед буквами в нижнем регистре.

В качестве аргумента в метод sort можно передать функцию для определения сортировки. Пользовательская функция сортировки может возвращать три значения:

  • число больше нуля, если условие сортировки истинно;
  • число меньше нуля, если условие сортировки ложно;
  • нуль (0), если сравниваемые значения равны.

Пример:


function compare(a, b) {
  if (a меньше, чем b по некоторому критерию) {
    return -1;
  } else if (a больше, чем b по некоторому критерию) {
    return 1;
  }

  // a эквивалентно b
  return 0;
}

var numbers = [7, 6, 1, 4, 2, 3, 5];

numbers.sort(function(a, b) {
  return a - b;
});

console.log(numbers); // [1, 2, 3, 4, 5, 6, 7]

var items = [
  { name: "Edward", value: 21 },
  { name: "Sharpe", value: 37 },
  { name: "And", value: 45 },
  { name: "The", value: -12 },
  { name: "Magnetic" },
  { name: "Zeros", value: 37 }
];

items.sort(function (a, b) {
  if (a.name > b.name) {
    return 1;
  } else if (a.name < b.name) {
    return -1;
  }

  return 0;
});
/*
  { name: "And", value: 45 },
  { name: "Edward", value: 21 },
  { name: "Magnetic" },
  { name: "Sharpe", value: 37 },
  { name: "The", value: -12 },
  { name: "Zeros", value: 37 }
*/

Псевдо-массив arguments

Данный объект служит для доступа к параметрам, переданным в функцию. Но почему псевдо? Потому что имеет свойство length, числовые ключи, но не имеет никакого отношения к Array, т.е. вы не сможете применить методы массива к arguments.


(function f() {
  console.log(arguments instanceof Array); // false
})();

function f(arg1, arg2, ..., argN) {
  // arg1 === arguments[0]
  // arg2 === arguments[1]
  // ...
  // argN === arguments[N]
}

arguments часто применяется для доступа к «лишним» аргументам функции, т.е. тем аргументам, которые не были указаны.


function user(name) {
  if (arguments.length > 1) {
    // arguments[1] === 'Smith'
  }
}

user('John', 'Smith');

В JavaScript arguments ссылается на переменные-параметры. Поэтому изменение arguments несёт за собой изменение переменной и наоборот.


function f(x, y) {
  console.log(x); // 2
  arguments[0] = 1;
  console.log(x); // 1
  x = 3;
  console.log(arguments[0]); // 3
}

f(2, 0);

Более того:


var x = {a: 1, b: 2};

function f(x) {
  arguments[0].a = 22;
}

console.log(x.a); // 1
f(x);
console.log(x.a); // 22

Для того, чтобы была возможность применять методы из Array.prototype для arguments создаётся копия объекта arguments, преобразованная в массив:


function f() {
  var args = Array.prototype.slice.call(arguments);
  //
}

Остановимся на этом моменте подробнее.

Если бы arguments был массивом, мы бы просто сделали:


arguments.slice();

Массивы в JavaScript являются экземплярами «класса» Array и, соответственно, имеют методы из Array.prototype. Как уже писалось выше, arguments — это псевдо-массив, не имеющий ничего общего с Array, поэтому в нём не содержится нужных нам методов для работы с массивом.


console(typeof Array.prototype.slice); // function

Как видите slice — это функция. Любая функция в JS имеет методы call и apply для вызова другого метода в заданном контексте.


var args = Array.prototype.slice.call(arguments);

Описание всех методов объекта Array в JavaScript.

Мутаторы (методы, изменяющие массив).
  1. fill — заполнение массива ОТ и ДО одинаковым значением.
  2. push — добавление одного или более элементов в конец массива. Возвращает длину нового массива.
  3. pop — удаляет последний элемент из массива. Возвращает удалённый элемент.
  4. reverse — изменяет порядок элементов в массиве на противоположный.
  5. shift — удаляет первый элемент из массива и возвращает его.
  6. unshift — добавляет один или более элементов в начало массива. Возвращает длину нового массива.
  7. sort — сортирует элементы массива.
  8. splice — добавление\удаление элементов в массиве.
Акцессоры (данные методы не вносят изменений в массив и служат для доступа к его элементам).
  1. concat — возвращает новый массив, полученный путём совмещения других массивов и\или значений.
  2. join — соединяет все элементы массива в строку.
  3. slice — извлекает кусок массива и возвращает новый.
  4. indexOf — возвращает первый индекс элемента в массиве, эквивалентный искомому значению. В случае, если элемент не был найден — возвращает -1.
  5. lastIndexOf - возвращает последний индекс элемента в массиве, эквивалентный искомому значению. В случае, если элемент не был найден — возвращает -1.
 Итераторы (проход по элементам массива, циклы)
  1. forEach — вызывает функцию для каждого метода массива.
  2. entries — возвращает новый объект Array Iterator, который содержит пару ключ\значение для каждого индекса массива.
  3. every — возвращает true, если каждый элемент массива соответствует условию, указанному в передаваемой функции.
  4. some - возвращает true, если, по-крайней, мере один элемент массива соответствует условию, указанному в передаваемой функции.
  5. filter - создаёт новый массив со всеми элементами текущего массива для которых указанная фильтрующая функция возвращает true.
  6. find - возвращает найденное значение массива, если этот элемент соответствует условию в передаваемой функции или undefined в случае неудачного поиска.
  7. findIndex - возвращает индекс массива, если элемент соответствует условию в передаваемой функции или -1 в случае неудачного поиска.
  8. keys - возвращает новый Array Iterator, который содержит ключ для каждого индекса в массиве.
  9. map - создаёт новый массив с результатом вызова передаваемой функции на каждом элементе массива.
  10. reduce - вызывает заданную функцию для всех элементов в массиве, возвращаемое значение которой представляет собой накопленный результат и предоставляется как аргумент в следующем вызове этой функции. Проход по массиву осуществляется слева-направо.
  11. reduceRight - то же самое, что и reduce, однако проход начинается справа-налево.

А также методы, унаследованные от  Object.prototype:  toSource, toString, hasOwnProperty, isPrototypeOf, valueOf  и пр.

Трюки, хаки и интересные решения

1) Создание массива через конструктор Array.


var arr1 = new Array(); // Пустой массив, с length равным 0
var arr2 = new Array(5); // Пустой массив, с length равным 5
var arr3 = new Array(1, 2, 3); // Массив с тремя элементами 1, 2 и 3 и length = 3

К слову сказать, что вызов Array как функции мы получим такой же эффект как и с оператором new.


var arr1 = Array(); // Пустой массив, с length равным 0
var arr2 = Array(5); // Пустой массив, с length равным 5
var arr3 = Array(1, 2, 3); // Массив с тремя элементами 1, 2 и 3 и length = 3

2) Переопределение Array.

Пожалуй, это основная причина по которой не стоит использовать конструктор Array для создания массивов в JS.


var Array = 100;
var foo = new Array(); // TypeError: number is not a function

Поэтому используйте для создания массива сокращённую литеральную нотацию:


var myArr = [];

3) Длина массива равна порядковому номеру последнего элемента в нём.


var myArr = [1, 2, 3];

console.log(myArr.length); // 3
myArr[100] = 100;
console.log(myArr.length); // 101
console.log(myArr); // [1, 2, 3, undefined x 97, 100];
console.log(myArr[33]); // undefined

Важное замечание.

Работать с массивами в JS следует как с непрерывным набором данных — плотный (dense) массив, без разрывов в индексах — разреженный (sparse) массив. Например, в случае, если у нас есть некий массив myArr, соответствующий [1, 2, 3], и вы делаете myArr[100] = 100, движок JS начинает работать с массивом, как с обычным объектом, что может привести к потере тех преимуществ, которые даёт нам именно массив.

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


var myArr = [1, 2, 3];
delete myArr[1];

Да, это удалит значение «2″ в массиве, однако смещения «3″ на позицию «2″ не произойдет и мы получаем «дырку» между единицей и тройкой.


console.log(myArr); // [1, undefined, 3]

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


var sparse = [ , , 'c'];
var dense = [undefined, undefined, 'c'];

0 in sparse  // false
0 in dense  // true

4) Отрицательные, дробные и текстовые индексы в массиве.


var myArr = [1, 2, 3];

console.log(myArr); // [1, 2, 3]
console.lor(myArr.length); // 3
myArr[-1] = 'test1';
myArr['test'] = 'test2';
myArr[1.7] = 'test3';
console.log(myArr); // [1, 2, 3]
console.lor(myArr.length); // 3

console.log(myArr[-1]); // test1
console.log(myArr['test']); // test2
console.log(myArr[1.7]); // test3

dir(myArr);

dir

это достигается за счёт того, что почти всё в JS является объектом. Поэтому мы может делать так:


var myArr = [];

myArr.val = 124;
myArr['val2'] = 'value';

5) Параметр length можно переопределить.

Это может пригодится для удаления элементов массива (усечения).


var myArr = [1, 2, 3, 4, 5, 6, 7];

myArr.length = 3;
console.log(myArr); // [1, 2, 3]

Увеличивая длину массива через length мы не получаем новых элементов. Т.е. по сути элементов, кроме 1, 2 и 3 в данном массиве не существует.


var myArr = [1, 2, 3];

myArr.length = 10;
console.log(myArr); // [1, 2, 3]
console.log(myArr[5]); // undefined

6) Поиск объекта в массиве.


var myArr = [{a: 1}, {b: 1}];

console.log(myArr.indexOf({b: 1})); // -1

Однако, можно поступить следующим образом:


var obj1 = {a: 1};
var obj2 = {a: 2};
var myArr = [];

myArr.push(obj1, obj2);
console.log(myArr.indexOf(obj2)); // 1

7) Проверка наличия индекса в массиве.


var myArr = [1, 2, 3, 4, 5, 6, 'test', 11];

console.log(1 in myArr); // true
console.log('1' in myArr); // true
console.log(3 in myArr); // true
console.log(7 in myArr); // true
console.log('test' in myArr); // false
console.log('test2' in myArr); // false

8) Многомерный массив.

Как такового понятия многомерного массива в JS нет, однако это можно эмулировать:


var a = [
  ['0x0', '0x1', '0x2']
  ['1x0', '1x1', '1x2']
  ['2x0', '2x1', '2x2']
];

console.log(a[2][1]);   // 2x1
console.log(a[2][2]);   // 2x2

Более правильно это называть массив массивов, нежели, чем многомерный массив.

9) Поиск в массиве с определённой позиции.


var myArr = [1,2,3,4,3,5,3];

console.log(myArr.indexOf(3)); // 2

Но что, если нам необходимо получить тройку по-середине?


console.log(myArr.indexOf(3, 3)); // 4

10) Reduce и undefined.


[1, 2, 3, , , , , , 'hello'].reduce(function(len, el) {
  return ++len;
}, 0);
// 4

Как видите, undefined элементы не учитываются.

11) Минимальный и максимальный элемент в массиве.


var foo = [1, 2, 3];

Math.min(foo); // NaN
Math.min.apply(null, foo); // 1
Math.max.apply(null, foo); // 3

12) Слияние массивов.


var foo = [1, 2, 3];
foo.push.apply(foo, [4, 5, 6]);

// или

var foo = [4, 5, 6]
bar = [1, 2, 3].concat(foo); // 1, 2, 3, 4, 5, 6

13) Клонирование массива.


var arrayA = [1, 2, 3];
var arrayB = arrayA;

arrayB[1] = 222;
console.log(arrayB); // [1, 222, 3]
console.log(arrayA); // [1, 222, 3]

Как видите, массивы передаются по ссылке и следовательно, при внесении изменений в один массив мы получим изменения и в другом. Нижеприведённый код позволяет клонировать массив путём создания нового.


var foo = [1, 2, 3];
var bar = foo.slice(0); // bar = [1, 2, 3]

14) Равенство массивов.

Учитывая, что


var a = [1, 2, 3];
var b = [1, 2, 3];

console.log(a === b); // false

и даже


console.log(a == b); // false

нам нужна функция, которая будет проверять эквивалентны ли два массива.


Array.prototype.compare = function(arr) {
  if (this.length !== arr.length) {
    return false;
  }

  return arr.join('') === this.join('');
}

var a = [1, 2, 3];
var b = [1, 2, 3];

console.log(a.compare(b)); // true

15) Заполнение массива.

Просто трюк, вряд ли где-либо будет применимо.


var myArr = [null, null, null, null, null];

myArr = myArr.map(function (x, i) {
  return i;
});

console.log(myArr); // [0, 1, 2, 3, 4]

var myArr = new Array(5).map(function (x, i) {
  return i;
});

console.log(myArr); // []

var myArr = Array.apply(null, Array(5));

myArr = myArr.map(function (x, i) {
  return i;
});

console.log(myArr); // [0, 1, 2, 3, 4]

16) Выход за пределы.


var myArr = ['a', 'b', 'c'];

myArr[Math.pow(2,32) - 1] = 'd';

console.log(myArr); // [ 'a', 'b' ]
console.log(arr.length); // 2
console.log(arr[Math.pow(2,32)-1]); // "d"

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


[].length = -1;  // RangeError: Invalid array length
[].length = Math.pow(2, 32);  // RangeError: Invalid array length
[].length = 'abc';  // RangeError: Invalid array length

А вот со свойством length всё серьёзней. При попытке установить не подходящие значения мы получаем ошибки.

17) Преобразование типов.


!![]; // true
!![1]; // true
[] == false // true
[1,2,3] == [1,2,3]; // false
[] == []; // false
[1,2,3].toString(); // "1,2,3,4"
parseInt([], 10);  // NaN
parseInt([12, 22, 23], 10); // 12
typeof []; // "object"
[1, 2, 3] instance of array; // true
[1, 2, 3] instance of object; // true

Дополнительная и расширенная документация и информация:

  1. http://learn.javascript.ru/array
  2. http://learn.javascript.ru/array-methods
  3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
  4. http://dev.opera.com/articles/javascript-array-extras-in-detail/
  5. http://es5.github.io/#x15.4
  6. http://www.2ality.com/2012/06/dense-arrays.html

3 thoughts on “Всё, что вы хотели знать о массивах в JavaScript

  1. Reply николай Авг 19, 2014 12:03

    очень интересная и полезная статья мне понравилось кое какие полезные моменты я узнал для себя спасибо за статью

  2. Reply Наталья Сен 3, 2014 02:19

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

    • Reply Евгений Злобин Сен 9, 2014 17:27

      Привет! Безмерно рад, что не зря потратил время и это кому-то пригодилось.

Leave a Reply

  

  

  


1 × 6 =