содержание

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

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

Существует распространённое ошибочное мнение, что length равно максимальному индексу в массиве плюс 1. Но это лишь частный случай. В общем случае length строго больше максимального индекса в массиве. Например, мы можем самостоятельно менять length.

var a = [1, 2, 3];
a.length = 5;
alert(a.length + '; ' + [0 in a, 1 in a, 2 in a, 3 in a, 4 in a]);    // 5; true,true,true,false,false
// Видим, что length равен 5, хотя элементов в массиве всего 3.

a.length = 1;
alert(a.length + '; ' + [0 in a, 1 in a, 2 in a, 3 in a, 4 in a]);    // 1; true,false,false,false,false
// length уменьшилось и все элементы, индексы которых больше или равны length, были удалены.

Если взять определение объекта в JavaScript

Объект — это неупорядоченное множество пар ключ-значение.

То, в силу того, что массивы те же объекты, можно сказать, что массивы — это неупорядоченное множество пар ключ-значение с особой обработкой числовых ключей и свойства length.

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

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

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

var a0 = [10, 11, 12];
var a1 = [20, 21, 22];
var a2 = [30, 31, 32];
var a = [a0, a1, a2];
alert(a[0]);  // 10,11,12
alert(a[1]);  // 20,21,22
alert(a[2]);  // 30,31,32

Но не забывайте, что это всё-таки не многомерный массив, а массив массивов. В чём разница: двумерный массив, как частный случай многомерного, это матрица MxN, т.е., грубо говоря, таблица, из M строк и N столбцов. В каждой строке у неё одинаковое количество элементов, равно как и в каждом столбце. В JavaScript иначе, тут в массиве верхнего уровня лежат ссылки на другие произвольные массивы. Они могут быть одинакового размера, могут быть разного, а могут быть вообще не массивами. Многомерный массив — это нечто целое, массив массивов — это множество различных массивов, ссылки на которые лежат ещё в одном массиве.

var a = [
    [1, 2, 3, 4, 5],    // В этом подмассиве у нас пять чисел
    ['a', 'b', 'c'],    // В этом подмассиве три строки
    {foo: 'bar'}        // А тут вообще объект
];
alert(a[0][2]);       // 3
alert(a[1][1]);       // b
alert(a[2].foo);      // bar
alert(a[2]['foo']);   // bar

Тем не менее при аккуратном использовании массив массивов может хорошо выполнять роль многомерного массива.

var a = [
    ['0x0', '0x1', '0x2', '0x3'],
    ['1x0', '1x1', '1x2', '1x3'],
    ['2x0', '2x1', '2x2', '2x3'],
    ['3x0', '3x1', '3x2', '3x3']
];
alert(a[2][1]);   // 2x1
alert(a[3][3]);   // 3x3

Ассоциативные массивы

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

// Следующие "ассоциативные массивы" аналогичны  
var a1 = [];
a1.foo = 'bar';

var a2 = new Number();
a2.foo = 'bar';

// Но лучше всё-таки так
var a3 = {};
a3.foo = 'bar';
// Или так
var a4 = {foo: 'bar'};

Создание массивов

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

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

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

Но делать этого не стоит.

Во-первых, поведение этого конструктора неоднозначно. В общем случае конструктор создаёт массив с элементами, переданными ему в аргументах. Но невозможно с его помощью создать массив с одним целочисленным элементом. Т.к. в этом случае будет создан пустой массив с length, равным единственному переданному аргументу.

А во-вторых, в JavaScript есть более краткая и более удобная литеральная нотация для создания массивов: квадратные скобки.

var a1 = [];        // Пустой массив
var a2 = [5];       // Массив с одним элементом 5
var a3 = [1, 2, 3]; // Массив с тремя элементами 1, 2 и

В JScript, т.е. во всех версиях Internet Explorer, есть одна неприятная особенность — если конструктору в качестве элемента передать undefined, то элемент создан не будет.

var a = [1, undefined, 3, undefined, 5];   // Аналогично и в случае a = new Array(1, undefined, 3, undefined, 5)
alert(a.length + '; ' + [0 in a, 1 in a, 2 in a, 3 in a, 4 in a]);
// Во всех браузерах кроме IE этот код выведет 5; true,true,true,true,true
// В IE будет 5; true,false,true,false,true

Возможно такое поведение объясняется не слишком большой разницей между отсутствием свойства и свойством, равным undefined. Однако эту особенность следует иметь в виду, т.к. она может привести к непредсказуемым последствиям. Например, при использовании методов forEach/map/filter/reduce.

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

var a = [1, , 3, , 5];
alert(a.length + '; ' + [0 in a, 1 in a, 2 in a, 3 in a, 4 in a]);
// 5; true,false,true,false,true

a = [, , 1, , ];
alert(a.length + '; ' + [0 in a, 1 in a, 2 in a, 3 in a, 4 in a]);
// В IE 5; false,false,true,false,false
// в остальных браузерах 4; false,false,true,false,false

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

