10 ноября 2008

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

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

Посмотрим, что представляют из себя массивы и хэши.

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

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

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

Хэши хранят данные по произвольным строково-числовым ключам, упорядоченным соответственно времени добавления их в хэш. Используется для создания разветвлённых структур данных, позволяя выбирать только одно из множества значений, сохранённых в нем, по заранее определённому ключу.

Начало жизни или как можно создать массив или хэш

// Таким образом мы можем создать массив
var array = []
//А Вот таким - хэш
var hash = {}

JavaScript не создает автоматически массивы или хэши. Соответственно если он встретит следующие конструкции без объявления переменных, он обидится на вас:

// Неверное использование. Следующая строка вернет ошибку ReferenceError: array is not defined
array[5][1] = 'some string'
// Неверное использование. Следующая строка вернет ошибку ReferenceError: hash is not defined
hash['number'] = 8
// Неверное использование. Следующая строка вернет ошибку ReferenceError: array is not defined
array[8] = true

Чтобы использовать массив или хэш его нужно сначала объявить следующим образом:

// Объявляем пустой массив
var array = []
// Заполняем его значениями
array[0] = 'first string'
array[1] = 'second string'
array[2] = 'third string'
// Объявляем пустой хэш
var hash = {}
// Заполняем его значениями
hash['first'] = 21
hash['second'] = 22
hash['third'] = 23

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

// Создаем массив, заполненный значениями
var array = [ 'first string', 'second string', 'third string' ]
// Создаем хэш, заполненный значениями
var hash = { 'first' : 21, 'second' : 22, 'third' : 23 }

Отвлечемся на многомерные структуры

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

// Создаем пустой одномерный массив
var array = []
// Получаем ошибку TypeError: array[1] is undefined
// Она означает что array[1] не определён. А точнее - равен неопределённому значению
array[1][3] = 'some value'

Аналогично и с хэшами:

// Создаем пустой одноуровневый хэш
var hash = {}
// Получаем ошибку TypeError: hash.first is undefined
// Она означает что hash['first'] не определён. А точнее - равен неопределённому значению
hash['first']['third'] = 'some value'

Правильно будет делать так:

// Создаем пустой одномерный массив
var array = []
// Создаем второй массив, как вложенный в первый.
array[1] = []
// И только после этого обращаемся ко второму измерению
array[1][3]='some value'

Или, если указывать данные в момент создания хэша или массива, код может выглядеть следующим образом:

array =
[
        [
                'first-first value',
                'first-second value',
                'first-third value'  // Обращаем внимание на запятую. Объяснение будет ниже.
        ],
        [
                'second-first value',
                'second-second value',
                'second-third value'  // Обращаем внимание на запятую. Объяснение будет ниже.
        ],
        [
                'third-first value',
                'third-second value',
                'third-third value'  // Обращаем внимание на запятую. Объяснение будет ниже.
        ]  // Обратите внимание, что здесь не должно быть запятой.
           // Противный IE будет ругаться, если найдет её здесь и откажется выполнять дальнейший код.
           // Все остальные браузеры воспринимают запятую после последнего элемента вполне благосклонно.
]
hash =
{
        'first' :
        {
                'first' : 'one' ,
                'second' : 'two' ,
                'third' : 'three'  // Обратите внимание, что здесь, как и в случае с массивами, не должно быть запятой.
        } ,
        'second' :
        {
                'first' : 'four' ,
                'second' : 'five' ,
                'third' : 'six'  // Обратите внимание, что здесь, как и в случае с массивами, не должно быть запятой.
        } ,
        'third' :
        {
                'first' : 'seven' ,
                'second' : 'eight' ,
                'third' : 'nine'  // Обратите внимание, что здесь, как и в случае с массивами, не должно быть запятой.
        }  // Обратите внимание, что здесь, как и в случае с массивами, не должно быть запятой.
}

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

О запятых, подлом IE и кроссбраузерности

Я думаю вы заметили, что я старался указать с каким подлым браузером приходится иметь дело. Он видите ли не приемлет запятую после последнего элемента в перечислении. Когда я обнаружил сию «фишку» в ходе была только шестая версия. С тех пор я взял себе за правило контролировать эту вещь. Посему буду благодарен читателям за информацию, сохранилась ли эта особенность в последующих версиях этого «народного браузера».

Когда теряешь половину рабочего дня на осознание подобных милых привычек, главное побороть в себе желание разбить монитор. Такова жизнь. Таких сюрпризов много. Они ждут всех, кто собирается делать что-то с помощью JavaScript. Могу заметить что не все они исходят от IE. Но IE определённо занимает лидирующую позицию в идиотских «фишках». К сожалению у него нет нормального дебаггера, а следовательно зачастую приходится следовать своей интуиции, дабы распознать что же именно ему не нравится.

А теперь запомните. Чтобы Internet Explorer не ругался на определение массива или хэша, вам нужно проследить, дабы нигде, на на одном уровне объявления после последнего элемента не стояло запятой.

Если вы это не запомните – будете искать сами эту ошибку :)

Перебор по массивам и хэшам

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

Для перебора по массивам используется длина массива и некоторая переменная.

for(var i = 0; i<array.length; i++)
{
        // Здесь мы можем сделать что-нибудь с переменной array[i]
        // В ней содержится значение массива array с ключем i
}

Хэш перебирается по всем ключам. Т.е. Чтобы добраться до последнего – нужно пройтись по всем предыдущим.

for(var k in hash)
{
        // Здесь мы можем сделать что-нибудь с переменной hash[k]
        // В ней содержится значение хэша hash с ключем k
}

