Глаза страшат, а руки делают

ЧАСТЬ ВТОРАЯ


21. Объект Array

  • понятие массивов и хэшей;
  • инициализация массивов;
  • свойство length;
  • перечень методов;
  • методы, не изменяющие массив: concat(), join(), slice(), toString(), toLocaleString();
  • методы, изменяющие массив: pop(), push(), reverse(), shift(), splice(), unshift();
  • создание собственного метода indexOf().

В большинстве языков программирования массив (Array) является особым типом данных. В JavaScript нет отдельного типа для массива данных, но есть предопределённый объект Array с методами и свойствами для работы с массивами.

Во многих языках программирования существует два типа массивов:

  • Числовые — в которых доступ к элементам осуществляется по индексам (порядковым номерам).
  • Ассоциативные (или хэши) — в которых вместо индексов используются заданные строки-ключи.

В JavaScript, как ни парадоксально это звучит, нет массивов-хэшей, но они в нём есть. Каким образом они там существуют, мы узнаем при исследовании объекта Object. Обмолвлюсь, что их можно делать и из «обычных» массивов, хотя лучше не надо.

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

Инициализация

Мы уже знакомились с созданием массивов и работой с ними в уроках 9 и 10. Сейчас кратко упорядочим наши знания.

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

var myArray = new Array() — создаёт пустой массив, то есть именованную «обёртку» для массива, которую можно заполнять элементами.

var myArray = new Array(размер) — создаёт массив с указанной размерностью (то есть количеством элементов), но конкретные элементы не заданы.

var myArray = new Array(элемент0, элемент1, ..., элементN) — создаёт массив, уже укомплектованный конкретными элементами. Его размерность определяется количеством этих элементов.

Но использовать конструктор new не обязательно. Как и строку, массив может быть создан с помощью инициализатора. Если «особой приметой» строки являются кавычки, то у массива это квадратные скобки:

var myArray = [элемент0, элемент1, ..., элементN]

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

var myArray = []

Я уже упомянул, что массив в JavaScript не имеет своего типа данных. Но его элементы могут содержать любые типы данных, причём в пределах одного массива.

var habur_chabur = ["крякозубр", 13, false];
/* массив содержит строку, число и булево значение */

Как уже говорилось в уроках первой части, у каждого элемента в массиве есть свой индекс — порядковый номер. Отсчёт индексов начинается с нуля. Таким образом, индекс последнего элемента массива всегда на 1 меньше размерности массива. Например, массив из 3 элементов будет иметь размерность 3 (по количеству элементов), а индексами элементов будут 0, 1 и 2.

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

document.write(habur_chabur [0]); 

Свойства объекта Array

Объект Array имеет те же три свойства, что и объект String:

  • constuctor
  • length
  • prototype

О свойствах constuctor и prototype опять же поговорим, когда будем изучать Object.

Свойство length

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

Размерность показывает не количество элементов, а количество возможных индексов.

Что это значит?

Давайте создадим два таких массива:

var arrYear = new Array();
var arrLimon = new Array();
arrYear[0] = "январь";
arrYear[11] = "декабрь";
arrLimon[999999] = "лимон!";

В первом из них два элемента, а максимальный индекс — 11 (то есть это 12-й элемент при учёте нулевого индекса). Во втором — только один элемент, но миллионный.

Теперь сделаем скрипт для вывода значений их размерности:

// Выводим оба свойства length:
var dlina1 = arrYear.length.toString(10);
var dlina2 = arrLimon.length.toString(10);
document.write("Размерность массива arrYear: " + dlina1 + "<br>Размерность массива arrLimon: " + dlina2);

Результат

То есть все существующие и несуществующие элементы от 0 до 11 в первом случае и от 0 до 999999 — во втором.

Однако никакой лишней памяти эти несуществующие элементы не занимают (в отличие от языков, имеющих тип данных Array).

Давайте немножко похулиганим и попробуем вывести какой-нибудь несуществующий элемент одного из этих массивов:

document.write(arrLimon[20]);

Результат

То есть: элемент не определён.

Отступление: маленькая хитрость

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

Предположим, пишу я эти уроки вразбивку, но знаю, что будет, допустим, 40 уроков. Создаю массив для меню на 40 элементов, заполняю то, что есть, а в коде пишу:

for (i=0; i<arrMenu.length; i++)
{
    if (arrMenu[i] == undefined)
        {document.write("");}
    else
        {document.write(/* здесь код вывода меню */);}
}

Таким образом, помещая новый урок, я просто добавляю его элемент в массив, но не редактирую скрипт вывода меню. И никаких грязных «undefined» на web-страницах.

Методы объекта Array

Красным помечены методы, которые уже знакомы нам по объекту String.

Метод

Описание