Добавлять элементы в массив, равно как и удалять их, можно также, как и обычные свойства любых других объектов. С тем лишь отличием, что при добавлении числовых свойств может изменятся свойство length, а при изменении свойства length могут удаляться числовые свойства. В общем случае алгоритм установки свойств у массивов примерно следующий:

    При добавлении несуществующего числового свойства i, если length меньше или равен i, то length устанавливается равным i + 1.
    При изменении свойства length
        Если присваиваемое значение меньше 0, то бросается RangeError.
        Удаляются все числовые свойства, индексы которых больше и равны новому length.

var a = [0, 1, 2];
alert(a.length);  // 3
a[5] = 5;
alert(a.length);  // 6
delete a[5];
alert(a.length);  // 6: при удалении элементов length не изменяется.
a.length = 1;
alert([0 in a, 1 in a, 2 in a]);   // true,false,false: элементы с индексами 1 и 2 удалены
try {
    a.length = -1;
} catch (e) {
    alert(e.message);  // RangeError
}

Из того факта, что при установке length удаляются "лишние" элементы, следует способ очистки массива. Если вас по какой-то причине не устраивает присваивание переменной нового пустого массива, а нужно именно обнулить существующий, то достаточно присвоить его свойству lengthзначение 0.

var a = [0, 1, 2, 3, 4, 5], b = a;
// Имеем две переменных, хранящих ссылки на один и тот же массив
a = [];    // Если сделаем так, то в a и b будут лежать ссылки на разные массивы
alert([a.length, b.length]);  // 0,6

var a = [0, 1, 2, 3, 4, 5], b = a;
a.length = 0; // Очистили массив, на который ссылаются значения обеих переменных
alert([a.length, b.length])   // 0,0

Методы push, pop, shift и unshift

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

Метод push добавляет переданные элементы в конец массива. Метод pop возвращает последний элемент массива и удаляет его.

var a = [];
a.push(5, 6, 7);
alert(a.length + ';' + a);  // 3;5,6,7
alert(a.pop());  // 7
alert(a.length + ';' + a);  // 2;5,6

Метод unshift добавляет переданные элементы в начало массива. Элементы будут располагаться в том же порядке, в каком они были переданы. Метод shift возвращает первый элемент массива и удаляет его.

var a = [1];
a.unshift(9, 8, 7);
alert(a.length + ';' + a);  // 4;9,8,7,1
alert(a.shift());  // 9
alert(a.length + ';' + a);  // 3;8,7,1

В Internet Explorer младше 8-й версии unshift возвращает undefined, во всех остальных браузерах — новое значение length. Так что лучше не надеяться на возвращаемое от unshift значение.

var a = [];
alert(a.unshift(1, 2, 3));   // IE 6,7: undefined
                             // IE8 и остальные: 3

Добавление/удаление элементов в середине массива

Метод splice имеет сигнатуру Array.prototype.splice(start, deleteCount [, item1 [, item2 [, ...]]]).

Он удаляет из массива deleteCount элементов начиная с индекса start. Если передано больше двух аргументов, то третий и последующие аргументы вставляются в массив заместо удалённых. Если start отрицательный, то индекс, с которого начнётся удаление, будет равен length + start. Возвращает массив из удалённых элементов. Таким образом с помощью метода splice можно удалять элементы из середины массива или добавлять произвольное количество в произвольное место массива.

var a = [0, 1, 2, 3, 4, 5, 6];
// Удалим три средних элемента
a.splice(2, 3);
alert(a);  // 0,1,5,6
// Добавим два элемена перед последним
a.splice(-1, 0, 7, 8);
alert(a);  // 0,1,5,7,8,6
// Заменим второй и третий элементы на три строки
a.splice(1, 2, 'a', 'b', 'c');
alert(a);  // 0,a,b,c,7,8,6

В простейшем случае, если нужно удалить элемент с индексом i, то нужно у массива вызвать метод splice с аргументами i и 1.

Вообще говоря, второй аргумент у метода splice необязательный, но поведение функции с одним аргументов отличается в разных браузерах.

В Firefox, в последних версиях Opera, в Safari и в Chrome будут удалены все элементы от

  • start до конца массива.
  • В IE не будет удалён ни один элемент.
  • В более ранних версиях Opera поведение ещё менее предсказуемое — будет удалён один элемент с индексом start - 1, т.е. start-й по счёту.

Поэтому всегда следует передавать в этот метод как минимум два элемента.

Как не надо перебирать элементы

Перед тем, как рассказать, как следует перебирать элементы массивов, стоит сказать, как их не следует перебирать. Уж слишком распространён перебор элементов с помощью цикла for-in.

var a = [0, 1, 2, 3, 4, 5, 6], props = '';
for (var i in a) {
    props += i;
}
alert(props); // В большинстве случаев 0123456