Теперь обратите внимание на несколько моментов:

  • В обоих случаях используется ключевое слово var. С помощью него объявляется переменная, используемая в качестве счётчика. Это делается для того, чтобы не дать этой переменной стать глобальной. В противном случае она может затереть переменную с более высокого уровня. Это приведет к тому, что вы потратите несколько дней на поиск подобной ошибки. Лучше сразу взять использование var в циклах за правило.
  • В случае с массивами идёт самый обычный перебор. Его можно сделать и несколькими другими способами, но о них в следующей статье. В данном случае используется обычный счётчик, что увеличивается от нуля до длины массива. Последний элемент в массиве имеет номер, равный длина массива минус один. Длина массива определяется свойством .length. У хэшей такого нет. Соответственно хэши так перебрать не получится.
  • В хэшах просто перебираются все ключи. А зная ключи, можно узнать и значения. В данном случае ничего сложного нет. Однако стоит понимать, что если применить данный способ не к хэшу, а к массиву, мы можем получить несколько больше, чем ожидаем. Подробнее об этом я напишу позднее. А новичкам советую использовать перебор массивов через их длину, а не перебор по ключам.

Закругляемся

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

В продолжение темы:

JavaScript. Хэши и массивы – копаем дальше.
JavaScript. Хэши как объекты и другие типы данных.

Возможно это тоже будет интересно
12 комментариев

12 комментариев на запись “JavaScript. Массивы и хэши.”

  1. Zeroglif пишет:

    Зачем чуждый js термин ‘хэш’? Или это в смысле хэш==ассоциативный массив? Тогда массив такой же ‘хэш’, как и объект. Никакой вообще разницы в смысле ассоциативности (’строковости’) свойств…

  2. nekt пишет:

    Zeroglif, про объекты еще успеется. Хэш == ассоциативный массив. Это да. Но они отличаются в использовании от обычного массива.

  3. Zeroglif пишет:

    Почему ‘хэш’, а не ‘объект’? Хочу понять, что вы вкладываете в этот термин. Чем массив менее хэш чем объект?

  4. nekt пишет:

    Zeroglif в JavaScript хэш и объект это одно и тоже. В моем повествовании я разделяю их только по тому признаку, как они используются. Т.е. если объект используется исключительно для хранения данных – это хэш (что, впрочем, не мешает быть ему объектом). А объект, соответственно, более всеобъемлющее понятие.

  5. Святослав пишет:

    Nekt, вообще-то зероглиф – однозначно лучший знаток js в рунете, равных которому и близко нет, сколько бы ты ни знал, на каждом его комментарии обучаешься, жалко книги человек не пишет =( Так что не надо оправдываться и поучать его =) И тут он как всегда прав, хеш – инородное и чужое слово для js, пришедшее из perl. А фраза «если объект используется исключительно для хранения данных – это хэш» – глупость какая-то, честное слово…

  6. nekt пишет:

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

    В любом случае рекомендую подождать – возможно я развею претензии к этим названиям в дальнейшем :)

  7. Zeroglif пишет:

    > я использую это слово лишь для указания противопоставления

    nekt, чем же массив менее хэш, чем сам хэш? В чём противопоставление? И тот, и тот – нативный объект одного и того же типа (type Object), у обоих свойства можно добавлять/удалять в рантайм, у обоих эти свойства приводятся «насильно» к строке (вот и ассоциативность), у обоих эти свойства могут быть беспорядочны и перебрать их можно (с разным успехом!) via ‘for-in’ или ‘for’, значения свойств и там, и там – любые и т.д. Разница само собой есть, но она не в плоскости ‘массив vs. хэш’ или ‘ассоциативный массив vs. индексный’, т.к. в этих плоскостях и объект, и массив – одно и то же, такого рода разделение/противопоставление надуманное и никак не отражает сути отличий.

    з.ы. IE не откажется выполнять код, если в конце массива запятая, для массивов – это нормально.

  8. nekt пишет:

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

    // Создаем массив с перечислением рабочих дней недели
    var week = ['monday','tuesday','wednesday','thursday','freeday']
    // Добавляем в массив выходные
    week['startOfWeekend']='saturday'
    week['endOfWeekend']='sunday'
    // И понимаем что работа занимает у нас вcю жизнь
    for( var i=0; i<week.length; i++ )
    {
        if( i==5 or i==6 ) // Если у нас наконец выходой
            document.write('Уррра! Отдыхаем!') // радуемся
        else
            document.write('Работай негр! Луна ещё не взошла.') // грустим
    }
    

    Именно подобную разницу я и хочу осветить.

    ЗЫ проверим-с :)

  9. Denis пишет:

    Нормальная практичная статья, много народу переходит на js с того же перла, им проще будет

  10. max пишет:

    Denis
    Гы-гы, интересно действительно есть ли хоть один человек, который перешёл с перла на js )))

  11. Yadfewm пишет:

    max
    Гы-г) может он имел в виду что зная перл легко учить js благодаря схожему синтаксису языка.

    На счет статьи: полезна, но очень мутит голову «Хэш», сразу хочется узнать о новом перспективном объекте в js Ыыыы))

  12. Denius пишет:

    Я, являясь разработчиком на Perl, иногда использую JS для разработки www-интерфейсов. До этой статьи я знал об ассоциативных массивах, но только после этой стати у меня в голове все встало окончательно на свои места. Автору огромное спасибо за данную статью и принятый в ней подход. Гуру JS может и более правы, но на то они и гуру, что отлично знают свой язык и считают, что людей, начинающих его изучать или использующих по случаю нет и быть не может. Думаю, что таким людям данная статья не нужна и не предназначена. А вот для остальных разработчиков все изложено довольно понятно и легко. Еще раз моя благодарность.

Оставить комментарий