Возвращаемое
значение

Изменяет
исходный
массив

concat()

Конкатенация массивов, объединяет несколько массивов в один новый массив.

массив

нет

join()

Создаёт строку из элементов массива с указанным разделителем между ними.

строка

нет

pop()

Удаляет последний элемент массива.

значение
удаленного
элемента

да

push()

Добавляет к массиву указанное значение в качестве последнего элемента.

новая длина
массива

да

reverse()

Изменяет порядок следования элементов массива на противоположный.

массив

да

shift()

Удаляет первый элемент массива.

значение
удаленного
элемента

да

slice()

Создает массив из элементов исходного массива с индексами указанного диапазона.

массив

нет

sort()

Сортирует (упорядочивает) элементы массива с помощью функции сравнения.

массив

да

splice()

Удаляет из массива несколько элементов и возвращает массив из удаленных элементов или заменяет значения элементов.

массив

да

toLocaleString()

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

строка

нет

toString()

Преобразует содержимое массива в символьную строку.

строка

нет

unshift()

Добавляет к массиву указанное значение в качестве первого элемента.

ничего
(undefined)

да

Методы, не изменяющие массив

Метод concat()

Работает аналогично подобному методу объекта String, только объединяет не строки, а массивы.

Синтаксис

объект.concat(массив1 [, массив2, ..., массивN])

Аргументы

объект — исходный массив.

массивN — N-ный массив, присоединённый к исходному массиву.

Пример

var arrRu = ["Иванов", "Петров", "Сидоров"]
var arrEn = ["Джонсон", "Томсон", "Джексон"]
var arrDe = ["Вебер", "Вагнер", "Краус"]
var arrMiruMir = arrRu.concat(arrEn, arrDe)

document.write(arrMiruMir)
// возвращает:
// Иванов,Петров,Сидоров,Джонсон,Томсон,Джексон,Вебер,Вагнер,Краус

Сами массивы, склеенные этим методом, остаются без изменений.

Метод join()

Возврашает строку, состоящую из элементов массива с указанным разделителем между ними.

Синтаксис

объект.join([разделитель])

Аргумент

разделитель — символ или строка символов, использующиеся в качестве разделителя.

Примечание

Если разделитель не указан, по умолчанию используется запятая (без пробела).

Примеры

document.write(arrMiruMir.join())

Результат

document.write(arrMiruMir.join(""))

Результат

document.write(arrMiruMir.join("&"))

Результат

document.write(arrMiruMir.join(", понимаешь, "))

Результат

Метод slice()

Здесь всё работает так же, как и с объектом String, только «единицей измерения» служит не символ строки, а элемент массива.

Синтаксис тот же, что и для объекта String (см. урок 19).

Давайте сопоставим, как работает этот метод со строками и массивами. Задаём строку и массив.

var strLamer = "ЛАМЕР";
var arrLamer = ["Ленивый", "Агрессивный", "Маленький", "Ершистый", "Ребёнок"];

Теперь применим метод. Сначала к строке:

document.write(strLamer.slice(1, 4))

Результат

А теперь — к массиву:

document.write(arrLamer.slice(1, 4))

Результат

По-моему, всё понятно. Специфика нумерации аргументов та же, что и для объекта String.

Методы toString() и toLocaleString()

С методом toString() мы уже эпизодически встречались, когда конвертировали число в строку. Метод конвертирует в строки не только числа, но и массивы (и вообще все «не-строки»). Отличие применения его для массивов только в том, что не нужно указывать в качестве аргумента систему счисления, поскольку этот объект не математический.

Синтаксис

массив.toString()

Пример

var tt = ["эники", "беники", "ели", "вареники"]
var dd = tt.toString()
document.write("Это массив: " + tt + "<br>")
document.write("А это строка: " + dd)

Результат

Вроде бы получилось то же самое. То же, да не то же. Давайте протестируем обе переменные на длину:

document.write("Длина tt: " + tt.length + "<br>")
document.write("Длина dd: " + dd.length)

Результат

Как видно из примера, длина tt соответствует размерности массива, а длина dd — количеству символов в строке.

Обратите внимание

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

Если нам нужно зачем-либо узнать количество всех символов в массиве (без учёта разделителей), то можно вывести формулу. Заметим, что разделителей всегда на 1 меньше, чем элементов (последний элемент без разделителя), то есть массив.length - 1

Значит, из длины строкового значения надо вычесть эту величину:

массив.toString().length - массив.length + 1

(Надеюсь, понятно, почему минус поменялся на плюс?)

document.write("Количество букв в массиве tt: " + (tt.toString().length - tt.length + 1))

Результат

Обратите внимание

Для вывода этого значения мне понадобилось заключить математическое выражение в скобки. Если этого не сделать, результат будет NaN, то есть «не число».