Чем же так плох for-in для перебора элементов массива? Тем, что он перебирает все свойства объекта, не имеющие атрибута DontEnum. Из этого следуют как минимум 2 проблемы.

Во-первых, свойства не обязаны быть как-либо отсортированными, т.е. спецификация языка не обещает, что в данном случае элементы будут перебраны по порядку от 0 до length - 1. Хотя на данный момент все JS-движки сортируют свойства массивов, а некоторые версии браузеров сортируют даже числовые свойства у обычных объектов. Поэтому данная проблема не очень актуальна.

Во-вторых, как уже было сказано, в таком случае перебираются все свойства массива, а не только числовые индексы. Обычно в таких случаях говорят, что не надо засорять Array.prototype собственными методами, но даже без этого могут возникнуть проблемы. Возьмём, к примеру, массив, возвращаемый строковым методом match.

var a = 'ab'.match(/((a)(b))/), props = '';
for (var i in a) {
    props += i + ' ';
}
alert(props);
// IE: input index lastIndex 0 1 2 3
// Остальные браузеры: 0 1 2 3 index input

Т.е. стандартный метод строки возвращает массив, в котором помимо числовых индексов имеется несколько других свойств. Поэтому, если перебирать такой массив с помощью for-in, мы получим не только числовые индексы.

Как перебирать элементы массивов

Минимально возможный индекс в массиве — 0, максимально возможный — length - 1, поэтому для того, чтобы перебрать все элементы массива, нужно перебрать числа от 0 до length - 1 включительно. А для этого существует цикл for.

var a = [0, 1, 2, 3, 4, 5], sum = 0;
for (var i = 0; i < a.length; i++) {
    sum += a[i];
}
alert(sum); // 15

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

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

var props = '';
for (var i = 0; i < a.length; i++) {
    props += a[i] + ' ';
}
alert(props);   // 1 2 undefined undefined 3 undefined 5
// Перебрали и несуществующие элементы

props = '';
for (var i = 0; i < a.length; i++) {
    if (i in a) {
        props += a[i] + ' ';
    }
}
alert(props);  // 1 2 3 5
// Перебрали только существующие элементы

// Не забываем, что undefined обманчиво
a[2] = undefined;
props = '';
for (var i = 0; i < a.length; i++) {
    if (i in a) {
        props += a[i] + ' ';
    }
}
alert(props);  // 1 2 undefined 3 5

Наиболее правильной конструкцией для перебора элементов массива является

for (var i = 0, length = a.length; i < length; i++) {
    if (i in a) {
        // делаем что-то с элементом массива.
    }
}

А чтобы не писать такие конструкции, лучше воспользоваться методом forEach

a.forEach(function(element, index) {
    // делаем что-то с элементом element, у которого индекс index
});

Итераторы

Во всех современных браузерах, кроме Internet Explorer, у массивов есть методы, предназначенные для перебора элементов и выполнения последующих различных действий над ними. Это методы forEach, map, filter, every, some, reduce и reduceRight. В IE эти методы отсутствуют, но их можно реализовать, расширив прототип Array (дштл).

Эти методы перебирают элементы массива от 0 до length - 1 и, если элемент существует, передают его в callback-функцию. Наличие элемента проверяется оператором in, без hasOwnProperty. Это значит, что перебирутся и элементы, находящиеся в прототипе Array, если вдруг такое произойдёт. Но на length свойства прототипа не влияют, следовательно, для того, чтобы это произошло, элемент в прототипе должен быть меньше length, а в самом массиве на его месте должен быть пропуск.

Array.prototype[3] = 3;
var a = [0];
a[5] = 5;
var elements = [];
a.forEach(function(e) {
    elements.push(e);
});
alert(elements);    // 0,3,5

Вторым аргументом во все функции, кроме reduce и reduceRight передаётся контекст вызова callback-функции. Также стоит отметить, что свойство length кэшируется до входа в цикл, поэтому, если внутри callback-функции будут добавляться элементы в массив, то перебор всё равно не зациклится.

var a = [0, 1, 2], i = 0;
a.forEach(function(num) {
    i++;
    a.push(num);
});
alert([i, a.length]); // 3,6

В callback-функцию все методы, кроме, опять же, reduce и reduceRight передают элемент массива, его индекс и сам массив. Ни один из этих методов не изменяет исходный массив. Таким образом, реализация forEach, например, должна иметь вид.

Array.prototype.forEach = function(fn, thisObj) {
    for (var i = 0, l = this.length; i < l; i++) {
        if (i in this) {
            fn.call(thisObj, this[i], i, this);
        }
    }
};

Простой перебор элементов

Перебор элементов для выполнения над ними произвольных действий осуществляется методом forEach. Он просто вызывает callback-функцию для каждого элемента, не производя больше никаких действий.

var a; // Допустим, есть массив объектов, у которых необходимо вызвать метод foo
a.forEach(function(object, index) {
    object.foo(index);
});

Callback-функция может быть определена и заранее.

function bar(obj, i) {
    obj.foo(i);
}
a.forEach(bar);

Модификация элементов

Метод map используется для получения массива, аналогичного исходному, но элементы которого пропущены через callback-функцию. Индексы массива при этом сохраняются, т.е. если в исходном массиве были пропуски, то и в результирующем массиве они тоже будут.

var a = [0, 1, , , , 4, , 8];
var b = a.map(function(x) {
    return x + 1;
});
alert(a + '\n' + b);  // 0,1,,,,4,,8
                      // 1,2,,,,5,,9

Фильтрация элементов

Метод filter возвращает массив элементов из исходного массива, для которых callback-функция вернула истинное значение. Индексы этот метод не сохраняет, результирующий массив будет заведомо без пропусков.

// Традиционный вывод чётных/нечётных чисел
alert([0, 1, 2, 3, 4, 5, 6, 7, 8, 9].filter(function(x) { return x % 2; }));   //  1,3,5,7,9

Наличие и отсутствие нужных элементов в массиве

Метод every возвращает true, если для каждого элемента массива callback-функция возвращает истинное значение. Как только callback вернёт ложное значение, перебор элементов прекращается.

Метод some возвращает true, если хотя бы для одного элемента массива callback-функция возвращает истинное значение. Как только callback вернёт истинное значение, перебор элементов заканчивается.

var a = [1, 3, 5, 6, 7, 9];
// Все ли элементы в массиве нечётные?
alert(a.every(function(x) { return x % 2; }));  // false
// Есть в массиве хотя бы один чётный элемент?
alert(a.some(function(x) { return !(x % 2); }));  // true

Неочевидный вариант использования методов every и some — перебор элементов до нужного с прекращением дальнейшего перебора.

var a = [1, 3, 5, 6, 7, 9], firstEven, i = 0;
a.some(function(x) {
    i++;
    if (!(x % 2)) {
        // Нашли нужный элемент, дальнейший перебор нам не нужен
        firstEven = x;
        return true;
    }
});
alert([firstEven, i]);   // 6,4

Сведение массива к единственному значению

Методы reduce и reduceRight последовательно вызывают callback-функцию, передавая ей результат её выполнения на предыдущей итерации и очередной элемент массива. Возвращают они результат последнего вызова callback-функции. reduceRight отличается от reduce тем, что элементы перебираются в обратном порядке.

Вторым аргументом обе функции принимают инициирующее значение, которое будет передано при первом вызове callback-функции. Если инициирующее значение не передано, то для первого элемента массива (не обязательно элемента с индексом 0, а первого имеющегося) callback-функция вызвана не будет, а этот элемент будет инициирующим значением. Если reduce или reduceRight будут вызваны без инициирующего значения для пустого массива, то будет брошен TypeError. Если в массиве один элемент, и инициирующее значение не передано, callback-функция не будет вызвана ни разу, а метод вернёт единственный элемент.

var a = [2, 3, 5, 6, 7, 9];

// Сумма элементов, инициирующее значение можно не передавать

alert(a.reduce(function(sum, x) {return sum + x; }));  // 32
// Сумма квадратов, инициирующее значение не передавать нельзя, т.к. иначе
// квадрат первого элемента не посчитается

alert(a.reduce(function(sum, x) {return sum + x * x; }, 0));  // 204

Замена циклам for

Истинные ценители могут совсем отказаться от использования цикла for и писать все циклы в функциональном стиле. Для этого необходимо написать функцию, создающую массив последовательных чисел. Например

Array.range = function(start, count) {
    if (arguments.length == 1) {
        count = start;
        start = 0;
    }
    var a = [];
    for (var i = start; i < start + count; i++) {
        a.push(i);
    }
    return a;
}

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

// Добавим в документ пять элементов, при клике на которых выводится его номер

Array.range(5).forEach(function(i) {
    var div = document.createElement('div');
    div.innerHTML = i;
    div.onclick = function() {
        alert(i);
    };
    document.body.appendChild(div);
});

Поиск элемента в массиве

Для поиска элементов в браузерах Firefox, Opera версий 9.50 и старше, Chrome и Safari у массивов есть методы indexOf и lastIndexOf. Для Internet Explorer и младших версий Opera их можно реализовать, расширив прототип Array (ссылка).

Метод indexOf возвращает индекс первого встреченного элемента в массиве, равного переданному аргументу, или -1, если ничего не было найдено. Метод lastIndexOf возвращает индекс последнего встреченного элемента в массиве, равного переданному аргументу, или -1, если ничего не было найдено.

var a = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
alert(a.indexOf(3));      // 2
alert(a.lastIndexOf(3));  // 7

Сравнение производится строгое (===), поэтому типы искомого значения и эталона должны совпадать.