Дело в том, что перед математической операцией находится строка, и плюс, стоящий после неё, является знаком конкатенации (склеивания) строк. Но следующие плюс и минус уже относятся к вычислениям и ставят интерпретатор в тупик. «Это не число!» — беспомощно ругается он. Чтобы отграничить знак склейки от знаков сложения и вычитания, необходимо заключить всю математику в скобки.

Эта путаница — следствие той самой «нечёткой типизации» языка, о которой я уже не раз говорил.

Метод toLocaleString() теоретически отличается от toString() только тем, что алгоритм преобразования символов строк зависит от локальных языковых настроек операционной системы клиента. Но в настоящее время для русского языка это уже неактуально, так что можно спокойно пользоваться методом toString().

Методы, изменяющие массив

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

Метод pop()

Удаляет последний элемент из массива и возвращает этот элемент. Принимает тип данных этого последнего элемента. Исходный массив становится укороченным на один элемент.

Синтаксис

массив.pop()

Предположим:

var tt = ["эники", "беники", "ели", "вареники"]

Допустим, мы решили оградить массив от посягательств и перезаписали его в другую переменную, которую и будем коверкать:

var kk = tt
var kkPop = kk.pop()

Не поможет! Если мы выведем kkPop, то возвратятся вареники, а при выводе tt, так же, как и при выводе kk, возвратятся только эники,беники,ели.

Можно ли в этом случае сохранить исходный массив в целости?

Можно!

Нужно объединить его в строку, а потом разрезать эту строку на те же кусочки, можно даже «в одном действии»:

var kk = tt.join("%&^").split("%&^")

Обратите внимание

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

Теперь можно как угодно издеваться над массивом kk, сохраняя tt в первозданном виде.

Метод push()

Добавляет в конец массива новый элемент (или несколько элементов), значения которых указываются в аргументе. Возвращает число, соответствующее новой размерности массива.

Синтаксис

массив.push(значение_элемента1 [, ..., значение_элементаN])

Аргумент

значение_элемента — одно или несколько (через запятые) значений новых элементов, продолжающих исходный массив.

Пример

Для начала снова «кольцом самотворчества крепко обезопасим себя вовне»:

var tt = ["эники", "беники", "ели", "вареники"]
var ss = tt.join("%&^").split("%&^")

Обратите внимание

Если мы не сделаем эту «фишку», то исходный массив будет изменён.

А теперь протестируем:

var ssPush = ss.push("с маслом", "и со сметаной")
document.write("Размерность изменённого массива: " + ssPush + "<br>")
document.write("Изменённый массив: " + ss + "<br>")
document.write("Не тронутый нами исходный массив: " + tt + "<br>")
document.write("Размерность исходного массива: " + tt.length)

Результат

Метод reverse()

Bзменяет порядок элементов массива задом наперёд. Возвращает новый изменённый массив и зачем-то абсолютно точно так же изменяет исходный массив (шило == мыло).

(Думаю, что рациональнее было бы сделать этот метод не изменяющим исходный массив... Но это так — мысли вслух.)

Синтаксис

массив.reverse()

Пример

var tt = ["эники", "беники", "ели", "вареники"]
var rr = tt.join("%&^").split("%&^")
var rrReverse = rr.reverse()
document.write("Результат изменения: " + rrReverse + "<br>")
document.write("Массив rr, который подвергся изменению: " + rr + "<br>")
document.write("Не тронутый нами исходный массив tt: " + tt)

Результат

Метод shift()

Удаляет первый элемент из массива и возвращает этот элемент. Принимает тип данных этого первого элемента. Исходный массив становится укороченным на один элемент.

То есть абсолютно то же самое, что и метод pop(), только не с последним, а с первым элементом.

Далее по алфавиту идёт метод sort(). Но этому методу и вспомогательным функциям для него мы посвятим весь следующий урок. А пока перескочим к методу splice().

Метод splice()

Изменяет массив, удаляя из него один или несколько элементов, и (если указано) вставляет в это место один или несколько новых элементов. Возвращает новый массив из удалённых элементов.

Синтаксис

массив.splice(индекс, количество [, элем1, элем2, ..., элемN])

Аргументы

индекс — индекс элемента, с которого начинается удаление.

количество — количество элементов, которые будут удалены.

элем1 — элемN — значения новых элементов, которые будут вставлены.

Примеры

Создадим массив:

var ddd = ["красный", "оранжевый", "жёлтый", "зелёный", "голубой", "синий", "фиолетовый"]

Сделаем два «клона», с которыми будем работать:

var kkk = ddd.join("%&^").split("%%&^")
var nnn = kkk.join("%%&^").split("%%&^")

1. Вынимаем и вставляем:

var kkkSplice = kkk.splice(2, 3, "<font color='red'>белый</font>", "<font color='red'>чёрный</font>")

2. Только вынимаем:

var nnnSplice = nnn.splice(4, 2)

Выводим результаты:

document.write("массив <b>kkkSplice</b>: " + kkkSplice + "<br>")
document.write("массив <b>kkk</b>: " + kkk + "<br>")
document.write("массив <b>nnnSplice</b>: " + nnnSplice + "<br>")
document.write("массив <b>nnn</b>: " + nnn)

Результат

Примечания

1. Если параметр аргумента индекс больше последнего индекса исходного массива, то метод splice() возвращает пустой массив, а исходный массив остаётся без изменений.

2. Если параметр аргумента количество превышает количество возможных элементов для изъятия из исходного массива, то метод splice() изымает элементы от указанного индекса до конца исходного массива.

Метод unshift()

Добавляет к массиву указанное значение (или несколько значений) в качестве первого элемента (первых элементов). Возвращает undefined. Исходный массив возвращается изменённым.

Синтаксис

массив.unshift(значение1 [, ..., значениеN])

Аргументы

значение1 — значениеN — вставляемые элементы.

Пример

Создадим исходный массив и клонируем из него рабочий массив для изменения:

var bbb = ["снег", "падает", "на землю"]
var ccc = bbb.join("%&^").split("%&^")

Изменим рабочий массив методом unshift():

var cccUnshift = ccc.unshift("белый", "пушистый")

Выведем результаты:

document.write("Сохранённый нами массив: " + bbb + "<br>")
document.write("Метод возвращает: " + cccUnshift + "<br>")
document.write("Массив, к которому применён метод: " + ccc)

Результат

Метод, которого не хватает

Отыскать значение элемента массива по его индексу, как мы убедились, очень просто. Но иногда требуется найти индекс элемента, зная его значение. Во всех «цивилизованных» языках программирования есть такие встроенные «фишки». Но в JavaScript этого почему-то не оказалось. Сейчас исправим.

Примечание

Этот метод появился в JavaScript 1.6 под названием indexOf(). Но его поддерживают только Mozilla Firefox 1.5+ и IE  8.

Создание собственных методов

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

Для создания методов используется свойство prototype и пишется функция, в которой выполняется метод. Для назначения метода используется следующий синтаксис:

объект.prototype.имяМетода = имяФункции

объект — объект (а не экземпляр), на который назначается метод.

имяМетода — имя нашего метода.

имяФункции — имя нашей функции.

(Обратите внимание: нигде никаких скобок.)

Имена метода и функции могут совпадать. Для наглядности так и сделаем. Назовём метод так же, как и в JavaScript 1.6 (потом, в конце, я покажу, как обойти конфликт в новых браузерах).

Array.prototype.indexOf = indexOf;
function indexOf(value) {
// код функции
}

Аргумент функции станет аргументом нашего метода.

Теперь заполним функцию.

1. Назначим переменную счётчика:

function indexOf(value) {
var i;
// ...
// ...
}

2. Запустим счётчик по элементам массива:

    for (i=0; i<this.length; i++)
    {

Ключевым словом this инициализируются значения функции (подробнее см. в последующих уроках).

3. Натыкаясь на нужное значение элемента, говорим «брек»:

        if (this[i] == value)
            break;
    }

Понятно, что мы ищем уже существующее value, а не назначаем на него нечто? Поэтому знак равенства двойной.

4. Осталось возвратить «застрявший» номер:

return i
}

Ключевое слово return указывает, что именно должна возвратить функция (в данном случае — локальную переменную i).

Вот наша функция целиком:

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

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

if (!Array.prototype.indexOf)
{
    // далее весь наш код
}

Префиксный восклицательный знак, как вы должны помнить, означает отрицание.

При наличии этой инструкции JavaScript проверит, существует ли indexOf() среди методов объекта Array(). Если есть, то пройдёт мимо и будет выполнять стандартный метод, а если нет — пойдёт по нашей ветке.

Для наглядности приведу всю конструкцию целиком, а то, знаете, мало ли

if (!Array.prototype.indexOf)
{
    Array.prototype.indexOf = indexOf;
    function indexOf(value)
    {
        var i;
            for (i=0; i<this.length; i++)
            {
                if (this[i] == value)
                    break;
            }
        return i
    }
}

Вот теперь можно проверить метод, скажем, на «эниках-бениках»:

document.write("Индекс элемента \"беники\" = " + tt.indexOf("беники"))

Смотрим результат

Yes!

Всё правильно, поскольку «эники» — это 0.

Естественно, этот метод не изменяет массив.


Итак, мы узнали:

как работать с методами массивов и создавать собственные методы.

А также научились:

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


К следующему уроку >>


 009022