var
 a = [
var a = [3, '3'];
alert([3 == '3', 3 === '3', a.indexOf('3'), a.lastIndexOf(3)]);   // true,false,1,0

При этом, как и в прочих итераторах, перебираются только существующие элементы массива.

var a = [1];
a[5] = undefined;
alert(a[1] === undefined);  // true
// Но
alert(a.indexOf(undefined)); // 5

Инверсия массива

Для изменения порядка элементов в массиве на противоположный используется метод reverse. Этот метод изменяет порядок элементов в исходном массиве и возвращает ссылку на самого себя.

var a = [1, 2, 3, 4, 5];
a.reverse();
alert(a);    // 5,4,3,2,1

При этом пропуски в массиве сохраняются.

var a = [];
a[1] = 1;
a[2] = 2;
alert(a + ' : ' + [0 in a, 1 in a, 2 in a]);   // ,1,2 : false,true,true
a.reverse();
alert(a + ' : ' + [0 in a, 1 in a, 2 in a]);   // 2,1, : true,true,false

Сортировка элементов

Метод sort сортирует элементы массива, изменяя порядок элементов в исходном массиве. sort принимает единственным аргументом функцию, сравнивающую элементы массива. Если функция сравнения не задана, то элементы сортируются в лексикографическом порядке.

var a = ['d', 'b', 'a', 'e', 'c'];
a.sort();
alert(a);  // a,b,c,d,e

Даже если отсортировать массив, состоящий только из чисел, они всё равно будут отсортированы как строки, а не как числа.

var a = [4, 2, 10, 5, 30];
a.sort();
alert(a);  // 10,2,30,4,5

Чтобы отсортировать массив чисел, необходимо передать методу sort функцию сравнения двух чисел. Функция, передаваемая методу sort, поочерёдно вызывается для различных пар элементов массива, и должна вернуть отрицательное число, если первый аргумент меньше второго, положительное число, если первый аргумент больше второго, и 0, если они равны. Таким образом код, сортирующий массив чисел будет выглядеть следующим образом.

var a = [4, 2, 10, 5, 30];
a.sort(function(a, b) { return a - b; });
alert(a);  // 2,4,5,10,30

С помощью callback-функции массивы можно сортировать по любому критерию. Например, отсортировать массив объектов по значению какого-либо свойства, или даже нескольких свойств.

var a = [
    {x: 4, y: 2},
    {x: 3, y: 5},
    {x: 1, y: 5},
    {x: 3, y: 2},
    {x: 4, y: 1}
];
a.sort(function(a, b) {
    // Сортируем по полю x, если оно одинаковое, то по полю y
    return (a.x - b.x) || (a.y - b.y);
});
alert(a.map(function(a) { return 'x = ' + a.x + ', y = ' + a.y }).join('\n'));
// x = 1, y = 5
// x = 3, y = 2
// x = 3, y = 5
// x = 4, y = 1
// x = 4, y = 2

Callback-функция обязательно должна возвращать число, в противном случае Internet Explorer выбросит ошибку, а остальные браузеры просто не отсортируют массив.

Пропуски в массиве, равно как и элементы массива, равные undefined при сортировке не учитываются вовсе. Данные элементы просто оказываются в конце массива.

var a = [];
a[1] = 1;
a[3] = undefined;
alert(a);       // ,1,,
a.sort(function(a, b) {
    alert([a, b]);
    // Функция не будет вызвана ни разу, в массиве только один значащий элемент
    return 0;
});
alert(a);       // 1,,,

Чтобы отсортировать массив в обратном лексикографическом порядке, достаточно после вызова sort без аргументов вызвать метод reverse. Для остальных типов сортировки придётся видоизменять callback-функцию.

var a = [4, 2, 10, 5, 30];
a.sort(function(a, b) { return b - a; });
alert(a);  // 30,10,5,4,2

Перемешивание элементов

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

Array.prototype.shuffle = function() {
    return this.sort(function() {
        return 0.5 - Math.random();
    });
};

Однако данный способ нельзя назвать эффективным, он переменшивает по разному в разных браузерах, но всегда очень неравномерно. Создадим тест: будем многократно перемешивать массив из десяти последовательных чисел от 0 до 9, и подсчитаем, сколько раз каждое из чисел окажется на первом месте.

Array.prototype.shuffle = function() {
    return this.sort(function() {
        return 0.5 - Math.random();
    });
};

var N = 10000, a = [], i;
for (i = 0; i < 10; i++) {
    a[i] = 0;
}
for (i = 0; i < N; i++) {
    a[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle()[0]]++;
}
alert(a);

В Firefox результат теста будет сильно похож на 1860,1841,1223,704,633,613,409,255,1226,1236, т.е. чаще всего под нулевым индексом перемешанного массива будет нулевой же индекс исходного массива, а реже всего — седьмой.

В IE и Safari результаты совсем другие, но не лучше: 471,890,855,1362,1090,1088,844,1252,1084,1064, тут нулевой индекс исходного массива будет гораздо реже оказываться под нулевым индексом в отсортированном массиве.

В Opera разброс гораздо больше, но упорядоченнее: 2810,2861,1391,233,206,914,946,459,86,94. В Chrome похожая ситуация, но ещё хуже: 2868,2930,1962,1076,574,291,164,82,33,20.

Вывод — использовать такое перемешивание очень неэффективно. Гораздо лучше перебрать все элементы и поменять их на произвольный с большим или равным индексом. Только перебирать элементы будем с конца массива, чтобы взятие случайного числа было проще.

Array.prototype.shuffle = function() {
    for (var i = this.length - 1; i > 0; i--) {
        var num = Math.floor(Math.random() * (i + 1));
        var d = this[num];
        this[num] = this[i];
        this[i] = d;
    }
    return this;
}

Предыдущий тест для такого способа будет давать для каждого индекса 1000 плюс-минус 40 повторений, что является, несомненно, более хорошим результатом. Более того, данный способ быстрее предыдущего в разных браузерах на 40-70%.

Объединение массивов

Иногда необходимо объединить несколько массивов в один, содержащий элементы из исходных массивов. Для этого в JavaScript есть метод concat. Он создаёт копию исходного массива, добавляя к нему переданные аргументы, подобно методу push. Однако, в отличие от push, если методу concat передан другой массив, то будут добавлены элементы этого массива, а не сам массив единственным элементом.

Array.prototype.toString = function() {
    return '[' + this.join(', ') + ']';
};
var a = [1, 2, 3];
alert(a.concat(4, 5, 6));      // [1, 2, 3, 4, 5, 6]
alert(a.concat([4, 5], [6]));  // [1, 2, 3, 4, 5, 6]

Однако дальше одного уровня concat аргументы-массивы не разворачивает.

Array.prototype.toString = function() {
    return '[' + this.join(', ') + ']';
};
var a = [1, 2, 3];
alert(a.concat([4, [5], 6]));      // [1, 2, 3, 4, [5], 6]

С разреженными массивами ситуация обстоит ожидаемая: пропуски как из исходного массива, так и из массивов-аргументов, сохраняются.

Array.prototype.toString = function() {
    return '[' + this.join(', ') + ']';
};
var a = [1, , 3];
alert(a.concat([4, , 6]));      // [1, , 3, 4, , 6]

Деление массива

Метод slice имеет сигнатуру slice(begin[, end]) и возвращает подмассив исходного массива, начиная с индекса begin и заканчивая индексом end - 1. Чтобы легче запомнить такую странную нумерацию, лучше считать, что передаются индексы не элементов, а "межэлементного пространства".

Индексы элементов:   0 1 2 3 4 5 6 7 8
                    ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
                    | | | | | | | | | |
Индексы для slice:  0 1 2 3 4 5 6 7 8 9

Тогда становится понятно, что ar.slice(0, 1) вернёт только нулевой элемент, а slice(5, 9) — с пятого по восьмой элементы.

var a = [0, 1, 2, 3, 4, 5];
alert(a.slice(0, 2));    // 0,1
alert(a.slice(4, 5));    // 4

Если второй аргумент является отрицательным числом, то отсчёт второго индекса идёт с конца массива.

var a = [0, 1, 2, 3, 4, 5];
alert(a.slice(0, -2));    // 0,1,2,3

Если же второй аргумент не передан вообще, то возвращается копия массива от begin до конца.

var a = [0, 1, 2, 3, 4, 5];
alert(a.slice(3));    // 3,4,5

Клонирование массива

В JavaScript нет нативного метода для клонирования массивов, однако его легко сэмулировать, помня, что concat и slice не меняют исходный массив. Достаточно вызвать метод concat без аргументов или метод slice с единственным аргументом равным 0.

var a = [1, 2, 3];
var b = a.concat();
var c = a.slice(0);
alert(a + ' ; ' + b + ' ; ' + c);    // 1,2,3 ; 1,2,3 ; 1,2,3
alert([a == b, a == c, b == c]);     // false,false,false

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

// Клонируется только массив верхнего уровня
var a = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];
var b = a.concat();
alert(a[0][1]);   // 2
alert(b[0][1]);   // 2
b[0][1] = 15;
alert(a[0][1]);   // 15

// Клонируем вложенные массивы
var a = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];
var b = [];
a.forEach(function(subArray) {
    b.push(subArray.concat());
});
alert(a[0][1]);   // 2
alert(b[0][1]);   // 2
b[0][1] = 15;
alert(a[0][1]);   // 2

Создание строки из элементов массива

Для объединения всех элементов массива в одну строку используется метод массива join, принимающий единственным аргументом строку, которой необходимо разделить элементы.

var a = [1, 2, 3, 4];
alert(a.join('-'));   // 1-2-3-4

Метод joinобъединяет элементы для каждого индекса от 0 до length - 1, при этом значения undefinedи null преобразуются в пустую строку, остальные элементы приводятся к строке.

var a = [1, , null, undefined, 2];
alert(a.join('-'));   // 1----2

В спецификации языка указано

If no separator is provided, a single comma is used as the separator.

Т.е. если сепаратор не был передан, то в качестве него нужно использовать запятую. Данное предложение, однако, можно трактовать двояко: сепаратор не передан вообще или в качестве сепаратора передан undefined. Впрочем чуть ниже в спецификации указана чёткая инструкция.

If separator is undefined, let separator be the single-character string ",".

Т.е. если сепаратор равен undefined, то используется запятая. Именно такое поведение реализуют все браузеры, кроме Internet Explorer. IE подставляет запятую, только если join был вызван без аргументов.

var a = [1, 2, 3, 4];
alert(a.join());             // 1,2,3,4 во всех браузерах
alert(a.join(undefined));    // 1undefined2undefined3undefined4 в IE, 1,2,3,4 в остальных браузерах

Метод join можно использовать также для элегантной реализации повторения строки, т.е. из строки 'ab', например, получить строку 'abababab'. Действительно, пустые элементы массива преобразуются в пустые строки, но при этом разделяются сепаратором. Следовательно, чтобы повторить строку n раз, нужно взять пустой массив, у которого length равен n + 1, и вызвать у него метод join с аргументом, равным размножаемой строке.

/**
 * Повторяет исходную строку n раз.
 * @param {Number} n Число повторений строки
 * @return {String} Размноженная строка.
 */
String.prototype.times = function(n) {
    return new Array(n + 1).join(this);
};

alert('01'.times(4));  // 01010101

Массивы, как и любой другой объект в JavaScript, имеют встроенный метод toString, вызываемый при неявном преобразовании в строку. Этот метод у них аналогичен вызову метода join без аргументов, т.е. элементы разделяются запятой.

var str = 'ab', a = [1, 2, 3, 4];
alert(str + a);   // ab1,2,3,4

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

var a = [1, 2, ['a', 'b', 'c'], 3, 4];
alert(a);      // 1,2,a,b,c,3,4
Array.prototype.toString = function() {
    return '[' + this.join(', ') + ']';
};
alert(a);      // [1, 2, [a, b, c], 3, 4]

Создание массива из строки

Для обратного преобразования строки в массив у строк есть метод split, аналогично join принимающий разделитель, по которому требуется разбить строку.

var str = '1-2-3-4';
alert(str.split('-'));  // 1,2,3,4

Обратите внимание, что если вызвать split у пустой строки, то мы получим не пустой массив, а массив с одним элементом, равным пустой строке.

var a = ''.split(',');
alert([a.length, a[0] == '']);   // 1,true

Немассивы в JavaScript

Как это ни странно, не всякий объект, имеющий числовые свойства и свойство length является массивом. Не являются массивами, например, объект arguments и коллекции возвращаемые DOM методами. Это означает, что у этих объектов нет методов, имеющихся у массивов, а также другое, не такое как у массивов, поведение при манипулировании числовыми свойствами и свойством length.

function f() {
    var a = [1, 2, 3];
    alert(arguments.join);                                                      // undefined
    alert([a.length, arguments.length]);                                        // 3,3
    alert([a[0] == arguments[0], a[1] == arguments[1], a[2] == arguments[2]]);  // true,true,true
    a.length = 0;
    arguments.length = 0;
    alert([a.length, arguments.length]);                                        // 0,0
    alert([a[0] == arguments[0], a[1] == arguments[1], a[2] == arguments[2]]);  // false,false,false
    alert(a);                                                                   // пустое сообщение
    alert([arguments[0], arguments[1], arguments[2]]);                          // 1,2,3
}
f(1, 2, 3);

Как же отличить массивы от других похожих объектов? Если интересующий вас объект может быть как массивом, так и любым другим объектом, а вам всего лишь нужно перебрать его элементы, то можно смело, без всяких проверок, перебирать элементы циклом for от 0 до length - 1. Если же нужно точно знать, имеется ли у нас массив или что-то другое, то будем проверять.

Самая логичная и, в принципе, самая правильная проверка — это instanceof.

var a = [], s = '';
alert(a instanceof Array);   // true
alert(s instanceof Array);   // false

В большинстве случаев такой проверки будет достаточно, однако её нельзя назвать универсальной. В случае, если к вам придёт массив, созданный в другом окне/фрейме, то он не пройдёт проверку, т.к. прототип у этого массива будет другой.

Есть более надёжный и, что немаловажно, тоже компактный способ определения массива, как впрочем и любого другого native-объекта, — использование Object.prototype.toString. Заглянем в спецификацию

When the toString method is called, the following steps are taken:

  1. Get the [[Class]] property of this object.
  2. Compute a string value by concatenating the three strings "[object ", Result(1), and "]".
  3. Return Result(2).

Т.е. метод toString у любого объекта должен возвращать строку [object [[Class]]], где [[Class]] — внутреннее свойство объекта, недоступное из пользовательских скриптов. У массивов оно равно Array, у строк — String, у чисел — Number, у регулярных выражений — RegExp. Таким образом функция Object.prototype.toString, вызванная в контексте массива, должна вернуть строку [object Array]. Поэтому можно написать такой isArray.

function isArray(o) {
    return Object.prototype.toString.call(o) == '[object Array]';
}
alert(isArray([]));   // true
alert(isArray(3));    // false
alert(isArray({}));   // false

А можно и ещё короче.

function isArray(o) {
    return {}.toString.call(o) == '[object Array]';
}

Такой метод не сможет верно определить массив только если переопределить Object.prototype.toString, а такая ситуация, к счастью, случается крайне редко.

Приведение к массиву

Для начала нужно понять, а зачем объект приводить к массиву. Как правило, это нужно для того, чтобы стали доступны соответствующие методы, но в большинстве случаев для использования этих методов не обязательно использовать массив. Практически все методы у массивов реализованы так, чтобы работать в тех случаях, если они были вызваны не в контексте массива.

function f() {
    return [].slice.call(arguments, 1, 4);
}
alert(f(1, 2, 3, 4, 5));     // 2,3,4

К сожалению в Internet Explorer младше 9-й версии мир DOM — это особый мир, который плохо уживается с миром JavaScript. Поэтому вызов в контексте DOM-объекта метода, взятого у массива, вызовет ошибку.

function f() {
    return [].slice.call(document.getElementsByTagName('div'), 0, 2);
}
f();   // Во всех браузерах вернёт массив из первых двух элементов div
       // В IE вызовет ошибку

Если вы пишете кроссбраузерный скрипт, то вам придётся избегать таких конструкций. Если же IE не входит в круг поддерживаемых браузеров, то скорее всего приведение к массиву вам и не понадобится.

Однако, в IE нет нативной реализации итераторов[link] и indexOf/lastIndexOf[link], поэтому, если их правильно реализовать[link], то ими можно кроссбраузерно пользоваться не только для массивов.

function f() {
    return [].filter.call(document.getElementsByTagName('div'), function(el) {
        return el.offsetWidth;
    });
}
f();   // Вернёт массив элементов div, у которых положительная ширина.

Если же всё-таки требуется, преобразовать кроссбраузерно объект в массив возможно несколькими способами. Для объекта arguments достаточно вызвать метод slice.

function f() {
    return [].slice.call(arguments, 0);
}
alert(typeof f(1, 2, 3));  // true

Если для IE реализованы методы map или filter, то ими можно преобразовать любой объект в массив.

// В обоих случаях получим массив элементов
[].map.call(document.getElementsByTagName('div'), function(e) { return e; });
[].filter.call(document.getElementsByTagName('div'), function() { return true; });

Если ни один из способов не подходит, придётся создавать новый пустой массив и вручную добавлять туда элементы, чем впрочем и занимаются методы map и filter.

var a = [];
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
    a.push(divs[i]);
}

Задачи

1 создать массив

// Example
var ourArray = ["John", 23];

// Only change code below this line.
var myArray = [];

2

myArrayдолжен иметь по крайней мере один массив, вложенный в другой массив.

// Example
var ourArray = [["the universe", 42], ["everything", 101010]];

// Only change code below this line.
var myArray = [];

3

ПеременнаяmyDataдолжна быть равна первому значениюmyArray.

myArrayДоступ кданным в переменной следует получить с помощью скобок.

// Ex
var ourArray = [1,2,3];
var ourData = ourArray[0]; // equals 1

// Setup
var myArray = [1,2,3];

// Only change code below this line.
var myData = myArray[];

4

myArrayтеперь должно быть [3,2,3].

Вы должны использовать правильный индекс для изменения значения вmyArray.

var ourArray = [1,2,3];
ourArray[1] = 3; // ourArray now equals [1,3,3].

// Setup
var myArray = [1,2,3];

// Only change code below this line

5

myDataдолжен быть равен8.

Вы должны использовать условное обозначение для чтения значенияmyArray.

Вы должны читать только одно значениеmyArray.

// Setup
var myArray = [[1,2,3], [4,5,6], [7,8,9], [[10,11,12], 13, 14]];

// Only change code below this line.
var myData = myArray[][];

6

myArrayдолжен теперь равняться[["John", 23], ["cat", 2], ["dog", 3]].

// Setup
var ourArray = ["Stimpson", "J", "cat"];
ourArray.push(["happy", "joy"]); 
// ourArray now equals ["Stimpson", "J", "cat", ["happy", "joy"]]

// Setup
var myArray = [["John", 23], ["cat", 2]];

// Only change code below this lin

7

myArray должен содержать только [["John", 23]].

Использовать pop() на myArray

removedFromMyArrayдолжен содержать только ["cat", 2].

// Example
var ourArray = [1,2,3];
var removedFromOurArray = ourArray.pop(); 
// removedFromOurArray now equals 3, and ourArray now equals [1,2]

// Setup
var myArray = [["John", 23], ["cat", 2]];

// Only change code below this line.
var removedFromMyArray;

results matching ""

    No results matching ""