Название книги в оригинале: Флэнаган Дэвид. JavaScript. Подробное руководство, 6-е издание

A- A A+ White background Book background Black background

На главную » Флэнаган Дэвид » JavaScript. Подробное руководство, 6-е издание.





Читать онлайн JavaScript. Подробное руководство, 6-е издание. Флэнаган Дэвид.









Дэвид Флэнаган

JavaScript. Подробное руководство, 6-е издание

 Сделать закладку на этом месте книги

Эта книга посвящается всем, кто учит жить в мире и противостоит насилию.

Предисловие

 Сделать закладку на этом месте книги

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

Книга делится на четыре части. Часть I охватывает сам язык JavaScript. Часть II охватывает клиентский JavaScript: прикладные программные интерфейсы JavaScript, определяемые стандартом HTML5 и сопутствующими ему стандартами и реализованные в веб-броузерах. Часть III представляет собой справочник по базовому языку, а часть IV - справочник по клиентскому JavaScript. Глава 1 включает краткий обзор глав первой и второй частей книги (раздел 1.1).

Это шестое издание книги охватывает стандарты ECMAScript 5 (последняя версия спецификации базового языка) и HTML5 (последняя версия спецификации веб-платформы). Положения стандарта ECMAScript 5 будут рассматриваться на протяжении всей первой части. Нововведения, появившиеся в HTML5, в основном будут обсуждаться в конце части II, но мы будем рассматривать их и в других главах. Совершенно новыми в этом издании являются глава 11 «Подмножества и расширения JavaScript», глава 12 «Серверный JavaScript», глава 19 «Библиотека jQuery» и глава 22 «Прикладные интерфейсы HTML5».

Читатели предыдущих изданий могут заметить, что в этом издании я полностью переписал многие главы. Главы первой части книги, посвященные основам языка и охватывающие объекты, массивы, функции и классы, были переписаны заново и приведены в соответствие с современными приемами программирования. Ключевые главы второй части, описывающие документы и события, точно так же были полностью переписаны, чтобы привести их к современному уровню.



Несколько слов о пиратстве

Если вы читаете электронную версию этой книги, за которую вы (или ваш работодатель) ничего не платили (или позаимствовали ее у третьего лица, не заплатившего за книгу), то, скорее всего, эта копия является пиратской. Работа над шестым изданием продолжалась более года, и мне приходилось трудиться над книгой полный рабочий день в течение всего этого времени. Оплату за свой труд я получаю, лишь когда кто-то покупает эту книгу. И единственным источником дохода, который позволит мне продолжить работу над седьмым изданием, является гонорар от продажи шестого издания.

Я не приветствую пиратство, но, если вы читаете пиратскую копию, прочитайте несколько глав. Это позволит вам убедиться, что данная книга является ценным источником информации о JavaScript, лучше организованным и более качественным, чем бесплатные (и законные) источники информации, доступные в Веб. Если вы согласитесь с тем, что эта книга является ценным источником информации, пожалуйста, заплатите за эту ценность, приобретя легальную копию книги (электронную или бумажную). Если же вы посчитаете, что эта книга ничуть не лучше открытых источников информации в Веб, пожалуйста, уничтожьте пиратскую копию и пользуйтесь открытыми источниками информации.



Типографские соглашения

В этой книге приняты следующие типографские соглашения:

Курсив 

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

Моноширинный шрифт

Применяется для форматирования программного кода на языке JavaScript, листингов CSS и HTML и вообще всего, что непосредственно набирается на клавиатуре при программировании.

Моноширинный курсив  

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



Использование программного кода примеров

Примеры для этой книги доступны в электронном виде. Соответствующие ссылки можно найти на странице книги на веб-сайте издательства:

https://oreilly.com/catalog/9780596805531/

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

Если вы соберетесь использовать программный код из этой книги, я приветствую, но не требую добавлять ссылку на первоисточник при цитировании. Под ссылкой подразумевается указание авторов, издательства и ISBN. Например: «JavaScript: The Definitive Guide, by David Flanagan (O’Reilly). Copyright 2011 David Flanagan, 978-0-596-80552-4».

Дополнительную информацию о порядке использования программного кода примеров можно найти на странице https://oreilly.eom/pub/a/oreilly/ask_tim/2001/code-policy.html. За получением разрешения на использование значительных объемов программного кода примеров из этой книги обращайтесь по адресу [email protected] oreilly.com.



Ошибки и контактная информация

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

https://oreilly.com/catalog/9780596805531

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

[email protected]

Дополнительную информацию о книгах, обсуждения, Центр ресурсов издательства O’Reilly вы найдете на веб-сайте:

https://www.oreilly.com

Можно найти нас на сайте Facebook: https://facebook.com/oreilly Следить за последними новостями на сайте Twitter: https://twitter.com/oreillymedia.

Просматривать видеоматериалы на сайте YouTube: https://www.youtube.com/oreillymedia.



Благодарности

Работая над этой книгой, я получал помощь от многих людей. Я хотел бы поблагодарить моего редактора, Майка Лукидеса (Mike Loukides), который старался удержать меня в рамках планов и вносил полезные комментарии. Спасибо также моим техническим редакторам: Захару Кессину (Zachary Kessin), просмотревшему многие главы в первой части, и Рафаэлю Цекко (Raffaele Сессо), который редактировал главу 19 и материал с описанием тега <canvas> в главе 21. Как обычно, блестяще справился со своей работой производственный отдел издательства O’Reilly: Дэн Фокссмит (Dan Fauxsmith), руководивший производственным процессом, Тереза Элси (Teresa Elsey), выполнявшая литературную правку, Роб Романо (Rob Romano), готовивший иллюстрации, и Элен Тротман Цайг (Ellen Troutman Zaig), создавшая алфавитный указатель.

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

В работе над предыдущими изданиями книги принимали участие следующие редакторы и рецензенты: Эндрю Шульман (Andrew Schulman), Анжело Сиригос (Angelo Sirigos), Аристотель Пагальцис (Aristotle Pagaltzis), Брендан Эйх (Brendan Eich), Кристиан Хейльманн (Christian Heilmann), Дэн Шейфер (Dan Shafer), Дэйв С. Митчелл (Dave С. Mitchell), Деб Камерон (Deb Cameron), Дуглас Крокфорд (Douglas Crockford), д-р Танкред Хиршманн (Dr. Tankred Hirschmann), Дилан Піймай (Dylan Schiemann), Френк Уиллисон (Frank Willison), Джефф Штерне (Geoff Stearns), Герман Вентер (Herman Venter), Джей Ходжес (Jay Hodges), Джефф Яте (Jeff Yates), Джозеф Кесселман (Joseph Kesselman), Кен Купер (Ken Cooper), Ларри Салливан (Larry Sullivan), Линн Роллинс (Lynn Rollins), Нил Беркман (Neil Berk-man), Ник Томпсон (Nick Thompson), Норрис Бойд (Norris Boyd), Паула Фергюсон (Paula Ferguson), Питер-Пауль Кох (Peter-Paul Koch), Филипп Ле Хегарет (Philippe Le Hegaret), Ричард Якер (Richard Yaker), Сандерс Клейнфельд (Sanders Kleinfeld), Скотт Фурман (Scott Furman), Скотт Иссакс (Scott Issacs), Шон Каценбергер (Shon Katzenberger), Терри Аллен (Terry Allen), Тодд Дихендорф (Todd Ditchendorf), Вайдур Annapao (Vidur Apparao) и Валдемар Хорват (Waldemar Horwat).

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


Дэвид Флэнаган (https://www.davidflanagan.com), март 2011


Глава 1.

Введение в JavaScript

 Сделать закладку на этом месте книги

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

Если вы знаете другие языки программирования, вам может оказаться полезна информация, что JavaScript является высокоуровневым, динамическим, нетипизированным и интерпретируемым языком программирования, который хорошо подходит для программирования в объектно-ориентированном и функциональном стилях. Свой синтаксис JavaScript унаследовал из языка Java, свои первоклассные функции - из языка Scheme, а механизм наследования на основе прототипов - из языка Self. Но вам не требуется знать все эти языки или быть знакомыми с их терминологией для чтения этой книги и изучения JavaScript.

Название языка «JavaScript» может вводить в заблуждение. За исключением поверхностной синтаксической схожести, JavaScript полностью отличается от языка программирования Java. JavaScript давно перерос рамки языка сценариев, превратившись в надежный и эффективный универсальный язык программирования. Последняя версия языка (смотрите врезку) определяет множество новых особенностей, позволяющих использовать его для разработки крупномасштабного программного обеспечения.



JavaScript: названия и версии

JavaScript был создан в компании Netscape на заре зарождения Веб. Название «JavaScript» является торговой маркой, зарегистрированной компанией Sun Microsystems (ныне Oracle), и используется для обозначения реализации языка, созданной компанией Netscape (ныне Mozilla). Компания Netscape представила язык для стандартизации европейской ассоциации производителей компьютеров ЕСМА (European Computer Manufacturer’s Association), но из-за юридических проблем с торговыми марками стандартизованная версия языка получила несколько неуклюжее название «ECMAScript». Из-за тех же юридических проблем версия языка от компании Microsoft получила официальное название «JScript». Однако на практике все эти реализации обычно называют JavaScript. В этой книге мы будем использовать название «ECMAScript» только для ссылки на стандарт языка.

В течение прошлого десятилетия все веб-броузеры предоставляли реализацию версии 3 стандарта ECMAScript, и в действительности разработчикам не было необходимости задумываться о номерах версий: стандарт языка был стабилен, а его реализации в веб-броузерах в значительной мере были совместимыми. Недавно вышла новая важная версия стандарта языка под названием ECMAScript 5, и к моменту написания этих строк производители броузеров приступили к созданию его реализации. Эта книга охватывает все нововведения, появившиеся в ECMAScript 5, а также все особенности, предусмотренные стандартом ECMAScript 3. Иногда вам будут встречаться названия этих версий языка, сокращенные до ES3 и ES5, а также название JavaScript, сокращенное до JS.

Когда речь заходит непосредственно о самом языке, при этом подразумеваются только версии 3 и 5 стандарта ECMAScript. (Четвертая версия стандарта ECMAScript разрабатывалась много лет, но из-за слишком амбициозных целей так и не была выпущена.) Однако иногда можно встретить упоминание о версии JavaScript, например: JavaScript 1.5 или JavaScript 1.8. Эти номера версий присваивались реализациям JavaScript, выпускаемым компанией Mozilla, причем версия 1.5 соответствует базовому стандарту ECMAScript 3, а более высокие версии включают нестандартные расширения (подробнее об этом рассказывается в главе 11). Наконец, номера версий также присваиваются отдельным интерпретаторам, или «механизмам» JavaScript. Например, компания Google разрабатывает свой интерпретатор JavaScript под названием V8, и к моменту написания этих строк текущей версией механизма V8 была версия 3.0.


***

Чтобы представлять хоть какой-то интерес, каждый язык программирования должен иметь свою платформу, или стандартную библиотеку, или API функций для выполнения таких базовых операций, как ввод и вывод. Ядро языка JavaScript определяет минимальный прикладной интерфейс для работы с текстом, массивами, датами и регулярными выражениями, но в нем отсутствуют операции ввода-вывода. Ввод и вывод (а также более сложные возможности, такие как сетевые взаимодействия, сохранение данных и работа с графикой) перекладываются на «окружающую среду», куда встраивается JavaScript. Обычно роль окружающей среды играет веб-броузер (однако в главе 12 мы увидим два примера использования JavaScript без привлечения веб-броузера). Первая часть этой книги охватывает сам язык JavaScript и его минимальный прикладной интерфейс. Вторая часть описывает использование JavaScript в веб-броузерах и охватывает прикладной интерфейс, предоставляемый броузерами, который иногда называют «клиентским JavaScript».

Третья часть книги представляет собой справочник по базовому API языка. Например, чтобы ознакомиться с прикладным интерфейсом JavaScript для работы с массивами, вы можете отыскать и прочитать раздел «Array» в этой части книги. Четвертая часть - это справочник по клиентскому JavaScript. Например, чтобы ознакомиться с прикладным интерфейсом JavaScript для работы с графикой, определяемым стандартом HTML5 для тега <canvas>, можно отыскать и прочитать раздел «Canvas» в четвертой части книги.

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


Исследование JavaScript

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

Далее в этой главе мы увидим, что код на языке JavaScript можно встраивать в HTML-файлы, в теги <script>, и при загрузке HTML-файла этот код будет выполняться броузером. К счастью, нам не требуется поступать так всякий раз, когда нужно опробовать короткий фрагмент программного кода JavaScript. Появление мощного и оригинального расширения Firebug для Firefox (изображено на рис. 1.1 и доступно для загрузки на сайте https://getfirebug.com/) подтолкнуло производителей веб-броузеров к включению в них инструментов веб-разработчика, необходимых для отладки, проверки и изучения. Обычно эти инструменты можно отыскать в меню Tools (Инструменты или Сервис) броузера в виде пункта Developer Tools (Средства разработчика) или Web Console (Веб-консоль). (Броузер Firefox 4 включает собственный встроенный инструмент Web Console, но к настоящему моменту расширение Firebug обладает более широкими возможностями.) Как правило, консоль можно запустить нажатием горячей комбинации клавиш, такой как F12 или Ctrl-Shift-J. Обычно эти инструменты открываются в виде отдельной панели в верхней или нижней части окна броузера, но некоторые броузеры открывают их в отдельном окне (как изображено на рис. 1.1), что часто более удобно.

Панель или окно типичного «инструмента разработчика» включает множество вкладок, позволяющих исследовать структуру HTML-документа, стили CSS, наблюдать за выполнением сетевых запросов и т. д. Среди них имеется вкладка JavaScript console (Консоль JavaScript), где можно вводить строки программного кода JavaScript и выполнять их. Это самый простой способ поэкспериментировать с JavaScript, и я рекомендую использовать его во время чтения этой книги.

В современных броузерах имеется простой переносимый API консоли. Для вывода текста в консоль можно использовать функцию console.log(). Зачастую такая возможность оказывается удивительно полезной при отладке, и некоторые примеры из этой книги (даже в разделе, посвященном базовому языку) используют console.log() для вывода простого текста. Похожий, но более навязчивый способ вывода информации или отладочных сообщений заключается в передаче строки текста функции alert(), которая отображает его в окне модального диалога.




1.1 Базовый JavaScript

 Сделать закладку на этом месте книги

Этот раздел представляет собой обзор языка JavaScript, а также обзор первой части этой книги. После этой вводной главы мы опустимся на самый нижний уровень JavaScript: в главе 2 «Лексическая структура» будут описаны основные лексические конструкции JavaScript, такие как комментарии, точки с запятой и набор символов Юникода. В главе 3 «Типы данных, значения и переменные» мы начнем рассматривать более интересные темы: здесь будут описаны переменные JavaScript и значения, которые можно присваивать этим переменным. Ниже приводится пример программного кода, иллюстрирующий предмет обсуждения этих двух глав:


// Все, что следует за двумя символами слэша, является комментарием.

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


// Переменная - это символическое имя некоторого значения.

// Переменные объявляются с помощью ключевого слова var:

var х; // Объявление переменной с именем х.


// Присваивать значения переменным можно с помощью знака =

х = 0;             // Теперь переменная х имеет значение 0

х                  // => 0: В выражениях имя переменной замещается ее значением.


// JavaScript поддерживает значения различных типов

х = 1;             // Числа.

х = 0.01;          // Целые и вещественные числа представлены одним типом,

х = "hello world"; // Строки текста в кавычках,

х = 'JavaScript';  // Строки можно также заключать в апострофы,

х = true;          // Логические значения,

х = false;         // Другое логическое значение.

х = null;          // null - особое значение, обозначающее "нет значения",

х = undefined;     // Значение undefined подобно значению null.


Двумя другими очень важными типами данных, которыми могут манипулировать программы на JavaScript, являются объекты и массивы. Они будут рассматриваться в главе 6 «Объекты» и в главе 7 «Массивы» однако они настолько важны, что вы не раз встретитесь с ними, прежде чем дойдете до этих глав.


// Наиболее важным типом данных в JavaScript являются объекты.

// Объект - это коллекция пар имя/значение или отображение строки в значение.

var book = {           // Объекты заключаются в фигурные скобки.

  topic: "JavaScript", // Свойство "topic" имеет значение "JavaScript",

  fat: true            // Свойство "fat" имеет значение true.

};                     // Фигурная скобка отмечает конец объекта.


// Доступ к свойствам объектов выполняется с помощью . или []:

book.topic                // => "JavaScript"

book["fat"]               // => true: другой способ получить значение свойства,

book.author = "Flanagan": // Создать новое свойство присваиванием,

book.contents = {};       // {} - пустой объект без свойств.


// JavaScript поддерживает массивы (списки с числовыми индексами) значений:

var primes = [2, 3, 5, 7]; // Массив из 4 значений, ограничивается [ и ].

primes[0]                  // => 2: первый элемент (с индексом 0) массива,

primes.length              // => 4: количество элементов в массиве.

primes[primes.length-1]    // => 7: последний элемент массива.

primes[4] = 9:             // Добавить новый элемент присваиванием.

primes[4] = 11;            // Или изменить значение имеющегося элемента.

var empty = [];            // [] - пустой массив без элементов.

empty.length               // => О


// Массивы и объекты могут хранить другие массивы и объекты:

var points = [             // Массив с 2 элементами.

  {х:0, у:0},              // Каждый элемент - это объект.

  {х: 1, у: 1}

];

var data = { // Объект с 2 свойствами

  triall: [[1.2], [3.4]],  // Значение каждого свойства - это массив.

  trial2: [[2,3]. [4,5]]   // Элементами массива являются массивы.

};


Синтаксические конструкции, представленные выше и содержащие списки элементов массивов в квадратных скобках или отображения свойств объектов в значения внутри фигурных скобок, часто называют выражениями инициализации, которые будут рассматриваться в главе 4 «Выражения и операторы». Выражение - это фраза на языке JavaScript, которую можно вычислить, чтобы получить значение. Например, применение . и [ ] для ссылки на значение свойства объекта или элемента массива является выражением. Возможно, вы заметили, что в листинге, приведенном выше, в строках, содержащих только выражение, комментарии начинаются со стрелки (=>), за которой следует значение выражения. Этому соглашению мы будем следовать на протяжении всей книги.

Наиболее типичным способом формирования выражений в JavaScript является использование операторов, подобно тому, как показано ниже:


// Операторы выполняют действия со значениями (операндами) и воспроизводят

// новое значение. Наиболее часто используемыми являются арифметические операторы:

3+2      // => 5: сложение

3-2      // => 1: вычитание

3*2      // => 6: умножение

3/2      // => 1.5: деление

points[1].x - points[0].x // => 1: можно использовать более сложные операнды

"3" + "2"                 // => "32": + складывает числа, объединяет строки


// В JavaScript имеются некоторые сокращенные формы арифметических операторов

var count = 0; // Объявление переменной

count++;       // Увеличение значения переменной на 1

count--;       // Уменьшение значения переменной на 1

count += 2;    // Добавить 2: то же, что count = count + 2;

count *= 3;    // Умножить на 3: то же, что count = count * 3;

count          // => 6: имена переменных сами являются выражениями


// Операторы сравнения позволяют проверить два значения на равенство

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

// Они возвращают значение true или false.

var х = 2, у = 3; // Знаки = выполняют присваивание, а не сравнение

x == y            // => false: равенство

x != y            // => true: неравенство

x < y             // => true: меньше

x <= y            // => true: меньше или равно

x > y             // => false: больше

x >= y            // => false: больше или равно

"two" == "three"  // => false: две разных строки

"two" > "three"   // => true: при упорядочении по алфавиту строка "tw” больше, чем "th"

false == (х > у)  // => true: false равно false


// Логические операторы объединяют или инвертируют логические значения

(х == 2) && (у == 3) // => true: оба сравнения истинны. && - "И"

(х > 3) || (у < 3)   // => false: оба сравнения ложны. || - "ИЛИ"

!(х == у)            // => true: ! инвертирует логическое значение


Если фразы в языке JavaScript называются выражениями, то полные предложения называются инструкциями ; они рассматриваются в главе 5 «Инструкции». В программном коде, приведенном выше, строки, заканчивающиеся точками с запятой, являются инструкциями. (В примере ниже можно


убрать рекламу






увидеть инструкции, состоящие из нескольких строк, которые не завершаются точками с запятой.) Между инструкциями и выражениями много общего. Грубо говоря, выражение - это конструкция, которая вычисляет значение, но ничего не делает: она никак не изменяет состояние программы. Инструкции, напротив, не имеют значения (или их значение не представляет интереса для нас), но они изменяют состояние программы. Выше уже были показаны инструкции объявления переменных и присваивания значений. Еще одной обширной категорией инструкций являются управляющие конструкции, такие как условные инструкции и инструкции циклов. Примеры этих инструкций будут показаны далее, после того, как мы познакомимся с функциями.

Функция  - это именованный и параметризованный блок программного кода JavaScript, который определяется один раз, а использоваться может многократно. Формальное знакомство с функциями мы отложим до главы 8 «Функции», однако, как и в случае с объектами и массивами, мы много раз встретимся с функциями, прежде чем доберемся до этой главы. Ниже приводятся несколько примеров простых функций:


// Функции - это параметризованные блоки программного кода JavaScript,

// которые можно вызывать многократно.

function plus1(x) { // Определить функцию с именем "plusr и с параметром "х"

  return х+1;       // Вернуть значение на 1 больше полученного

}                   // Функции заключаются в фигурные скобки


plus1(y) // => 4: у имеет значение 3, поэтому этот вызов вернет 3+1


var square = function(x) { // Функции можно присваивать переменным

  return х*х;              // Вычислить значение функции

};                         // Точка с запятой отмечает конец присваивания.


square(plus1(y))           // => 16: вызов двух функций в одном выражении


При объединении функций с объектами получаются методы.


// Функции, присвоенные свойствам объектов, называются методами.

// Все объекты в JavaScript имеют методы:

var а = [];    // Создать пустой массив

а.push(1,2,3); // Метод push() добавляет элементы в массив

a.reverse();   // Другой метод: переставляет элементы в обратном порядке


// Можно определять собственные методы. Ключевое слово "this" ссылается на объект,

// в котором определен метод: в данном случае на массив points,

points.dist = function() { // Метод вычисления расстояния между точками

var р1 = this[0];      // Первый элемент массива, относительно которого вызван метод

var р2 = this[1];      // Второй элемент объекта "this"

var а = р2.х-р1.х;     // Разность координат X

var b = р2.у-р1.у;     // Разность координат Y

return Math.sqrt(a*a + // Теорема Пифагора

                 b*b); // Math.sqrtO вычисляет корень квадратный

};

points.dist() // => 1.414: расстояние между 2-мя точками


Теперь, как было обещано, рассмотрим несколько функций, которые демонстрируют применение наиболее часто используемых управляющих инструкций JavaScript:


// В JavaScript имеются условные инструкции и инструкции циклов, синтаксически

// похожие на аналогичные инструкции С, C++, Java и в других языках.

function abs(x) {  // Функция, вычисляющая абсолютное значение

  if (х >= 0) {    // Инструкция if ...

    return х;      // выполняет этот код, если сравнение дает true.

  }                // Конец предложения if.

  else {           // Необязательное предложение else выполняет свой код,

    return -x;     // если сравнение дает значение false.

  }                // Фигурные скобки можно опустить, если предложение

                   // содержит 1 инструкцию.

}                  // Обратите внимание на инструкции return внутри if/else.


function factorial(n) { // Функция, вычисляющая факториал

  var product = 1;      // Начать с произведения, равного 1

  while(n > 1) {        // Повторять инструкции в {}, пока выраж. в () истинно

    product *= n;       // Сокращенная форма выражения product = product * n;

    n--;                // Сокращенная форма выражения n = п - 1

  }                     // Конец цикла

  return product;       // Вернуть произведение

}

factorial(4)            // => 24: 1*4*3*2


function factorial2(n) { // Другая версия, использующая другой цикл

  var і, product = 1;    // Начать с 1

  for(i=2; і <= n; i++)  // і автоматически увеличивается с 2 до n

    product *= i;        // Выполнять в каждом цикле. {} можно опустить,

                         // если тело цикла состоит из 1 инструкции

  return product;        // Вернуть факториал

}

factorial2(5)            // => 120: 1*2*3*4*5


JavaScript - объектно-ориентированный язык, но используемая в нем объектная модель в корне отличается от модели, используемой в большинстве других языков. В главе 9 «Классы и модули» детально рассматривается объектно-ориентированное программирование на языке JavaScript на большом количестве примеров; эта глава является одной из самых больших в книге. Ниже приводится очень простой пример, демонстрирующий определение класса JavaScript для представления точек на плоскости. Объекты, являющиеся экземплярами этого класса, обладают единственным методом с методом r(), который вычисляет расстояние между данной точкой и началом координат:


// Определение функции-конструктора для инициализации нового объекта Point

function Point(x,y) { // По соглашению имя конструкторов начинается с заглавного символа

  this.x = x;         // this - ссылка на инициализируемый объект

  this.у = у;         // Сохранить аргументы в свойствах объекта

} // Ничего возвращать не требуется


// Чтобы создать новый экземпляр, необходимо вызвать функцию-конструктор

// с ключевым словом "new"

var р = new Point(1, 1); // Точка на плоскости с координатами (1,1)


// Методы объектов Point определяются за счет присваивания функций свойствам

// объекта-прототипа, ассоциированного с функцией-конструктором.

Point.prototype.r = function() {

  return Math.sqrt( // Вернуть корень квадратный от x2 + y2

          this.x * this.x + // this - это объект Point, относительно которого...

          this.у * this.у   // ...вызывается метод.

  );

};


// Теперь объект p типа Point (и все последующие объекты Point) наследует метод r()

p.r()       // => 1.414...


Глава 9 является кульминацией первой части, а главы, которые следуют за ней, связывают некоторые оборванные концы и завершают исследование базового языка. В главе 10 «Шаблоны и регулярные выражения» описывается грамматика регулярных выражений и демонстрируются приемы использования регулярных выражений для реализации сопоставления с текстовыми шаблонами. В главе 11 «Подмножества и расширения JavaScript» рассматриваются подмножества и расширения базового языка JavaScript. Наконец, прежде чем перейти к исследованию клиентского JavaScript в веб-броузерах, в главе 12 «Серверный JavaScript» будут представлены два способа использования JavaScript за пределами веб-броузеров.


1.2. Клиентский JavaScript

 Сделать закладку на этом месте книги

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

Глава 13 «JavaScript в веб-броузерах» является первой главой второй части, в которой описываются детали использования JavaScript в веб-броузерах. Самое важное, что вы узнаете в этой главе, - программный код JavaScript может встраиваться в HTML-файлы с помощью тега <script>:


<html>

  <head>

    <script src="library.js"></script> <!-- подключить библиотеку JavaScript -->

  </head>

  <body>

    <р>Это абзац HTML</p>

    <script>

      // Это некоторый программный код на клиентском JavaScript,

      // встроенный непосредственно в HTML-файл

    </script>

    <р>Далее опять следует разметка HTML.</р>

  </body>

</html>


Глава 14 «Объект Window» исследует приемы управления веб-броузером и описывает некоторые наиболее важные глобальные функции клиентского JavaScript. Например:


<script>

function moveon() {

  // Вывести модальный диалог, чтобы получить ответ пользователя

  var answer = confirm("Ready to move on?");


    // Если пользователь щелкнул на кнопке "OK", заставить броузер загрузить новую страницу

    if (answer) window.location = "https://google.com";

  }


  // Запустить функцию, объявленную выше, через 1 минуту (60000 миллисекунд).

  setTimeout(moveon, 60000);

</script>


Обратите внимание, что примеры программного кода на клиентском JavaScript в этом разделе длиннее примеров на базовом языке, которые мы видели выше в этой главе. Эти примеры не предназначены для ввода в окне консоли Firebug (или в другом подобном инструменте). Однако вы можете вставлять их в HTML-файлы и затем запускать, открывая файлы в веб-броузере. Так, пример, приведенный выше, является самостоятельным HTML-файлом.

Глава 15 «Работа с документами» переходит к исследованию фактической работы, выполняемой с помощью JavaScript на стороне клиента, - управлению содержимым документа HTML. Она покажет вам, как выбирать определенные элементы HTML из документов, как устанавливать HTML-атрибуты этих элементов, как изменять содержимое элементов и как добавлять в документ новые элементы. Следующая функция демонстрирует некоторые из простейших приемов поиска и изменения элементов документа:


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

// Если документ не содержит такой области, она создается.

function debug(msg) {

  // Отыскать область для отладочных сообщений в документе, поиск по HTML-атрибуту id

  var log = document.getElementByld("debuglog");


  // Если элемент с атрибутом id="debuglog" отсутствует, создать его.

  if (!log) {

    log = document.createElementC'div"); // Создать элемент <div>

    log.id = "debuglog"; // Установить атрибут id

    log.innerHTML = "<h1>Debug Log</h1>"; // Начальное содержимое

    document.body.appendChild(log); // Добавить в конец документа

  }


  // Теперь обернуть сообщение в теги <рге> и добавить в элемент log

  var рге = document.createElement("pre"); // Создать тег <рге>

  var text = document.createTextNode(msg); // Обернуть msg в текстовый узел

  pre.appendChild(text); // Добавить текст в тег <рге>

  log.appendChild(pre);  // Добавить <рге> в элемент log

}


Глава 15 демонстрирует, как с помощью JavaScript можно управлять HTML-элементами, которые определяют содержимое веб-страниц. Глава 16 «Каскадные таблицы стилей» демонстрирует, как с помощью JavaScript можно управлять каскадными таблицами стилей CSS, определяющими представление содержимого. Чаще всего для этой цели используется атрибут HTML-элементов style или class:


function hide(e, reflow) { // Скрывает элемент e, изменяя его стиль

  if (reflow) {// Если 2-й аргумент true,

    e.style.display = "none" // скрыть элемент и использовать

  } // занимаемое им место

  else { // Иначе

    e.style.visibility = "hidden"// сделать е невидимым, но оставить

  } // занимаемое им место пустым

}


function highlight(е) { // Выделяет е, устанавливая класс CSS

  // Просто добавляет или переопределяет HTML-атрибут class.

  // Предполагается, что таблица стилей CSS уже содержит определение класса "hilite"

  if (!е.className)

    e.className = "hilite";

  else

    e.className += " hilite";

}


JavaScript позволяет не только управлять содержимым и оформлением HTML-документов в броузерах, но и определять поведение этих документов с помощью обработчиков событий. Обработчик событий - это функция JavaScript, которая регистрируется в броузере и вызывается броузером, когда возникает событие определенного типа. Таким событием может быть щелчок мышью или нажатие клавиши (или какое-то движение двумя пальцами на экране смартфона). Обработчик события может также вызываться броузером по окончании загрузки документа, при изменении размеров окна броузера или при вводе данных в элемент HTML-формы. Глава 17 «Обработка событий» описывает, как определять и регистрировать обработчики событий и как вызываются эти обработчики при появлении событий.

Простейший способ объявления обработчиков событий заключается в использовании HTML-атрибутов, имена которых начинаются с приставки «оп». Обработчик «onclick» особенно удобен при создании простых тестовых программ. Предположим, что вы сохранили функции debug() и hide(), представленные выше, в файлах с именами debug.js и hide.js. В этом случае можно было бы написать простой тестовый HTML-файл, использующий элементы <button> с атрибутами onclick, определяющими обработчики событий:


<script src="debug.js"></script>

<script src="hide. js"x/script>

Hello

<button onclick="hide(this,true); debug('hide button 1'); ">Hide1</button>

<button onclick="hide(this); debug('hide button 2');">Hide2</button>

World


Ниже приводится еще один пример программного кода на клиентском JavaScript, использующего механизм событий. Он регистрирует обработчик очень важного события «load» и дополнительно демонстрирует более сложный способ регистрации обработчика события «click»:


// Событие "load" возбуждается, когда документ будет полностью загружен.

// Обычно мы вынуждены ждать этого события, прежде чем можно будет запустить

// наш программный код JavaScript.

window.onload = function() { // Запустит функцию после загрузки документа


// Отыскать все теги <img> в документе

var images = document.getElementsByTagName("img");


// Обойти их все в цикле, добавить к каждому обработчик события "click",

// чтобы обеспечить сокрытие любого изображения после щелчка на нем.

for(var і = 0; і < images.length; i++) {

  var image = images[i];

  if (image.addEventListener) // Другой способ регистрации обработчика

    image.addEventListener("click", hide, false);

  else // Для совместимости с версией IE8 и ниже

    image.attachEvent("onclick", hide);

}

// Это функция-обработчик событий, которая регистрируется выше

function hide(event) { event.target.style.visibility = "hidden"; }


Главы 15, 16 и 17 описывают, как с помощью JavaScript управлять содержимым (HTML), представлением (CSS) и поведением (обработка событий) веб-страниц. Прикладной интерфейс, описываемый в этих главах, является достаточно сложным, и до недавнего времени испытывал проблемы с совместимостью между броузерами. По этим причинам многие или большинство программистов на клиентском JavaScript предпочитают использовать клиентские библиотеки или фреймворки, упрощающие программирование. Наиболее популярна из этих библиотек - библиотека jQuery, которая обсуждается в главе 19 «Библиотека jQuery». Библиотека jQuery определяет простой и удобный программный интерфейс для управления содержимым документа, его представлением и поведением. Она была тщательно протестирована и может использоваться во всех основных броузерах, включая довольно старые, такие как IE6.

Программный код, использующий jQuery, легко отличить по частому использованию функции $(). Ниже показано, как будет выглядеть функция debug(), представленная выше, если переписать ее с использованием jQuery:


function debug(msg) {

  var log = $("#debuglog"); // Отыскать элемент для вывода msg.

  if (log.length == 0) { // Если отсутствует, создать его...

    log = $("<div id='debuglog'><h1>Debug Log</h1></div>");

    log.appendTo(document.body); // и вставить в конец тела документа.

  }

  log.append($("<pre/>").text(msg)); // Завернуть msg в тег <рге>

} // и добавить в элемент log


В этих четырех главах из второй части в действительности рассматривается все, что касается веб-страниц. Другие четыре главы переключают внимание на вебприложения. Эти главы не о том, как использовать веб-броузеры для отображения документов, содержимое, представление и поведение которых управляется с помощью JavaScript. Они рассказывают об использовании веб-броузеров как прикладной платформы и описывают прикладной интерфейс, предоставляемый современными броузерами для поддержки сложных, современных клиентских веб-приложений. Глава 18 «Работа с протоколом HTTP» описывает, как с помощью JavaScript можно управлять HTTP-запросами - своего рода сетевой прикладной интерфейс. Глава 20 «Сохранение данных на стороне клиента» описывает механизмы, позволяющие сохранять данные (и даже целые приложения) на стороне клиента для использования в последующих сеансах работы. Глава 21 «Работа с графикой и медиафайлами на стороне клиента» охватывает клиентский прикладной интерфейс, позволяющий создавать произвольные графические изображения в HTML-теге <canvas>. И наконец, глава 22 «Прикладные интерфейсы HTML5» охватывает новые прикладные интерфейсы веб-приложений, определяемые или принятые стандартом HTML5. Сетевые взаимодействия, организация хранения данных, работа с графикой - все эти службы операционных систем, доступные посредством вебброузеров, образуют новую, платформонезависимую среду выполнения приложений. Если вы нацелены на броузеры, которые поддерживают эти новые прикладные интерфейсы, то сейчас наступает самое интересное время для программистов на клиентском JavaScript. Здесь не приводятся примеры программного кода из этих заключительных четырех глав, однако расширенный пример, представленный ниже, использует некоторые из этих новых прикладных интерфейсов.


1.2.1. Пример: калькулятор платежей по ссуде на JavaScript

 Сделать закладку на этом месте книги

Эта глава завершается расширенным примером, объединяющим в себе многие из описанных выше приемов и демонстрирующим полноценную программу на клиентском JavaScript (плюс HTML и CSS). В примере 1.1 представлена реализация простого калькулятора для вычисления платежей по ссуде (рис. 1.2).




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


• Поиск элементов в документе.

• Получение ввода пользователя с помощью элементов форм.

• Изменение содержимого элементов документа.

• Сохранение данных в броузере.

• Управление НТТР-запросами.

• Создание графики с помощью элемента <canvas>.


Пример 1.1. Калькулятор вычисления платежей по ссуде на JavaScript 


<!DOCTYPE html>

<html>

<head>

  <title>JavaScript Loan Calculator</title>

  <style> /* Таблица стилей CSS: определяет внешний вид вывода программы */

    .output { font-weight: bold; } /* Жирный шрифт для вычисленных значений */

    #payment { text-decoration: underline: } /* Для элементов с id="payment" */

    #graph { border: solid black 1px; } /* Простая рамка для диаграммы */

    th, td { vertical-align: top: } /* Выравнивание в ячейках таблицы */

  </style>

</head>

<body>

<! --

Это HTML-таблица с элементами <input>, позволяющими вводить данные, и с элементами <span>,

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

такие как "interest" и "years". Данные идентификаторы используются в JavaScript-коде,

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

элементов ввода определены обработчики событий "onchange" и "onclick".

В них заданы строки JavaScript-кода, выполняемого при вводе данных или щелчке на кнопке.

-->

<table>

  <tr>

    <th>Enter Loan Data:</th>

    <td></td>

    <th>Loan Balance, Cumulative Equity, and Interest Payments</th>

  </tr>

  <tr>

    <td>Amount of the loan ($):</td>

    <td><input id="amount" onchange=”calculate(); "></td>

    <td rowspan=8>

      <canvas id="graph" width="400" height="250"></canvas>

    </td>

  </tr>

  <tr>

    <td>Annual interest (%):</td>

    <td><input id="apr" onchange="calculate(); "></td>

  </tr>

  <tr>

    <td>Repayment period (years):</td>

    <td><input id="years" onchange="calculate(); "></td>

  </tr>

  <tr>

    <td>Zipcode (to find lenders):</td>

    <td><input id="zipcode" onchange="calculate(); "></td>

  <tr>

    <th>Approximate Payments:</th>

    <td><button onclick="calculate();">Calculate</button></td>

  </tr>

  <tr>

    <td>Monthly payment:</td>

    <td>$<span class="output" id="payment"></span></td>

  </tr>

  <tr>

    <td>Total payment:c/td>

    <td>$<span class="output" id="total"></span></td>

  </tr>

  <tr>

    <td>Total interest:c/td>

    <td>$<span class="output" id="totalinterest"></span></td>

  </tr>

  <tr>

    <th>Sponsors:c/th>

    <td colspan=2>

      Apply for your loan with one of these fine lenders:

      <div id="lenders"></div>

    </td>

  </tr>

</table>


<!-- Остальная часть примера - JavaScript-код в теге <script> ниже. Обычно сценарии -->

<!-- помещаются в начало документа, в заголовок <head>, но в данном случае вам проще -->

<!-- будет понять пример, если JavaScript-код будет находиться ниже HTML-содержимого. -->

<script>

"use strict"; // Использовать строгий режим ECMAScript 5, если броузер поддерживает его

/*

* Этот сценарий определяет функцию calculate(), вызываемую обработчиками событий

* в разметке HTML выше. Функция читает значения из элементов <input>, вычисляет размеры

* платежей по ссуде, отображает результаты в элементах <span>. Кроме того, она сохраняет

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

*/


function calculate() {

  // Отыскать элементы ввода и вывода в документе

  var amount = document.getElementById("amount");

  var apr = document.getElementByld("apr”);

  var years = document.getElementById("years");

  var zipcode = document.getElementById("zipcode");

  var payment = document.getElementById("payment");

  var total = document.getElementById("total");

  var to


убрать рекламу






talinterest = document.getElementById("totalinterest");


  // Получить ввод пользователя из элементов ввода. Предполагается, что все данные

  // являются корректными. Преобразовать процентную ставку из процентов

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

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

  var principal = parseFloat(amount.value);

  var interest = parseFloat(apr.value) / 100 / 12;

  var payments = parseFloat(years.value) * 12;


  // Теперь вычислить сумму ежемесячного платежа.

  var х = Math.pow(1 + interest, payments); // Math.pow() вычисляет степень

  var monthly = (principal * x * interest)/(x-1);


  // Если результатом является конечное число, следовательно, пользователь

  // указал корректные данные и результаты можно отобразить

  if (isFinite(monthly)) {

    // Заполнить поля вывода, округлив результаты до 2 десятичных знаков

    payment.innerHTML = monthly.toFixed(2);

    total.innerHTML = (monthly * payments).toFixed(2);

    totalinterest.innerHTML = ((monthly*payments)-principal).toFixed(2);


    // Сохранить ввод пользователя, чтобы можно было восстановить данные

    // при следующем открытии страницы

    save(amount.value, apr.value, years.value, zipcode.value);

    // Реклама: отыскать и отобразить ссылки на сайты местных

    // кредитных учреждений, но игнорировать сетевые ошибки

    try { // Перехватывать все ошибки, возникающие в этих фигурных скобках

      getLenders(amount.value, apr.value, years.value, zipcode.value);

    }

    catch(e) { /* И игнорировать эти ошибки */ }


    // В заключение вывести график изменения остатка по кредиту, а также

    // графики сумм, выплачиваемых в погашение кредита и по процентам

    chart(principal, interest, monthly, payments);

  }

  else {

    // Результат не является числом или имеет бесконечное значение,

    // что означает, что были получены неполные или некорректные данные.

    // Очистить все результаты, выведенные ранее,

    payment.innerHTML = "";

    // Стереть содержимое этих элементов total.innerHTML = totalinterest.innerHTML = "";

    chart(); // При вызове без аргументов очищает диаграмму

  }

}


// Сохранить ввод пользователя в свойствах объекта localStorage. Значения этих свойств

// будут доступны при повторном посещении страницы. В некоторых броузерах (например.

// в Firefox) возможность сохранения не поддерживается, если страница открывается

// с адресом URL вида file://. Однако она поддерживается при открытии страницы через HTTP.

function save(amount, apr, years, zipcode) {

  if (window.localStorage) { // Выполнить сохранение, если поддерживается

    localStorage.loan_amount = amount;

    localStorage.loan_apr = apr;

    localStorage.loan_years = years;

    localStorage.loan_zipcode = zipcode;

  }

}


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

window.onload = function() {

  // Если броузер поддерживает localStorage и имеются сохраненные данные

  if (window.localStorage && localStorage.loan_amount) {

    document.getElementById("amount").value = localStorage.loan_amount;

    document.getElementById("apr").value = localStorage.loan_apr;

    document.getElementById("years").value = localStorage.loan_years;

    document.getElementById("zipcode").value = localStorage.loan_zipcode;

  }

};


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

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

// Данный пример не включает фактическую реализацию такого сценария поиска кредитных

// учреждений. Но если такой сценарий уже имеется, данная функция могла бы работать с ним.

function getLenders(amount, apr, years, zipcode) {

  // Если броузер не поддерживает объект XMLHttpRequest, не делать ничего

  if (!window.XMLHttpRequest) return;


  // Отыскать элемент для отображения списка кредитных учреждений

  var ad = document.getElementById("lenders”);

  if (!ad) return; // Выйти, если элемент отсутствует


  // Преобразовать ввод пользователя в параметры запроса в строке URL

  var url = "getLenders.php" + // Адрес URL службы плюс

    "?amt=" + encodeURIComponent(amount) + // данные пользователя

    "&apr=" + encodeURIComponent(apr) + //в строке запроса

    "&yrs=" + encodeURIComponent(years) +

    "&zip=" + encodeURIComponent(zipcode);


  // Получить содержимое по заданному адресу URL с помощью XMLHttpRequest

  var req = new XMLHttpRequest(); // Создать новый запрос

  req.open("GET", url); // Указать тип запроса HTTP GET для url

  req.send(null); // Отправить запрос без тела


  // Перед возвратом зарегистрировать обработчик события, который будет вызываться

  // при получении HTTP-ответа от сервера. Такой прием асинхронного программирования

  // является довольно обычным в клиентском JavaScript,

  req.onreadystatechange = function() {

    if (req.readyState == 4 && req.status == 200) {

      // Если мы попали сюда, следовательно, был получен корректный НТТР-ответ

      var response = req.responseText; // HTTP-ответ в виде строки

      var lenders = JSON.parse(response); // Преобразовать в JS-массив


      // Преобразовать массив объектов lender в HTML-строку

      var list = "";

      for(var і = 0; і < lenders.length; i++) {

        list += "<li><a href=' " + lenders[i].url + " '>"+

          lenders[i].name + "</a>";

      }

      // Отобразить полученную HTML-строку в элементе,

      // ссылка на который была получена выше.

      ad.innerHTML = "<ul>" + list + "</ul>";

    }

  }

}


// График помесячного изменения остатка по кредиту, а также графики сумм,

// выплачиваемых в погашение кредита и по процентам в HTML-элементе <canvas>.

// Если вызывается без аргументов, просто очищает ранее нарисованные графики,

function chart(principal, interest, monthly, payments) {

  var graph = document.getElementById("graph"); // Ссылка на тег <canvas>

  graph.width = graph.width; // Магия очистки элемента canvas


  // Если функция вызвана без аргументов или броузер не поддерживает

  // элемент <canvas>, то просто вернуть управление,

  if (arguments.length == 0 || !graph.getContext) return;


  // Получить объект "контекста" для элемента <canvas>,

  // который определяет набор методов рисования

  var g = graph.getContext("2d"); // Рисование выполняется с помощью этого объекта

  var width = graph.width, height = graph.height; // Получить размер холста


  // Следующие функции преобразуют количество месячных платежей

  // и денежные суммы в пикселы

  function paymentToX(n) { return n * width/payments; }

  function amountToY(a) { return height-(a*height/(monthly*payments*1.05));}


  // Платежи - прямая линия из точки (0,0) в точку (payments,monthly*payments)

  g.moveTo(paymentToX(0), amountToY(0)); // Из нижнего левого угла

  g.lineTo(paymentToX(payments),         // В правый верхний

     amountToY(monthly*payments));

  g.lineTo(paymentToX(payments), amountToY(0)); // В правый нижний

  g.closePath();                         // И обратно в начало

  g.fillStyle = "#f88";                  // Светло-красный

  g.fill();                              // Залить треугольник

  g.font = "bold 12px sans-serif";       // Определить шрифт

  g.fillText("Total Interest Payments", 20,20); // Вывести текст в легенде


  // Кривая накопленной суммы погашения кредита не является линейной

  // и вывод ее реализуется немного сложнее

  var equity = 0;

  g.beginPath(); // Новая фигура

  g.moveTo(paymentToX(0), amountToY(O)); // из левого нижнего угла

  for(var р = 1; р <= payments; р++) {

    // Для каждого платежа выяснить долю выплат по процентам

    var thisMonthsInterest = (principal-equity)*interest;

    equity += (monthly - thisMonthsInterest);  // Остаток - погашение кред.

    g.lineTo(paymentToX(p),amountToY(equity)); // Линию до этой точки

  }

  g.lineTo(paymentToX(payments), amountToY(O)); // Линию до оси X

  g.closePath();                                // И опять в нач. точку

  g.fillStyle = "green";                        // Зеленый цвет

  g.fill();                                     // Залить обл. под кривой

  g.fillText("Total Equity", 20,35);            // Надпись зеленым цветом


  // Повторить цикл, как выше, но нарисовать график остатка по кредиту

  var bal = principal;

  g.beginPath();

  g.moveTo(paymentToX(O),amountToY(bal));

  for(var p = 1; p <= payments; p++) {

    var thisMonthsInterest = bal*interest;

    bal -= (monthly - thisMonthsInterest) //Остаток от погаш. по кредиту

    g.lineTo(paymentToX(p),amountToY(bal)); // Линию до этой точки

  }

  g.LineWidth = 3;     //Жирная линия

  g.stroke;            //Нарисовать кривую графика

  g.fillStle="black";  //Черный цвет для текста

  g.fillText("Loan Balabce", 20, 50); //Элемент легенды


  //Нарисовать отметки лет на оси Х

  g.textAling="center"; //Текст меток по центру

  var y = amounToY(0); //Координата Y на оси X

  for (var year=1;year*12 <= payments; year++) { //Для каждого года

    var x=paymentToX(year*12); //Вычислить позицию метки

    g.fillRect(x-0.5, y-3, 1, 3); //Нарисовать метку


    if (year == 1) g.fillText("Year", x, y-5); //  Подписать ось

    if (year % 5 == 0 && year*12 !== payments) // Числа через каждые 5 лет

              g.fillText(String(year), x, y-5);

  }


  //Суммы платежей у правой границы

  g.textAling="right";      //Текст по правому краю

  g.textBaseLine="middle";  //Центрировать по вертикали

  var ticks = [monthly*payments, principal]; //Вывести две суммы

  var rightEdge = paymentToX(payments);      //Координата X на оси Y

  for (var i = 0; i < ticks.Length; i++) {   //Для каждой из 2 сумм

    var y = amountToY(ticks[i]);              //Определить координату Y

    g.fillRect(rightEdge - 3, y - 0.5, 3, 1); //Нарисовать метку

    g.fillText(String(ticks[i].toFixed(0)),    //И вывести рядом сумму.

              rightEdge-5, y);

  }

}

</script>

</body>

</html>


I

Базовый JavaScript

 Сделать закладку на этом месте книги

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

• Глава 2 «Лексическая структура»

• Глава 3 «Типы данных, значения и переменные»

• Глава 4 «Выражения и операторы»

• Глава 5 «Инструкции»

• Глава 6 «Объекты»

• Глава 7 «Массивы»

• Глава 8 «Функции»

• Глава 9 «Классы и модули»

• Глава 10 «Шаблоны и регулярные выражения»

• Глава 11 «Подмножества и расширения JavaScript»

• Глава 12 «Серверный JavaScript»

2

Лексическая структура

 Сделать закладку на этом месте книги

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

2.1. Набор символов

 Сделать закладку на этом месте книги

При написании программ на JavaScript используется набор символов Юникода. Юникод является надмножеством кодировок ASCII и Latin-І и поддерживает практически все письменные языки, имеющиеся на планете. Стандарт ЕСМА-Script 3 требует, чтобы реализации JavaScript обеспечивали поддержку стандарта Юникода версии 2.1 или выше, а стандарт ECMAScript 5 требует, чтобы реализации обеспечивали поддержку стандарта Юникода версии 3 или выше. Более подробно о Юникоде и JavaScript говорится во врезке в разделе 3.2.

2.1.1. Чувствительность к регистру

 Сделать закладку на этом месте книги

JavaScript - это язык, чувствительный к регистру символов. Это значит, что ключевые слова, имена переменных и функций и любые другие идентификаторы языка должны всегда содержать одинаковые наборы прописных и строчных букв. Например, ключевое слово while должно набираться как «while», а не «While» или «WHILE». Аналогично online, Online, OnLine и ONLINE - это имена четырех разных переменных.

Заметим, однако, что язык разметки HTML (в отличие от XHTML) не чувствителен к регистру. Так как HTML и клиентский JavaScript тесно связаны, это различие может привести к путанице. Многие JavaScript-объекты и их свойства имеют те же имена, что и теги и атрибуты языка HTML, которые они обозначают. Однако если в HTML эти теги и атрибуты могут набираться в любом регистре, то в JavaScript они обычно должны набираться строчными буквами. Например, атрибут onclick обработчика события чаще всего задается в HTML как onClick, однако в JavaScript-коде (или в XHTML-документе) он должен быть обозначен как onclick.

2.1.2. Пробелы, переводы строк и символы управления форматом

 Сделать закладку на этом месте книги

JavaScript игнорирует пробелы, которые могут присутствовать между лексемами в программе. Кроме того, JavaScript также по большей части игнорирует символы перевода строки (за одним исключением, о котором рассказывается в разделе 2.5). Поэтому пробелы и символы перевода строки могут без ограничений использоваться в исходных текстах программ для форматирования и придания им удобочитаемого внешнего вида.

Помимо обычного символа пробела (\u0020) JavaScript дополнительно распознает как пробельные следующие символы: табуляция (\u0009), вертикальная табуляция (\u000В), перевод формата (\u000C), неразрывный пробел (\u00А0), маркер порядка следования байтов (\uFEFF), а также все символы Юникода, относящиеся к категории Zs. Следующие символы распознаются интерпретаторами JavaScript как символы конца строки: перевод строки (\u000А), возврат каретки (\u000D), разделитель строк (\u2028) и разделитель абзацев (\u2029). Последовательность из символов возврата каретки и перевода строки интерпретируется как единственный символ завершения строки.

Символы Юникода, управляющие форматом (категория Cf), такие как RIGHT-TO-LEFT MARK (\u200F) и LEFT-TO-RIGHT MARK (\u200E), управляют визуальным представлением текста, в котором они присутствуют. Они имеют большое значение для корректного отображения текста на некоторых языках и являются допустимыми в комментариях JavaScript, строковых литералах и в литералах регулярных выражений, но не в идентификаторах (таких как имена переменных), определяемых в программах JavaScript. Исключение составляют ZERO WIDTH JOINER (\u200D) и ZERO WIDTH NON-JOINER (\u200C), которые можно использовать в идентификаторах при условии, что они не являются первыми символами идентификаторов. Как отмечалось выше, символ управления порядком следования байтов (\uFEFF) интерпретируется как пробельный символ.

2.1.3. Экранированные последовательности Юникода

 Сделать закладку на этом месте книги

Некоторые компьютеры и программное обеспечение не могут отображать или обеспечивать ввод полного набора символов Юникода. Для поддержки программистов, использующих подобную устаревшую технику, JavaScript определяет специальные последовательности, состоящие из шести символов ASCII, представляющие 16-битные кодовые пункты Юникода. Эти экранированные последовательности Юникода начинаются с символов \и, за которыми следуют точно четыре шестнадцатеричные цифры (при этом символы A-F могут быть и строчными, и прописными). Экранированные последовательности Юникода могут появляться в строковых литералах JavaScript, в литералах регулярных выражений и в идентификаторах (но не в ключевых словах языка). Экранированная последовательность Юникода для символа e, например, имеет вид \u00E9, и с точки зрения JavaScript следующие две строки являются идентичными:

"cafe" === "caf\u00e9" // => true

Экранированные последовательности Юникода могут также появляться в комментариях, но поскольку комментарии игнорируются, в данном контексте они воспринимаются как последовательность символов ASCII и не интерпретируются как символы Юникода.

2.1.4. Нормализация

 Сделать закладку на этом месте книги

Юникод позволяет закодировать один и тот же символ несколькими способами. Строка «e», например, может быть закодирована как единственный символ Юникода \u00E9 или как обычный ASCII-символ е, со следующим за ним диакритическим знаком \u0301. Эти два способа представления обеспечивают одинаковое отображение в текстовом редакторе, но имеют различные двоичные коды и с точки зрения компьютера считаются различными. Стандарт Юникода определяет предпочтительные способы кодирования для всех символов и задает процедуру нормализации для приведения текста к канонической форме, пригодной для сравнения. Интерпретаторы JavaScript полагают, что интерпретируемый программный код уже был нормализован, и не предпринимают никаких попыток нормализовать идентификаторы, строки или регулярные выражения.

2.2. Комментарии

 Сделать закладку на этом месте книги

JavaScript поддерживает два способа оформления комментариев. Любой текст между символами // и концом строки рассматривается как комментарий и игнорируется JavaScript. Любой текст между символами /* и */ также рассматривается как комментарий. Эти комментарии могут состоять из нескольких строк, но не могут быть вложенными. Следующие строки представляют собой корректные JavaScript-комментарии:

// Это однострочный комментарий.

/* Это тоже комментарий */ // а это другой комментарий.

/*

* Это еще один комментарий.

* Он располагается в нескольких строках.

*/

2.3. Литералы

 Сделать закладку на этом месте книги

Литерал - это значение, указанное непосредственно в тексте программы. Ниже приводятся примеры различных литералов:

12              // Число двенадцать

1.2             // Число одна целая две десятых

"hello world"   // Строка текста

’Hi'            // Другая строка

true            // Логическое значение

false           // Другое логическое значение

/javascript/gi  // Литерал "регулярного выражения” (для поиска по шаблону)

null            // Пустой объект


сложные выражения (смотрите раздел 4.2), которые могут служить литералами массивов и объектов:

{ х:1. у:2 } // Инициализатор объекта

[1,2,3,4,5]  // Инициализатор массива

2.4. Идентификаторы и зарезервированные слова

 Сделать закладку на этом месте книги

Идентификатор - это просто имя. В JavaScript идентификаторы выступают в качестве имен переменных и функций, а также меток некоторых циклов. Идентификаторы в JavaScript должны начинаться с буквы, с символа подчеркивания (_) или знака доллара ($). Далее могут следовать любые буквы, цифры, символы подчеркивания или знаки доллара. (Цифра не может быть первым символом, так как тогда интерпретатору трудно будет отличать идентификаторы от чисел.) Примеры допустимых идентификаторов:


і

my_variable_name

v13

_dummy

$str


Для совместимости и простоты редактирования для составления идентификаторов обычно используются только символы ASCII и цифры. Однако JavaScript допускает возможность использования в идентификаторах букв и цифр из полного набора символов Юникода. (Технически стандарт ECMAScript также допускает наличие в идентификаторах символов Юникода из категорий Мп, Мс и Рс при условии, что они не являются первыми символами идентификаторов.) Это позволяет программистам давать переменным имена на своих родных языках и использовать в них математические символы:


var si = true; var pi=3.14;


Подобно другим языкам программирования, JavaScript резервирует некоторые идентификаторы. Эти «зарезервированные слова» не могут использоваться в качестве обычных идентификаторов. Они перечислены ниже.

2.4.1. Зарезервированные слова

 Сделать закладку на этом месте книги

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

break delete function return typeof
case do if switch var
catch else in this void
continue false instanceof throw while
debugger finally new true with
default for null try  

JavaScript также резервирует некоторые ключевые слова, которые в настоящее время не являются частью языка, но которые могут войти в его состав в будущих версиях. Стандарт ECMAScript 5 резервирует следующие слова:


class const enum export extends import super


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


implements let private public yiel


убрать рекламу






d

interface package protected static


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


arguments eval


Стандарт ECMAScript 3 резервирует все ключевые слова языка Java, и, хотя это требование было ослаблено в стандарте ECMAScript 5, тем не менее следует избегать использования этих идентификаторов, если необходимо обеспечить работоспособность JavaScript-кода при использовании реализаций JavaScript, соответствующих стандарту ECMAScript 3:

abstract double goto native static
boolean enum implements package super
byte export import private synchronized
char extends int protected throws
class final interface public transient
const float long short volatile

В языке JavaScript имеется множество предопределенных глобальных переменных и функций, имена которых не следует использовать для создания своих собственных переменных и функций:

arguments encodeURI Infinity Number RegExp
Array encodeURIComponent isFinite Object String
Boolean Error isNaN parseFloat SyntaxError
Date eval JSON parselnt TypeError
decodeURI EvalError Math RangeError undefined
decodeURIComponent Function NaN ReferenceError URIError

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

2.5. Необязательные точки с запятой

 Сделать закладку на этом месте книги

Как и в других языках программирования, для отделения инструкций (глава 5) друг от друга в языке JavaScript используется точка с запятой (;). Использование точек с запятой имеет важное значение для ясного выражения намерений программиста: без этого разделителя по ошибке можно принять конец одной инструкции за начало следующей и наоборот. Обычно в JavaScript точку с запятой между инструкциями можно не ставить, если они находятся в разных строках. (Точку с запятой можно также опустить в конце программы или если следующей лексемой в программе является закрывающая фигурная скобка }.) Многие программисты на JavaScript используют точки с запятой для явного обозначения концов инструкций (этот же прием используется в примерах для этой книги), даже если в этом нет необходимости. Другие опускают точки с запятой везде, где только возможно, используя их лишь в некоторых ситуациях, где они совершенно необходимы. Прежде чем выбрать тот или иной стиль, вам необходимо познакомиться с некоторыми особенностями использования точек с запятой в JavaScript.

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


а = 3;

b = 4;


Однако если эти инструкции записать, как показано ниже, первая точка с запятой становится обязательной:


а = 3; b = 4;


Обратите внимание, что в JavaScript не все разрывы строк интерпретируются как точка с запятой: разрывы строк обычно интерпретируются как точки с запятой, только когда интерпретатор оказывается неспособен выполнить синтаксический анализ программного кода без точек с запятой. Если говорить более формально (с двумя исключениями, описываемыми ниже), JavaScript интерпретирует разрыв строки как точку с запятой, если следующий непробельный символ не может быть интерпретирован как продолжение текущей инструкции. Взгляните на следующий фрагмент:


var а а

=

3

console.log(a)


JavaScript интерпретирует этот программный код, как показано ниже:


var а; а = 3; console.log(а);


Интерпретатор JavaScript будет интерпретировать первый разрыв строки как точку с запятой, потому что он не сможет проанализировать фрагмент var а а без точки с запятой. Второй идентификатор а можно было бы интерпретировать как инструкцию а;, но JavaScript не будет воспринимать второй разрыв строки как точку с запятой, потому что он сможет продолжить синтаксический анализ и получить более длинную инструкцию а = 3;.

Эти правила интерпретации разрывов строк могут приводить к странным, на первый взгляд, ситуациям. Следующий фрагмент выглядит как две отдельные инструкции, отделенные символом перевода строки:


var у = х + f

(a+b).toString()


Однако круглые скобки во второй строке могут быть интерпретированы как вызов функции f из первой строки, и JavaScript будет интерпретировать этот фрагмент, как показано ниже:


var у = х + f(a+b).toString();


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

В целом, если инструкция начинается с символа (,[,/, + или -, есть вероятность, что она будет воспринята интерпретатором как продолжение предыдущей инструкции. Инструкции, начинающиеся с символов /, + и -, редко встречаются на практике, но инструкции, начинающиеся с символов ( и [, встречаются достаточно часто, по крайней мере, при использовании некоторых стилей программирования на JavaScript. Некоторые программисты любят вставлять защитную точку с запятой в начало каждой такой инструкции, чтобы обеспечить корректную ее работу, даже если предыдущая инструкция будет изменена и ранее имевшаяся завершающая точка с запятой исчезнет:


var х = 0                         // Здесь точка с запятой опущена

;[х,х+1,х+2].forEach(console.log) // Защитная ; обеспечивает обособленность

                                  // этой инструкции


Из общего правила, согласно которому интерпретатор JavaScript воспринимает разрывы строк как точки с запятой, когда он не может интерпретировать вторую строку как продолжение инструкции в первой строке, имеется два исключения. Первое исключение связано с инструкциями return, break и continue (глава 5). Эти инструкции часто используются отдельно, но иногда вслед за ними указываются идентификаторы или выражения. Если разрыв строки находится сразу за любым из этих слов (перед любой другой лексемой), JavaScript всегда будет интерпретировать этот разрыв строки как точку с запятой. Например, если записать:


return

true;


интерпретатор JavaScript предположит, что программист имеет в виду следующее:


return; true;


Хотя на самом деле программист, видимо, хотел написать:


return true;


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

Второе исключение связано с операторами ++ и -- (раздел 4.8). Эти операторы могут быть префиксными, т. е. располагаться перед выражением, и постфиксными, т. е. располагаться после выражения. Если вам потребуется использовать любой из этих операторов в постфиксной форме записи, он должен находиться в той же строке, что и выражение, к которому применяется этот оператор. В противном случае разрыв строки будет интерпретироваться как точка с запятой, а оператор ++ или -- будет интерпретироваться как префиксный оператор, применяемый к выражению, следующему далее. Например, взгляните на следующий фрагмент:


х

++

У


Он будет интерпретирован как х; ++у;, а не как х++; у.


3. Типы данных, значения и переменные

 Сделать закладку на этом месте книги

 В процессе работы компьютерные программы манипулируют значениями , такими как число 3,14 или текст «Hello World». Типы значений, которые могут быть представлены и обработаны в языке программирования, известны как типы данных , и одной из наиболее фундаментальных характеристик любого языка программирования является поддерживаемый им набор типов данных. Когда в программе необходимо сохранить значение, чтобы использовать его позже, это значение присваивается (или сохраняется в) переменной . Переменная определяет символическое имя для значения и обеспечивает возможность получить это значение по имени. Принцип действия переменных является еще одной фундаментальной характеристикой любого языка программирования. В этой главе рассматриваются типы, значения и переменные в языке JavaScript. В этих вводных абзацах дается только краткий обзор, и в процессе их чтения вам, возможно, окажется полезным возвращаться к разделу 1.1. Более полное обсуждение этих тем вы найдете в последующих разделах.

Типы данных в JavaScript можно разделить на две категории: простые типы  и объекты , К категории простых типов в языке JavaScript относятся числа, текстовые строки (которые обычно называют просто строками ) и логические (или булевы ) значения. Значительная часть этой главы посвящена подробному описанию числового (раздел 3.1) и строкового (раздел 3.2) типов данных. Логический тип рассматривается в разделе 3.3.

Специальные значения null и undefined являются элементарными значениями, но они не относятся ни к числам, ни к строкам, ни к логическим значениям. Каждое из них определяет только одно значение своего собственного специального типа. Подробнее о значениях null и undefined рассказывается в разделе 3.4.

Любое значение в языке JavaScript, не являющееся числом, строкой, логическим значением или специальным значением null или undefined, является объектом . Объект (т. е. член объектного типа данных) представляет собой коллекцию свойств, каждое из которых имеет имя и значение (либо простого типа, такое как число или строка, либо объектного). В разделе 3.5 мы рассмотрим один специальный объект, глобальный объект, но более подробно объекты обсуждаются в главе 6.

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

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

Функции, которые пишутся для инициализации вновь создаваемых объектов (с оператором new), называются конструкторами . Каждый конструктор определяет класс объектов - множество объектов, инициализируемых этим конструктором. Классы можно представлять как подтипы объектного типа. В дополнение к классам Array и Function в базовом языке JavaScript определены еще три полезных класса. Класс Date определяет объекты, представляющие даты. Класс RegExp определяет объекты, представляющие регулярные выражения (мощный инструмент сопоставления с шаблоном, описываемый в главе 10). А класс Error определяет объекты, представляющие синтаксические ошибки и ошибки времени выполнения, которые могут возникать в программах на языке JavaScript. Имеется возможность определять собственные классы объектов, объявляя соответствующие функции-конструкторы. Подробнее об этом рассказывается в главе 9.

Интерпретатор JavaScript автоматически выполняет сборку мусора в памяти. Это означает, что программа может создавать объекты по мере необходимости, но программисту нет необходимости беспокоиться об уничтожении этих объектов и освобождении занимаемой ими памяти. Когда объект выходит за пределы области видимости (т. е. когда программа утрачивает возможность доступа к этому объекту) и интерпретатор обнаруживает, что данный объект никогда больше не сможет использоваться, он автоматически освобождает занимаемую им память.

JavaScript - это объектно-ориентированный язык программирования. В общих чертах это означает, что вместо глобальных функций для обработки значений различных типов типы сами могут определять методы для обработки значений. Например, чтобы отсортировать элементы массива а, необязательно передавать массив а функции sort(). Вместо этого можно просто вызвать метод sort() массива а:


a.sort(); // Объектно-ориентированная версия вызова sort(а).


Порядок определения методов описывается в главе 9. С технической точки зрения в языке JavaScript только объекты могут иметь методы. Однако числа, строки и логические значения ведут себя так, как если бы они обладали методами (данная особенность описывается в разделе 3.6). Значения null и undefined являются единственными в языке JavaScript, которые не имеют методов.

Типы данных в языке JavaScript можно разделить на простые и объектные. Их также можно разделить на типы с методами и типы без методов. Кроме того, типы можно характеризовать как изменяемые и неизменяемые. Значение изменяемого типа можно изменить. Объекты и массивы относятся к изменяемым типам: программа на языке JavaScript может изменять значения свойств объектов и элементов массивов. Числа, логические значения, null и undefined являются неизменяемыми - не имеет даже смысла говорить об изменчивости, например, значения числа. Строки можно представить себе как массивы символов, отчего можно счесть, что они являются изменяемыми. Однако строки в JavaScript являются неизменяемыми: строки предусматривают возможность обращения к символам по числовым индексам, но в JavaScript отсутствует возможность изменить существующую текстовую строку. Различия между изменяемыми и неизменяемыми значениями будут рассматриваться ниже, в разделе 3.7.

В языке JavaScript значения достаточно свободно могут быть преобразованы из одного типа в другой. Например, если программа ожидает получить строку, а вы передаете ей число, интерпретатор автоматически преобразует число в строку. Если вы укажете нелогическое значение там, где ожидается логическое, интерпретатор автоматически выполнит соответствующее преобразование. Правила преобразований описываются в разделе 3.8. Свобода преобразований типов значений в JavaScript затрагивает и понятие равенства, и оператор == проверки на равенство выполняет преобразование типов, как описывается в разделе 3.8.1.

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

3.1. Числа

 Сделать закладку на этом месте книги

В отличие от многих языков программирования, в JavaScript не делается различий между целыми и вещественными значениями. Все числа в JavaScript представляются вещественными значениями (с плавающей точкой). Для представления чисел в JavaScript используется 64-битный формат, определяемый стандартом IEEE 754.[1] Этот формат способен представлять числа в диапазоне от ±1,7976931348623157 х 10308 до ±5 х 10-324.

Формат представления вещественных чисел в JavaScript позволяет точно представлять все целые числа от -9007199254740992 (-253) до 9007199254740992 (253) включительно. Для целых значений вне этого диапазона может теряться точность в младших разрядах. Следует отметить, что некоторые операции в JavaScript (такие как обращение к элементам массива по индексам и битовые операции, описываемые в главе 4) выполняются с 32-разрядными целыми значениями.

Число, находящееся непосредственно в программе на языке JavaScript, называется числовым литералом . JavaScript поддерживает числовые литералы нескольких форматов, описанных в последующих разделах. Обратите внимание, что любому числовому литералу может предшествовать знак «минус» (-), делающий числа отрицательными. Однако фактически минус представляет собой унарный оператор смены знака (см. главу 4), не являющийся частью синтаксиса числовых литералов.

3.1.1. Целые литералы

 Сделать закладку на этом месте книги

В JavaScript целые десятичные числа записываются как последовательность цифр. Например:


0

3

10000000


Помимо десятичных целых литералов JavaScript распознает шестнадцатеричные значения (по основанию 16). Шестнадцатеричные литералы начинаются с последовательности символов «0х» или «0Х», за которой следует строка шестнадцатеричных цифр. Шестнадцатеричная цифра - это одна из цифр от 0 до 9 или букв от а (или А) до f (или F), представляющих значения от 10 до 15. Ниже приводятся примеры шестнадцатеричных целых литералов:


Oxff           // 15*16 + 15 = 255 (по основанию 10)

0xCAFE911


Хотя стандарт ECMAScript не поддерживает представление целых литералов в восьмеричном формате (по основанию 8), некоторые реализации JavaScript допускают подобную возможность. Восьмеричный литерал начинается с цифры 0, за которой могут следовать цифры от 0 до 7. Например:


0377 // 3*64 + 7*8 + 7 = 255 (по основанию 10)


Поскольку некоторые реализации поддерживают восьмеричные литералы, а некоторые нет, никогда не следует писать целый литерал с ведущим нулем, ибо нельзя сказать наверняка, как он будет интерпретирован данной реализацией - как восьмеричное число или как десятичное. В строгом (strict) режиме, определяемом стандартом ECMAScript 5 (раздел 5.7.3), восьмеричные литералы явно запрещены.

3.1.2. Литералы вещественных чисел

 Сделать закладку на этом месте книги

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

Литералы вещественных чисел могут также представляться в экспоненциальной нотации: вещественное число, за которым следует буква е (или Е), а затем необязательный знак плюс или минус и целая экспонента. Такая форма записи обозначает вещественное число, умноженное на 10 в степени, определяемой значением экспоненты.

Ниже приводится более лаконичное определение синтаксиса:


[цифры][.цифры][(Е|е)[(+|-)]цифры]


Например:


3.14

2345.789

.333333333333333333

6.02е23 // 6.02 х 1023

1.4738223Е-32 // 1.4738223 х 10-32


3.1.3. Арифметические операции в JavaScript

 Сделать закладку на этом месте книги

Обработка чисел в языке JavaScript выполняется с помощью арифметических операторов. В число таких операторов входят: оператор сложения +, оператор вычитания -, оператор умножения *, оператор деления / и оператор деления по модулю % (возвращает остаток от деления). Полное описание этих и других операторов можно найти в главе 4.

Помимо этих простых арифметических операторов JavaScript поддерживает более сложные математические операции, с помощью функций и констант, доступных в виде свойств объекта Math:


Math.pow(2,53) // => 9007199254740992: 2 в степени 53

Math.round(.6) // => 1.0: округление до ближайшего целого

Math.ceil(.6)  // => 1.0: округление вверх

Math.floor(.6) // => 0.0: округление вниз

Math.abs(-5)   // => 5: абсолютное значение

Math.max(x,y,z)// Возвращает наибольший аргумент

Math.min(x,y,z)// Возвращает наименьший аргумент

Math.random()  // Псевдослучайное число х, где 0 <= х < 1.0

Math.PI        // пи: длина окружности / диаметр

Math.E         // е: Основание натурального логарифма

Math.sqrt(3)   // Корень квадратный из 3

Math.pow(3, 1/3) // Корень кубический из 3

Math.sin(0)    // Тригонометрия: имеются также Math.cos, Math.atan и другие.

Math.log(10)   // Натуральный логарифм 10

Math.log(100)/Math.LN10 // Логарифм 100 по основанию 10 (десятичный)

Math.log(512)/Math.LN2  // Логарифм 512 по основанию 2

Math.exp(3)    // Math.E в кубе


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

Арифметические операции в JavaScript не возбуждают ошибку в случае переполнения, потери значащих разрядов или деления на ноль. Если результат арифметической операции окажется больше самого большого представимого значения (переполнение), возвращается специальное значение «бесконечность» , которое в JavaScript обозначается какInfinity. Аналогично, если абсолютное значение отрицательного результата окажется больше самого большого представимого значения, возвращается значение «отрицательная бесконечность»,  которое обозначается как -Infinity. Эти специальные значения, обозначающие бесконечность, ведут себя именно так, как и следовало ожидать: сложение, вычитание, умножение или деление бесконечности на любое значение дают в результате бесконечность (возможно, с обратным знаком).

Потеря значащих разрядов происходит, когда результат арифметической операции оказывается ближе к нулю, чем минимально возможное значение. В этом случае возвращается число 0. Если потеря значащих разрядов происходит в отрицательном результате, возвращается специальное значение, известное как «отрицательный ноль». Это специальное значение практически ничем не отличается от обычного нуля, и у программистов на JavaScript редко возникает необходимость выделять его.

Деление на ноль не считается ошибкой в JavaScript: в этом случае просто возвращается бесконечность или отрицательная бесконечность. Однако есть одно исключение: операция деления нуля на ноль не имеет четко определенного значения, поэтому в качестве результата такой операции возвращается специальное значение «не число» (not-a-number), которое обозначается как NaN. Значение NaN возвращается также при попытке разделить бесконечность на бесконечность, извлечь квадратный корень из отрицательного числа или выполнить арифметическую операцию с нечисловыми операндами, которые не могут быть преобразованы в числа.

В JavaScript имеются предопределенные глобальные переменные Infinity и NaN, хранящие значения положительной бесконечности и «не число». В стандарте ECMAScript 3 эти переменные доступны для чтения/записи и могут изменяться в программах. Стандарт ECMAScript 5 исправляет эту оплошность и требует, чтобы эти переменные были доступны только для чтения. Объект Number предоставляет альтернативные представления некоторых значений, доступные только для чтения даже в ECMAScript 3. Например:


Infinity                   // Переменная, доступная для чтения/записи,

                           // инициализированная значением Infinity.

Number.POSITIVE_INFINITY   // То же значение, доступное только для чтения.

1/0                        // То же самое значение.

Number.MAX_VALUE + 1       // Это выражение также возвращает Infinity.

Number.NEGATIVE_INFINITY   // Возвращают отрицательную бесконечность.

-Infinity

-1/0

-Number.MAX_VALUE - 1

NaN    


убрать рекламу






                    // Переменная, доступная для чтения/записи,

                           // инициализированная значением NaN.

Number.NaN                 // Свойство, доступное только для чтения, с тем же значением.

0/0                        // Возвращает NaN.

Number.MIN_VALUE/2         // Потеря значащих разрядов: возвращает 0

-Number.MIN_VALUE/2        // Отрицательный ноль

-1/Infinity                 // Также отрицательный ноль

-0


Значение «не число» в JavaScript обладает одной необычной особенностью: операция проверки на равенство всегда возвращает отрицательный результат, даже если сравнить его с самим собой. Это означает, что нельзя использовать проверку х == NaN, чтобы определить, является значение переменной х значением NaN. Вместо этого следует выполнять проверкух != х. Эта проверка вернет true тогда и только тогда, когда х имеет значение NaN. Аналогичную проверку можно выполнить с помощью функции isNaN(). Она возвращает true, если аргумент имеет значение NaN или если аргумент является нечисловым значением, таким как строка или объект. Родственная функция isFinite() возвращает true, если аргумент является числом, отличным от NaN, Infinity или -Infinity.

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


var zero = 0;     // Обычный ноль

var negz = -0;    // Отрицательный ноль

zero === negz     // => true: ноль и отрицательный ноль равны

1/zero === 1/negz // => false: Infinity и -Infinity не равны


3.1.4. Двоичное представление вещественных чисел и ошибки округления

 Сделать закладку на этом месте книги

Вещественных чисел существует бесконечно много, но формат представления вещественных чисел в JavaScript позволяет точно выразить лишь ограниченное их количество (точнее, 18437736874454810627). Это значит, что при работе с вещественными числами в JavaScript представление числа часто будет являться округлением фактического числа.

Стандарт представления вещественных чисел IEEE-754, используемый в JavaScript (и практически во всех других современных языках программирования), определяет двоичный формат их представления, который может обеспечить точное представление таких дробных значений, как 1/2, 1/8 и 1/1024. К сожалению, чаще всего мы пользуемся десятичными дробями (особенно при выполнении финансовых расчетов), такими как 1/10,1/100 и т. д. Двоичное представление вещественных чисел неспособно обеспечить точное представление таких простых чисел, как 0.1.

Точность представления вещественных чисел в JavaScript достаточно высока и позволяет обеспечить очень близкое представление числа 0.1. Но тот факт, что это число не может быть представлено точно, может приводить к проблемам. Взгляните на следующий фрагмент:


var х = .3 - .2; // тридцать копеек минус двадцать копеек

var у = .2 - .1; // двадцать копеек минус 10 копеек

x == y; // => false: получились два разных значения

X == .1 // => false: .3-.2 не равно .1

У == .1 // => true: .2-.1 равно .1


Из-за ошибок округления разность между аппроксимациями чисел .3 и .2 оказалась не равной разности между аппроксимациями чисел .2 и .1. Важно понимать, что эта проблема не является чем-то характерным для JavaScript: она проявляется во всех языках программирования, где используется двоичное представление вещественных чисел. Кроме того, обратите внимание, что значения х и у в примере выше очень близки друг к другу и к истинному значению. Точность округления вполне приемлема для большинства применений: проблема возникает лишь при попытках проверить значения на равенство.

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

3.1.5. Дата и время

 Сделать закладку на этом месте книги

В базовом языке JavaScript имеется конструктор Date() для создания объектов, представляющих дату и время. Эти объекты Date обладают методами для выполнения простых вычислений с участием дат. Объект Date не является фундаментальным типом данных, как числа. Этот раздел представляет собой краткое пособие по работе с датами. Полное описание можно найти в справочном разделе:


var then = new Date(2010, 0, 1); // Первый день первого месяца 2010 года

var later = new Date(2010, 0, 1, 17, 10, 30);// Та же дата, в 17:10:30 локального времени

var now = new Date();// Текущие дата и время

var elapsed = now - then; // Разность дат: интервал в миллисекундах

later.getFullYear()// => 2010

later.getMonth() // => 0: счет месяцев начинается с нуля

later.getDate()  // => 1: счет дней начинается с единицы

later.getDay() // => 5: день недели. 0 - воскр., 5 - пяти.

later.getHours()   // => 17: 17 часов локального времени

later.getUTCHours() // часы по UTC; зависит от часового пояса

later.toStrlng()

// => "Fri Jan 01 2010 17:10:30 GMT+0300"

later.toUTCString() // => "Fri, 01 Jan 2010 14:10:30 GMT"

later.toLocaleDateString() // => "1 Январь 2010 г."

later.toLocaleTimeString() // => "17:10:30"

later.toIS0String()  // => "2010-01-01T14:10:30.000Z"


3.2. Текст

 Сделать закладку на этом месте книги

Строка - это неизменяемая, упорядоченная последовательность 16-битных значений, каждое из которых обычно представляет символ Юникода. Строки в JavaScript являются типом данных, используемым для представления текста. Длина строки - это количество 16-битных значений, содержащихся в ней. Нумерация символов в строках (и элементов в массивах) в языке JavaScript начинается с нуля: первое 16-битное значение находится в позиции 0, второе - в позиции 1 и т. д. Пустая строка - это строка, длина которой равна 0. В языке JavaScript нет специального типа для представления единственного элемента строки. Для представления единственного 16-битного значения просто используется строка с длиной, равной 1.

3.2.1. Строковые литералы

 Сделать закладку на этом месте книги

Чтобы включить литерал строки в JavaScript-программу, достаточно просто заключить символы строки в парные одинарные или двойные кавычки (' или "). Символы двойных кавычек могут содержаться в строках, ограниченных символами одинарных кавычек, а символы одинарных кавычек - в строках, ограниченных символами двойных кавычек. Ниже приводятся несколько примеров строковых литералов:


// Это пустая строка: в ней ноль символов

'testing'

"3.14"

'name="myform"'

"Вы предпочитаете книги издательства O'Reilly, не правда ли?"

"В этом строковом литерале\nдве строки”

"пи - это отношение длины окружности к ее диаметру"


В ECMAScript 3 строковые литералы должны записываться в одной строке программы и не могут разбиваться на две строки. Однако в ECMAScript 5 строковые литералы можно разбивать на несколько строк, заканчивая каждую строку, кроме последней, символом обратного слэша (\). Ни один из символов обратного слэша, как и следующие за ними символы перевода строки, не будут включены в строковый литерал. Чтобы включить в строковый литерал символ перевода строки, следует использовать последовательность символов \п (описывается ниже):


"две\пстроки" // Строковый литерал, представляющий две строки

"одна\        // Одна строка, записанная в трех строках. Только в ECMAScript 5

длинная\

строка"



Символы, кодовые пункты и строки JavaScript

Для представления символов Юникода в языке JavaScript используется кодировка UTF-16, а строки JavaScript являются последовательностями 16-битных значений без знака. Большинство наиболее часто используемых символов Юникода (из «основной многоязыковой матрицы») имеют кодовые пункты, умещающиеся в 16 бит, и могут быть представлены единственным элементом строки. Символы Юникода, кодовые пункты которых не умещаются в 16 бит, кодируются в соответствии с правилами кодировки UTF-16 как последовательности (известные как «суррогатные пары») из двух 16-битных значений. Это означает, что строка JavaScript, имеющая длину, равную 2 (два 16-битных значения), может представлять единственный символ Юникода:


var Р = "71": // 71 - это 1 символ с 16-битным кодовым пунктом 0х0Зс0

var е = "е ";  // е  - это 1 символ с 17-битным кодовым пунктом 0x1d452

р.length      // => 1: р содержит единственный 16-битный элемент

е.length      // => 2: в кодировке UTF-16 символ е  определяется двумя

              // 16-битными значениями: "\ud835\udc52"


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


*********************************************

Обратите внимание, что, ограничивая строку одинарными кавычками, необходимо проявлять осторожность в обращении с апострофами, употребляемыми в английском языке для обозначения притяжательного падежа и в сокращениях; как, например, в словах «can’t» и «O’Reilly’s». Поскольку апостроф и одиночная кавычка - это одно и то же, необходимо при помощи символа обратного слэша (\) «экранировать» апострофы, расположенные внутри одиночных кавычек (подробнее об этом - в следующем разделе).

Программы на клиентском JavaScript часто содержат строки HTML-кода, а HTML-код, в свою очередь, часто содержит строки JavaScript-кода. Как и в JavaScript, в языке HTML для ограничения строк применяются либо одинарные, либо двойные кавычки. Поэтому при объединении JavaScript- и HTML-кода есть смысл придерживаться одного «стиля» кавычек для JavaScript, а другого - для HTML. В следующем примере строка «Спасибо» в JavaScript-выражении заключена в одинарные кавычки, а само выражение, в свою очередь, заключено в двойные кавычки как значение HTML-атрибута обработчика событий:


<button onclick="alert('Спасибо')">Щелкни на мнe</button>


3.2.2. Управляющие последовательности в строковых литералах

 Сделать закладку на этом месте книги

Символ обратного слэша (\) имеет специальное назначение в JavaScript-строках. Вместе с символами, следующими за ним, он обозначает символ, не представимый внутри строки другими способами. Например, \n - это управляющая последовательность (escape sequence), обозначающая символ перевода строки.

Другой пример, упомянутый выше, - это последовательность \', обозначающая символ одинарной кавычки. Эта управляющая последовательность необходима для включения символа одинарной кавычки в строковый литерал, заключенный в одинарные кавычки. Теперь становится понятно, почему мы называем эти последовательности управляющими - здесь символ обратного слэша позволяет управлять интерпретацией символа одинарной кавычки. Вместо того чтобы отмечать ею конец строки, мы используем ее как апостроф:


'You\'re right, it can\'t be a quote'


В табл. 3.1 перечислены управляющие последовательности JavaScript и обозначаемые ими символы. Две управляющие последовательности являются обобщенными; они могут применяться для представления любого символа путем указания кода символа из набора Latin-І или Unicode в виде шестнадцатеричного числа. Например, последовательность \хА9 обозначает символ копирайта, который в кодировке Latin-І имеет шестнадцатеричный код А9. Аналогично управляющая последовательность, начинающаяся с символов \и, обозначает произвольный символ Юникода, заданный четырьмя шестнадцатеричными цифрами. Например, \u03c0 обозначает символ л.

Если символ «\» предшествует любому символу, отличному от приведенных в табл. 3.1, обратный слэш просто игнорируется (хотя будущие версии могут, конечно, определять новые управляющие последовательности). Например, \# - это то же самое, что и #. Наконец, как отмечалось выше, стандарт ECMAScript 5 позволяет добавлять в многострочные строковые литералы символ обратного слэша перед разрывом строки.

Таблица 3.1. Управляющие последовательности JavaScript
Последовательность Представляемый символ
Символ NUL (\u0000)
«Забой» (\u0008)
\t Горизонтальная табуляция (\u0009)
\n Перевод строки (\u000А)
\v Вертикальная табуляция (\u000В)
\f Перевод страницы (\u000С)
\r Возврат каретки (\u000D)
\" Двойная кавычка (\u0022)
\' Одинарная кавычка (\u0027)
\\ Обратный слэш (\u005C)
\хХХ Символ Latin-1, заданный двумя шестнадцатеричными цифрами XX
\uxXXXX Символ Unicode, заданный четырьмя шестнадцатеричными цифрами хххх

3.2.3. Работа со строками

 Сделать закладку на этом месте книги

Одной из встроенных возможностей JavaScript является способность конкатенировать строки. Если оператор + применяется к числам, они складываются, а если к строкам - они объединяются, при этом вторая строка добавляется в конец первой. Например:


msg = "Hello, " + "world"; // Получается строка "Hello, world"

greeting = "Добро пожаловать на мою домашнюю страницу," + " " + name;


Для определения длины строки - количества содержащихся в ней 16-битных значений - используется свойство строки length. Например, длину строки s можно получить следующим образом:


s.length


Кроме того, в дополнение к свойству length строки имеют множество методов (как обычно, более полную информацию ищите в справочном разделе):


var s = "hello, world"    // Начнем с того же текста.

s.charAt(0)               // => "h": первый символ.

s.charAt(s.length-1)      // => "d": последний символ.

s.substring(1,4)          // => "ell": 2-й, 3-й и 4-й символы.

s.slice(1,4)              // => "ell": то же самое

s.slice(-3)               // => "rld": последние 3 символа

s.indexOf("l")            // => 2: позиция первого символа l.

s.lastlndexOf('l')        // => 10: позиция последнего символа l.

s.indexOf("l", 3)         // => 3: позиция первого символа ”1", следующего

                          // за 3 символом в строке

s.split(",")              // => ["hello", "world"] разбивает на подстроки

s.replace("h", "H")       // => "Hello, world": замещает все вхождения подстроки

s.toUpperCase()           // => "HELLO, WORLD"


Не забывайте, что строки в JavaScript являются неизменяемыми. Такие методы, как герlасе() и toUpperCase() возвращают новые строки: они не изменяют строку, относительно которой были вызваны.

В стандарте ECMAScript 5 строки могут интерпретироваться как массивы, доступные только для чтения, и вместо использования метода charAt() к отдельным символам (16-битным значениям) строки можно обращаться с помощью индексов в квадратных скобках:


s = "hello, world";

s[0]             // => "h"

s[s.length-1]    // => "d"


Веб-броузеры, основанные на движке Mozilla, такие как Firefox, уже давно предоставляют такую возможность. Большинство современных броузеров (заметным исключением из которых является IE) последовали за Mozilla еще до того, как эта особенность была утверждена в стандарте ECMAScript 5.

3.2.4. Сопоставление с шаблонами

 Сделать закладку на этом месте книги

В языке JavaScript определен конструктор RegExp(), предназначенный для создания объектов, представляющих текстовые шаблоны. Эти шаблоны описываются с помощью регулярных выражений, синтаксис которых был заимствован языком JavaScript из языка Perl. И строки, и объекты RegExp имеют методы, позволяющие выполнять операции сопоставления с шаблоном и поиска с заменой при помощи регулярных выражений.

RegExp не относится к числу фундаментальных типов данных языка JavaScript. Подобно объектам Date, они просто являются специализированной разновидностью объектов с удобным прикладным интерфейсом. Грамматика регулярных выражений и прикладной интерфейс отличаются повышенной сложностью. Они подробно описываются в главе 10. Однако поскольку объекты RegExp обладают широкими возможностями и часто используются на практике, мы коротко познакомимся с ними в этом разделе.

Несмотря на то что объекты RegExp не относятся к фундаментальным типам данных языка, они имеют синтаксис литералов и могут вставляться непосредственно в текст программы на языке JavaScript. Текст, заключенный в пару символов слэша, интерпретируется как литерал регулярного выражения. За вторым символом слэша из этой пары может следовать один или более символов, которые модифицируют поведение шаблона. Например:


/"HTML/            // Соответствует символам Н Т М L в начале строки

/[ 1-9][0-9]*/     // Соответствует цифре, кроме нуля, за которой следует любое число цифр

/\bjavascript\b/i  // Соответствует подстроке "javascript”

                   // как отдельному слову, учитывает регистр символов


Объекты RegExp обладают множеством полезных методов. Кроме того, строки также обладают методами, которые принимают объекты RegExp в виде аргументов. Например:


var text = "testing: 1, 2, 3”; // Образец текста

var pattern = /\d+/g       // Соответствует всем вхождениям одной или более цифр

pattern.test(text)         // => true: имеется совпадение

text.search(pattern)       // => 9: позиция первого совпадения

text.match(pattern)        // => ["1", "2", "3"]: массив всех совпадений

text.replace(pattern,'#'); // => "testing: tf, tf. #"

text.split(/\D+/);         // => "1","2","3"]: разбить по нецифровым символам


3.3. Логические значения

 Сделать закладку на этом месте книги

Логическое значение говорит об истинности или ложности чего-то. Логический тип данных имеет только два допустимых логических значения. Эти два значения представлены литералами true и false.

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


а == 4


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

Логические значения обычно используются в управляющих конструкциях JavaScript. Например, инструкция if/else в JavaScript выполняет одно действие, если логическое значение равно true, и другое действие, если false. Обычно сравнение, создающее логическое значение, непосредственно объединяется с инструкцией, в которой оно используется. Результат выглядит так:


if (а == 4)

  b = b + 1;

else

  а = а + 1:


Здесь выполняется проверка равенства значения переменной а числу 4. Если равно, к значению переменной b добавляется 1; в противном случае число 1 добавляется к значению переменной а.

Как будет говориться в разделе 3.8, любое значение в языке JavaScript может быть преобразовано в логическое значение. Следующие значения в результате такого преобразования дают логическое значение (и затем работают как) false:


undefined

null

0

-0

NaN

// пустая строка


Все остальные значения, включая все объекты (и массивы), при преобразовании дают в результате значение (и работают как) true. Значениеfalse и шесть значений, которые при преобразовании приводятся к этому значению, иногда называют ложными, а все остальные - истинными. В любом контексте, когда интерпретатор JavaScript ожидает получить логическое значение, ложные значения интерпретируются как false, а истинные значения - как true.

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


if (о !== null) ...


Оператор «не равно» !== сравнит переменную о со значением null и вернет в результате true или false. Однако вы можете опустить оператор сравнения и положиться на тот факт, что null является ложным значением, а объект - истинным:


if (о) ...


В первом случае тело инструкции if будет выполнено, только если значение переменной о не равно null. Во втором - ставится менее жесткое условие: тело инструкции if будет выполнено, только если о не содержит false или другое ложное значение (такое как null или undefined). Какая инструкция if больше подходит для вашей программы, зависит от того, какие значения могут присваиваться переменной о. Если в программе необходимо отличать значение null от 0 и "" , то следует использовать явную операцию сравнения.

Логические значения имеют метод toString(), который можно использовать для преобразования этих значений в строки «true» или «false», но они не имеют других полезных методов. Несмотря на простоту прикладного интерфейса, в языке имеется три важных логических оператора.

Оператор && выполняет логическую операцию И. Он возвращает истинное значение, только если оба операнда истинны - в противном случае он возвращает ложное значение. Оператор || выполняет логическую операцию ИЛИ: он возвращает истинное значение, если хотя бы один (или оба) из операндов является истинным, и ложное значение - если оба операнда являются ложными. Наконец, унарный оператор ! выполняет логическую операцию НЕ: он возвращает значение true для ложного операнда и false - для истинного. Например:


if ((х == 0 && у == 0) И !(z == 0)) {

// х и у содержат значение 0 или z не равна нулю

}


Полное описание этих операторов приводится в разделе 4.10.

3.4. Значения null и undefined

 Сделать закладку на этом месте книги

Ключевое слово null в языке JavaScript имеет специальное назначение и обычно используется для обозначения отсутствия значения. Оператор typeof для значения null возвращает строку «object», что говорит о том, что значение null является специальным «пустым» объектом. Однако на практике значение null обычно считается единственным членом собственного типа и может использоваться как признак отсутствия значения, такого как число, строка или объект. В большинстве других языков программирования имеются значения, аналогичные значению null в JavaScript: вам они могут быть известны как null или nil.

В языке JavaScript имеется еще одно значение, свидетельствующее об отсутствии значения. Значение undefined, указывающее на полное отсутствие какого-либо значения. Оно возвращается при обращении к переменной, которой никогда не присваивалось значение, а также к несуществующему свойству объекта или элементу массива. Кроме того, значение undefined возвращается функциями, не имеющими возвращаемого значения, и присваивается параметрам функций для аргументов, которые не были переданы при вызове. Идентификатор undefined является именем предопреде


убрать рекламу






ленной глобальной переменной (а не ключевым словом, как null), которая инициализирована значением undefined. В ECMAScript 3 undefined является переменной, доступной для чтения/записи, которой можно присвоить любое другое значение. Эта проблема была исправлена в ECMAScript 5, и в реализациях JavaScript, соответствующих этому стандарту, переменная undefined доступна только для чтения. Оператор typeof для значения undefined возвращает строку «undefined», показывающую, что данное значение является единственным членом специального типа.

Несмотря на эти отличия, оба значения, null и undefined, являются признаком отсутствия значения и часто являются взаимозаменяемыми. Оператор равенства == считает их равными. (Чтобы отличать их в программе, можно использовать оператор идентичности ===.) Оба они являются ложными значениями - в логическом контексте они интерпретируются как значение false. Ни null, ни undefined не имеют каких-либо свойств или методов. На практике попытка использовать . или [], чтобы обратиться к свойству или методу этих значений, вызывает ошибку ТуреЕrror.

Значение undefined можно рассматривать как признак неожиданного или ошибочного отсутствия какого-либо значения, a null - как признак обычного или вполне ожидаемого отсутствия значения. Если в программе потребуется присвоить одно из этих значений переменной или свойству или передать одно из этих значений функции, практически всегда предпочтительнее использовать значение null.

3.5. Глобальный объект

 Сделать закладку на этом месте книги

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


• глобальные свойства, такие как undefined, Infinity и NaN;

• глобальные функции, такие как isNaN(), parseInt() (раздел 3.8.2) и eval() (раздел 4.12);

• функции-конструкторы, такие как Date(), RegExp(), String(), Object() и Array() (раздел 3.8.2);

• глобальные объекты, такие как Math и JS0N (раздел 6.9).


Имена первоначально устанавливаемых свойств глобального объекта не являются зарезервированными словами, но вы вполне можете считать их таковыми. Все эти свойства перечислены в разделе 2.4.1. Некоторые из глобальных свойств уже описывались в этой главе. Большинство других будут рассматриваться в разных разделах книги. Кроме того, их все можно отыскать по именам в справочном разделе по базовому JavaScript или в описании самого глобального объекта, под именем «Global». В клиентском JavaScript имеется объект Window, определяющий другие глобальные свойства, описание которых можно найти в справочном разделе по клиентскому JavaScript.

В программном коде верхнего уровня, т. е. в JavaScript-коде, который не является частью функции, сослаться на глобальный объект можно посредством ключевого слова this:


var global = this; // Определить глобальную переменную для ссылки на глобальный объект


В клиентском JavaScript роль глобального объекта для всего JavaScript-кода, содержащегося в соответствующем ему окне броузера, играет объект Window. Этот глобальный объект имеет свойство window, ссылающееся на сам объект, которое можно использовать вместо ключевого слова this для ссылки на глобальный объект. Объект Window определяет базовые глобальные свойства, а также дополнительные глобальные свойства, характерные для веб-броузеров и клиентского JavaScript.

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

3.6. Объекты-обертки

 Сделать закладку на этом месте книги

Объекты в языке JavaScript являются составными значениями: они представляют собой коллекции свойств, или именованных значений. Обращение к свойствам мы будем выполнять с использованием точечной нотации. Свойства, значениями которых являются функции, мы будем называть методами. Чтобы вызвать метод m объекта о, следует использовать инструкциюо.m().

Мы уже видели, что строки обладают свойствами и методами:


var s = "hello world!"; // Строка

var word = s.substring.indexOf(" ")+1, s.length); // Использование свойств строки


Однако строки не являются объектами, так почему же они обладают свойствами? Всякий раз когда в программе предпринимается попытка обратиться к свойству строки s, интерпретатор JavaScript преобразует строковое значение в объект, как если бы был выполнен вызов new String(s). Этот объект наследует (раздел 6.2.2) строковые методы и используется интерпретатором для доступа к свойствам. После обращения к свойству вновь созданный объект уничтожается. (От реализаций не требуется фактически создавать и уничтожать этот промежуточный объект, но они должны вести себя так, как если бы объект действительно создавался и уничтожался.)

Наличие методов у числовых и логических значений объясняется теми же причинами: при обращении к какому-либо методу создается временный объект вызовом конструктора Number() или Boolean(), после чего производится вызов метода этого объекта. Значения null и undefined не имеют объектов-оберток: любые попытки обратиться к свойствам этих значений будет вызывать ошибку ТуреError.

Рассмотрим следующий фрагмент и подумаем, что происходит при его выполнении:


var s = "test"; // Начальное строковое значение.

s.len = 4;      // Установить его свойство.

var t = s.len;  // Теперь запросить значение свойства.


В начале этого фрагмента переменная t имеет значение undefined. Вторая строка создает временный объект String, устанавливает его свойство lеn равным 4 и затем уничтожает этот объект. Третья строка создает из оригинальной (неизмененной) строки новый объект String и пытается прочитать значение свойства len. Строки не имеют данного свойства, поэтому выражение возвращает значение undefined. Данный фрагмент показывает, что при попытке прочитать значение какого-либо свойства (или вызвать метод) строки числа и логические значения ведут себя подобно объектам. Но если попытаться установить значение свойства, эта попытка будет просто проигнорирована: изменение затронет только временный объект и не будет сохранено.

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

Обратите внимание, что существует возможность (но в этом почти никогда нет необходимости или смысла) явно создавать объекты-обертки вызовом конструктора String(), Number() илиBoolean():


var s = "test", n = 1, b = true; // Строка, число и логическое значение,

var S = new String(s);  // Объект String

var N = new Number(n);  // Объект Number

var В = new Boolean(b); // Объект Boolean


При необходимости интерпретатор JavaScript обычно автоматически преобразует объекты-обертки, т. е. объекты S, N и В в примере выше, в обертываемые ими простые значения, но они не всегда ведут себя точно так же, как значения s, n и Ь. Оператор равенства == считает равными значения и соответствующие им объекты-обертки, но оператор идентичности === отличает их. Оператор typeof также обнаруживает отличия между простыми значениями и их объектами-обертками.

3.7. Неизменяемые простые значения и ссылки на изменяемые объекты

 Сделать закладку на этом месте книги

Между простыми значениями (undefined, null, логическими значениями, числами и строками) и объектами (включая массивы и функции) в языке JavaScript имеются фундаментальные отличия. Простые значения являются неизменяемыми: простое значение невозможно изменить (или «трансформировать»). Это очевидно для чисел и логических значений - нет никакого смысла изменять значение числа. Однако для строк это менее очевидно. Поскольку строки являются массивами символов, вполне естественно было бы ожидать наличие возможности изменять символы в той или иной позиции в строке. В действительности JavaScript не позволяет сделать это, и все строковые методы, которые, на первый взгляд, возвращают измененную строку, на самом деле возвращают новое строковое значение. Например:


var s = "hello"; // Изначально имеется некоторый текст из строчных символов

s.toUpperCase(); // Вернет "HELLO", но значение s при этом не изменится

s // =>» "hello": оригинальная строка не изменилась


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

Объекты отличаются от простых типов. Во-первых, они являются изменяемыми - их значения можно изменять:


var о = { x:1 }; // Начальное значение объекта

о.x = 2;         // Изменить, изменив значение свойства

о.у = 3;         // Изменить, добавив новое свойство

var а = [1,2,3]  // Массивы также являются изменяемыми объектами

а[0] = 0;        // Изменить значение элемента массив

а[3] = 4;        // Добавить новый элемент


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


var о = {х:1}, р = {х:1};// Два объекта с одинаковыми свойствами

о === р          // => false: разные объекты не являются равными

var а = [], Ь = [];      // Два различных пустых массива 

а === b          // => false: различные массивы не являются равными


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


var а = []; // Переменная а ссылается на пустой массив.

var b = а;  // Теперь b ссылается на тот же массив.

Ь[0] = 1;   // Изменение массива с помощью ссылки в переменной Ь.

а[0]        // => 1: изменение можно наблюдать в переменной а.

а === b     // => true: а и b ссылаются на один и тот же объект, поэтому они равны.


Как следует из примера выше, операция присваивания объекта (или массива) переменной фактически присваивает ссылку: она не создает новую копию объекта. Если в программе потребуется создать новую копию объекта или массива, необходимо будет явно скопировать свойства объекта или элементы массива. Следующий пример демонстрирует такое копирование с помощью цикла for (раздел 5.5.3):


var a=['a,,,b,,'c’]; // Копируемый массив

var b = []; // Массив, куда выполняется копирование

for(var і = 0; і < a.length; i++) { // Для каждого элемента в массиве а[]

  b[і] = а[і]; // Скопировать элемент а[] в b[]

}


Точно так же, если потребуется сравнить два отдельных объекта или массива, необходимо будет сравнить значения их свойств или элементов. Ниже приводится определение функции, сравнивающей два массива:


function equalArrays(a, b) {

  if (a.length != b.length) return false; // Массивы разной длины не равны

  for(var і = 0; і < a.length; і++) // Цикл по всем элементам

    if (а[і] !== b[i]) return false; // Если хоть один элемент

                // отличается, массивы не равны

  return true;  // Иначе они равны

}


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

 Сделать закладку на этом месте книги

JavaScript может гибко преобразовывать один тип в другой. Мы уже могли убедиться в этом на примере логических значений: везде, где интерпретатор JavaScript ожидает получить логическое значение, можно указать значение любого типа и JavaScript автоматически выполнит необходимое преобразование. Одни значения («истинные» значения) преобразуются в значение true, а другие («ложные») - в false. То же относится и к другим типам: если интерпретатор ожидает получить строку, он автоматически преобразует любое другое значение в строку. Если интерпретатор ожидает получить число, он попробует преобразовать имеющееся значение в число (в случае невозможности такого преобразования будет получено значение NaN). Например:


10 + " objects" // => "10 objects". Число 10 преобразуется в строку

"7" * "4" // => 28: обе строки преобразуются в числа

var n = 1 - "x"; // => NaN: строка "x" не может быть преобразована в число

n + " objects" // => "NaN objects": NaN преобразуется в строку "NaN"


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

Преобразования одного простого типа в другой, показанные в табл. 3.2, выполняются относительно просто. Преобразование в логический тип уже обсуждалось в разделе 3.3. Преобразование всех простых типов в строку четко определено. Преобразование в число выполняется немного сложнее. Строки, которые могут быть преобразованы в числа, преобразуются в числа. В строке допускается наличие пробельных символов в начале и в конце, но присутствие других непробельных символов, которые не могут быть частью числа, при преобразовании строки в число приводят к возврату значения NaN. Некоторые особенности преобразования значений в числа могут показаться странными: значение true преобразуется в число 1, а значение false и пустая строка "" преобразуются в 0.

Преобразование простых типов в объекты также выполняется достаточно просто: значения простых типов преобразуются в соответствующие объекты-обертки (раздел 3.6), как если бы вызывался конструктор String(), Number() или Boolean().

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




3.8.1. Преобразования и равенство

 Сделать закладку на этом месте книги

Благодаря гибкости преобразований типов в JavaScript оператор равенства == также гибко определяет равенство значений. Например, все следующие сравнения возвращают true:


null == undefined // Эти два значения считаются равными.

"0" == 0          // Перед сравнением строка преобразуется в число.

0 == false        // Перед сравнением логич. значение преобразуется в число.

"0" == false      // Перед сравнением оба операнда преобразуются в числа.


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

Имейте в виду, что возможность преобразования одного значения в другое не означает равенства этих двух значений. Если, например, в логическом контексте используется значение undefined, оно будет преобразовано в значение false. Но это не означает, что undefined == false. Операторы и инструкции JavaScript ожидают получить значения определенных типов и выполняют преобразования в эти типы. Инструкцияif преобразует значение undefined в false, но оператор == никогда не пытается преобразовать свои операнды в логические значения.

3.8.2. Явные преобразования

 Сделать закладку на этом месте книги

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

Простейший способ выполнить преобразование типа явно заключается в использовании функций Boolean(), Number(), String() и Object(). Мы уже видели, как эти функции используются в роли конструкторов объектов-оберток (раздел 3.6). При вызове без оператора new они действуют как функции преобразования и выполняют преобразования, перечисленные в табл. 3.2:


Number("3") // => 3

String(false) // => "false" или можно использовать false.toString()

Boolean([]) // => true

Object(3) // => new Number(3)


Обратите внимание, что все значения, кроме null или undefined, имеют метод toString(), результатом которого обычно является то же значение, которое возвращается функцией String(). Кроме того, обратите внимание, что в табл. 3.2 отмечается, что при попытке преобразовать значение null или undefined в объект возбуждается ошибка ТуреЕrror. Функция Object() в этом случае не возбуждает исключение, вместо этого она просто возвращает новый пустой объект.

Определенные операторы в языке JavaScript неявно выполняют преобразования и иногда могут использоваться для преобразования типов. Если один из операндов оператора + является строкой, то другой операнд также преобразуется в строку. Унарный оператор + преобразует свой операнд в число. А унарный оператор ! преобразует операнд в логическое значение и инвертирует его. Все это стало причиной появления следующих своеобразных способов преобразования типов, которые можно встретить на практике:


x + "" // То же, что и String(х)

+x // То же, что и Number(x). Можно также встретить x-0

!!x //То же, что и Boolean(x). Обратите внимание на два знака !


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

Метод toString() класса Number принимает необязательный аргумент, определяющий основание системы счисления для преобразования. Если этот аргумент не определен, преобразование выполняется в десятичной системе счисления. Но вы можете производить преобразование в любой системе счисления (с основанием от 2 до 36). Например:


var n = 17;

binary_string = n.toString(2); // Вернет "10001"

octal_string = "0" + n.toString(8); // Вернет "021"

hex_string = "Ox" + n.toString(16); // Вернет "0x11"


При выполнении финансовых или научных расчетов может потребоваться обеспечить преобразование чисел в строки с точностью до определенного числа десятичных знаков или до определенного количества значащих разрядов или получать представление чисел в экспоненциальной форме. Для подобных преобразований чисел в строки класс Number определяет три метода. Метод toFixed() преобразует число в строку, позволяя указывать количество десятичных цифр после запятой. Он никогда не возвращает строки с экспоненциальным представлением чисел. Метод toExponential() преобразует число в строку в экспоненциальном представлении, когда перед запятой находится единственный знак, а после запятой следует указанное количество цифр (т. е. количество значащих цифр в строке получается на одну больше, чем было указано при вызове метода). Метод toPrecision() преобразует число в строку, учитывая количество заданных значащих разрядов. Если заданное количество значащих разрядов оказывается недостаточным для отображения всей целой части числа, преобразование выполняется в экспоненциальной форме. Обратите внимание, что все три метода округляют последние цифры или добавляют нули, если это необходимо. Взгляните на следующие примеры:


var п = 123456.789;

n.toFixed(0); // ”123457"

n.toFixed(2); // "123456.79"

n.toFixed(5); // "123456.78900"

n.toExponential(1); // "1.2e+5"

n.toExponential(3); // "1.235Є+5"

n.toPrecision(4); // "1.235e+5"

 n.toPrecision(7); // "123456.8"

n.toPrecision(IO);// "123456.7890"


Если передать строку функции преобразования Number(), она попытается разобрать эту строку как литерал целого или вещественного числа. Эта функция работает только с десятичными целыми числами и не допускает наличие в строке завершающих символов, не являющихся частью литерала числа. Функции parseInt() иparseFloat() (это глобальные функции, а не методы какого-либо класса) являются более гибкими. Функция parseInt() анализирует только целые числа, тогда как функция parseFloat() позволяет анализировать строки, представляющие и целые, и вещественные числа. Если строка начинается с последовательности «Ох» или «ОХ», функция parseInt() интерпретирует ее как представление шестнадцатеричного числа.[2] Обе функции, parseInt() и parseFloat(), пропускают начальные пробельные символы, пытаются разобрать максимально возможное количество символов числа и игнорируют все, что следует за ними. Если первый непробельный символ строки не является частью допустимого числового литерала, эти функции возвращают значение NaN:


parseInt("3 blind mice") // => 3

parseFloat("3.14 meters") // => 3.14

parseInt("-12.34") // => -12

parseInt("0xff") // => 255

parseInt("0xFF") // => 255

parseInt("-0xFF") // => -255

parseFloat(".1") // => 0.1

parseInt("0.1") // => 0

parseInt(".1") // => NaN: целые числа не могут начинаться с "."

parseFloat("$72.47"); // => NaN: числа не могут начинаться с "$"


Функция parseInt() принимает второй необязательный аргумент, определяющий основание системы счисления для разбираемого числа. Допустимыми являются значения от 2 до 36. Например:


parselnt("11", 2);  // => 3 (1*2 + 1)

parselnt("ff”, 16); // => 255 (15*16 + 15)

parselnt("zz", 36); // => 1295 (35*36 + 35)

parselnt("077", 8); // => 63 (7*8 + 7)

parselnt("077", 10) // => 77 (7*10 + 7)


3.8.3. Преобразование объектов в простые значения

 Сделать закладку на этом месте книги

Преобразование объектов в логические значения выполняется очень просто: все объекты (включая массивы и функции) преобразуются в значение true. Это справедливо и для объектов-оберток: результатом вызова new Boolean (false) является объект, а не простое значение, поэтому он также преобразуется в значение true.

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

Все объекты наследуют два метода преобразования. Первый из них называется toString(), он возвращает строковое представление объекта. По умолчанию метод toString() не возвращает ничего особенно интересного (хотя эта информация иногда может оказаться полезной, как будет показано в примере 6.4):


({x:1, y:2}).toString() // => "[object Object]"


Многие классы определяют более специализированные версии метода <


убрать рекламу






code>toString(). Например, метод toString() класса Array преобразует все элементы массива в строки и объединяет результаты в одну строку, вставляя запятые между ними. Метод toString() класса Function возвращает строковое представление функции, зависящее от реализации. На практике обычно реализации преобразуют пользовательские функции в строки с исходным программным кодом на языке JavaScript.

Класс Date определяет метод toString(), возвращающий строку с датой и временем в удобочитаемом формате (который может быть разобран средствами JavaScript). Класс RegExp определяет метод toString(), преобразующий объект RegExp в строку, которая выглядит как литерал регулярного выражения:


[1,2.3].toString() // => "1.2,3"

(function(x) { f(x); }).toString() // => "function(x) {\n f(x);\n}"

/\d+/g.toString() // => "/\\d+/g"

new Date(2010,0,1).toString() // => "Fri Jan 01 2010 00:00:00 GMT+0300"


Другая функция преобразования объектов называется valueOf(). Задача этого метода определена не так четко: предполагается, что он должен преобразовать объект в представляющее его простое значение, если такое значение существует. Объекты по своей природе являются составными значениями, и большинство объектов не могут быть представлены в виде единственного простого значения, поэтому по умолчанию метод valueOf() возвращает не простое значение, а сам объект. Классы-обертки определяют методы valueOf(), возвращающие обернутые простые значения. Массивы, функции и регулярные выражения наследуют метод по умолчанию. Вызов метода valueOf() экземпляров этих типов возвращает сам объект. Класс Date определяет метод valueOf(), возвращающий дату во внутреннем представлении: количество миллисекунд, прошедших с 1 января 1970 года:


var d = new Date(2010, 0, 1); // 1 января 2010 года, (время Московское)

d.valueOf() // => 1262293200000


Теперь, разобравшись с методами toString() и valueOf(), можно перейти к обсуждению особенностей преобразования объектов в строки и в числа. Учтите, что существует несколько специальных случаев, когда JavaScript выполняет преобразование объектов в простые значения несколько иначе. Эти особые случаи рассматриваются в конце данного раздела.

Преобразование объектов в строку интерпретатор JavaScript выполняет в два этапа:

• Если объект имеет метод toString(), интерпретатор вызывает его. Если он возвращает простое значение, интерпретатор преобразует значение в строку (если оно не является строкой) и возвращает результат преобразования. Обратите внимание, что правила преобразований простых значений в строку четко определены для всех типов и перечислены в табл. 3.2.

• Если объект не имеет метода toString() или этот метод не возвращает простое значение, то интерпретатор проверяет наличие метода valueOf(). Если этот метод определен, интерпретатор вызывает его. Если он возвращает простое значение, интерпретатор преобразует это значение в строку (если оно не является строкой) и возвращает результат преобразования.

• В противном случае интерпретатор делает вывод, что ни toString(), ни valueOf() не позволяют получить простое значение и возбуждает исключение TypeError.

При преобразовании объекта в число интерпретатор выполняет те же действия, но первым пытается применить метод valueOf():

• Если объект имеет метод valueOf(), возвращающий простое значение, интерпретатор преобразует (при необходимости) это значение в число и возвращает результат.

• Иначе, если объект имеет метод toString(), возвращающий простое значение, интерпретатор выполняет преобразование и возвращает полученное значение.

• В противном случае возбуждается исключение ТуреError.

Описанный алгоритм преобразования объекта в число объясняет, почему пустой массив преобразуется в число 0, а массив с единственным элементом может быть преобразован в обычное число. Массивы наследуют по умолчанию метод valueOf(), который возвращает сам объект, а не простое значение, поэтому при преобразовании массива в число интерпретатор опирается на метод toString(). Пустые массивы преобразуются в пустую строку. А пустая строка преобразуется в число 0. Массив с единственным элементом преобразуется в ту же строку, что и единственный элемент массива. Если массив содержит единственное число, это число преобразуется в строку, а затем опять в число.

Оператор + в языке JavaScript выполняет сложение чисел и конкатенацию строк. Если какой-либо из его операндов является объектом, JavaScript преобразует объект, используя специальное преобразование объекта в простое значение вместо преобразования объекта в число, используемого другими арифметическими операторами. То же относится и к оператору равенства ==. Если выполняется сравнение объекта с простым значением, оператор выполнит преобразование объекта с использованием правил преобразования в простое значение.

Преобразование объектов в простые значения, используемое операторами + и ==, предусматривает особый подход для объектов Date. Класс Date является единственным типом данных в базовом JavaScript, который определяет осмысленные преобразования и в строку, и в число. Преобразование любого объекта, не являющегося датой, в простое значение основано на преобразовании в число (когда первым применяется метод valueOf()), тогда как для объектов типа Date используется преобразование в строку (первым применяется метод toString()). Однако преобразование выполняется не совсем так, как было описано выше: простое значение, возвращаемое методом valueOf() или toString(), используется непосредственно, без дополнительного преобразования в число или в строку.

Оператор < и другие операторы отношений выполняют преобразование объектов в простые значения подобно оператору ==, но не выделяя объекты Date: для любого объекта сначала предпринимается попытка применить метод valueOf(), а затем метод toString(). Любое простое значение, полученное таким способом, используется непосредственно, без дальнейшего преобразования в число или в строку.

+, ==, != и операторы отношений являются единственными, выполняющими специальное преобразование строки в простое значение. Другие операторы выполняют более явные преобразования в заданный тип и не предусматривают специальной обработки объектов Date. Оператор -, например, преобразует свои операнды в числа. Следующий фрагмент демонстрирует поведение операторов +, -, == и > при работе с объектами Date:


var now = new Date(); // Создать объект Date

typeof (now +1) // => "строка": + преобразует дату в строку

typeof (now - 1) // => "число": - выполнит преобразование объекта в число

now == now.toString() // => true: неявное и явное преобразование в строку

now > (now -1) // => true: > преобразует объект Date в число


3.9. Объявление переменных

 Сделать закладку на этом месте книги

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


var і;

var sum;


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


var і, sum;


Объявление переменных можно совмещать с их инициализацией:


var message = "hello";

var i = 0, j = 0. k = 0;


Если начальное значение в инструкции var не задано, то переменная объявляется, но ее начальное значение остается неопределенным (undefined), пока не будет изменено программой.

Обратите внимание, что инструкция var также может включаться в циклы for и for/in (о которых рассказывается в главе 5), что позволяет объявлять переменную цикла непосредственно в самом цикле. Например:


for(var і = 0; і < 10; i++) console.log(i);

for(var і = 0, j=10; і < 10; i++,J —) console.log(i*j);

for(var p in o) console.log(p);


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

var і = 10;

 і = "ten";


3.9.1 Повторные и опущенные объявления

 Сделать закладку на этом месте книги

С помощью инструкции var МОЖНО объявить одну и ту же переменную несколько раз. Если повторное объявление содержит инициализатор, то оно действует как обычная инструкция присваивания.

Если попытаться прочитать значение необъявленной переменной, JavaScript сгенерирует ошибку. В строгом режиме, предусмотренном стандартом ECMAScript 5 (раздел 5.7.3), ошибка также возбуждается при попытке присвоить значение необъявленной переменной. Однако исторически и при выполнении не в строгом режиме, если присвоить значение переменной, не объявленной с помощью инструкции var, то JavaScript создаст эту переменную как свойство глобального объекта, и она будет действовать практически так же (но с некоторыми отличиями, описываемыми в разделе 3.10.2), как корректно объявленная переменная. Это означает, что глобальные переменные можно не объявлять. Однако это считается дурной привычкой и может явиться источником ошибок, поэтому всегда старайтесь объявлять свои переменные с помощью var.

3.10. Область видимости переменной

 Сделать закладку на этом месте книги

Область видимости (scope) переменной - это та часть программы, для которой эта переменная определена. Глобальная переменная имеет глобальную область видимости - она определена для всей JavaScript-программы. В то же время переменные, объявленные внутри функции, определены только в ее теле. Они называются локальными и имеют локальную область видимости. Параметры функций также считаются локальными переменными, определенными только в теле этой функции.

Внутри тела функции локальная переменная имеет преимущество перед глобальной переменной с тем же именем. Если объявить локальную переменную или параметр функции с тем же именем, что у глобальной переменной, то фактически глобальная переменная будет скрыта:


var scope = "global"; // Объявление глобальной переменной

function checkscope() {

  var scope = "local"; // Объявление локальной переменной с тем же именем

  return scope; // Вернет локальное значение, а не глобальное

}

checkscope()   // => "local"


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


scope = "global";// Объявление глобальной переменной, даже без var.

function checkscope2() {

scope = "local"; // Ой! Мы изменили глобальную переменную.

myscope = "local"; // Неявно объявляется новая глоб. переменная.

return [scope, myscope];// Вернуть два значения.


checkscope2() // => ["local", "local"]: имеется побочный эффект!

scope // => "local": глобальная переменная изменилась.

myscope // => "local": нарушен порядок в глобальном пространстве имен.


Определения функций могут быть вложенными. Каждая функция имеет собственную локальную область видимости, поэтому может быть несколько вложенных уровней локальных областей видимости. Например:


var scope = "global scope"; // Глобальная переменная

function checkscopeO {

  var scope = "local scope”; // Локальная переменная

  function nestedO {

    var scope = "nested scope"; // Вложенная область видимости локальных переменных

    return scope; // Вернет значение этой переменной scope

  }

  return nested();

}


3.10.1. Область видимости функции и подъем

 Сделать закладку на этом месте книги

В некоторых С-подобных языках программирования каждый блок программного кода внутри фигурных скобок имеет свою собственную область видимости, а переменные, объявленные внутри этих блоков, невидимы за их пределами. Эта особенность называется областью видимости блока, но она не поддерживается в языке JavaScript. Вместо этого в JavaScript используется такое понятие, как область видимости функции: переменные, объявленные внутри функции, доступны внутри функции, где они объявлены, а также внутри всех функций, вложенных в эту функцию.

В следующем фрагменте переменные i, j и к объявляются в разных местах, но все они имеют одну и ту же область видимости - все три переменные доступны из любого места в теле функции:


function test(o) {

  var і = 0; // і определена в теле всей функции

  if (typeof о == "object") {

    var j = 0; // j определена везде, не только в блоке

    for(var k=0; k < 10; k++) { // к определена везде, не только в цикле

      console.log(k); // выведет числа от 0 до 9

    }

    console.log(k); // к по-прежнему определена: выведет 10

  }

  console.log(j); // j определена, но может быть неинициализирована

}


Область видимости функции в языке JavaScript подразумевает, что все переменные, объявленные внутри функции, видимы везде в теле функции. Самое интересное, что переменные оказываются видимыми еще до того, как будут объявлены. Эта особенность JavaScript неофициально называется подъемом: программный код JavaScript ведет себя так, как если бы все объявления переменных внутри функции (без присваивания инициализирующих значений) «поднимались» в начало функции. Рассмотрим следующий фрагмент:


var scope = "global";

function f() {

  console.log(scope); // Выведет "undefined", а не "global"

  var scope = "local"; // Инициализируется здесь, а определена везде

  console.log(scope); // Выведет "local"

}


Можно было бы подумать, что первая инструкция внутри функции должна вывести слово «global», потому что инструкция var с объявлением локальной переменной еще не была выполнена. Однако вследствие действия правил области видимости функции выводится совсем другое значение. Локальная переменная определена во всем теле функции, а это означает, что глобальная переменная с тем же именем оказывается скрытой для всей функции. Хотя локальная переменная определена во всем теле функции, она остается неинициализированной до выполнения инструкции var. То есть функция выше эквивалентна реализации, приведенной ниже, в которой объявление переменной «поднято» в начало функции, а инициализация переменной выполняется там же, где и раньше:


function f() {

  var scope; // Объявление локальной переменной в начале функции

  console.log(scope); // Здесь она доступна, но имеет значение "undefined"

  scope = "local";  // Здесь она инициализируется и получает свое значение

  console.log(scope); // А здесь она имеет ожидаемое значение

}


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

3.10.2. Переменные как свойства

 Сделать закладку на этом месте книги

При объявлении глобальной переменной в JavaScript в действительности создается свойство глобального объекта (раздел 3.5). Если глобальная переменная объявляется с помощью инструкции var, создается ненастраиваемое свойство (раздел 6.7), т. е. свойство, которое невозможно удалить с помощью оператора delete. Как уже отмечалось выше, если не используется строгий режим и необъявленной переменной присваивается некоторое значение, интерпретатор JavaScript автоматически создает глобальную переменную. Переменные, созданные таким способом, становятся обычными, настраиваемыми свойствами глобального объекта и могут быть удалены:


var truevar = 1; // Правильно объявленная глобальная переменная, неудаляемая.

fakevar = 2;  // Создается удаляемое свойство глобального объекта.

this.fakevar2 = 3; // То же самое.

delete truevar // => false: переменная не была удалена

delete fakevar  // => true: переменная удалена

delete this.fakevar2 // => true: переменная удалена


Глобальные переменные в языке JavaScript являются свойствами глобального объекта, и такое положение вещей закреплено в спецификации ECMAScript. Это не относится к локальным переменным, однако локальные переменные можно представить как свойства объекта, ассоциированного с каждым вызовом функции. В спецификации ECMAScript 3 этот объект называется «объектом вызова» (call object), а в спецификации ECMAScript 5 он называется «записью с описанием окружения» (declarative environment record). Интерпретатор JavaScript позволяет ссылаться на глобальный объект с помощью ключевого слова this, но он не дает никакой возможности сослаться на объект, в котором хранятся локальные переменные. Истинная природа объектов, в которых хранятся локальные переменные, зависит от конкретной реализации и не должна заботить нас. Однако сам факт наличия объектов с локальными переменными имеет большое значение, и эта тема будет рассматриваться в следующем разделе.

3.10.3 Цепочки областей видимости

 Сделать закладку на этом месте книги

JavaScript - это язык программирования с лексической областью видимости: область видимости переменной распространяется на строки с исходным программным кодом, для которых определена переменная. Глобальные переменные определены для всей программы в целом. Локальные переменные определены для всей функции, в которой они объявлены, а также для любых функций, вложенных в эту функцию.

Если считать локальные переменные свойствами некоторого объекта, зависящего от реализации, то появляется возможность взглянуть на области видимости переменных с другой стороны. Каждый фрагмент программного кода на JavaScript (глобальный программный код или тело функции) имеет цепочку областей видимости, ассоциированную с ним. Эта цепочка областей видимости представляет собой список, или цепочку объектов, определяющих переменные, которые находятся «в области видимости» данного фрагмента программного кода. Когда интерпретатору требуется отыскать значение переменной x (этот процесс называется разрешением переменной), он начинает поиск с первого объекта в цепочке. Если этот объект имеет свойство с именем x, используется значение этого свойства. Если первый объект не имеет свойства с именем x, интерпретатор JavaScript продолжает поиск в следующем объекте в цепочке. Если второй объект не имеет свойства с именем x, интерпретатор переходит к следующему объекту и т. д. Если ни один из объектов в цепочке областей видимости не имеет свойства с именем x, то интерпретатор считает, что переменная x находится вне области видимости данного программного кода и возбуждает ошибку ReferenceError.

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

Такой взгляд на цепочку областей видимости будет полезен для понимания инструкции with (раздел 5.7.1) и чрезвычайно важен для понимания замыканий (раздел 8.6).


4

Выражения и операторы

 Сделать закладку на этом месте книги

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

Наиболее типичный способ конструирования сложных выражений из более простых выражений заключается в использовании операторов. Операторы объединяют значения своих операндов (обычно двух) некоторым способом и вычисляют новое значение. Простейшим примером может служить оператор умножения *. Выражение х * у вычисляется как произведение значений выражений х и у. Иногда для простоты мы говорим, что оператор возвращает значение вместо «вычисляет» значение.

Эта глава описывает все операторы JavaScript, а также выражения (такие как индексирование массивов и вызов функций), в которых не используются операторы. Те, кто знаком с другими языками программирования, использующими С-подобный синтаксис, заметят, что в JavaScript выражения и операторы имеют похожий синтаксис.

4.1. Первичные выражения

 Сделать закладку на этом месте книги

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

Первичными выражениями в языке JavaScript являются константы, или литералы, некоторые ключевые слова и ссылки на переменные.

Литералы и константы встраиваются непосредственно в программный код. Они выглядят, как показано ниже:


1.23 // Числовой литерал

"hello" // Строковый литерал

/pattern/ // Литерал регулярного выражения


Синтаксис числовых литералов в JavaScript был описан в разделе 3.1.0 строковых литералах рассказывалось в разделе 3.2. Синтаксис литералов регулярных выражений был представлен в разделе 3.2.4 и подробно будет описываться в главе 10.

Ниже приводятся некоторые из зарезервированных слов JavaScript, являющихся первичными выражениями:


true // Возвращает логическое значение true

false // Возвращает логическое значение false

null // Возвращает значение null

this // Возвращает "текущий" объект


Мы познакомились со значениями true, false и null в разделах 3.3 и 3.4. В отличие от других ключевых слов, this не является константой - в разных местах программы оно может возвращать разные значения. Ключевое слово this используется в объектно-ориентированном программировании. Внутри метода this возвращает объект, относительно которого был вызван метод. Дополнительные сведения о ключевом слове this можно найти в разделе 4.5, в главе 8 (особенно в разделе 8.2.2) и в главе 9.

Наконец, третьим типом первичных выражений являются ссылки на переменные:


і // Возвращает значение переменной і

sum // Возвращает значение переменной sum

undefined // undefined - глобальная переменная, а не ключевое слово, как null


Когда в программе встречается идентификатор, интерпретатор JavaScript предполагает, что это имя переменной и пытается отыскать ее значение. Если переменной с таким именем не существует, возвращается значение undefined. Однако в строгом режиме, определяемом стандартом ECMAScript 5, попытка получить значение несуществующей переменной оканчивается исключением ReferenceError.

4.2. Инициализаторы объектов и массивов

 Сделать закладку на этом месте книги

Инициализаторы объектов и массивов - это выражения, значениями которых являются вновь созданные объекты и массивы. Эти выражения-инициализаторы иногда называют «литералами объектов» и «литералами массивов». Однако, в отличие от истинных литералов, они не являются первичными выражениями, потому что включают множество подвыражений, определяющих


убрать рекламу






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

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


[] // Пустой массив: отсутствие выражений в квадратных скобках

   // означает отсутствие элементов

[1+2,3+4] // Массив из 2 элементов. Первый элемент - 3, второй - 7


Выражения в инициализаторе массива, определяющие значения его элементов, сами могут быть инициализаторами массивов, благодаря чему имеется возможность создавать вложенные массивы:


var matrix = [[1,2,3], [4,5,6], [7,8,9]];


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

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


var sparseArray = [1,,,,5];


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

Выражения-инициализаторы объектов похожи на выражения-инициализаторы массивов, но вместо квадратных скобок в них используются фигурные скобки, а каждое подвыражение предваряется именем свойства и двоеточием:


var р = { х:2.3, у:-1.2 }; // Объект с 2 свойствами

var q = {}; // Пустой объект без свойств

q.x = 2.3; q.y = -1.2; // Теперь q имеет те же свойства, что и р


Литералы объектов могут быть вложенными. Например:


var rectangle = { upperLeft: { х: 2, у: 2 },

lowerRight: { х: 4, у: 5 } };


Выражения внутри инициализаторов объектов вычисляются всякий раз, когда вычисляется значение самого инициализатора, и не обязательно должны быть константами: они могут быть произвольными выражениями JavaScript. Кроме того, имена свойств в литералах объектов могут быть строками, а не идентификаторами (это удобно, когда возникает желание дать свойствам имена, совпадающие с зарезервированными словами, которые иначе не могут использоваться в качестве идентификаторов):


var side = 1;

var square = { "upperLeft": { x: p.x, y: p.y },

'lowerRight': { x: p.x + side, y: p.y + side}};


Мы еще вернемся к инициализаторам объектов и массивов в главах 6 и 7.

4.3. Выражения определений функций

 Сделать закладку на этом месте книги

определения функции является «литералом функции» подобно тому, как инициализаторы объектов являются «литералами объектов». Выражение определения функции обычно состоит из ключевого слова function, за которым следует список из нуля или более идентификаторов (имен параметров), разделенных запятыми, в круглых скобках и блок программного кода на языке JavaScript (тело функции) в фигурных скобках. Например:


// Эта функция возвращает квадрат переданного ей значения

var square = function(x) { return x * x; }


Выражение определения функции также может включать имя функции. Кроме того, функции можно определять с помощью инструкции function, вместо выражения определения функции. Подробное описание особенностей определения функций приводится в главе 8.

4.4. Выражения обращения к свойствам

 Сделать закладку на этом месте книги

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

выражение . идентификатор выражение [ выражение ]

Первый способ обращения к свойствам заключается в использовании выражения, за которым следуют точка и идентификатор. Выражение определяет объект, а идентификатор - имя требуемого свойства. Первый способ заключается в использовании выражения (объект или массив), за которым следует другое выражение, заключенное в квадратные скобки. Второе выражение определяет имя требуемого свойства или индекс элемента массива. Ниже приводится несколько конкретных примеров:


var о = {x:1,y:{z:3}};  // Пример объекта

var а = [о,4, [5,6]];   // Пример массива, содержащего объект

о.х                     //свойство x выражения o

о.у.z                   //свойство z выражения o.y

о["х"]                  //свойство x объекта o

а[1]                    // элемент с индексом 1 выражения а

а[2]["1"]               // элемент с индексом 1 выражения а[2]

 а[0].х                 //свойство x выражения а[0]


Независимо от способа обращения к свойству первым вычисляется выражение, стоящее перед . или [. Если значением этого выражения является null или undefined, возбуждается исключение ТуреЕггог, потому что эти два значения в JavaScript не имеют свойств. Если значение выражения не является объектом (или массивом), оно будет преобразовано в объект (раздел 3.6). Если за первым выражением следует точка и идентификатор, интерпретатор попытается отыскать значение свойства с именем, совпадающим с идентификатором, которое и станет значением всего выражения. Если за первым выражением следует другое выражение в квадратных скобках, интерпретатор вычислит второе выражение и преобразует его в строку. В этом случае значением всего выражения станет значение свойства, имя которого совпадает со строкой. В любом случае, если свойство с указанным именем не существует, значением выражения обращения к свойству станет значение undefined.

Из двух способов обращения к свойству синтаксис .идентификатор выглядит проще, но обратите внимание, что этот способ может использоваться, только если именем свойства, к которому выполняется обращение, является допустимый идентификатор, и это имя известно на этапе создания программы. Если имя свойства совпадает с зарезервированным словом, включает пробелы или знаки пунктуации, или когда оно является числом (в случае массивов), необходимо использовать синтаксис с квадратными скобками. Кроме того, квадратные скобки можно использовать, когда имя свойства является не статическим, а результатом некоторых вычислений (пример можно найти в разделе 6.2.1).

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

4.5. Выражения вызова

 Сделать закладку на этом месте книги

Выражение вызова в языке JavaScript служит для вызова (или выполнения) функции или метода. Оно начинается с выражения, возвращающего функцию, идентифицирующего вызываемую функцию. Вслед за выражением получения функции следуют открывающая круглая скобка, список из нуля или более выражений аргументов, разделенных запятыми, и закрывающая круглая скобка. Например:


f(0)            // f - выражение функции; 0 - выражение аргумента.

Math.max(x,у,z) // Math.max - функция; x, у и z - аргументы.

a.sort()        // a.sort - функция; здесь нет аргументов.


При вычислении выражения вызова первым вычисляется выражение, возвращающее функцию, а затем вычисляются выражения аргументов и создается список значений аргументов. Если значением выражения, возвращающего функцию, не является вызываемый объект, возбуждается исключение ТуреЕггог. (Все функции являются вызываемыми объектами. Объекты среды выполнения также могут быть вызываемыми, даже если они не являются функциями. Это отличие рассматривается в разделе 8.7.7.) Далее значения аргументов присваиваются в порядке их следования именам параметров, которые указаны в определении функции, после чего выполняется тело функции. Если внутри функции используется инструкция return, возвращающая некоторое значение, это значение становится значением выражения вызова. В противном случае выражение вызова возвращает значение undefined. Полное описание механизма вызова функций, включая описание того, что происходит, когда количество выражений аргументов не совпадает с количеством параметров в определении функции, вы найдете в главе 8.

Все выражения вызова включают пару круглых скобок и выражение перед открывающей круглой скобкой. Если это выражение является выражением обращения к свойству, такой вызов называется вызовом метода. При вызове метода объект или массив, к свойству которого производится обращение, становится значением параметра this, доступного в теле функции во время его выполнения. Это обеспечивает поддержку парадигмы объектно-ориентированного программирования, согласно которой функции (в ООП обычно называются «методами») получают возможность манипулировать объектом, частью которого они являются. Подробности приводятся в главе 9.

ECMAScript 5, если функция определяется в строгом режиме, при вызове она получает в ключевом слове this не глобальный объект, а значение undefined. Подробнее о строгом режиме рассказывается в разделе 5.7.3.

4.6. Выражения создания объектов

 Сделать закладку на этом месте книги

Выражение создания объекта создает новый объект и вызывает функцию (называемую конструктором) для инициализации свойств этого объекта. Выражения создания объектов похожи на выражения вызова, за исключением того, что им предшествует ключевое слово new:


new Object()

new Point(2,3)


Если в выражении создания объекта функции-конструктору не передается ни одного аргумента, пустую пару круглых скобок можно опустить:


new Object

new Date


При вычислении выражения создания объекта интерпретатор JavaScript сначала создает новый пустой объект, как если бы для создания использовался пустой инициализатор объекта {}, а затем вызывает указанную функцию с указанными аргументами, передавая ей новый объект в качестве значения ключевого слова this. Функция может использовать его для инициализации свойств только что созданного объекта. Функции, которые создаются специально, чтобы играть роль конструктора, не должны возвращать значение, а значением выражения создания объекта становится созданный и инициализированный объект. Если конструктор возвращает какой-либо объект, этот объект становится значением всего выражения создания объекта, а вновь созданный объект уничтожается.

Более подробно конструкторы описываются в главе 9.

4.7. Обзор операторов

 Сделать закладку на этом месте книги

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

Таблица 4.1. Операторы JavaScript
Оператор Операция А N Типы значений
++ Префиксный и постфиксный инкремент R 1 левостороннее выражение —> число
-- Префиксный и постфиксный декремент R 1 левостороннее выражение —> число
- Унарный минус R 1 число —> число
+ Преобразование в число R 1 число —> число
~ Поразрядная инверсия R 1 целое —> целое
Оператор Операция А N Типы значений
! Логическая инверсия R 1 логическое —> логическое
delete Удаление свойства R 1 левостороннее выражение —> логическое
typeof Определение типа операнда R 1 любое —> строка
void Возврат неопределенного значения R 1 любое —> undefined
 *, /, % Умножение, деление, деление по модулю L 2 число, число —> число
+, - Сложение, вычитание L 2 число, число —> число
+ Конкатенация строк L 2 строка, строка —> строка
<< Сдвиг влево L 2 целое, целое —> целое
>> Сдвиг вправо с сохранением знака L 2 целое, целое —> целое
>>> Сдвиг вправо с заполнением нулями L 2 целое, целое -> целое
<, <=, >, >= Сравнение числовых значений L 2 число, число —> логическое
<, <=, >, >= Сравнение строк L 2 строка, строка —> логическое
instanceof Проверка на принадлежность классу L 2 объект, функция —> логическое
in Проверка наличия свойства L 2 строка, объект —> логическое
== Проверка равенства L 2 любое, любое —»логическое
!= Проверка неравенства L 2 любое, любое —> логическое
=== Проверка идентичности L 2 любое, любое —> логическое
!== Проверка неидентичности L 2 любое, любое —»логическое
& Поразрядное И L 2 целое, целое —»целое
^ Поразрядное ИСКЛЮЧАЮЩЕЕ ИЛИ L 2 целое, целое —»целое
| Поразрядное ИЛИ L 2 целое, целое —»целое
&& Логическое И L 2 любое, любое —> любое
|| Логическое ИЛИ L 2 любое, любое —> любое
?: Выбор второго или третьего операнда R 3 логическое, любое, любое —> любое
= Присваивание переменной или свойству R 2 левостороннее выражение, любое —> любое
*=. /=. %=. +=. -=, &=, "=, |=, <<=, >>=, >>>= Операция с присваиванием R 2 левостороннее выражение, любое —> любое
, Отбросить первый операнд, вернуть второй L 2 любое, любое —> любое

Обратите внимание, что большинство операторов обозначаются символами пунктуации, такими как + и =, а некоторые - ключевыми словами, например delete и instanceof. И ключевые слова, и знаки пунктуации обозначают обычные операторы, просто первые имеют менее лаконичный синтаксис.

Операторы в табл. 4.1  перечисленны в порядке их приоритетов. Операторы перечисленные первыми имеют более высокий приоритет. Операторы отделенные горизонтальной линией, имеют разные приоритеты. Столбец «А» в этой таблице содержит ассоциативность оператора (либо L - слева направо, либо R - справа налево), а столбец «N» определяет количество операндов. В столбце «Типы значений» указаны ожидаемые типы операндов и (после символа —») тип результата, возвращаемого оператором. В подразделах, следующих за таблицей, описываются концепции приоритетов, ассоциативности и типов операндов. Вслед за этим приводится обсуждение самих операторов.

4.7.1. Количество операндов

 Сделать закладку на этом месте книги

Операторы могут быть разбиты на категории по количеству требуемых им операндов. Большинство JavaScript-операторов, таких как оператор умножения *, являются двухместными. Такие операторы объединяют два выражения в одно, более сложное. То есть эти операторы работают с двумя операндами. JavaScript поддерживает также несколько унарных операторов, которые преобразуют одно выражение в другое, более сложное. Оператор - в выражении -х является унарным оператором, выполняющим смену знака операнда х. И, наконец, JavaScript поддерживает один тернарный условный оператор ?:, который объединяет три выражения в одно.

4.7.2. Типы данных операндов и результата

 Сделать закладку на этом месте книги

Некоторые операторы могут работать со значениями любых типов, но большинство из них требуют, чтобы операнды имели значения определенного типа, и большинство операторов возвращают значение определенного типа. Колонка «Типы значений» в табл. 4.1 определяет типы операндов (перед стрелкой) и тип результата (после стрелки) для операторов.

Операторы в языке JavaScript обычно преобразуют типы своих операндов (как описывается в разделе 3.8) по мере необходимости. Оператор умножения * ожидает получить числовые операнды, однако выражение "3" * "5" считается вполне допустимым благодаря тому, что интерпретатор выполнит преобразование строковых операндов в числа. Значением этого выражения будет число 15, а не строка "15". Не забывайте также, что любое значение в JavaScript может быть «истинным» или «ложным», поэтому операторы, ожидающие получить логические операнды, будут работать с операндами любого типа.

Некоторые операторы ведут себя по-разному в зависимости от типа операндов. Самый яркий пример - оператор +, который складывает числовые операнды и выполняет конкатенацию строк. Аналогично операторы сравнения, такие как <, сравнивают значения как числа или как строки, в зависимости от типов операндов. О зависимостях от типов операндов и о выполняемых преобразованиях будет рассказываться в описаниях отдельных операторов.

4.7.3. Левосторонние выражения

 Сделать закладку на этом месте книги

Обратите внимание, что операторы присваивания, как и некоторые другие, перечисленные в табл. 4.1, ожидают получить в качестве операндов левосторонние выражения  (lvalue). Левостороннее выражение - это исторический термин, обозначающий «выражение, которое может присутствовать слева от оператора присваивания». В JavaScript левосторонними выражениями являются переменные, свойства объектов и элементы массивов. Спецификация ECMAScript разрешает встроенным функциям возвращать левосторонние выражения, но не определяет никаких встроенных функций, ведущих себя подобным образом.

4.7.4. Побочные эффекты операторов

 Сделать закладку на этом месте книги

Вычисление простого выражения, такого как 2*3, никак не отразится на состоянии программы и никак не затронет последующие вычисления, выполняемые программой. Однако некоторые выражения могут иметь побочные эффекты, и их вычисление может оказывать влияние на результаты последующих вычислений. Наиболее очевидным примером являются операторы присваивания: если переменной или свойству присвоить некоторое значение, это повлияет на результат любого выражения, в котором используется эта переменная или свойство. Аналогичный побочный эффект имеют операторы инкремента ++ и декремента поскольку они неявно выполняют присваивание. Оператор delete также имеет побочный эффект: операция удаления свойства напоминает (хотя и недостаточно близко) присваивание свойству значения undefined.

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

4.7.5. Приоритет операторов

 Сделать закладку на этом месте книги

Операторы перечислены в табл. 4.1 в порядке уменьшения приоритета, и горизонтальные линии отделяют группы операторов с разным уровнем приоритета. Приоритет оператора управляет порядком, в котором выполняются операции. Операторы с более высоким приоритетом (ближе к началу таблицы) выполняются раньше операторов с более низким приоритетом (ближе к концу таблицы).

Рассмотрим следующее выражение:


w = х + у * z;


Оператор умножения * имеет более высокий приоритет по сравнению с оператором сложения +, поэтому умножение выполняется раньше сложения. Оператор присваивания = имеет наименьший приоритет, поэтому присваивание выполняется после завершения всех операций в правой части.

Приоритет операторов может быть переопределен с помощью скобок. Чтобы сложение в предыдущем примере выполнялось раньше, надо написать:


w = (х + у)* z;


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


typeof my.functions[x](y)


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

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

4.7.6. Ассоциативность операторов

 Сделать закладку на этом месте книги

В табл. 4.1 в столбце «А» указана ассоциативность операторов. Значение L указывает на ассоциативность слева направо, а значение R- на ассоциативность справа налево. Ассоциативность оператора определяет порядок выполнения операций с одинаковым приорите


убрать рекламу






том. Ассоциативность слева направо означает, что операции выполняются слева направо. Например, оператор вычитания имеет ассоциативность слева направо, поэтому следующие два выражения эквивалентны:


w = х - у - z;

w = ((х - у) - z);


С другой стороны, выражения


X = ~-у;

w = х = у = z;

q = a?b:c?d:e?f:g;


эквивалентны следующим выражениям:


x = ~(-у);

w = (х = (у = z));

q = a?b:(c?d:(e?f:g));


Причина в том, что унарные операторы, операторы присваивания и условные тернарные операторы имеют ассоциативность справа налево.

4.7.7. Порядок вычисления

 Сделать закладку на этом месте книги

Приоритет и ассоциативность операторов определяют порядок их выполнения в комплексных выражениях, но они не оказывают влияния на порядок вычислений в подвыражениях. Выражения в языке JavaScript всегда вычисляются слева направо. Например, в выражении w=x+y*z первым будет вычислено подвыражение w, затем х, у и z. После этого будет выполнено умножение значений у и z, затем сложение со значением х и результат будет присвоен переменной или свойству, определяемому выражением w. Добавляя в выражения круглые скобки, можно изменить относительный порядок выполнения операций умножения, сложения и присваивания, но нельзя изменить общий порядок вычислений слева направо.

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

4.8. Арифметические выражения

 Сделать закладку на этом месте книги

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

Основными арифметическими операторами являются * (умножение), / (деление), % (деление по модулю: остаток от деления), + (сложение) и - (вычитание). Как уже отмечалось, оператор + будет рассматриваться в отдельном разделе. Другие основные четыре оператора просто определяют значения своих операндов, преобразуют их значения в числа, если это необходимо, и вычисляют произведение, частное, остаток или разность значений. Нечисловые операнды, которые не могут быть преобразованы в числа, преобразуются в значение NaN. Если какой-либо из операндов имеет (или преобразуется в) значение NaN, результатом операции также будет значение NaN.

Оператор / делит первый операнд на второй. Если вам приходилось работать с языками программирования, в которых целые и вещественные числа относятся к разным типам, вы могли бы ожидать получить целый результат от деления одного целого числа на другое целое число. Однако в языке JavaScript все числа являются вещественными, поэтому все операции деления возвращают вещественный результат: выражение 5/2 вернет 2.5, а не 2. Деление на ноль возвращает положительную или отрицательную бесконечность, тогда как выражение 0/0 возвращает NaN; ни в одном из этих случаев не возбуждается исключение.

Оператор % производит деление по модулю первого операнда на второй. Иными словами, он возвращает остаток от целочисленного деления первого операнда на второй. Знак результата определяется знаком первого операнда. Например, выражение 5 % 2 вернет 1, а выражение -5 % 2 вернет -1.

Несмотря на то что оператор по модулю обычно применяется к целым числам, он также может оперировать вещественными значениями. Например, выражение 6.5 % 2.1 вернет 0,2.

4.8.1. Оператор +

 Сделать закладку на этом месте книги

Двухместный оператор + складывает числовые операнды или выполняет конкатенацию строковых операндов:


1 + 2                        // => З

"hello" + " " + "there"      // => "hello there"

"1" + "2"                    // => "12"


Когда значениями обоих операндов являются числа или строки, результат действия оператора + очевиден. Однако в других случаях возникает необходимость преобразования типов, а выполняемая операция зависит от результатов преобразований. В соответствии с правилами преобразований оператор + отдает предпочтение операции конкатенации строк: если один из операндов является строкой или объектом, который может быть преобразован в строку, другой операнд также преобразуется в строку, после чего выполняется операция конкатенации строк. Операция сложения выполняется, только если ни один из операндов не является строкой.

Формально оператор + использует следующий алгоритм работы:

• Если значением любого из операндов является объект, он преобразуется в простое значение с использованием алгоритма преобразования объекта в простое значение, описанного в разделе 3.8.3: объекты Date преобразуются с помощью их метода toString(), а все остальные объекты преобразуются с помощью метода valueOf(), если он возвращает простое значение. Однако большинство объектов не имеют метода valueOf(), поэтому они также преобразуются с помощью метода toString().

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

• В противном случае оба операнда преобразуются в числа (или в NaN) и выполняется операция сложения.

Например:


1 + 2     // => 3: сложение

"1" + "2" // => "12": конкатенация

"1" + 2   // => "12": конкатенация после преобразования числа в строку

1 + {}    // => ”1[object Object]": конкатенация после

          // преобразования объекта в строку

true + true // => 2: сложение после преобразования логического значения в число

2 + null  // => 2: сложение после преобразования null в 0

2 + undefined // => NaN: сложение после преобразования undefined в NaN


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


1 + 2 + "blind mice":   // => "3 blind mice"

1 + (2 + "blind mice"); // => ”12 blind mice"


В первом выражении отсутствуют скобки и оператор + имеет ассоциативность слева направо, благодаря чему сначала выполняется сложение двух чисел, а их сумма объединяется со строкой. Во втором выражении порядок выполнения операций изменен с помощью скобок: число 2 объединяется со строкой, давая в результате новую строку. А затем число 1 объединяется с новой строкой, что дает окончательный результат.

4.8.2. Унарные арифметические операторы

 Сделать закладку на этом месте книги

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

Ниже перечислены унарные арифметические операторы:

Унарный плюс (+)  

Унарный минус (-)  

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

Инкремент (++)  

Оператор ++ инкрементирует (т. е. увеличивает на единицу) свой единственный операнд, который должен быть левосторонним выражением (переменной, элементом массива или свойством объекта). Оператор преобразует свой операнд в число, добавляет к этому числу 1 и присваивает результат сложения обратно переменной, элементу массива или свойству.

Значение, возвращаемое оператором ++, зависит от его положения по отношению к операнду. Если поставить его перед операндом (префиксный оператор инкремента), то к операнду прибавляется 1, а результатом является увеличенное значение операнда. Если же он размещается после операнда (постфиксный оператор инкремента), то к операнду прибавляется 1, однако результатом является первоначальное, неувеличенное значение операнда. Взгляните на различия в следующих двух выражениях:


var і = 1, j = ++i; // і и j содержат значение 2

var і = 1, j = і++; // і содержит значение 2, j содержит значение 1


Обратите внимание, что выражение ++х не всегда возвращает тот же результат, что и выражение х=х+1. Оператор ++ никогда не выполняет операцию конкатенации строк: он всегда преобразует свой операнд в число и увеличивает его. Если х является строкой «1», выражение ++х вернет число 2, тогда как выражение х+1 вернет строку «11».

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

Данный оператор в обеих своих формах (префиксной и постфиксной) чаще всего применяется для увеличения счетчика, управляющего циклом for (раздел 5.5.3).

Декремент (--) 

Оператор — ожидает получить в качестве операнда левостороннее выражение. Он преобразует значение операнда в число, вычитает 1 и присваивает уменьшенное значение обратно операнду. Подобно оператору ++, точное поведение оператора -- зависит от его положения относительно операнда. Если он стоит перед операндом, то уменьшает операнд и возвращает уменьшенное значение; если оператор стоит после операнда, он уменьшает операнд, но возвращает первоначальное, неуменьшенное значение. При использовании постфиксной формы операнда не допускается вставлять перевод строки между оператором и операндом.

4.8.3. Поразрядные операторы

 Сделать закладку на этом месте книги

Поразрядные операторы выполняют низкоуровневые манипуляции с битами в двоичных представлениях чисел. Несмотря на то что они не выполняют арифметические операции в традиционном понимании, тем не менее они относятся к категории арифметических операторов, потому что оперируют числовыми операндами и возвращают числовое значение. Эти операторы редко используются в программировании на языке JavaScript, и если вы не знакомы с двоичным представлением целых чисел, то можете пропустить этот раздел. Четыре из этих операторов выполняют поразрядные операции булевой алгебры над отдельными битами операндов и действуют так, как если бы каждый бит каждого операнда был представлен логическим значением (1= true, 0=false). Три других поразрядных оператора применяются для сдвига битов влево и вправо.

Поразрядные операторы работают с целочисленными операндами и действуют так, как если бы эти значения были представлены 32-битными целыми, а не 64-битными вещественными значениями. При необходимости эти операторы преобразуют свои операнды в числа и затем приводят числовые значения к 32-битным целым, отбрасывая дробные части и любые биты старше 32-го. Операторы сдвига требуют, чтобы значение правого операнда было целым числом от 0 до 31. После преобразования этого операнда в 32-битное беззнаковое целое они отбрасывают любые биты старше 5-го, получая число в соответствующем диапазоне. Самое интересное, что эти операторы преобразуют значения NaN, Infinity и -Infinity в 0.

Поразрядное И (&) 

Оператор & выполняет операцию «логическое И» над каждым битом своих целочисленных аргументов. Бит результата устанавливается, если соответствующий бит установлен в обоих операндах. Например, выражение 0x1234 & OxOOFF даст в результате число 0x0034.

Поразрядное ИЛИ (|) 

Оператор | выполняет операцию «логическое ИЛИ» над каждым битом своих целочисленных аргументов. Бит результата устанавливается, если соответствующий бит установлен хотя бы в одном операнде. Например, выражение 0x1234 | 0x00FF даст в результате 0x12FF.

Поразрядное исключающее ИЛИ (^) 

Оператор ~ выполняет логическую операцию «исключающее ИЛИ» над каждым битом своих целочисленных аргументов. Исключающее ИЛИ означает, что должен быть истинен либо первый операнд, либо второй, но не оба сразу. Бит результата устанавливается, если соответствующий бит установлен в одном (но не в обоих) из двух операндов. Например, выражение OxFFOO ~ 0xF0F0 даст в результате 0x0FF0.

Поразрядное НЕ (~) 

Оператор ~ представляет собой унарный оператор, указываемый перед своим единственным целым операндом. Он выполняет инверсию всех битов операнда. Из-за способа представления целых со знаком в JavaScript применение оператора ~ к значению эквивалентно изменению его знака и вычитанию 1. Например, выражение “0x0f даст в результате 0xFFFFFFF0, или -16.

Сдвиг влево (<<) 

Оператор << сдвигает все биты в первом операнде влево на количество позиций, указанное во втором операнде, который должен быть целым числом в диапазоне от 0 до 31. Например, в операции а « 1 первый бит в а становится вторым битом, второй бит становится третьим и т. д. Новым первым битом становится ноль, значение 32-го бита теряется. Сдвиг значения влево на одну позицию эквивалентен умножению на 2, на две позиции - умножению на 4 и т. д. Например, выражение 7 << 2 даст в результате 28.

Сдвиг вправо с сохранением знака (>>) 

Оператор >> сдвигает все биты своего первого операнда вправо на количество позиций, указанное во втором операнде (целое между 0 и 31). Биты, сдвинутые за правый край, теряются. Самый старший бит не изменяется, чтобы сохранить знак результата. Если первый операнд положителен, старшие биты результата заполняются нулями; если первый операнд отрицателен, старшие биты результата заполняются единицами. Сдвиг значения вправо на одну позицию эквивалентен делению на 2 (с отбрасыванием остатка), сдвиг вправо на две позиции эквивалентен делению на 4 и т. д. Например, выражение 7 >> 1 даст в результате 3, а выражение -7 >> 1 даст в результате -4.

Сдвиг вправо с заполнением нулями (>>>) 

Оператор >>> аналогичен оператору », за исключением того, что при сдвиге старшие разряды заполняются нулями, независимо от знака первого операнда. Например, выражение -1 >>> 4 даст в результате -1, а выражение -1 >>> 4 даст в результате 0x0FFFFFFF.

4.9. Выражения отношений

 Сделать закладку на этом месте книги

В этом разделе описаны операторы отношения в языке JavaScript. Это операторы проверяют отношение между двумя значениями (такое как «равно», «меньше» или «является ли свойством») и возвращают true или false в зависимости от того, как соотносятся операнды. Выражения отношений всегда возвращают логические значения, и эти значения чаще всего применяются в инструкциях if, while и for для управления ходом исполнения программы (глава 5). В следующих подразделах описываются операторы равенства и неравенства, операторы сравнения и два других оператора отношений, in и instanceof.

4.9.1. Операторы равенства и неравенства

 Сделать закладку на этом месте книги

Операторы == и === проверяют два значения на совпадение, используя два разных определения совпадения. Оба оператора принимают операнды любого типа и возвращают true, если их операнды совпадают, и false, если они различны. Оператор ===, известный как оператор идентичности, проверяет два операнда на «идентичность», руководствуясь строгим определением совпадения. Оператор ==, оператор равенства, проверяет, равны ли два его операнда в соответствии с менее строгим определением совпадения, допускающим преобразования типов.

В языке JavaScript поддерживаются операторы =, == и ===. Убедитесь, что вы понимаете разницу между операторами присваивания, равенства и идентичности. Будьте внимательны и применяйте правильные операторы при разработке своих программ! Очень заманчиво назвать все три оператора «равно», но во избежание путаницы лучше читать оператор = как «получается», или «присваивается», оператор == читать как «равно», а словом «идентично» обозначать оператор ===.

Операторы != и !== выполняют проверки в точности противоположные операторам == и ===. Оператор неравнства != возвращает значение false если два значения равны друг другу в том смысле, в каком они считаются равными оператором ==, и true в противном случае. Как будет рассказываться в разделе 4.10, оператор ! выполняет логическую операцию НЕ. Отсюда легко будет запомнить, что операторы!= и ! == означают «не равно» и «не идентично».

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

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

• Если два значения имеют различные типы, они не идентичны.

• Если оба операнда являются значением null или undefined, они идентичны.

• Если оба операнда являются логическим значением true или оба являются логическим значением false, они идентичны.

• Если одно или оба значения являются значением NaN, они не идентичны. Значение NaN никогда не бывает идентичным никакому значению, даже самому себе! Чтобы проверить, является ли значение х значением NaN, следует использовать выражение х !== х. ЗначениеNaN - единственное, для которого такая проверка вернет true.

• Если оба значения являются числами с одним и тем же значением, они идентичны. Если один операнд имеет значение 0, а другой -0, они также идентичны.

• Если оба значения являются строками и содержат одни и те же 16-битные значения (подробности во врезке в разделе 3.2) в одинаковых позициях, они идентичны. Если строки отличаются длиной или содержимым, они не идентичны. Две строки могут иметь один и тот же смысл и одинаково выглядеть на экране, но содержать отличающиеся последовательности 16-битных значений. Интерпретатор JavaScript не выполняет нормализацию символов Юникода, поэтому подобные пары строк не считаются операторами === и == ни равными, ни идентичными. Другой способ сравнения строк обсуждается в части III книги, в описании метода String.localeCompare().

• Если оба значения ссылаются на один и тот же объект, массив или функцию, то они идентичны. Если они ссылаются на различные объекты (массивы или функции), они не идентичны, даже если оба объекта имеют идентичные свойства.

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

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

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

• Если одно значение null, а другое -undefined, то они равны.

• Если одно значение является числом, а другое - строкой, то строка преобразуется в число и выполняется сравнение с преобразованным значением!

• Если какое-либо значение равно true, оно преобразуется в 1 и сравнение выполняется снова. Если какое-либо значение равно false, оно преобразуется в 0 и сравнение выполняется снова.

• Если одно из значений является объектом, а другое - числом или строкой, объект преобразуется в простой тип (как описывалось в разделе 3.8.3) и сравнение выполняется снова. Объект преобразуется в значение простого типа либо с помощью своего метода toString(), либо с помощью своего метода valueOf(). Встроенные классы базового языка JavaScript сначала пытаются выполнить преобразование valueOf(), а затем toString(), кроме класса Date, который всегда выполняет преобразование toString(). Объекты, не являющиеся частью базового JavaScript, могут преобразовывать себя в значения простых типов способом, определенным их реализацией.

• Любые другие комбинации значений не являются равными.

В качестве примера проверки на равенство рассмотрим сравнение:


"1" == true


Результат этого выражения равен true, т. е. эти по-разному выглядящие значения фактически равны. Логическое значение true преобразуется в число 1, и сравнение выполняется снова. Затем строка 1” преобразуется в число 1. Поскольку оба числа теперь совпадают, оператор сравнения возвращает true.

4.9.2. Операторы сравнения

 Сделать закладку на этом месте книги

Операторы сравнения определяют относительный порядок двух величин (числовых или строковых):

Меньше (<) 

Оператор < возвращает true, если первый операнд меньше, чем второй операнд; в противном случае он возвращает false.

Больше (>) 

Оператор > возвращает true, если его первый операнд больше, чем второй операнд; в противном случае он возвращает false.

Меньше или равно (<=) 

Оператор <= возвращает true, если первый операнд меньше или равен второму операнду; в противном случае он возвращает false.

Больше или равно (>=) 

Оператор >= возвращает true, если его первый операнд больше второго или равен ему; в противном случае он возвращает false.

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

• Если какой-либо операнд является объектом, этот объект преобразуется в простое значение, как было описано в конце раздела 3.8.3: если метод valueOf() объекта возвращает простое значение, используется это значение. В противном случае используется значение, возвращаемое методом toString().

• Если после преобразований объектов в простые значения оба операнда оказываются строками, они сравниваются как строки в соответствии с алфавитным порядком, где под «алфавитным порядком» понимается числовой порядок 16-битных значений кодовых пунктов Юникода, составляющих строки.

• Если после преобразований объектов в простые значения хотя бы один операнд не является строкой, оба операнда преобразуются в числа и сравниваются как числа. Значения 0 и -0 считаются равными. Значение Infinity считается больше любого другого числа, а значение -Infinity - меньше любого другого числа. Если какой-либо из операндов преобразуется в значение NaN, то оператор сравнения всегда возвращает false.

Не забывайте, что строки в JavaScript являются последовательностями 16-бит-ных целочисленных значений, и сравнение строк фактически сводится к числовому сравнению этих значений в строках. Порядок кодирования символов, определяемый стандартом Юникода, может не совпадать с традиционным алфавитным порядком, используемым в конкретных языках или регионах. Обратите внимание, что сравнение строк производится с учетом регистра символов и все прописные буквы в кодировке ASCII «меньше» соответствующих им строчных букв ASCII. Это правило может приводить к непонятным результатам. Например, согласно оператору < строка "Zoo” меньше строки "aardvark”.

При сравнении строк более надежные результаты позволяет получить метод String.localeCompare(), который учитывает национальные определения «алфавитного порядка». Для сравнения без учета регистра необходимо сначала преобразовать строки в нижний или верхний регистр с помощью метода String.toLowerCase() или String.toUpperCase().

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


1+2 // Сложение. Результат: 3.

"1" + "2" // Конкатенация. Результат: "12".

"1" +2 // Конкатенация. 2 преобразуется в "2". Результат: "12".

11 < 3 // Числовое сравнение. Результат: false.

"11" < "3" // Строковое сравнение. Результат: true.

”11" < 3 // Числовое сравнение. "11" преобразуется в 11. Результат: false

"one" < 3 // Числовое сравнение, "one" преобразуется в NaN. Результат: false.


Наконец, обратите внимание, что операторы <= (меньше или равно) и >= (больше или равно) для определения «равенства» двух значений не используют операторы равенства или идентичности. Оператор «меньше или равно» определяется просто как «не больше», а оператор «больше или равно» - как «не меньше». Единственное исключение имеет место, когда один из операндов представляет собой значение NaN (или преобразуется в него). В этом случае все четыре оператора сравнения возвращают false.

4.9.3. Оператор in

 Сделать закладку на этом месте книги

Оператор in требует, чтобы левый операнд был строкой или мог быть


убрать рекламу






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


var point = { х:1, у:1 }; // Определить объект

"х" in point // => true: объект имеет свойство с именем "х"

"z" in point // => false: объект не имеет свойства с именем "z"

"toString” in point // => true: объект наследует метод toString

var data = [7,8,9]; // Массив с элементами 0, 1 и 2

"0" in data  // => true: массив содержит элемент "0"

1 in data    // => true: числа преобразуются в строки

3 in data     // => false: нет элемента 3


4.9.4. Оператор instanceof

 Сделать закладку на этом месте книги

Оператор instanceof требует, чтобы левым операндом был объект, а правым - имя класса объектов. Результатом оператора будет значение true, если объект, указанный слева, является экземпляром класса, указанного справа. В противном случае результатом будет false. В главе 9 рассказывается, что классы объектов в языке JavaScript определяются инициализировавшей их функцией-конструктором. Следовательно, правый операнд оператора instanceof должен быть именем функции-конструктора. Например:


var d = new Date(); // Создать новый объект с помощью конструктора Date()

d instanceof Date; // Вернет true; объект d был создан с функцией Date()

d instanceof Object; // Вернет true; все объекты являются экземплярами Object

d instanceof Number; // Вернет false; d не является объектом Number


var a = [1, 2, 3]; // Создать массив с помощью литерала массива

a instanceof Array; // Вернет true; а - это массив

a instanceof Object; // Вернет true; все массивы являются объектами

a instanceof RegExp;// Вернет false; массивы не являются регулярными выражениями


Обратите внимание, что все объекты являются экземплярами класса Object. Определяя, является ли объект экземпляром класса, оператор instanceof принимает во внимание и «суперклассы». Если левый операнд instanceof не является объектом, instanceof возвращает false. Если правый операнд не является функцией, возбуждается исключение ТуреЕггог.

Чтобы понять, как действует оператор instanceof, необходимо познакомиться с таким понятием, как «цепочка прототипов». Это - механизм наследования в JavaScript; он описывается в разделе 6.2.2. Чтобы вычислить значение выражения о instanceof f, интерпретатор JavaScript определяет значение f.prototype и затем пытается отыскать это значение в цепочке прототипов объекта о. В случае успеха объект о считается экземпляром класса f (или суперкласса класса f), и оператор возвращает true. Если значение f.prototype отсутствует в цепочке прототипов объекта о, то объект о не является экземпляром класса f и оператор instanceof возвращает false.

4.10. Логические выражения

 Сделать закладку на этом месте книги

Логические операторы &&, || и ! используются для выполнения операций булевой алгебры и часто применяются в сочетании с операторами отношений для объединения двух выражений отношений в одно более сложное выражение. Эти операторы описываются в подразделах, следующих ниже. Чтобы понять, как они действуют, вам может потребоваться еще раз прочитать о концепции «истинности» и «ложности» значений в разделе 3.3.

4.10.1. Логическое И (&&)

 Сделать закладку на этом месте книги

Условно говоря, оператор && действует на трех уровнях. На самом простом уровне, когда в операции участвуют логические операнды, оператор && выполняет операцию «логическое И» над двумя значениями: он возвращает true тогда и только тогда, когда оба операнда имеют значение true. Если один или оба операнда имеют значение false, оператор возвращает false.

Оператор && часто используется для объединения двух выражений отношений:


х == 0 && у == 0 // true тогда и только тогда, когда х и у равны 0


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

Но оператор && не требует, чтобы его операнды были логическими значениями. Напомню, что все значения в языке JavaScript являются либо «истинными», либо «ложными». (Подробности в разделе 3.3. Ложными значениями являются false, null, undefined, 0, -0, NaN и "". Все другие значения, включая все объекты, являются истинными.) На втором уровне оператор && действует как логическое И для истинных и ложных значений. Если оба операнда являются истинными, оператор возвращает истинное значение. В противном случае, когда один или оба операнда являются ложными, возвращается ложное значение. В языке JavaScript все выражения и инструкции, использующие логические значения, будут также работать с истинными или ложными значениями, поэтому тот факт, что оператор && не всегда возвращает true или false, на практике не вызывает никаких проблем.

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

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


var о = { х : 1 };

var р = null;

о && о.х // => 1: о - истинное значение, поэтому возвращается о.х

р && р.х // => null: р - ложное значение, поэтому возвращается р,

         // а выражение р.х не вычисляется


Важно понимать, что оператор && может не вычислять выражение правого операнда. В примере выше переменная р имеет значение null, поэтому попытка вычислить выражение р.х привела бы к ошибке ТуреЕггог. Но здесь задействован оператор &&, благодаря чему выражение р.х вычисляется, только если р будет содержать истинное значение - не null или undefined.

Такое поведение оператора && иногда называют «короткой схемой вычислений», и иногда можно встретить программный код, в котором такое поведение оператора && используется специально для выполнения инструкций по условию. Например, следующие две строки дают одинаковый результат:


if (а == b) stop(); // Функция stop() вызывается, только если а == b

(а == b) && stop(); // То же самое


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

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

4.10.2. Логическое ИЛИ (||)

 Сделать закладку на этом месте книги

Оператор || выполняет операцию «логическое ИЛИ» над двумя операндами. Если один или оба операнда имеют истинное значение, он возвращает истинное значение. Если оба операнда имеют ложные значения, он возвращает ложное значение.

Хотя оператор || чаще всего применяется просто как оператор «логическое ИЛИ», он, как и оператор &&, ведет себя более сложным образом. Его работа начинается с вычисления первого операнда, выражения слева. Если значение этого операнда является истинным, возвращается истинное значение. В противном случае оператор вычисляет второй операнд, выражение справа, и возвращает значение этого выражения.

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

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


// Если переменная max_width определена, используется ее значение. В противном случае

// значение извлекается из объекта preferences. Если объект (или его свойство max_with)

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

var max = max_width || preferences.max_width || 500;


Этот прием часто используется в функциях для определения значений по умолчанию параметров:


// Скопировать свойства объекта о в объект р и вернуть р

function сору(о, р) {

  р = р || {}; // Если объект р не был передан, создать новый объект.

  // реализация тела функции

}


4.10.3. Логическое НЕ (!)

 Сделать закладку на этом месте книги

Оператор ! является унарным оператором, помещаемым перед одиночным операндом. Он используется для инверсии логического значения своего операнда. Например, если переменная х имеет истинное значение, то выражение возвращает значение false. Если х имеет ложное значение, то выражение возвращает значение false. (true - вообще-то)

В отличие от операторов && и ||, оператор ! преобразует свой операнд в логическое значение (используя правила, описанные в главе 3) перед тем, как инвертировать его. Это означает, что оператор ! всегда возвращает true или false что всегда можно преобразовать любое значение х в его логический эквивалент, дважды применив этот оператор: !!х (раздел 3.8.2).

Будучи унарным, оператор ! имеет высокий приоритет и тесно связан с операндом. Если вам потребуется инвертировать значение выражения, такого как р && q, необходимо будет использовать круглые скобки: ! (р && q). В булевой алгебре есть две теоремы, которые можно выразить на языке JavaScript:

// Следующие две проверки на идентичность дают положительный результат

// при любых значениях р и q

!(р && q) === !р || !q !(р || q) === !р && !q

4.11. Выражения присваивания

 Сделать закладку на этом месте книги

Для присваивания значения переменной или свойству в языке JavaScript используется оператор =. Например:


і = 0 // Присвоит переменной і значение 0.

о.х = 1 // Присвоит свойству х объекта о значение 1.


Левым операндом оператора = должно быть левостороннее выражение: переменная, элемент массива или свойство объекта. Правым операндом может быть любое значение любого типа. Значением оператора присваивания является значение правого операнда. Побочный эффект оператора = заключается в присваивании значения правого операнда переменной или свойству, указанному слева, так что при последующих обращениях к переменной или свойству будет получено это значение.

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


(а = b) == 0


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

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


i = j = к = 0; // Инициализировать 3 переменные значением 0


4.11.1. Присваивание с операцией

 Сделать закладку на этом месте книги

Помимо обычного оператора присваивания = в языке JavaScript поддерживается несколько других операторов, объединяющих присваивание с некоторой другой операцией. Например, оператор += выполняет сложение и присваивание. Следующее выражение:


total += sales_tax


эквивалентно выражению:


total = total + sales_tax


Как можно было ожидать, оператор += работает и с числами, и со строками. Для числовых операндов он выполняет сложение и присваивание, а для строковых -конкатенацию и присваивание.

Из подобных ему операторов можно назвать -=, *=, &= и др. Все операторы присваивания с операцией перечислены в табл. 4.2.



В большинстве случаев выражение:


а ор= b


где ор означает оператор, эквивалентно выражению:


а = a op b


В первой строке выражение а вычисляется только один раз. Во второй - дважды. Эти выражения отличаются, только если подвыражение а содержит операции, имеющие побочные эффекты, такие как вызов функции или оператор инкремента. Например, следующие два выражения присваивания неэквивалентны:


data[i++] *= 2;

data[i++] = data[i++] * 2;


4.12. Вычисление выражений

 Сделать закладку на этом месте книги

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


eval("3+2") // => 5


Динамическое выполнение строк с программным кодом является мощной особенностью языка, которая практически никогда не требуется на практике. Если у вас появится желание использовать функцию eval(), задумайтесь - действительно ли это необходимо.

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



eval() - функция или оператор?

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


var f = eval;

var g = f;


Если допустить такую возможность, интерпретатор не сможет обеспечить безопасность оптимизации любых функций, вызывающих g(). Данной проблемы можно было бы избежать, если бы eval была оператором (и зарезервированным словом). С ограничениями, накладываемыми на функцию eval(), которые делают ее более похожей на оператор, мы познакомимся в разделах ниже (разделы 4.12.2 и 4.12.3).

************************************************************************

4.12.1. eval()

 Сделать закладку на этом месте книги

Функция eval() принимает единственный аргумент. Если передать ей значение, отличное от строки, она просто вернет это значение. Если передать ей строку, она попытается выполнить синтаксический анализ этой строки как программного кода на языке JavaScript и возбудит исключение SyntaxError в случае неудачи. В случае успеха она выполнит этот программный код и вернет значение последнего выражения или инструкции в строке либо значение undefined, если последнее выражение или инструкция не имеют значения. Если программный код в строке возбудит исключение, функция eval() передаст это исключение дальше.

Ключевой особенностью функции eval() (когда она вызывается таким способом) является то обстоятельство, что она использует окружение программного кода, вызвавшего ее. То есть она будет отыскивать значения переменных и определять новые переменные и функции, как это делает локальный программный код. Если функция определит локальную переменную х и затем вызовет eval("x”), она получит значение локальной переменной. Вызов eval( "x=1") изменит значение локальной переменной. А если выполнить вызов eval("vaг у = 3; ”), будет объявлена новая локальная переменная у. Точно так же можно определять новые локальные функции:


eval("function f() { return x+1; }");


Если вызвать функцию eval() из программного кода верхнего уровня, она, разумеется, будет оперировать глобальными переменными и глобальными функциями.

Обратите внимание, что программный код в строке, передаваемой функции eval(), должен быть синтаксически осмысленным - эту функцию нельзя использовать, чтобы вставить фрагмент программного кода в вызывающую функцию. Например, бессмысленно писать вызов eval("return;"), потому что инструкция return допустима только внутри функций, а тот факт, что программный код в строке использует то же самое окружение, что и вызывающая функция, не делает его частью этой функции. Если программный код в строке может расцениваться как самостоятельный сценарий (пусть и очень короткий, такой как х=0), его уже можно будет передавать функции eval(). В противном случае eval() возбудит исключение SyntaxError.

4.12.2. Использование eval() в глобальном контексте

 Сделать закладку на этом месте книги

Способность функции eval() изменять локальные переменные представляет значительную проблему для оптимизаторов JavaScript. Для ее решения некоторые интерпретаторы просто уменьшают степень оптимизации всех функций, вызывающих eval(). Однако как быть интерпретатору JavaScript, когда в сценарии определяется псевдоним функции eval() и выполняется ее вызов по другому имени? Чтобы облегчить жизнь разработчикам интерпретаторов JavaScript, стандарт ECMAScript 3 требует, чтобы такая возможность в интерпретаторах была запрещена. Если функция eval() вызывается под любым другим именем, отличным от «eval», она должна возбуждать исключение EvalError.

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

Стандарт ECMAScript 5 отменяет возбуждение исключения EvalError и стандартизует поведение eval(), сложившееся де-факто. «Прямой вызов» - это вызов функции eval() по ее непосредственному имени «eval» (которое все больше начинает походить на зарезервированное слово). Прямые вызовы eval() используют окружение вызывающего контекста. Любые другие вызовы - косвенные вызовы - в качестве окружения используют глобальный объект и не могут получать, изменять или определять локальные переменные или функции. Это поведение демонстрируется в следующем фрагменте:


var geval = eval;         // Другое имя eval для вызова в глобальном контексте

var х = "global", у = "global"; // Две глобальные переменные

function f() { // Вызывает eval в локальном контексте

  var х = "local"; // Определение локальной переменной

  eval("x += 'changed';"); // Прямой вызов eval изменит локальную переменную

  return х; // Вернет измененную локальную переменную

}

function g() {     // Вызывает eval в глобальном контексте

  var у = "local"; // Локальная переменная

  geval("y += 'changed';") // Косвенный вызов eval изменит глоб. переменную

  return y;        // Вернет неизмененную локальную переменную

}

console.log(f(), х); //Изменилась локальная переменная: выведет "localchanged global":

console.log(g(), у); //Изменилась глобальная переменная: выведет "local globalchanged":


Обратите внимание, что обеспечение возможности вызывать функцию eval() в глобальном контексте - это не просто попытка удовлетворить потребности оптимизатора. Фактически это чрезвычайно полезная особенность: она позволяет выполнять строки с программным кодом, как если бы они были независимыми сценариями. Как уже отмечалось в начале этого раздела, действительная необходимость выполнять строки программного кода на практике возникает очень редко. Но если вы сочтете это необходимым, вам, скорее всего, потребуется вызывать функцию eval() именно в глобальном контексте, а не в локальном.

До появления версии IE9 Internet Explorer отличался от других броузеров: функцияeval(), вызванная под другим именем, выполняла переданный ей программный код не в глобальном контексте. (Однако она не возбуждала исключение EvalError: программный код просто выполнялся ею в локальном контексте.) Но IE определяет глобальную функцию execScript(), которая выполняет строку с программным кодом, переданную в виде аргумента, как если бы она была сценарием верхнего уровня. (Однако, в отличие от eval(), функция execScript() всегда возвращаетnull.)

4.12.3. Использование eval() в строгом режиме

 Сделать закладку на этом месте книги

Строгий режим (раздел 5.7.3), определяемый стандартом ECMAScript 5, вводит дополнительные ограничения на поведение функции eval() и даже на использование идентификатора «eval». Когда функция eval() вызывается из программного кода, выполняемого в строгом режиме, или когда строка, которая передается функции, начинается с директивы «use strict», то eval() выполняет программный код в частном окружении. Это означает, что в строгом режиме выполняемый программный код может обращаться к локальным переменным и изменять их, но он не может определять новые переменные или функции в локальной области видимости.

Кроме того, строгий режим делает функцию eval() еще более похожей на оператор, фактически превращая «eval» в зарезервированное слово. В этом режиме нельзя переопределить функцию eval() новым значением. А также нельзя объявить переменную, функцию, параметр функции или параметр блока catch с именем «eval».

4.13. Прочие операторы

 Сделать закладку на этом месте книги

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

4.13.1. Условный оператор (?:)

 Сделать закладку на этом месте книги

Условный оператор - это единственный тернарный (с тремя операндами) оператор в JavaScript, и иногда он так и называется - «тернарный оператор». Этот оператор обычно записывается как ?:, хотя в программах он выглядит по-другому. Он имеет три операнда, первый предшествует символу ?, второй - между ? и :, третий - после :. Используется он следующим образом:


х > 0 ? х : -х // Абсолютное значение х


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


убрать рекламу






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

Тот же результат можно получить с помощью инструкции if, но оператор ?: часто оказывается удобным сокращением. Ниже приводится типичный пример, в котором проверяется, определена ли переменная (и имеет истинное значение), и если да, то берется ее значение, а если нет, берется значение по умолчанию:


greeting = "hello " + (username ? username : "there");


Эта проверка эквивалентна следующей конструкции if, но более компактна:


greeting = "hello";

if (username)

  greeting += username;

else

  greeting += "there";


4.13.2. Оператор typeof

 Сделать закладку на этом месте книги

Унарный оператор typeof помещается перед единственным операндом, который может иметь любой тип. Его значением является строка, указывающая на тип данных операнда. Следующая таблица определяет значения оператора typeof для всех значений, возможных в языке JavaScript:



Оператор typeof может применяться, например, в таких выражениях:


(typeof value == "string") ? ..... + value + ..... : value


Оператор typeof можно также использовать в инструкции switch (раздел 5.4.3). Обратите внимание, что операнд оператора typeof можно заключить в скобки, что делает оператор typeof более похожим на имя функции, а не на ключевое слово или оператор:


typeof(і)


Обратите внимание, что для значения null оператор typeof возвращает строку «object». Если вам потребуется отличать null от других объектов, добавьте проверку для этого спецслучая. Для объектов, определяемых средой выполнения, оператор typeof может возвращать строку, отличную от «object». Однако на практике большинство таких объектов в клиентском JavaScript имеют тип «object».

Для всех объектных типов и типов массивов результатом оператора typeof является строка «object», поэтому он может быть полезен только для определения принадлежности значения к объектному или к простому типу. Чтобы отличить один класс объектов от другого, следует использовать другие инструменты, такие как оператор instanceof (раздел 4.9.4), атрибут class (раздел 6.8.2) или свойство constructor (разделы 6.8.1 и 9.2.2).

Несмотря на то что функции в JavaScript также являются разновидностью объектов, оператор typeof отличает функции, потому что они имеют собственные возвращаемые значения. В JavaScript имеется тонкое отличие между функциями и «вызываемыми объектами». Функции могут вызываться, но точно так же можно создать вызываемый объект - который может вызываться подобно функции, -не являющийся настоящей функцией. В спецификации ECMAScript 3 говорится, что оператор typeof должен возвращать строку «function» для всех объектов базового языка, которые могут вызываться. Спецификация ECMAScript 5 расширяет это требование и требует, чтобы оператор typeof возвращал строку «function» для всех вызываемых объектов, будь то объекты базового языка или среды выполнения. Большинство производителей броузеров для реализации методов своих объектов среды выполнения используют обычные объекты-функции базового языка JavaScript. Однако корпорация Microsoft для реализации своих клиентских методов всегда использовала собственные вызываемые объекты, вследствие чего в версиях до IE9 оператор typeof возвращает строку «object» для них, хотя они ведут себя как функции. В версии ІЕ9 клиентские методы были реализованы как обычные объекты-функции базового языка. Подробнее об отличиях между истинными функциями и вызываемыми объектами рассказывается в разделе 8.7.7.

4.13.3. Оператор delete

 Сделать закладку на этом месте книги

Унарный оператор delete выполняет попытку удалить свойство объекта или элемент массива, определяемый операндом.[3] Подобно операторам присваивания, инкремента и декремента, оператор delete обычно используется ради побочного эффекта, выражающегося в удалении свойства, а не ради возвращаемого значения. Ниже приводятся несколько примеров его использования:


var о = {х: 1, у: 2}; // Определить объект

delete о.х;           // Удалить одно из его свойств

"х" in о              // => false: свойство больше не существует

var а = [1,2,3]:      // Создать массив

delete а[2];          // Удалить последний элемент массива

2 in а                // => false: второй элемент больше не существует

a.length              // => 3: обратите внимание, что длина массива при этом не изменилась


Внимание: удаленное свойство или элемент массива не просто получает значениеundefined. После удаления свойства оно прекращает свое существование. Попытка прочитать значение несуществующего свойства возвратит значение undefined, но вы можете проверить фактическое наличие свойства с помощью оператора in (раздел 4.9.3). Операция удаления элемента массива оставляет в массиве «дырку» и не изменяет длину массива. В результате получается разреженный массив.

Оператор delete требует, чтобы его операнд был левосторонним выражением. Если операнд не является левосторонним выражением, оператор не будет выполнять никаких действий и вернет значение true. В противном случае delete попытается удалить указанное левостороннее выражение. В случае успешного удаления значения левостороннего выражения оператор delete вернет значение true. Не все свойства могут быть удалены: некоторые встроенные свойства из базового и клиентского языков JavaScript устойчивы к операции удаления. Точно так же не могут быть удалены пользовательские переменные, объявленные с помощью инструкции var. Кроме того, невозможно удалить функции, объявленные с помощью инструкции function, а также объявленные параметры функций.

В строгом режиме, определяемом стандартом ECMAScript 5, оператор delete возбуждает исключение SyntaxError, если его операндом является неквалифицированный идентификатор, такой как имя переменной, функции или параметра функции: он может оперировать только операндами, которые являются выражениями обращения к свойству (раздел 4.4). Кроме того, строгий режим определяет, что оператор delete должен возбуждать исключение ТуреЕггог, если запрошено удаление ненастраиваемого свойства (раздел 6.7). В обычном режиме в таких случаях исключение не возбуждается, и оператор delete просто возвращаетfalse, чтобы показать, что операнд не был удален.

Ниже приводится несколько примеров использования оператора delete:


var о = {х:1, у:2}; // Определить переменную; инициализировать ее объектом

delete о.х;         // Удалить одно из свойств объекта; вернет true

typeof о.х;         // Свойство не существует; вернет "undefined"

delete о.х;         // Удалить несуществующее свойство; вернет true

delete о;           // Объявленную переменную удалить нельзя; вернет false

                    // В строгом режиме возбудит исключение.

delete 1;    // Аргумент не является левосторонним выражением; вернет true

this.x =1;   // Определить свойство глобального объекта без var

delete х;    // Удалить: вернет true при выполнении в нестрогом режиме; в строгом

             // режиме возбудит исключение. Используйте 'delete this.x' взамен,

х;           // Ошибка времени выполнения: переменная х не определена


С оператором delete мы снова встретимся в разделе 6.3.


4.13.4. Оператор void

 Сделать закладку на этом месте книги

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

Чаще всего этот оператор применяется в клиентском JavaScript, в адресах URL вида JavaScript:, где он позволяет вычислить выражение ради его побочных действий, не отображая в броузере вычисленное значение. Например, оператор void можно использовать в HTML-теге <а>:


<а href="javascript:void window.open();">0ткрыть новое окно</а>


Эта разметка HTML была бы более очевидна, если бы вместо URL javascript: применялся обработчик события onclick, где в использовании оператора void нет никакой необходимости.

4.13.5. Оператор «запятая» (,)

 Сделать закладку на этом месте книги

Оператор «запятая» (,) является двухместным оператором и может принимать операнды любого типа. Он вычисляет свой левый операнд, вычисляет свой правый операнд и возвращает значение правого операнда. То есть следующая строка:


i=0, j=1, k=2;


вернет значение 2 и практически эквивалентна строке:


і = 0; j=1; k = 2;


Выражение слева вычисляется всегда, но его значение отбрасывается, поэтому применять оператор запятая имеет смысл только ради побочного эффекта левого операнда. Единственным типичным применением оператора запятая является его использование в циклах for (раздел 5.5.3) с несколькими переменными цикла:


// Первая запятая ниже является частью синтаксиса инструкции var

// Вторая запятая является оператором: она позволяет внедрить 2 выражения (i++ и j--)

// в инструкцию (цикл for), которая ожидает 1 выражение.

for(var i=0,j=10; і < j; i++,j --)

  console.log(i+j);




5

Инструкции

 Сделать закладку на этом месте книги

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

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

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

Условные инструкции , такие как if и switch, которые заставляют интерпретатор JavaScript выполнять или пропускать другие инструкции в зависимости от значения выражения.

Инструкции циклов , такие как while и for, которые многократно выполняют другие инструкции.

Инструкции переходов , такие как break, return и throw, которые заставляют интерпретатор выполнить переход в другую часть программы.

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

5.1. Инструкции-выражения

 Сделать закладку на этом месте книги

Простейший вид инструкций в JavaScript - это выражения, имеющие побочные эффекты. (Загляните в раздел 5.7.3, где описывается инструкция-выражение, не имеющая побочных эффектов.) Инструкции такого рода мы рассматривали в главе 4. Основной категорией инструкций-выражений являются инструкции присваивания. Например:


greeting = "Hello " + name;

і *= 3;


Операторы инкремента и декремента, ++ и -- схожи с инструкциями присваивания. Их побочным эффектом является изменение значения переменной, как при выполнении присваивания:


counter++;


Оператор delete имеет важный побочный эффект - он удаляет свойство объекта. Поэтому он почти всегда применяется как инструкция, а не как часть более сложного выражения:


delete о.х;


Вызовы функций - еще одна большая категория инструкций-выражений. Например:


alert(greeting);

window.close();


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


Math.cos(x);


Наоборот, надо вычислить значение и присвоить его переменной для дальнейшего использования:


сх = Math.cos(x);


Обратите внимание, что каждая строка в этих примерах завершается точкой с запятой.

5.2. Составные и пустые инструкции

 Сделать закладку на этом месте книги

Подобно оператору запятой (раздел 4.13.5), объединяющему несколько выражений в одно выражение, блок инструкций позволяет объединить несколько инструкций в одну составную инструкцию . Блок инструкций  - это просто последовательность инструкций, заключенная в фигурные скобки. Таким образом, следующие строки рассматриваются как одна инструкция и могут использоваться везде, где интерпретатор JavaScript требует наличия единственной инструкции:


{

  х = Math.PI;

  сх = Math.cos(x);

  console. log("cos(7t) = " + сх);

}


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

Объединение инструкций в более крупные блоки инструкций используется в языке JavaScript повсеместно. Подобно тому как выражения часто включают другие подвыражения, многие инструкции JavaScript могут содержать другие инструкции. Формальный синтаксис языка JavaScript обычно позволяет использовать не более одной подынструкции. Например, синтаксис инструкции цикла while включает единственную подынструкцию, которая служит телом цикла. Блоки инструкций позволяют помещать любое количество инструкций там, где требуется наличие единственной подынструкции.

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


Встретив пустую инструкцию, интерпретатор JavaScript не выполняет никаких действий. Пустая инструкция может оказаться полезной, когда требуется создать цикл с пустым телом. Взгляните на следующий цикл for (циклы for будут рассматриваться в разделе 5.5.3):


// Инициализировать массив а

for(i = 0; і < a.length; a[i++] = 0) ;


В этом цикле вся работа выполняется выражением a[i++] = 0, и тело цикла здесь не требуется. Однако синтаксис JavaScript требует, чтобы цикл имел тело, поэтому здесь использована пустая инструкция - просто точка с запятой.

Обратите внимание, что ошибочное добавление точки с запятой после закрывающей круглой скобки в инструкции for, while или if может вызывать появление досадных ошибок, которые сложно обнаружить. Например, следующий фрагмент наверняка будет делать не совсем то, что предполагал автор:


if ((а == 0) || (Ь == 0)); // Ой! Эта строка ничего не делает...

о = null;                  // а эта будет выполняться всегда.


Если вы собираетесь намеренно использовать пустую инструкцию, нелишним будет добавить комментарий, поясняющий ваши намерения. Например:


for(i = 0; і < a.length; a[i++] = 0) /* пустое тело цикла */;


5.3. Инструкции-объявления

 Сделать закладку на этом месте книги

Инструкции var и function являются инструкциями-объявлениями - они объявляют, или определяют, переменные и функции. Эти инструкции определяют идентификаторы (имена переменных и функций), которые могут использоваться повсюду в программе, и присваивают значения этим идентификаторам. Инструкции-объявления сами ничего особенного не делают, но, создавая переменные и функции, они в значительной степени определяют значение других инструкций в программе.

В подразделах, следующих ниже, описываются инструкции var и function, но они не дают исчерпывающего описания переменных и функций. Более подробная информация о переменных приводится в разделах 3.9 и 3.10, а полное описание функций - в главе 8.

5.3.1. Инструкция var

 Сделать закладку на этом месте книги

Инструкция var позволяет явно объявить одну или несколько переменных. Инструкция имеет следующий синтаксис:


var имя_1 [ = значение_1] [ ..... имя_n [= значение_n]]


За ключевым словом var следует список объявляемых переменных через запятую; каждая переменная в списке может иметь специальное выражение-инициализатор, определяющее ее начальное значение. Например:


var і;         // Одна простая переменная

var j = 0;     // Одна переменная, одно значение

va гр, q;      // Две переменные

var greeting = "hello" + name;        // Сложный инициализатор

var х = 2.34, у = Math.cos(0.75), r, theta; // Множество переменных

var х = 2, у = х*х;             // Вторая переменная использует первую

var х = 2,                      // Множество переменных...

f = function(x) { return х*х }, // каждая определяется

у = f(х);                       //в отдельной строке


Если инструкция var находится внутри тела функции, она определяет локальные переменные, видимые только внутри этой функции. При использовании в программном коде верхнего уровня инструкция var определяет глобальные переменные, видимые из любого места в программе. Как отмечалось в разделе 3.10.2, глобальные переменные являются свойствами глобального объекта. Однако, в отличие от других глобальных свойств, свойства, созданные с помощью инструкции var, нельзя удалить.

Если в инструкции var начальное значение переменной не указано, то переменная определяется, однако ее начальное значение остается неопределенным (undefined). Как описывалось в разделе 3.10.1, переменные определены во всем сценарии или в функции, где они были объявлены, - их объявления «поднимаются» в начало сценария или функции. Однако инициализация переменной производится в той точке программы, где находится инструкция var, а до этого переменная имеет значение undefined.

Обратите внимание, что инструкция var может также являться частью циклов for и for/in. (Объявления этих переменных так же поднимаются в начало сценария или функции, как и объявления других переменных вне цикла.) Ниже повторно приводятся примеры из раздела 3.9:


for(var і = 0; і < 10; i++) console.log(i);

for(var і = 0, j=10; і < 10; i++,j--) console.log(і *j);

for(var і in o) console.log(i);


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

5.3.2. Инструкция function

 Сделать закладку на этом месте книги

Ключевое слово function в языке JavaScript используется для определения функций. В разделе 4.3 мы уже встречались с выражением определения функции. Но функции можно также определять в форме инструкций. Взгляните на следующие две функции:


var f = function(x) { return x+1; } // Выражение присваивается переменной

function f(x) { return x+1; } // Инструкция включает имя переменной


Объявление функции в форме инструкции имеет следующий синтаксис:


function имя_функции ([арг1 [,арг2 [..., аргn]]]) {

  инструкции 

}


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

Тело функции состоит из произвольного числа JavaScript-инструкций, заключенных в фигурные скобки. Эти инструкции не выполняются при определении функции. Они просто связываются с новым объектом функции для выполнения при ее вызове. Обратите внимание, что фигурные скобки являются обязательной частью инструкции function. В отличие от блоков инструкций в циклах while и в других конструкциях, тело функции требует наличия фигурных скобок, даже если оно состоит только из одной инструкции.

Ниже приводится несколько примеров определений функций:


function hypotenuse(x, у) {

  return Math.sqrt(x*x + y*y); // Инструкция return описывается далее

}

function factorial(n) { // Рекурсивная функция

  if (n <= 1) return 1;

  return n * factorial(n - 1);

}


Инструкции объявления функций могут находиться в JavaScript-коде верхнего уровня или быть вложенными в определения других функций только на «верхнем уровне», т. е. объявления функций не могут находиться внутри инструкций if, циклов while или любых других конструкций. Из-за такого ограничения, накладываемого на объявления функций, спецификация ECMAScript не относит объявления функций к истинным инструкциям. Некоторые реализации JavaScript позволяют вставлять объявления функций в любые инструкции, но разные реализации по-разному обрабатывают эти случаи, поэтому включение объявлений функций в другие инструкции снижает переносимость программ.

Инструкция объявления функции отличается от выражения тем, что она включает имя функции. Обе формы создают новый объект функции, но инструкция объявления функции при этом объявляет имя функции - переменную, которой присваивается объект функции. Подобно переменным, объявляемым с помощью инструкции var, объявления функций, созданные с помощью инструкции function, неявно «поднимаются» в начало содержащего их сценария или функции, поэтому они видимы из любого места в сценарии или функции. Однако при использовании инструкции var поднимается только объявление переменной, а инициализация остается там, куда ее поместил программист. В случае же с инструкцией function поднимается не только имя функции, но и ее тело: все функции в сценарии или все функции, вложенные в функцию, будут объявлены до того, как начнется выполнение программного кода. Это означает, что функцию можно вызвать еще до того, как она будет объявлена.

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

5.4. Условные инструкции

 Сделать закладку на этом месте книги

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


убрать рекламу






перекрестках интерпретатор должен выбирать, по какой дороге двигаться дальше.

В подразделах ниже описывается основная условная инструкция языка JavaScript - инструкция if/else, а также более сложная инструкция switch, позволяющая создавать множество ответвлений.

5.4.1. Инструкция if

 Сделать закладку на этом месте книги

Инструкция if - это базовая управляющая инструкция, позволяющая интерпретатору JavaScript принимать решения или, точнее, выполнять инструкции в зависимости от условий. Инструкция имеет две формы. Первая:


if (выражение ) инструкция 


В этой форме сначала вычисляется выражение. Если полученный результат является истинным, то инструкция выполняется. Если выражение возвращает ложное значение, то инструкция не выполняется. (Определения истинных и ложных значений приводятся в разделе 3.3.) Например:


if (username == null) // Если переменная username равна null или undefined,

   username = "John Doe"; // определить ее


Аналогично:


// Если переменная username равна null, undefined, 0, "" или NaN,

// присвоить ей новое значение,

if (!username) username = "John Doe";


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

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


if (!address) {

  address = "";

  message = "Пожалуйста, укажите почтовый адрес.";

}


Вторая форма инструкции if вводит конструкцию else, выполняемую в тех случаях, когда выражение возвращает ложное значение. Ее синтаксис:


if (выражение)

  инструкция 1

else

  инструкция2


Эта форма инструкции выполняет инструкцию1, если выражение возвращает истинное значение, и инструкцию2, если выражение возвращает ложное значение. Например:


if (п == 1)

  console.log("Получено 1 новое сообщение.");

else

  console.log("Получено " + n + " новых сообщений.");


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


i = j = 1;

k = 2;

if (і == j)

  if (j == k)

    console.log("i равно k");

else

  console.log(”i не равно j”); // НЕПРАВИЛЬНО!!


В этом примере внутренняя инструкция if является единственной инструкцией, вложенной во внешнюю инструкцию if. К сожалению, неясно (если исключить подсказку, которую дают отступы), к какой инструкции if относится блок else. А отступы в этом примере выставлены неправильно, потому что в действительности интерпретатор JavaScript интерпретирует предыдущий пример так:


if (і == j) {

  if (j == k)

    console. log('i равно k");

  else

    console.log('i не равно j"); // Вот как!

}


Согласно правилам JavaScript (и большинства других языков программирования), конструкция else является частью ближайшей к ней инструкции if. Чтобы сделать этот пример менее двусмысленным и более легким для чтения, понимания, сопровождения и отладки, надо поставить фигурные скобки:


if (І == j) {

  if (j == k) {

    console.log('i равно k");

  }

}

else

{ // Вот какая разница возникает из-за добавления фигурных скобок!

  console.log('i не равно j");

}


Хотя этот стиль и не используется в данной книге, тем не менее многие программисты заключают тела инструкций if и else (а также других составных инструкций, таких как циклы while) в фигурные скобки, даже когда тело состоит только из одной инструкции. Последовательное применение этого правила поможет избежать неприятностей, подобных только что описанной.


5.4.2. Инструкция else if

 Сделать закладку на этом месте книги

Инструкция if/else вычисляет значение выражения и выполняет тот или иной фрагмент программного кода, а зависимости от результата. Но что если требуется выполнить один из многих фрагментов? Возможный способ сделать это состоит в применении инструкции else if. Формально она не является самостоятельной инструкцией JavaScript; это лишь распространенный стиль программирования, заключающийся в применении повторяющихся инструкций if/else:


if (n == 1) {

  // Выполнить блок 1

}

else if (n == 2) {

  // Выполнить блок 2

}

else if (n == 3) {

  // Выполнить блок З

}

else {

  // Если ни одна из предыдущих инструкций else не была выполнена, выполнить блок 4

}


В этом фрагменте нет ничего особенного. Это просто последовательность инструкций if, где каждая инструкция if является частью конструкции else предыдущей инструкции. Стиль else if предпочтительнее и понятнее записи в синтаксически эквивалентной форме, полностью показывающей вложенность инструкций:


if (n == 1) {

  // Выполнить блок 1

}

else {

  if (n == 2) {

    // Выполнить блок 2

  }

  else {

    if (n == 3) {

      // Выполнить блок З

    }

    else {

      // Если ни одна из предыдущих инструкций else // не была выполнена, выполнить блок 4

    }

  }

}


5.4.3. Инструкция switch

 Сделать закладку на этом месте книги

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

Инструкция switch предназначена именно для таких ситуаций. За ключевым словом switch следует выражение в скобках и блок кода в фигурных скобках:


switch(выражение) {

  инструкции

}


Однако полный синтаксис инструкции switch более сложен, чем показано здесь. Различные места в блоке помечены ключевым словом case, за которым следует выражение и символ двоеточия. Ключевое слово case напоминает инструкцию с меткой за исключением того, что оно связывает инструкцию с выражением , а не с именем. Когда выполняется инструкция switch, она вычисляет значение выражения, а затем ищет метку case, соответствующую этому значению (соответствие определяется с помощью оператора идентичности ===). Если метка найдена, выполняется блок кода, начиная с первой инструкции, следующей за меткой case. Если метка case с соответствующим значением не найдена, выполнение начинается с первой инструкции, следующей за специальной меткой default:. Если метка default: отсутствует, блок инструкции switch пропускается целиком.

Работу инструкции switch сложно объяснить на словах, гораздо понятнее выглядит объяснение на примере. Следующая инструкцияswitch эквивалентна повторяющимся инструкциям if/else, показанным в предыдущем разделе:


switch(n) {

case 1: // Выполняется, если п === 1

  // Выполнить блок 1.

  break; // Здесь остановиться

case 2: // Выполняется, если п === 2

  // Выполнить блок 2.

  break; // Здесь остановиться

case 3: // Выполняется, если п === 3

  // Выполнить блок 3.

  break; // Здесь остановиться

default: // Если все остальное не подходит...

  // Выполнить блок 4.

  break; // Здесь остановиться

}


Обратите внимание на ключевое слово break в конце каждого блока case. Инструкция break, описываемая далее в этой главе, приводит к передаче управления в конец инструкции switch и продолжению выполнения инструкций, следующих далее. Конструкции case в инструкции switch задают только начальную точку выполняемого программного кода, но не задают никаких конечных точек. В случае отсутствия инструкций break инструкция switch начнет выполнение блока кода с меткой case, соответствующей значению выражения, и продолжит выполнение инструкций до тех пор, пока не дойдет до конца блока. В редких случаях это полезно для написания программного кода, который переходит от одной метки case к следующей, но в 99% случаев следует аккуратно завершать каждый блок case инструкцией break. (При использовании switch внутри функции вместо break можно использовать инструкцию return. Обе эти инструкции служат для завершения работы инструкции switch и предотвращения перехода к следующей метке case.)

Ниже приводится более практичный пример использования инструкции switch; он преобразует значение в строку способом, зависящим от типа значения:


// Преобразовать число в шестнадцатеричное целое

// Вернуть строку, заключенную в кавычки

// Любой другой тип преобразуется обычным способом

function convert(x) {

  switch(typeof х) {

  case ’number':

    return x.toString(16);

  case 'string':

    return "" + x + "";

  default:

    return x.toStringO

  }

}


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

Инструкция switch сначала вычисляет выражение после ключевого слова switch, а затем выражения case в том порядке, в котором они указаны, пока не будет найдено совпадающее значение.[4] Факт совпадения определяется с помощью оператора идентичности ===, а не с помощью оператора равенства ==, поэтому выражения должны совпадать без какого-либо преобразования типов.

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

Как объяснялось ранее, если ни одно из выражений case не соответствует выражению switch, инструкция switch начинает выполнение с инструкции с меткой default:. Если метка default: отсутствует, тело инструкции switch полностью пропускается. Обратите внимание, что в предыдущих примерах метка default: указана в конце тела инструкции switch после всех меток case. Это логичное и обычное место для нее, но на самом деле она может располагаться в любом месте внутри инструкции switch.

5.5. Циклы

 Сделать закладку на этом месте книги

Чтобы понять действие условных инструкций, мы предлагали представить их в виде разветвлений на дороге, по которой двигается интерпретатор JavaScript. Инструкции циклов можно представить как разворот на дороге, возвращающий обратно, который заставляет интерпретатор многократно проходить через один и тот же участок программного кода. В языке JavaScript имеется четыре инструкции циклов: while, do/while, for и for/in. Каждому из них посвящен один из следующих подразделов. Одно из обычных применений инструкций циклов - обход элементов массива. Эта разновидность циклов подробно обсуждается в разделе 7.6, где также рассматриваются специальные методы итераций класса Array.

5.5.1. Инструкция while

 Сделать закладку на этом месте книги

Инструкция if является базовой условной инструкцией в языке JavaScript, а базовой инструкцией циклов для JavaScript можно считать инструкцию while. Она имеет следующий синтаксис:


while (выражение) инструкция


Инструкция while начинает работу с вычисления выражения . Если это выражение имеет ложное значение, интерпретатор пропускает инструкцию, составляющую тело цикла, и переходит к следующей инструкции в программе. Если выражение имеет истинное значение, то выполняется инструкция, образующая тело цикла, затем управление передается в начало цикла и выражение вычисляется снова. Иными словами, интерпретатор снова и снова выполняет инструкцию тела цикла, пока (while) значение выражения  остается истинным. Обратите внимание, что имеется возможность организовать бесконечный цикл с помощью синтаксиса while(true).

Обычно не требуется, чтобы интерпретатор JavaScript снова и снова выполнял одну и ту же операцию. Почти в каждом цикле с каждой итерацией цикла одна или несколько переменных изменяют свои значения. Поскольку переменная меняется, действия, которые выполняет инструкция, при каждом проходе тела цикла могут отличаться. Кроме того, если изменяемая переменная (или переменные) присутствует в выражении, значение выражения может меняться при каждом проходе цикла. Это важно, т. к. в противном случае выражение, значение которого было истинным, никогда не изменится и цикл никогда не завершится! Ниже приводится пример цикла while, который выводит числа от 0 до 9:


var count = 0;

while (count < 10) {

  console.log(count);

  count++;

}


Как видите, в начале переменной count присваивается значение 0, а затем ее значение увеличивается каждый раз, когда выполняется тело цикла. После того как цикл будет выполнен 10 раз, выражение вернет false (т.е. переменная count уже не меньше 10), инструкция while завершится и интерпретатор перейдет к следующей инструкции в программе. Большинство циклов имеют переменные-счетчики, аналогичные count. Чаще всего в качестве счетчиков цикла выступают переменные с именами i, j и k, хотя для того чтобы сделать программный код более понятным, следует давать счетчикам более наглядные имена.

5.5.2. Инструкция do/while

 Сделать закладку на этом месте книги

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


do

  инструкция

while (выражение);


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


function printArray(a) {

  var len = a.length, і = 0;

  if (len == 0)

    console.log("Пустой массив");

  else {

    do {

      console.log(a[і]);

    } while (++i < len);

  }

}


Между циклом do/while и обычным циклом while имеется два отличия. Во-первых, цикл do требует как ключевого слова do (для отметки начала цикла), так и ключевого слова while (для отметки конца цикла и указания условия). Во-вторых, в отличие от цикла while, цикл do завершается точкой с запятой. Цикл while необязательно завершать точкой с запятой, если тело цикла заключено в фигурные скобки.

5.5.3. Инструкция for

 Сделать закладку на этом месте книги

Инструкция for представляет собой конструкцию цикла, которая часто оказывается более удобной, чем инструкция while. Инструкция for упрощает конструирование циклов, следующих шаблону, общему для большинства циклов. Большинство циклов имеют некоторую переменную-счетчик. Эта переменная инициализируется перед началом цикла и проверяется перед каждой итерацией. Наконец, переменная-счетчик инкрементируется или изменяется каким-либо другим образом в конце тела цикла, непосредственно перед повторной проверкой переменной. Инициализация, проверка и обновление - это три ключевых операции, выполняемых с переменной цикла. Инструкция for делает эти три шага явной частью синтаксиса цикла:


for(инициализация; проверка; инкремент) инструкция


Инициализация, проверка и инкремент - это три выражения (разделенных точкой с запятой), которые ответственны за инициализацию, проверку и увеличение переменной цикла. Расположение их в первой строке цикла упрощает понимание того, что делает цикл for, и не позволяет забыть инициализировать или увеличить переменную цикла.

Проще всего объяснить работу цикла for, показав эквивалентный ему цикл while:[5]

)


инициализация;

while(проверка) {

  инструкция

  инкремент:

}


Другими словами, выражение инициализации вычисляется один раз перед началом цикла. Это выражение, как правило, является выражением с побочными эффектами (обычно присваиванием). В JavaScript также допускается, чтобы выражение инициализации было инструкцией объявления переменной var, поэтому можно одновременно объявить и инициализировать счетчик цикла. Выражение проверки вычисляется перед каждой итерацией и определяет, будет ли выполняться тело цикла. Если результатом проверки является истинное значение, выполняется инструкция, являющаяся телом цикла. В конце цикла вычисляется выражение инкремент. Чтобы использование этого выражения имело смысл, оно должно быть выражением с побочными эффектами. Обычно это либо выражение присваивания, либо выражение, использующее оператор ++ или --.

Вывести числа от 0 до 9 можно также с помощью цикла for, как показано ниже. В противовес эквивалентному циклу while, показанному в предыдущем разделе:


for(var count = 0; count < 10; count++)

  console.log(count);


Конечно, циклы могут быть значительно более сложными, чем в этих простых примерах, и иногда в каждой итерации цикла изменяется несколько переменных. Эта ситуация - единственный случай в JavaScript, когда часто применяется оператор «запятая» - он позволяет объединить несколько выражений инициализации и инкрементирования в одно выражение, подходящее для использования в цикле for:


var і, j

for(i =0, j = 10; і < 10; i++, j--)

  sum += і * j;


Во всех наших примерах циклов, представленных до сих пор, переменная цикла содержала число. Это достаточно распространенная, но не обязательная практика. В следующем примере цикл for используется для обхода связанного списка структур данных и получения последнего объекта в списке (например, первого объекта, который не имеет свойства next):


function tail(о) {                      // Возвращает последний элемент в списке о

  fоr(; о.next; о = о.next) /*пустое*/; // Выполнять обход, пока о.next

  return о;                             // является истинным значением

}


Обратите внимание на отсутствие выражения инициализации в примере выше. Любое из трех выражений цикла for может быть опущено, но две точки с запятой являются обязательными. Если опустить выражение проверки, цикл будет повторяться вечно, и форма записи for(;;) является еще одним способом написать бесконечный цикл, подобно while(true).

5.5.4. Инструкция for/in

 Сделать закладку на этом месте книги

Инструкция цикла for/in использует ключевое слово for, но она в корне отличается от инструкции обычного цикла for. Цикл for/in имеет следующий синтаксис:


for (переменная in объект)

  инструкция


В качестве переменной здесь обычно используется имя переменной, но точно так же можно использовать любое выражение, возвращающее левостороннее выражение (раздел 4.7.3), или инструкцию var, объявляющую единственную переменную, - практически все, что может находиться слева от оператора присваивания. Параметр объект - это выражение, возвращающее объект. И как обычно, инструкция - это инструкция или блок инструкций, образующих тело цикла.

Для обхода элементов массива естественно использовать обычный циклfor:


for(var і = 0; і < a.length; і++) // Присваивать индексы в массиве переменной і

console.log(a[і]); // Вывести значение каждого элемента массива


Инструкция for/in так же естественно позволяет выполнить обход свойств объекта.


for(var р in о) // Присваивать имена свойств объекта о переменной р

console.log(o[p]); // Вывести значение каждого свойства


Чтобы выполнить инструкцию for/in, интерпретатор JavaScript сначала вычисляет выражение объект. Если оно возвращает значение null или undfefined, интерпретатор пропускает цикл и переходит к следующей инструкции.[6] (Реализации, следующие стандарту ECMAScript 3, в этом случае могут возбуждать исключение ТуреЕггог.) Если выражение возвращает простое значение, оно преобразуется в эквивалентный объект-обертку (раздел 3.6). В противном случае выражение возвращает объект. Затем интерпретатор выполняет по одной итерации цикла для каждого перечислимого свойства объекта. Перед каждой итерацией интерпретатор вычисляет значение выражения переменная и присваивает ему имя свойства (строковое значение).

Обратите внимание, что переменная в цикле for/in может быть любым выражением, возвращающим значение, которое можно использовать слева от оператора присваивания. Это выражение вычисляется в каждой итерации цикла, т. е. каждый раз оно может возвращать разные значения. Например, чтобы скопировать имена всех свойств объекта в массив, можно использовать следующий цикл:


var о = {х:1. у:2. z:3};

var а = [];

var і = 0;

for(a[i++] in о) /* пустое тело цикла */;


Массивы в JavaScript - это просто специальный тип объектов, а индексы в массиве - свойства объекта, обход которых можно выполнить с помощью цикла for/in. Например, следующая инструкция перечислит индексы 0, 1 и 2 массива, объявленного выше:


fоr(і in a) console.log(i);


В действительности цикл for/in может совершать обход не по всем свойствам объекта, а только по перечислимым свойствам (раздел 6.7). Многочисленные встроенные методы, определяемые в базовом языке JavaScript, не являются перечислимыми. Например, все объекты имеют метод toString(), но цикл for/in не перечислит свойство toString. Кроме встроенных методов также не являются перечислимыми многие другие свойства встроенных объектов. При этом все свойства и методы, определяемые пользователем, являются перечислимыми. (Но в реализации, следующей стандарту ECMAScript 5, имеется возможность сделать их неперечислимыми, использовав прием, описанный в разделе 6.7.) Унаследованные свойства, которые были определены пользователем (раздел 6.2.2), также перечисляются циклом for/in.

Если в теле цикла for/in удалить свойство, которое еще не было перечислено, это свойство перечислено не будет. Если в теле цикла создать новые св


убрать рекламу






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

5.5.4.1. Порядок перечисления свойств

 Сделать закладку на этом месте книги

Спецификация ECMAScript не определяет порядок, в каком цикл for/in должен перечислять свойства объекта. Однако на практике реализации JavaScript во всех основных броузерах перечисляют свойства простых объектов в порядке, в каком они были определены, - когда ранее объявленные свойства перечисляются первыми. Если объект был создан с помощью литерала объекта, свойства перечисляются в том же порядке, в каком они следуют в литерале. В Интернете существуют сайты и библиотеки, которые опираются на такой порядок перечисления, поэтому маловероятно, что производители броузеров изменят его.

В абзаце выше описывается описывается порядок перечисления свойств "простых" объектов. Однаков разных реализациях порядок перечисления может отличаться, если:

• объект наследует перечислимые свойства;

• объект имеет свойства, которые являются целочисленными индексами массива;

• использовалась инструкция delete для удаления существующих свойств объекта или

• использовался метод Object.defineProperty() (раздел 6.7) или аналогичный ему для изменения атрибутов свойства объекта.

Обычно (но не во всех реализациях) унаследованные свойства (раздел 6.2.2) перечисляются после всех неунаследованных, «собственных» свойств объекта, но они также перечисляются в порядке их определения. Если объект наследует свойства более чем от одного «прототипа» (раздел 6.1.3) - например, когда в его «цепочке прототипов» имеется более одного объекта, - свойства каждого объекта-прототипа в цепочке перечисляются в порядке их создания перед перечислением свойств следующего объекта. Некоторые (но не все) реализации перечисляют свойства массива в порядке возрастания чисел, а не в порядке их создания, но при наличии в массиве свойств с нечисловыми именами происходит возврат к перечислению в порядке создания свойств, то же самое происходит и в случае разреженных массивов (т. е. когда в массиве отсутствуют некоторые элементы).

5.6. Переходы

 Сделать закладку на этом месте книги

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

Инструкция return заставляет интерпретатор перейти из вызванной функции обратно в точку ее вызова и вернуть значение вызова. Инструкция throw возбуждает исключение и предназначена для работы в сочетании с инструкцией try/catch/finally, которая определяет блок программного кода для обработки исключения. Это достаточно сложная разновидность инструкции перехода: при появлении исключения интерпретатор переходит к ближайшему объемлющему обработчику исключений, который может находиться в той же функции или выше, в стеке возвратов вызванной функции.

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

5.6.1. Метки инструкций

 Сделать закладку на этом месте книги

Любая инструкция может быть помечена указанным перед ней идентификатором и двоеточием:


идентификатор: инструкция


любую инструкцию, однако помечать имеет смысл только инструкции, имеющие тело, такие как циклы и условные инструкции. Присвоив имя циклу, его затем можно использовать в инструкциях break и continue, внутри цикла для выхода из него или для перехода в начало цикла, к следующей итерации. Инструкции break и continue являются единственными инструкциями в языке JavaScript, в которых можно указывать метки - о них подробнее рассказывается далее в этой главе. Ниже приводится пример инструкции while с меткой и инструкции continue, использующей эту метку:


mainloop: while(token != null) {

  // Программный код опущен...

  continue mainloop; // Переход к следующей итерации именованного цикла

  // Программный код опущен...

}


Идентификатор, используемый в качестве метки инструкции, может быть любым допустимым идентификатором JavaScript, кроме зарезервированного слова. Имена меток отделены от имен переменных и функций, поэтому в качестве меток допускается использовать идентификаторы, совпадающие с именами переменных или функций. Метки инструкций определены только внутри инструкций, к которым они применяются (и, конечно же, внутри вложенных в них инструкций). Вложенные инструкции не могут помечаться теми же идентификаторами, что и вмещающие их инструкции, но две независимые инструкции могут помечаться одинаковыми метками. Помеченные инструкции могут помечаться повторно. То есть любая инструкция может иметь множество меток.

5.6.2. Инструкция break

 Сделать закладку на этом месте книги

Инструкция break приводит к немедленному выходу из самого внутреннего цикла или инструкции switch. Синтаксис ее прост:


break;


Поскольку инструкция break приводит к выходу из цикла или инструкции switch, такая форма break допустима только внутри этих инструкций.

Выше мы уже видели примеры использования инструкции break внутри инструкции switch. В циклах она обычно используется для немедленного выхода из цикла, когда по каким-либо причинам требуется завершить выполнение цикла. Когда цикл имеет очень сложное условие завершения, зачастую проще бывает реализовать эти условия с помощью инструкций break, чем пытаться выразить их в одном условном выражении цикла. Следующий пример пытается отыскать элемент массива с определенным значением. Цикл завершается обычным образом по достижении конца массива или с помощью инструкции break, как только будет найдено искомое значение:


for(var і = 0; і < a.length; i++) {

  if (a[і] == target) break;

}


В языке JavaScript допускается указывать имя метки за ключевым словом break (идентификатор без двоеточия):


break имя_метки;


Когда инструкция break используется с меткой, она выполняет переход в конец именованной инструкции или прекращение ее выполнения. В случае отсутствия инструкции с указанной меткой попытка использовать такую форму инструкций break порождает синтаксическую ошибку. Именованная инструкция не обязана быть циклом или инструкцией switch: инструкция break с меткой может выполнять «выход» из любой вмещающей ее инструкции. Объемлющая инструкция может даже быть простым блоком инструкций, заключенным в фигурные скобки исключительно с целью пометить его.

Между ключевым словом break и именем метки не допускается вставлять символ перевода строки. Дело в том, что интерпретатор JavaScript автоматически вставляет пропущенные точки с запятой: если разбить строку программного кода между ключевым словом break и следующей за ним меткой, интерпретатор предположит, что имелась в виду простая форма этой инструкции без метки, и добавит точку с запятой (раздел 2.5).

Инструкция break с меткой необходима, только когда требуется прервать выполнение инструкции, не являющейся ближайшим объемлющим циклом или инструкцией switch. Следующий фрагмент демонстрирует это:


var matrix = getData(); // Получить 2-мерный массив чисел откуда-нибудь

// Найти сумму всех чисел в матрице,

var sum = 0,

success = false;

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

compute_sum: if (matrix) {

  for(var x = 0; x < matrix.length; x++) {

    var row = matrix[x];

    if (!row) break compute_sum;

    for(var у = 0; у < row.length; y++) {

      var cell = row[y];

      if (isNaN(cell)) break compute_sum;

      sum += cell;

    }

  }

  success = true;

}

// Здесь инструкция break выполняет переход. Если будет выполнено условие

// success == false, значит, что-то не так в полученной матрице.

// В противном случае переменная sum будет содержать сумму всех элементов матрицы.


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

5.6.3. Инструкция continue

 Сделать закладку на этом месте книги

Инструкция continue схожа  с инструкцией break. Однако вместо выхода из цикла инструкция continue запускает новую итерацию цикла. Синтаксис инструкции continue столь же прост, как и синтаксис инструкции break:


continue;


Инструкция continue может также использоваться с меткой:


continue имя_метки\


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

Когда выполняется инструкция continue, текущая итерация цикла прерывается и начинается следующая. Для разных типов циклов это означает разное:

• В цикле while указанное в начале цикла выражение проверяется снова, и если оно равно true, тело цикла выполняется с начала.

• В цикле do/while происходит переход в конец цикла, где перед повторным выполнением цикла снова проверяется условие.

• В цикле for вычисляется выражение инкремента и снова вычисляется выражение проверки, чтобы определить, следует ли выполнять следующую итерацию.

• В цикле for/in цикл начинается заново с присвоением указанной переменной имени следующего свойства.

Обратите внимание на различия в поведении инструкции continue в циклах while и for: цикл while возвращается непосредственно к своему условию, а цикл for сначала вычисляет выражение инкремента, а затем возвращается к условию. Ранее при обсуждении цикла for объяснялось поведение цикла for в терминах «эквивалентного» цикла while. Поскольку инструкция continue ведет себя в этих двух циклах по-разному, точно имитировать цикл for с помощью одного цикла while невозможно.

В следующем примере показано использование инструкции continue без метки для выхода из текущей итерации цикла в случае ошибки:


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

  if (!data[і]) continue; // He обрабатывать неопределенные данные

  total += data[i];

}


Инструкция continue, как и break, может применяться во вложенных циклах в форме, включающей метку, и тогда заново запускаемым циклом необязательно будет цикл, непосредственно содержащий инструкцию continue. Кроме того, как и для инструкции break, переводы строк между ключевым словом continue и именем метки не допускаются.

5.6.4. Инструкция return

 Сделать закладку на этом месте книги

Как вы помните, вызов функции является выражением и подобно всем выражениям имеет значение. Инструкция return внутри функций служит для определения значения, возвращаемого функцией. Инструкция return имеет следующий синтаксис:


return выражение;


Инструкция return может располагаться только в теле функции. Присутствие ее в любом другом месте является синтаксической ошибкой. Когда выполняется инструкция return, функция возвращает значение выражения вызывающей программе. Например:


function square(x) { return х*х; } // Функция с инструкцией return

square(2)                          // Этот вызов вернет 4


Если функция не имеет инструкции return, при ее вызове интерпретатор будет выполнять инструкции в теле функции одну за другой, пока не достигнет конца функции, и затем вернет управление вызвавшей ее программе. В этом случае выражение вызова вернет значение undefined. Инструкция return часто является последней инструкцией в функции, но это совершенно необязательно: функция вернет управление вызывающей программе, как только будет достигнута инструкция return, даже если за ней следуют другие инструкции в теле функции.

Инструкция return может также использоваться без выражения, тогда она просто прерывает выполнение функции и возвращает значение undefined вызывающей программе. Например:


function display_object(o) {

  // Сразу же вернуть управление, если аргумент имеет значение null или undefined

  if (!о) return;

  // Здесь находится оставшаяся часть функции...

}


Из-за того что интерпретатор JavaScript автоматически вставляет точки с запятой, нельзя разделять переводом строки инструкцию return и следующее за ней выражение.

5.6.5. Инструкция throw

 Сделать закладку на этом месте книги

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

Инструкция throw имеет следующий синтаксис:


throw выражение;


Результатом выражения может быть значение любого типа. Инструкции throw можно передать число, представляющее код ошибки, или строку, содержащую текст сообщения об ошибке. Интерпретатор JavaScript возбуждает исключения, используя экземпляр класса Error одного из его подклассов, и вы также можете использовать подобный подход. Объект Error имеет свойство name, определяющее тип ошибки, и свойство message, содержащее строку, переданную функции-конструктору (смотрите описание класса Error в справочном разделе). Ниже приводится пример функции, которая возбуждает объект Error при вызове с недопустимым аргументом:


function factorial(x) {

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

  if (х < 0) throw new Error("x не может быть отрицательным");

  // В противном случае значение вычисляется и возвращается нормальным образом

  for(var f = 1; х>1; f*=x, х--) /* пустое тело цикла */ ;

  return f;

}


Когда возбуждается исключение, интерпретатор JavaScript немедленно прерывает нормальное выполнение программы и переходит к ближайшему[7] обработчику исключений. В обработчиках исключений используется конструкция catch инструкции try/catch/finally, описание которой приведено в следующем разделе. Если блок программного кода, в котором возникло исключение, не имеет соответствующей конструкции catch, интерпретатор анализирует следующий внешний блок программного кода и проверяет, связан ли с ним обработчик исключений. Это продолжается до тех пор, пока обработчик не будет найден. Если исключение генерируется в функции, не содержащей инструкции try/catch/finally, предназначенной для его обработки, то исключение распространяется выше, в программный код, вызвавший функцию. Таким образом исключения распространяются по лексической структуре методов JavaScript вверх по стеку вызовов. Если обработчик исключения так и не будет найден, исключение рассматривается как ошибка и о ней сообщается пользователю.

5.6.6. Инструкция try/catch/finally

 Сделать закладку на этом месте книги

Инструкция try/catch/finally реализует механизм обработки исключений в JavaScript. Конструкция try в этой инструкции просто определяет блок кода, в котором обрабатываются исключения. За блоком try следует конструкция catch с блоком инструкций, вызываемых, если где-либо в блоке try возникает исключение. За конструкцией catch следует блок finally, содержащий программный код, выполняющий заключительные операции, который гарантированно выполняется независимо от того, что происходит в блоке try. И блок catch, и блок finally не являются обязательными, однако после блока try должен обязательно присутствовать хотя бы один из них. Блоки try, catch и finally начинаются и заканчиваются фигурными скобками. Это обязательная часть синтаксиса, и она не может быть опущена, даже если между ними содержится только одна инструкция.

Следующий фрагмент иллюстрирует синтаксис и назначение инструкции try/catch/finally:


try {

  // Обычно этот код без сбоев работает от начала до конца.

  // Но в какой-то момент в нем может быть сгенерировано исключение

  // либо непосредственно с помощью инструкции throw, либо косвенно -

  // вызовом метода, генерирующего исключение.

}

catch (е) {

  // Инструкции в этом блоке выполняются тогда и только тогда, когда в блоке try

  // возникает исключение. Эти инструкции могут использовать локальную переменную е,

  // ссылающуюся на объект Error или на другое значение, указанное в инструкции throw.

  // Этот блок может либо некоторым образом обработать исключение, либо

  // проигнорировать его, делая что-то другое, либо заново сгенерировать

  // исключение с помощью инструкции throw.

}

finally {

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

  // что произошло в блоке try. Они выполняются, если блок try завершился:

  // 1) как обычно, достигнув конца блока

  // 2) из-за инструкции break, continue или return

  // 3) с исключением, обработанным приведенным в блоке catch выше

  // 4) с неперехваченным исключением, которое продолжает свое

  // распространение на более высокие уровни

}


Обратите внимание, что за ключевым словом catch следует идентификатор в скобках. Этот идентификатор похож на параметр функции. Когда будет перехвачено исключение, этому параметру будет присвоено исключение (например, объект Error). В отличие от обычной переменной идентификатор, ассоциированный с конструкцией catch, существует только в теле блока catch.

Далее приводится более реалистичный пример инструкции try/catch. В нем вызываются метод factorial(), определенный в предыдущем разделе, и методы prompt() и alert() клиентского JavaScript для организации ввода и вывода:


try {

  // Запросить число у пользователя

  var n = Number(prompt("Введите положительное число",""));

  // Вычислить факториал числа, предполагая, что входные данные корректны

  var f = factorial(n);

  // Вывести результат

  alert(n + "! = " + f);

}

catch (ex) { // Если данные некорректны, управление будет передано сюда

  alert(ех); // Сообщить пользователю об ошибке

}


Это пример инструкции try/catch без конструкции finally. Хотя finally используется не так часто, как catch, тем не менее иногда эта конструкция оказывается полезной. Однако ее поведение требует дополнительных объяснений. Блок finally гарантированно исполняется, если исполнялась хотя бы какая-то часть блока try, независимо от того, каким образом завершилось выполнение программного кода в блоке try. Эта возможность обычно используется для выполнения заключительных операций после выполнения программного кода в предложении try.

В обычной ситуации управление доходит до конца блока try, а затем переходит к блоку finally, который выполняет необходимые заключительные операции. Если управление вышло из блока try как результат выполнения инструкций return, continue или break, перед передачей управления в другое место выполняется блок finally.

Если в блоке try возникает исключение и имеется соответствующий блок catch для его обработки, управление сначала передается в блок catch, а затем - в блок finally. Если отсутствует локальный блок catch, то управление сначала передается в блок finally, а затем переходит на ближайший внешний блок catch, который может обработать исключение.

Если сам блок finally передает управление с помощью инструкции return, continue, break или throw или путем вызова метода, генерирующего исключение, незаконченная команда на передачу управления отменяется и выполняется новая. Например, если блок finally сгенерирует исключение, это исключение заменит любое ранее сгенерированное исключение. Если в блоке finally имеется инструкция return, произойдет нормальный выход из метода, даже если генерировалось исключение, которое не было обработано.

Конструкции try и finally могут использоваться вместе без конструкции сatch. В этом случае блок finally - это просто набор инструкций, выполняющих заключительные операции, который будет гарантированно выполнен независимо от наличия в блоке try инструкции break, continue или return. Напомню, из-за различий в работе инструкции continue в разных циклах невозможно написать цикл while, полностью имитирующий работу цикла for. Однако если добавить инструкцию try/finally, можно написать цикл while, который будет действовать точно так же, как цикл for, и корректно обрабатывать инструкцию continue:


// Имитация цикла for( инициализация ; проверка ; инкремент ) тело цикла;

инициализация ;

while( проверка ) {

  try { тело цикла ; }

  finally { инкремент ; }

}


Обратите однако внимание, что тело цикла while, содержащее инструкцию break, будет вести себя несколько иначе (из-за выполнения лишней операции инкремента перед выходом), чем тело цикла for, поэтому даже используя конструкцию finally, невозможно точно сымитировать цикл for с помощью цикла while.

5.7. Прочие инструкции

 Сделать закладку на этом месте книги

В этом разделе описываются три остальные инструкции языка JavaScript - with, debugger и use strict.

5.7.1. Инструкция with

 Сделать закладку на этом месте книги

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


with (объект) инструкция


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

Инструкция with не может использоваться в строгом режиме (раздел 5.7.3) и не рекомендуетс


убрать рекламу






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

На практике инструкция with упрощает работу с глубоко вложенными иерархиями объектов. В клиентском JavaScript вам наверняка придется вводить выражения, как показано ниже, чтобы обратиться к элементам HTML-формы:


document.forms[0].address.value


Если подобные выражения потребуется записать много раз, можно воспользоваться инструкцией with, чтобы добавить объект формы в цепочку областей видимости:


with(document.forms[0]) {

  // Далее следуют обращения к элементам формы непосредственно, например:

  name.value = "";

  address.value = "";

  email.value = "";

}


Этот прием сокращает объем текста программы - больше не надо указывать фрагмент document.forms[0] перед каждым именем свойства. Этот объект представляет собой временную часть цепочки областей видимости и автоматически участвует в поиске, когда JavaScript требуется разрешить идентификаторы, такие как address. Избежать применения инструкции with достаточно просто, если записать предыдущий пример, как показано ниже:


var f = document.fоrms[0];

f. name, value = "";

f.address.value = "";

f.email.value =" ";


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


with(o) х = 1;


Если объект о имеет свойство х, то данный программный код присвоит значение 1 этому свойству. Но если х не является свойством объекта о, данный программный код выполнит то же действие, что и инструкция х = 1 без инструкции with. Он присвоит значение локальной или глобальной переменной с именем х или создаст новое свойство глобального объекта. Инструкция with обеспечивает более короткую форму записи операций чтения свойств объекта о, но не создания новых свойств этого объекта.

5.7.2. Инструкция debugger

 Сделать закладку на этом месте книги

Инструкция debugger обычно ничего не делает. Однако если имеется и запущена программа-отладчик, реализация JavaScript может (но не обязана) выполнять некоторые отладочные операции. Обычно эта инструкция действует подобно точке останова: интерпретатор JavaScript приостанавливает выполнение программного кода, и вы можете с помощью отладчика вывести значения переменных, ознакомиться с содержимым стека вызовов и т. д. Допустим, к примеру, что ваша функция f() порождает исключение, потому что она вызывается с неопределенным аргументом, а вы никак не можете определить, из какой точки программы производится этот вызов. Чтобы решить эту проблему, можно было бы изменить определение функции f (), чтобы она начиналась строкой, как показано ниже:


function f(o) {

if (о === undefined) debugger; // Временная строка для отладки

// Далее продолжается тело функции.


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

Официально инструкция debugger была добавлена в язык стандартом ЕСМА-Script 5, но производители основных броузеров реализовали ее уже достаточно давно. Обратите внимание, что недостаточно иметь отладчик: инструкция debugger не запускает отладчик автоматически. Однако, если отладчик уже запущен, эта инструкция будет действовать как точка останова. Если, к примеру, воспользоваться расширением Firebug для Firefox, это расширение должно быть активировано для веб-страницы, которую требуется отладить, и только в этом случае инструкция debugger будет работать.

5.7.3. "use strict"

 Сделать закладку на этом месте книги

"use strict” - это директива, введенная стандартом ECMAScript 5. Директивы не являются инструкциями (но достаточно близки, чтобы включить описание "use strict” в эту главу). Между обычными инструкциями и директивой "use strict" существует два важных отличия:

• Она не включает никаких зарезервированных слов языка: директива - это лишь выражение, содержащее специальный строковый литерал (в одиночных или двойных кавычках). Интерпретаторы JavaScript, не соответствующие стандарту ECMAScript 5, будут интерпретировать ее как простое выражение без побочных эффектов и ничего не будут делать. В будущих версиях стандарта ECMAScript, как ожидается, слово use будет переведено в разряд ключевых слов, что позволит опустить кавычки.

• Она может появляться только в начале сценария или в начале тела функции, перед любыми другими инструкциями. Однако она не обязательно должна находиться в самой первой строке сценария или функции: директиве "use strict" могут предшествовать или следовать за ней другие строковые выражения-литералы, а различные реализации JavaScript могут интерпретировать эти строковые литералы как директивы, определяемые этими реализациями. Строковые литералы, следующие за первой обычной инструкцией в сценарии или функции, интерпретируются как обычные выражения - они могут не восприниматься как директивы и не оказывать никакого эффекта.

Назначение директивы "use strict" состоит в том, чтобы показать, что следующий за ней программный код (в сценарии или функции) является строгим кодом. Строгим считается программный код верхнего уровня (не внутри функций), если в сценарии имеется директива "use strict". Строгим считается тело функции, если она определяется внутри строгого программного кода или если она содержит директиву "use strict". Строгим считается программный код, передаваемый методу eval(), если вызов eval() выполняется из строгого программного кода или если строка с кодом содержит директиву "use strict".

Строгий программный код выполняется в строгом режиме. Согласно стандарту ECMAScript 5, строгий режим определяет ограниченное подмножество языка, благодаря чему исправляет некоторые недостатки языка, а также обеспечивает более строгую проверку на наличие ошибок и повышенный уровень безопасности. Ниже перечислены различия между строгим и нестрогим режимами (первые три имеют особенно большое значение):

• В строгом режиме не допускается использование инструкции with.

• В строгом режиме все переменные должны объявляться: если попытаться присвоить значение идентификатору, который не является объявленной переменной, функцией, параметром функции, параметром конструкции catch или свойством глобального объекта, возбуждается исключение ReferenceError. (В нестрогом режиме такая попытка просто создаст новую глобальную переменную и добавит ее в виде свойства в глобальный объект.)

• В строгом режиме функции, которые вызываются как функции (а не как методы), получают в ссылке this значение undefined. (В нестрогом режиме функции, которые вызываются как функции, всегда получают в ссылке this глобальный объект.) Это отличие можно использовать, чтобы определить, поддерживает ли та или иная реализация строгий режим:


var hasStrictMode = (function() { "use strict"; return this===undefined}());


Кроме того, когда функция вызывается в строгом режиме с помощью саll() или аррlу(), значение ссылки this в точности соответствует значению, переданному в первом аргументе функции саll() или аррlу(). (В нестрогом режиме значения null и undefined замещаются ссылкой на глобальный объект, а простые значения преобразуются в объекты.)

• В строгом режиме попытки присвоить значения свойствам, недоступным для записи, или создать новые свойства в нерасширяемых объектах порождают исключение TypeError. (В нестрогом режиме эти попытки просто игнорируются.)

• В строгом режиме программный код, передаваемый функции eval(), не может объявлять переменные или функции в области видимости вызывающего программного кода, как это возможно в нестрогом режиме. Вместо этого переменные и функции помещаются в новую область видимости, создаваемую для функции eval(). Эта область видимости исчезает, как только eval() вернет управление.

• В строгом режиме объект arguments (раздел 8.3.2) в функции хранит статическую копию значений, переданных функции. В нестрогом режиме объект arguments ведет себя иначе - элементы массива arguments и именованные параметры функции ссылаются на одни и те же значения.

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

• В строгом режиме попытка удалить ненастраиваемое свойство приведет к исключению ТуреЕггог. (В нестрогом режиме эта попытка просто завершится неудачей и выражение delete вернет false.)

• В строгом режиме попытка определить в литерале объекта два или более свойств с одинаковыми именами считается синтаксической ошибкой. (В нестрогом режиме ошибка не возникает.)

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

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

• В строгом режиме идентификаторы eval и arguments интерпретируются как ключевые слова, и вы не можете изменить их значения. Вы сможете присвоить значения этим идентификаторам, объявив их как переменные, использовав их в качестве имен функций, имен параметров функций или идентификаторов блока catch.

• В строгом режиме ограничивается возможность просмотра стека вызовов. Попытки обратиться к свойствам arguments.caller и arguments.callee в строгом режиме возбуждают исключение ТуреЕrror. Попытки прочитать свойства caller и arguments функций в строгом режиме также возбуждают исключение ТуреЕггог. (Некоторые реализации определяют эти свойства в нестрогих функциях.)

5.8. Итоговая таблица JavaScript-инструкций

 Сделать закладку на этом месте книги

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






6

Объекты

 Сделать закладку на этом месте книги

Объект является фундаментальным типом данных в языке JavaScript. Объект -это составное значение: он объединяет в себе набор значений (простых значений или других объектов) и позволяет сохранять и извлекать эти значения по именам. Объект является неупорядоченной коллекцией свойств, каждое из которых имеет имя и значение. Имена свойств являются строками, поэтому можно сказать, что объекты отображают строки в значения. Такое отображение строк в значения может называться по-разному: возможно, вы уже знакомы с такой фундаментальной структурой данных, как «хеш», «словарь» или «ассоциативный массив». Однако объект представляет собой нечто большее, чем простое отображение строк в значения. Помимо собственных свойств объекты в языке JavaScript могут также наследовать свойства от других объектов, известных под названием «прототипы». Методы объекта - это типичные представители унаследованных свойств, а «наследование через прототипы» является ключевой особенностью языка JavaScript.

Объекты в языке JavaScript являются динамическими - обычно они позволяют добавлять и удалять свойства - но они могут использоваться также для имитации статических объектов и «структур», которые имеются в языках программирования со статической системой типов. Кроме того, они могут использоваться (если не учитывать, что объекты отображают строки в значения) для представления множеств строк.

Любое значение в языке JavaScript, не являющееся строкой, числом, true, false, null или undefined, является объектом. И даже строки, числа и логические значения, не являющиеся объектами, могут вести себя как неизменяемые объекты (раздел 3.6).

Как вы помните, в разделе 3.7 говорилось, что объекты являются изменяемыми значениями и операции с ними выполняются по ссылке, а не по значению. Если переменная х ссылается на объект, и выполняется инструкция var у = х;, в переменную у будет записана ссылка на тот же самый объект, а не его копия. Любые изменения, выполняемые в объекте с помощью переменной у, будут также отражаться на переменной х.

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

Свойство имеет имя и значение. Именем свойства может быть любая строка, включая и пустую строку, но объект не может иметь два свойства с одинаковыми именами. Значением свойства может быть любое значение, допустимое в языке JavaScript, или (в ECMAScript 5) функция чтения или записи (или обе). Поближе с функциями чтения и записи свойств мы познакомимся в разделе 6.6. В дополнение к именам и значениям каждое свойство имеет ряд ассоциированных с ним значений, которые называют атрибутами свойства:

• Атрибут writable определяет доступность значения свойства для записи.

• Атрибут enumerable определяет доступность имени свойства для перечисления в цикле for/in.

• Атрибут configurable определяет возможность настройки, т.е. удаления свойства и изменения его атрибутов.

До появления стандарта ECMAScript 5 все свойства в объектах, создаваемые программой, доступны для записи, перечисления и настройки. В ECMAScript 5 предусматривается возможность настройки атрибутов ваших свойств. Как это делается, описывается в разделе 6.7.

В дополнение к свойствам каждый объект имеет три атрибута объекта:

• Атрибут prototype содержит ссылку на другой объект, от которого наследуются свойства.

• Атрибут class содержит строку с именем класса объекта и определяет тип объекта.

• Флаг extensible (в ECMAScript 5) указывает на возможность добавления новых свойств в объект.

Поближе с прототипами и механизмом наследования свойств мы познакомимся в разделах 6.1.3и6.2.2,а более детальное обсуждение всех трех атрибутов объектов вы найдете в разделе 6.8.

Наконец, ниже приводится описание некоторых терминов, которые помогут нам различать три обширные категории объектов в языке JavaScript и два типа свойств:

Объект базового языка  - это объект или класс объектов, определяемый спецификацией ECMAScript. Массивы, функции, даты и регулярные выражения (например) являются объектами базового языка.

Объект среды выполнения  - это объект, определяемый средой выполнения (такой как веб-броузер), куда встроен интерпретатор JavaScript. Объекты HTMLElement, представляющие структуру веб-страницы в клиентском JavaScript, являются объектами среды выполнения. Объекты среды выполнения могут также быть объектами базового языка, например, когда среда выполнения определяет методы, которые являются обычными объектами Function базового языка JavaScript.

Пользовательский объект  - любой объект, созданный в результате выполнения программного кода JavaScript.

Собственное свойство  - это свойство, определяемое непосредственно в данном объекте.

Унаследованное свойство  - это свойство, определяемое прототипом объекта."

6.1. Создание объектов

 Сделать закладку на этом месте книги

Объекты можно создавать с помощью литералов объектов, ключевого слова new и (в ECMAScript 5) функции Object.create(). Все эти приемы описываются в следующих разделах.

6.1.1. Литералы объектов

 Сделать закладку на этом месте книги

Самый простой способ создать объект заключается во включении в программу литерала объекта. Литерал объекта - это заключенный в фигурные скобки список свойств (пар имя/значение), разделенных запятыми. Именем свойства может быть идентификатор или строковый литерал (допускается использовать пустую строку). Значением свойства может быть любое выражение, допустимое в JavaScript, - значение выражения (это может быть простое значение или объект) станет значением свойства. Ниже приводится несколько примеров создания объектов:


var empty = {};                         // Объект без свойств

var point = { x:0, y:0 };               // Два свойства

var point2 = { x:point.x, y:point.y+1 };// Более сложные значения

var book = {

"main title": "JavaScript",             // Имена свойств с пробелами

'sub-title': "The Definitive Guide",    // и дефисами, поэтому используются

                                        // строковые литералы

"for": "all audiences",                 // for - зарезервированное слово,

                                        // поэтому в кавычках

author: {                               // Значением этого свойства является

firstname: "David”,                     // объект. Обратите внимание, что

surname: "Flanagan"                     // имена этих свойств без кавычек.

  }

}


В ECMAScript 5 (и в некоторых реализациях ECMAScript 3) допускается использовать зарезервированные слова в качестве имен свойств без кавычек. Однако в целом имена свойств, совпадающие с зарезервированными словами, в ECMAScript 3 должны заключаться в кавычки. В ECMAScript 5 последняя запятая, следующая за последним свойством в литерале объекта, игнорируется. В большинстве реализаций ECMAScript 3 завершающие запятые также игнорируются, но IE интерпретирует их наличие как ошибку.

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

6.1.2. Создание объектов с помощью оператора new

 Сделать закладку на этом месте книги

Оператор new создает и инициализирует новый объект. За этим оператором должно следовать имя функции. Функция, используемая таким способом, называется конструктором и служит для инициализации вновь созданного объекта. Базовый JavaScript включает множество встроенных конструкторов для создания объектов базового языка. Например:


var о = new Object(); // Создать новый пустой объект: то же, что и {}.

var а = new Аггау();  // Создать пустой массив: то же, что и [].

var d = new Date();  // Создать объект Date, представляющий текущее время

var г = new RegExp("js"); // Создать объект RegExp для операций

                          // сопоставления с шаблоном.


Помимо этих встроенных конструкторов имеется возможность определять свои собственные функции-конструкторы для инициализации вновь создаваемых объектов. О том, как это делается, рассказывается в главе 9.

6.1.3. Прототипы

 Сделать закладку на этом месте книги

Прежде чем перейти к третьему способу создания объектов, необходимо сделать паузу, чтобы познакомиться с прототипами. Каждый объект в языке JavaScript имеет второй объект (или null, но значительно реже), ассоциированный с ним. Этот второй объект называется прототипом, и первый объект наследует от прототипа его свойства.

Все объекты, созданные с помощью литералов объектов, имеют один и тот же объект-прототип, на который в программе JavaScript можно сослаться так: Object.prototype. Объекты, созданные с помощью ключевого слова new и вызова конструктора, в качестве прототипа получают значение свойства prototype функции-конструктора. Поэтому объект, созданный выражением new Object(), наследует свойства объекта Object.prototype, как если бы он был создан с помощью литерала в фигурных скобках {}. Аналогично прототипом объекта, созданного выражением new Array(), является Array.prototype, а прототипом объекта, созданного выражением new Date(), являетсяDate.prototype.

Object.prototype - один из немногих объектов, которые не имеют прототипа: у него нет унаследованных свойств. Другие объекты-прототипы являются самыми обычными объектами, имеющими собственные прототипы. Все встроенные конструкторы (и большинство пользовательских конструкторов) наследуют прототип Object.prototype. Например, Date. prototype наследует свойства от Object.prototype, поэтому объект Date, созданный выражением new Date(), наследует свойства от обоих прототипов, Date.prototype и Object.prototype. Такая связанная последовательность объектов-прототипов называется цепочкой прототипов.

Описание механизма наследования свойств приводится в разделе 6.2.2. Как получить ссылку на прототип объекта, рассказывается в разделе 6.8.1. А в главе 9 более детально будет обсуждаться связь между прототипами и конструкторами: там будет показано, как определять новые «классы» объектов посредством объявления функций-конструкторов и как записывать ссылку на объект-прототип в их свойство prototype для последующего использования «экземплярами», созданными с помощью этого конструктора.

6.1.4. Object.create()

 Сделать закладку на этом месте книги

Стандарт ECMAScript 5 определяет метод Object.create(), который создает новый объект и использует свой первый аргумент в качестве прототипа этого объекта. Дополнительно Object.create() может принимать второй необязательный аргумент, описывающий свойства нового объекта. Описание этого второго аргумента приводится в разделе 6.7.

Object.create() является статической функцией, а не методом, вызываемым относительно некоторого конкретного объекта. Чтобы вызвать эту функцию, достаточно передать ей желаемый объект-прототип:


var о1 = Object.create({x:1, у:2}); // о1 наследует свойства х и у.


Чтобы создать объект, не имеющий прототипа, можно передать значение null, но в этом случае вновь созданный объект не унаследует ни каких-либо свойств, ни базовых методов, таких как toString() (а это означает, что этот объект нельзя будет использовать в выражениях с оператором +):


var о2 = Object.create(null); // о2 не наследует ни свойств, ни методов.


Если в программе потребуется создать обычный пустой объект (который, например, возвращается литералом {} или выражением new Object()), передайте в первом аргументеObject.prototype:


var о2 = Object.create(Object.prototype); // о3 подобен объекту, созданному

                                          // с помощью {} или new Object().


Возможность создавать новые объекты с произвольными прототипами (скажем иначе: возможность создавать «наследников» от любых объектов) является мощным инструментом, действие которого можно имитировать в ECMAScript 3 с помощью функции, представленной в примере 6.1.[8]

)

Пример 6.1. Создание нового объекта, наследующего прототип 


// inherit() возвращает вновь созданный объект, наследующий свойства

// объекта-прототипа р. Использует функцию Object.create() из ECMAScript 5,

// если она определена, иначе используется более старый прием,

function inherit(р) {

  if (р == null) throw ТуреЕrror(); // р не может быть значением null

  if (Object.create)                // Если Object.create() определена...

    return Object.create(p);        // использовать ее.

  var t = typeof p;                 // Иначе выяснить тип и проверить его

  if (t !== "object" && t !== "function") throw ТуреЕrror();

  function f() {};                  // Определить фиктивный конструктор,

  f.prototype = p;                  // Записать в его свойство prototype

                                    // ссылку на объект р.

  return new f();                   // Использовать f() для создания

                                    // "наследника" объекта р.

}


Реализация функции inherit() приобретет больше смысла, как только мы познакомимся с конструкторами в главе 9. А пока просто считайте, что она возвращает новый объект, наследующий свойства объекта в аргументе. Обратите внимание, что функция inherit() не является полноценной заменой для Object.create(): она не позволяет создавать объекты без прототипа и не принимает второй необязательный аргумент, как Object.сreate(). Тем не менее мы будем использовать функцию inherit() во многих примерах в этой главе и в главе 9.

Функцию inheri


убрать рекламу






t()
можно использовать, чтобы защитить объекты от непреднамеренного (не с целью нанести вред) изменения их библиотечными функциями, неподконтрольными вам. Вместо того чтобы передавать функции сам объект непосредственно, можно передать его наследника. Если функция прочитает свойства наследника, она получит унаследованные значения. Однако если она изменяет значения свойств, эти изменения коснутся только наследника и никак не отразятся на оригинальном объекте:


var о = { х: "не изменяйте это значение" };

library_function(inherit(o)); // Защита объекта о от непреднамеренного изменения


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

6.2. Получение и изменение свойств

 Сделать закладку на этом месте книги

Получить значение свойства можно с помощью операторов точки (.) и квадратных скобок ([ ]), описанных в разделе 4.4. Слева от оператора должно находиться выражение, возвращающее объект. При использовании оператора точки справа должен находиться простой идентификатор, соответствующий имени свойства. При использовании квадратных скобок в квадратных скобках должно указываться выражение, возвращающее строку, содержащую имя требуемого свойства:


var author = book.author; // Получить свойство "author" объекта book.

var name = author.surname // Получить свойство "surname" объекта author.

var title = book["main title"] // Получить свойство "main title" объекта book.


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


book.edition = 6; // Создать свойство "edition" объекта book.

book["main title"] = "ECMAScript"; // Изменить значение свойства "main title".


В ECMAScript 3 идентификатор, следующий за точкой, не может быть зарезервированным словом: нельзя записать обращение к свойству о.for или о.class, потому что for является ключевым словом, a class - словом, зарезервированным для использования в будущем. Если объект имеет свойства, имена которых совпадают с зарезервированными словами, для доступа к ним необходимо использовать форму записи с квадратными скобками: o["for"] и o["class"]. Стандарт ECMAScript 5 ослабляет это требование (как это уже сделано в некоторых реализациях ECMAScript 3) и допускает возможность использования зарезервированных слов после оператора точки.

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

6.2.1. Объекты как ассоциативные массивы

 Сделать закладку на этом месте книги

Как отмечалось выше, следующие два выражения возвращают одно и то же значение:


object.property

object["property"]


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

В С, C++, Java и других языках программирования со строгим контролем типов объект может иметь только фиксированное число свойств, а имена этих свойств должны определяться заранее. Поскольку JavaScript относится к языкам программирования со слабым контролем типов, данное правило в нем не действует: программы могут создавать любое количество свойств в любых объектах. Однако при использовании для обращения к свойству оператора точка (.) имя свойства определяется идентификатором. Идентификаторы должны вводиться в тексте программы буквально - это не тип данных, поэтому в программе невозможно реализовать вычисление идентификаторов.

Напротив, когда для обращения к свойствам объекта используется форма записи с квадратными скобками ([]), имя свойства определяется строкой. Строки в языке JavaScript являются типом данных, поэтому они могут создаваться и изменяться в ходе выполнения программы. Благодаря этому, например, в языке JavaScript имеется возможность писать такой программный код:


var addr = "";

for(i =0; і < 4; і++)

  addr += customer["address" + і] + ‘\n';


Этот фрагмент читает и объединяет в одну строку значения свойств address0, address1, address2 и address3 объекта customer.

Этот короткий пример демонстрирует гибкость использования формы записи с квадратными скобками и строковыми выражениями для доступа к свойствам объекта. Пример выше можно переписать с использованием оператора точки, но иногда встречаются случаи, когда доступ к свойствам можно организовать только с помощью формы записи с квадратными скобками. Представим, например, что необходимо написать программу, использующую сетевые ресурсы для вычисления текущего значения инвестиций пользователя в акции. Программа должна позволять пользователю вводить имя каждой компании, акциями которой он владеет, а также количество акций каждой компании. Для хранения этих данных можно было бы создать объект с именем portfolio. Объект имеет по одному свойству для каждой компании. Имя свойства одновременно является названием компании, а значение свойства определяет количество акций этой компании. То есть если, к примеру, пользователь владеет 50 акциями компании IBM, свойство portfolio.ibm будет иметь значение 50.

Следующая функция, добавляющая информацию об очередном пакете акций, могла бы быть частью такой программы:


function addstock(portfolio, stockname, shares) {

  portfolio[stockname] = shares;

}


Поскольку пользователь вводит имена компаний во время выполнения, нет никакого способа заранее определить эти имена. А так как на момент создания программы имена свойств нам неизвестны, мы не можем использовать оператор точки (.) для доступа к свойствам объекта portfolio. Однако мы можем задействовать оператор [], потому что для обращения к свойствам он позволяет использовать строковые значения (которые являются динамическими и могут изменяться во время выполнения) вместо идентификаторов (которые являются статическими и должны жестко определяться в тексте программы).

В главе 5 был представлен цикл for/in (и еще раз мы встретимся с ним чуть ниже, в разделе 6.5). Мощь этой инструкции языка JavaScript становится особенно очевидной, когда она применяется для работы с ассоциативными массивами. Ниже показано, как можно использовать ее для вычисления суммарного объема инвестиций в portfolio:



function getvalue(portfolio) {

  var total = 0.0;

  for(stock in portfolio) { // Для каждой компании в portfolio:

    var shares = portfolio[stock]; // получить количество акций

    var price = getquote(stock); // отыскать стоимость одной акции

    total += shares * price; // прибавить к суммарному значению

  }

  return total; // Вернуть сумму.

}



6.2.2. Наследование

 Сделать закладку на этом месте книги

Объекты в языке JavaScript обладают множеством «собственных свойств» и могут также наследовать множество свойств от объекта-прототипа. Чтобы разобраться в этом, необходимо внимательно изучить механизм доступа к свойствам. В примерах этого раздела для создания объектов с определенными прототипами используется функция inherit() из примера 6.1.

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


var о = {}          //о наследует методы объекта Object.prototype

o.х = 1;            //и обладает собственным свойством х.

var р = inherit(о); // р наследует свойства объектов о и Object.prototype

p.у = 2;            //и обладает собственным свойством у.

var q = inherit(p); // q наследует свойства объектов р, о и Object.prototype

q.z = 3;            //и обладает собственным свойством z.

var s = q.toString(); // toString наследуется от Object.prototype

q.x+q.y             // => 3: x и у наследуются от о и p


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

Операция присваивания значения свойству проверит наличие этого свойства в цепочке прототипов, чтобы убедиться в допустимости присваивания. Например, если объект о наследует свойство х, доступное только для чтения, то присваивание выполняться не будет. (Подробнее о том, когда свойство может устанавливаться, рассказывается в разделе 6.2.3.) Однако если присваивание допустимо, всегда создается или изменяется свойство в оригинальном объекте и никогда в цепочке прототипов. Тот факт, что механизм наследования действует при чтении свойств, но не действует при записи новых значений, является ключевой особенностью языка JavaScript, потому что она позволяет выборочно переопределять унаследованные свойства:


var unitcircle = { r:1 };    // Объект, от которого наследуется свойство

var с = inherit(unitcircle); // с наследует свойство г

с.х = 1; с.у = 1;            //с определяет два собственных свойства

с.r = 2;                     //с переопределяет унаследованное свойство

unitcircle.r;                // => 1: объект-прототип не изменился


Существует одно исключение из этого правила, когда операция присваивания значения свойству терпит неудачу или приводит к созданию/изменению свойства оригинального объекта. Если объект о наследует свойство х и доступ к этому свойству осуществляется посредством методов доступа (раздел 6.6), то вместо создания нового свойства х в объекте о производится вызов метода записи нового значения. Однако обратите внимание, что метод записи вызывается относительно объекта о, а не относительно прототипа, в котором определено это свойство, поэтому, если метод записи определяет какие-либо свойства, они будут созданы в объекте о, а цепочка прототипов опять останется неизменной.

6.2.3. Ошибки доступа к свойствам

 Сделать закладку на этом месте книги

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

Попытка обращения к несуществующему свойству не считается ошибкой. Если свойство х не будет найдено среди собственных или унаследованных свойств объ¬екта о, выражение обращения к свойству о.х вернет значение undefined. Напомню, что наш объект book имеет свойство с именем «sub-title», но не имеет свойства «subtitle»:


book.subtitle; // => undefined: свойство отсутствует


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


// Возбудит исключение ТуреЕrror. Значение undefined не имеет свойства length

var len = book.subtitle.length;


Если нет уверенности, что book и book.subtitle являются объектами (или ведут себя подобно объектам), нельзя использовать выражение book.subtitle.length, так как оно может возбудить исключение. Ниже демонстрируются два способа защиты против исключений подобного рода:


// Более наглядный и прямолинейный способ

var len = undefined;

if (book) {

  if (book.subtitle) len = book.subtitle.length;

}


// Более краткая и характерная для JavaScript альтернатива получения длины

// значения свойства subtitle

var len = book && book.subtitle && book.subtitle.length;


Чтобы понять, почему второе выражение позволяет предотвратить появление исключений ТуреЕrror, можете вернуться к описанию короткой схемы вычислений, используемой оператором &&, в разделе 4.10.1. Разумеется, попытка установить значение свойства для значения null или undefined также вызывает исключение ТуреЕrror.

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


// Свойства prototype встроенных конструкторов доступны только для чтения.

Object.prototype = 0; // Присваивание не возбудит исключения;

                      // значение Object.prototype не изменится


Этот исторически сложившийся недостаток JavaScript исправлен в строгом режиме, определяемом стандартом ECMAScript 5. Все неудачные попытки изменить значение свойства в строгом режиме приводят к исключению ТуреЕrror.

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

• Объект о имеет собственное свойство р, доступное только для чтения: нельзя изменить значение свойства, доступного только для чтения. (Обратите, однако, внимание на метод defineProperty(), который представляет собой исключение, позволяющее изменять значения настраиваемых свойств, доступных только для чтения.)

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

• Объект о не имеет собственного свойства р; объект о не наследует свойство р с методами доступа и атрибут extensible (раздел 6.8.3) объекта о имеет значение false. Если свойство р отсутствует в объекте о и для него не определен метод записи, то операция присваивания попытается добавить свойство р в объект о. Но поскольку объект о не допускает возможность расширения, то попытка добавить в него новое свойство потерпит неудачу.

6.3. Удаление свойств

 Сделать закладку на этом месте книги

Оператор delete (раздел 4.13.3) удаляет свойство из объекта. Его единственный операнд должен быть выражением обращения к свойству. Может показаться удивительным, но оператор delete не оказывает влияния на значение свойства - он оперирует самим свойством:


delete book.author; // Теперь объект book не имеет свойства author,

delete book["main title"]; // Теперь он не имеет свойства "main title".


Оператор delete удаляет только собственные свойства и не удаляет унаследованные. (Чтобы удалить унаследованное свойство, необходимо удалять его в объекте-прототипе, в котором оно определено. Такая операция затронет все объекты, наследующие этот прототип.)

Выражение delete возвращает значение true в случае успешного удаления свойства или когда операция удаления не привела к изменению объекта (например, при попытке удалить несуществующее свойство). Выражение delete также возвращает true, когда этому оператору передается выражение, не являющееся выражением обращения к свойству:


о = {х:1}; //о имеет собственное свойство х и наследует toString

delete о.х; // Удалит х и вернет true

delete о.х; // Ничего не сделает (х не существует) и вернет true

delete о.toString; // Ничего не сделает (toString не собственное свойство) и вернет true

delete 1; // Бессмысленно, но вернет true


Оператор delete не удаляет ненастраиваемые свойства, атрибут configurable которых имеет значение false. (Однако он может удалять настраиваемые свойства нерасширяемых объектов.) Ненастраиваемыми являются свойства встроенных объектов, а также свойства глобального объекта, созданные с помощью инструкций объявления переменных и функций. Попытка удалить ненастраиваемое свойство в строгом режиме вызывает исключение Type Error. В нестрогом режиме (и в реализациях ECMAScript 3) в таких случаях оператор delete просто возвращает false:


delete Object.prototype; // Удаление невозможно - ненастраиваемое свойство

var х = 1; // Объявление глобальной переменной

delete this.x; // Это свойство нельзя удалить

function f() {} // Объявление глобальной функции

delete this.f; // Это свойство также нельзя удалить


При удалении настраиваемых свойств глобального объекта в нестрогом режиме допускается опускать ссылку на глобальный объект и передавать оператору delete только имя свойства:


this.x =1; // Создать настраиваемое глобальное свойство (без var)

delete х; // И удалить его


Однако в строгом режиме оператор delete возбуждает исключение SyntaxError, если его операндом является неквалифицированный идентификатор, такой как х, поэтому необходимо указывать явное выражение обращения к свойству:


delete х; // В строгом режиме возбудит исключение SyntaxError

delete this.x; // Такой способ работает


6.4. Проверка существования свойств

 Сделать закладку на этом месте книги

Объекты в языке JavaScript можно рассматривать как множества свойств, и нередко бывает полезно иметь возможность проверить принадлежность к множеству - проверить наличие в объекте свойства с данным именем. Выполнить такую проверку можно с помощью оператора in, с помощью методов hasOwnProperty() и propertylsEnumerable() или просто обратившись к свойству.

Оператор in требует, чтобы в левом операнде ему было передано имя свойства (в виде строки) и объект в правом операнде. Он возвращает true, если объект имеет собственное или унаследованное свойство с этим именем:


var о = { х: 1 }

”х" in о; // true: о имеет собственное свойство "х"

"у" in о; // false: о не имеет свойства "у"

"toString" in о; // true: о наследует свойство toString


Метод hasOwnProperty() объекта проверяет, имеет ли объект собственное свойство с указанным именем. Для наследуемых свойств он возвращаетfalse:


var о = { х: 1 }

о.hasOwnPropertyC'x"); // true: о имеет собственное свойство х

о.hasOwnPropertyC'y"); // false: не имеет свойства у

о.hasOwnProperty("toString"); // false: toString - наследуемое свойство


Метод propertylsEnumerable() накладывает дополнительные ограничения по сравнению с hasOwnProperty(). Он возвращает true, только если указанное свойство является собственным свойством, атрибут enumerable которого имеет значение true. Свойства встроенных объектов не являются перечислимыми. Свойства, созданные обычной программой на языке JavaScript, являются перечислимыми, если не был использован один из методов ECMAScript 5, представленных ниже, которые делают свойства неперечислимыми.


var о = inherit({ у: 2 }); о.х = 1;

о.propertyIsEnumerable("x“); // true: о имеет собств. перечислимое свойство х

о.propertyIsEnumerable("у"); // false: у - унаследованное свойство, не собств.

Object.prototype.propertyIsEnumerable("toString”); // false: неперечислимое

var о = { х: 1 }

о.х !== undefined; // true: о имеет свойство х

о.у !== undefined; // false; о не имеет свойства у

o.toString !== undefined; // true: о наследует свойство toString


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


var о = { х: undefined } // Свойству явно присвоено значение undefined

о.х !== undefined        // false: свойство имеется, но со значением undefined

о.у !== undefined        // false: свойство не существует

"х" in о                 // true: свойство существует

"у” in о                 // false: свойство не существует

delete о.х;              // Удалить свойство х

"х" in о                 // false: оно больше не существует


Обратите внимание, что в примере выше использован оператор !==, а не !=. Опера¬торы !== и === отличают значения undefined и null, хотя иногда в этом нет необходимости:


// Если о имеет свойство X, значение которого отлично от null и undefined,

// то удвоить это значение,

if (о.х != null) о.х *= 2;

// Если о имеет свойство х, значение которого не может быть преобразовано в false,

// то удвоить это значение. Если х имеет значение undefined, null, false, 0 или NaN,

// оставить его в исходном состоянии,

if (о.х) о.х *= 2;



6.5. Перечисление свойств

 Сделать закладку на этом месте книги

Вместо проверки наличия отдельных свойств иногда бывает необходимо обойти все имеющиеся свойства или получить список всех свойств объекта. Обычно для этого используется цикл for/in, однако стандарт ECMAScript 5 предоставляет две удобные альтернативы.

Инструкция цикла for/in рассматривалась в разделе 5.5.4. Она выполняет тело цикла для каждого перечислимого свойства (собственного или унаследованного) указанного объекта, присваивая имя свойства переменной цикла. Встроенные методы, наследуемые объектами, являются неперечислимыми, а свойства, добавляемые в объекты вашей программой, являются перечислимыми (если только не использовались функции, описываемые ниже, позволяющие сделать свойства неперечислимыми). Например:


var о = {х:1, у:2, z:3}; // Три собственных перечислимых свойства

о.propertyIsEnumerable("toString") // => false: неперечислимое

for(p in о) // Цикл по свойствам

  console.log(p); // Выведет х, у и z, но не toString


Некоторые библиотеки добавляют новые методы (или другие свойства) в объект Object.prototype, чтобы они могли быть унаследованы и быть доступны всем объектам. Однако до появления стандарта ECMAScript 5 отсутствовала возможность сделать эти дополнительные методы неперечислимыми, поэтому они оказывались доступными для перечисления в циклах for/in. Чтобы решить эту проблему, может потребоваться фильтровать свойства, возвращаемые циклом for/in. Ниже приводятся два примера реализации такой фильтрации:


fог(р in о) {

  if (!о.hasOwnProperty(p)) continue; // Пропустить унаследованные свойства

}

for(p in о) {

  if (typeof о[р] === "function”) continue; // Пропустить методы

}


В примере 6.2 определяются вспомогательные функции, использующие цикл for/in для управления свойствами объектов. Функция extend(), в частности, часто используется в библиотеках JavaScript.[9]


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

/*

*       Копирует перечислимые свойства из объекта р в объект о и возвращает о.

*       Если о и р имеют свойства с одинаковыми именами, значение свойства

*       в объекте о затирается значением свойства из объекта р.

*       Эта функция не учитывает наличие методов доступа и не копирует атрибуты.

*/

function extend(o, р) {

  fоr(ргор in р) {            // Для всех свойств в р.

    о[рг


убрать рекламу






ор] = р[prop];        // Добавить свойство в о.

  }

  return о;

}

/*

*       Копирует перечислимые свойства из объекта р в объект о и возвращает о.

*       Если о и р имеют свойства с одинаковыми именами, значение свойства

*       в объекте о остается неизменным.

*       Эта функция не учитывает наличие методов доступа и не копирует атрибуты.

*/

function merge(o, р) {

  fоr(ргор in р) {                        // Для всех свойств в р.

    if (о.hasOwnProperty[prop]) continue; // Кроме имеющихся в о.

    о[prop] = р[prop];                    // Добавить свойство в о.

  }

  return о;

}

/*

*       Удаляет из объекта о свойства, отсутствующие в объекте р.

*       Возвращает о.

*/

function restricts, р) {

  fоr(prop in о) {                     // Для всех свойств в о

    if (!(prop in р)) delete о[prop]; // Удалить, если отсутствует в р

  }

  return о;

}

/*

*       Удаляет из объекта о свойства, присутствующие в объекте р. Возвращает о.

*/

function subtracts, р) {

  for(prop in р) {      // Для всех свойств в р

    delete о[ргор];     // Удалить из о (удаление несуществующих

                        // свойств можно выполнять без опаски)

  }

  return о;

}

/*

*       Возвращает новый объект, содержащий свойства, присутствующие хотя бы в одном

*       из объектов, о или р. Если оба объекта, о и р, имеют свойства с одним

*       и тем же именем, используется значение свойства из объекта р.

*/

function union(o,p) { return extend(extend({},о), p); }

/*

*       Возвращает новый объект, содержащий свойства, присутствующие сразу в обоих

*       объектах, о или р. Результат чем-то напоминает пересечение о и р,

*       но значения свойств объекта р отбрасываются */

function intersection(o, р) { return restrict(extend({}, о), р); }

/*

*       Возвращает массив имен собственных перечислимых свойств объекта о.

*/

function keys(o) {

  if (typeof о !== "object”) throw ТуреЕггогО; // Apr. должен быть объектом

  var result = [];         // Возвращаемый массив

  for(var prop in о) {                // Для всех перечислимых свойств

    if (о.hasOwnProperty(prop)) // Если это собственное свойство,

         result.push(prop); // добавить его в массив array.

  }

  return result;      // Вернуть массив.

}


В дополнение к циклу for/in стандарт ECMAScript 5 определяет две функции, перечисляющие имена свойств. Первая из них, Object.keys(), возвращает массив имен собственных перечислимых свойств объекта. Она действует аналогично функции keys() из примера 6.2.


Вторая функция ECMAScript 5, выполняющая перечисление свойств, - Object.getOwnPropertyNames(). Она действует подобно функции Object.keys(), но возвращает имена всех собственных свойств указанного объекта, а не только перечислимые. В реализациях ECMAScript 3 отсутствует возможность реализовать подобные функции, потому что ECMAScript 3 не предусматривает возможность получения неперечислимых свойств объекта.

6.6. Методы чтения и записи свойств

 Сделать закладку на этом месте книги

Выше уже говорилось, что свойство объекта имеет имя, значение и набор атрибутов. В ECMAScript 5[10]

значение может замещаться одним или двумя методами, известными как методы чтения (getter) и записи (setter). Свойства, для которых определяются методы чтения и записи, иногда называют свойствами с методами доступа, чтобы отличать их от свойств с данными, представляющих простое значение.

Когда программа пытается получить значение свойства с методами доступа, интерпретатор вызывает метод чтения (без аргументов). Возвращаемое этим методом значение становится значением выражения обращения к свойству. Когда программа пытается записать значение в свойство, интерпретатор вызывает метод записи, передавая ему значение, находящее справа от оператора присваивания. Этот метод отвечает за «установку» значения свойства. Значение, возвращаемое методом записи, игнорируется.

В отличие от свойств с данными, свойства с методами доступа не имеют атрибута writable. Если свойство имеет оба метода, чтения и записи, оно доступно для чтения/записи. Если свойство имеет только метод чтения, оно доступно только для чтения. А если свойство имеет только метод записи, оно доступно только для записи (такое невозможно для свойств с данными) и попытки прочитать значение такого свойства всегда будут возвращать undefined.

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


var о = {

  // Обычное свойство с данными

  data_prop: value,

  // Свойство с методами доступа определяется как пара функций

  get accessor_prop() { /* тело функции */ },

  set accessor_prop(value) { /* тело функции */ }

};


Свойства с методами доступа определяются как одна или две функции, имена которых совпадают с именем свойства и с заменой ключевого слова function на get и/или set. Обратите внимание, что не требуется использовать двоеточие для отделения имени свойства от функции, управляющей доступом к свойству, но по-прежнему необходимо использовать запятую после тела функции, чтобы отделить метод от других методов или свойств с данными. Для примера рассмотрим следующий объект, представляющий Декартовы координаты точки на плоскости. Для представления координат X и Y в нем имеются обычные свойства с данными, а также свойства с методами доступа, позволяющие получить эквивалентные полярные координаты точки:


var р = {

  // х и у - обычные свойства с данными, доступные для чтения/записи.

  х: 1.0,

  у: 1.0,

  //r - доступное для чтения/записи свойство с двумя методами доступа.

  // Не забывайте добавлять запятые после методов доступа,

  get r() { return Math.sqrt(this.x*this.x + this.y*this.y); },

  set r(newvalue) {

    var oldvalue = Math.sqrt(this.x*this.x + this.y*this.y);

    var ratio = newvalue/oldvalue;

    this.x *= ratio;

    this.у *= ratio;

  }

  // theta - доступное только для чтения свойство с единственным методом чтения,

  get theta() { return Math.atan2(this.у, this.x); }

};


Обратите внимание на использование ключевого слова this в методах чтения и записи выше. Интерпретатор будет вызывать эти функции, как методы объекта, в котором они определены, т.е. в теле функции this будет ссылаться на объект точки. Благодаря этому метод чтения свойства r может ссылаться на свойства x и у, как this.x и this.у. Подробнее о методах и ключевом слове this рассказывается в разделе 8.2.2.

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


var q = inherit(p);   // Создать новый объект, наследующий методы доступа

q.x = 1; q.y = 1;     // Создать собственные свойства с данными в объекте q

console.log(q.r);     // И использовать унаследованные свойства

console.log(q.theta); // с методами доступа


Фрагмент выше использует свойства с методами доступа для определения API, обеспечивающего представление единого набора данных в двух системах координат (Декартовой и полярной). Еще одной причиной использования свойств с методами доступа может быть необходимость проверки значения перед записью и возврат разных значений при каждом чтении свойства:


// Этот объект генерирует последовательность увеличивающихся чисел

var serialnum = {

  // Это свойство с данными хранит следующее число в последовательности.

  // Знак $ в имени свойства говорит о том, что оно является частным.

  $n: 0,

  // Возвращает текущее значение и увеличивает его

  get next() { return this.$n++; },

  // Устанавливает новое значение n, но только если оно больше текущего

  set next(n) {

    if (n >= this.Sn) this.$n = n;

    else throw "число может быть только увеличено ";

  }

};


Наконец, ниже приводится еще один пример использования метода чтения для реализации свойства с «таинственным» поведением.


// Этот объект имеет свойства с методами доступа, при обращении к которым возвращаются

// случайные числа. Например, каждый раз при вычислении выражения ”random.octet"

// будет возвращаться случайное число в диапазоне от 0 до 255.

var random = {

  get octet() { return Math.floor(Math.random()*256); },

  get uint16() { return Math.floor(Math.random()*65536); },

  get int16() { return Math.floor(Math.random()*65536)-32768; }

};


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

6.7. Атрибуты свойств

 Сделать закладку на этом месте книги

Помимо имени и значения свойства обладают атрибутами, определяющими их доступность для записи, перечисления и настройки. В ECMAScript 3 не предусматривается возможность изменения атрибутов: все свойства, создаваемые программами, выполняющимися под управлением реализации ECMAScript 3, доступны для записи, перечисления и настройки, и нет никакой возможности изменить эти атрибуты. Данный раздел описывает прикладной интерфейс (API), определяемый стандартом ECMAScript 5 для получения и изменения атрибутов свойств. Данный API имеет особое значение для разработчиков библиотек, потому что он позволяет:

• добавлять методы в объекты-прототипы и делать их неперечислимыми, подобно встроенным методам;

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

Для целей данного раздела мы будем рассматривать методы чтения и записи свойств с методами как атрибуты свойств. Следуя этой логике, можно даже сказать, что значение свойства с данными также является атрибутом. Таким образом, свойства имеют имя и четыре атрибута. Четырьмя атрибутами свойств с данными являются: значение (value), признак доступности для записи (writable), признак доступности для перечисления (enumerable) и признак доступности для настройки (configurable). В свойствах с методами доступа отсутствуют атрибуты value и writable: их доступность для записи определяется наличием или отсутствием метода записи. Поэтому четырьмя атрибутами свойств с методами доступа являются: метод чтения (get), метод записи (set), признак доступности для перечисления (enumerable) и признак доступности для настройки (configurable).

Методы получения и записи значений атрибутов свойств, предусмотренные стандартом ECMAScript 5, используют объект, называемый дескриптором свойства (property descriptor), представляющий множество из четырех атрибутов. Объект дескриптора свойства обладает свойствами, имена которых совпадают с именами атрибутов свойства, которое он описывает. То есть объекты-дескрипторы свойств с данными имеют свойства с именами value, writable, enumerable и configurable. А дескрипторы свойств с методами доступа вместо свойств value и writable имеют свойства get и set. Свойства writable, enumerable и configurable являются логическими значениями, а свойства get и set - функциями.

Получить дескриптор свойства требуемого объекта можно вызовом Object.get-OwnPropertyDescriptor():


// Вернет {value: 1, writable:true, enumerable:true, configurable:true}

Object.getOwnPropertyDescriptor({x:1}, "x");

// Теперь получим свойство octet объекта random, объявленного выше.

// Вернет { get: /*func*/. set:undefined, enumerable:true, configurable:true}

Object.getOwnPropertyDescriptor(random, "octet");

// Вернет undefined для унаследованных и несуществующих свойств.

Object.getOwnPropertyDescriptor({}, "х"); // undefined, нет такого свойства

Object.getOwnPropertyDescriptor({}, "toString"); // undefined, унаследованное


Как можно заключить из названия метода, Object.getOwnPropertyDescriptor() работает только с собственными свойствами. Чтобы получить атрибуты унаследованного свойства, необходимо явно выполнить обход цепочки прототипов (смотрите описание Object.getPrototypeOf() в разделе 6.8.1).

Чтобы изменить значение атрибута свойства или создать новое свойство с заданными значениями атрибутов, следует вызвать метод Object.defineProperty(), передав ему объект, в котором требуется выполнить изменения, имя создаваемого или изменяемого свойства и объект дескриптора свойства:


var о = {}; // Создать пустой объект без свойств

// Создать неперечислимое простое свойство х со значением 1.

Object.defineProperty(o, "х", { value : 1,

    writable: true, enumerable: false, configurable: true}):

// Убедиться, что свойство создано и является неперечислимым

о.х;           // => 1

Object.keys(o) // => []

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

Object.defineProperty(o, "х", { writable: false });

// Попытаться изменить значение свойства

о.х = 2; // Неудача, в строгом режиме возбудит ТуреЕrror


Дескриптор свойства, передаваемый методу Object.defineProperty(), необязательно должен иметь все четыре атрибута. При создании нового свойства отсутствующие атрибуты получат значение false или undefined. При изменении существующего свойства для отсутствующих атрибутов будут сохранены текущие значения. Обратите внимание, что этот метод изменяет существующее собственное свойство или создает новое собственное свойство - он не изменяет унаследованные свойства.

Если возникнет необходимость создать или изменить сразу несколько свойств, можно воспользоваться методом Object.defineProperties(). Первым аргументом ему передается объект, который требуется изменить. Вторым аргументом - объект, отображающий имена создаваемых или модифицируемых свойств в дескрипторы этих свойств. Например:


var р = Object.defineProperties({},{

  х:{

    value: 1,

    writable: true,

    enumerable:true,

    configurable:true

  },

  y:{

    value: 1,

    writable: true,

    enumerable:true,

    configurable:true },

  r:{

    get: function() { return Math.sqrt(this.x*this.x + this.y*this.y) },

    enumerable:true,

    configurable:true

  }

});


В этом примере все начинается с пустого объекта, в который затем добавляются два свойства с данными и одно свойство с методами доступа, доступное только для чтения. Он опирается на тот факт, что Object.defineProperties() возвращает модифицированный объект (подобно методу Object.defineProperty()).

С методом Object.сreate(), определяемым стандартом ECMAScript 5, мы познакомились в разделе 6.1, где узнали, что первым аргументом этому методу передается объект, который будет служить прототипом для вновь созданного объекта. Этот метод также принимает второй необязательный аргумент, такой же, как и второй аргумент метода Object.defineProperties(). Если методу Object.create() передать множество дескрипторов свойств, они будут использованы для создания свойств нового объекта.

Методы Object.defineProperty() и Object.defineProperties() возбуждают исключение ТуреError, когда создание или изменение свойств запрещено. Например, при попытке добавить новое свойство в нерасширяемый объект (раздел 6.8.3). Другие причины, по которым эти методы могут возбудить исключение ТуреЕrror, имеют непосредственное отношение к атрибутам. Атрибут writable контролирует попытки изменить атрибут value. А атрибут configurable контролирует попытки изменить другие атрибуты (а также определяет возможность удаления свойства). Однако все не так просто. Например, значение свойства, доступного только для чтения, можно изменить, если это свойство доступно для настройки. Кроме того, свойство, доступное только для чтения, можно сделать доступным для записи, даже если это свойство недоступно для настройки. Ниже приводится полный перечень правил. Вызовы Object.defineProperty() или Object.defineProperties(), нарушающие их, возбуждают исключение ТуреЕrror:

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

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

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

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

• Если свойство с данными недоступно для настройки, нельзя изменить значение его атрибута writable с false на true, но его можно Изменить с true на false.

• Если свойство с данными недоступно для настройки и для записи, нельзя изменить его значение. Однако изменить значение свойства, недоступного для записи можно, если оно доступно для настройки (потому что свойство можно сделать доступным для записи, изменить его значение и затем опять сделать свойство доступным только для чтения).

Пример 6.2 включает функцию extend(), которая копирует свойства из одного объекта в другой. Эта функция просто копирует имена и значения свойств и игнорирует их атрибуты. Кроме того, она не копирует методы чтения и записи из свойств с методами доступа, а просто преобразует их в свойства со статическими данными. В примере 6.3 показана новая версия extend(), которая копирует все атрибуты свойств с помощью Object.getOwnPropertyDescriptor() и Object.defineProperty(). Но на этот раз данная версия оформлена не как функция, а как новый метод объекта и добавляется в Object.prototype как свойство, недоступное для перечисления.


Пример 6.3. Копирование атрибутов свойств 

/*

* Добавляет неперечислимый метод extend() в Object.prototype.

* Этот метод расширяет объекты возможностью копирования свойств из объекта,

* переданного в аргументе. Этот метод копирует не только значение свойств,

* но и все их атрибуты. Из объекта в аргументе копируются все собственные

* свойства (даже недоступные для перечисления), за исключением одноименных

* свойств, имеющихся в текущем объекте.

*/

Object.defineProperty(Object.prototype,

  "extend", // Определяется Object.prototype.extend

  {

    writable: true,

    enumerable: false, // Сделать неперечислимым

    configurable: true,

    value: function(o) { // Значением свойства является данная функция

    // Получить все собственные свойства, даже неперечислимые

    var names = Object.getOwnPropertyNames(o);

    // Обойти их в цикле

    for(var і = 0: і < names.length; i++) {

      // Пропустить свойства, уже имеющиеся в данном объекте

      if (names[i] in this) continue;

      // Получить дескриптор свойства из о

      var desc = Object.getOwnPropertyDescriptor(o,names[i]);

      // Создать с его помощью свойство в данном объекте

      Object.defineProperty(this, names[i], desc);

    }

  }

});


6.7.1. Устаревшие приемы работы с методами чтения и записи

 Сделать закладку на этом месте книги

Синтаксис определения свойств с методами доступа в литералах объектов, описанный разделе 6.6, позволяет определять свойства с методами в новых объектах, но, он не дает возможности получать методы чтения и записи и добавлять новые свойства с методами доступа к существующим объектам. В ECMAScript 5 для этих целей можно использовать Object.getOwnPropertyDescriptor() и Object.defineProperty().

Большинство реализаций JavaScript (за исключением веб-броузера IE) поддерживали синтаксис get и set в литералах объектов еще до принятия стандарта ECMAScript 5. Эти реализации поддерживают нестандартный, устаревший API для получения и назначения методов чтения и записи. Этот API состоит из четырех методов, доступных во всех объектах. __lookupGetter__() и __lookupSetter__() возвращают методы чтения и записи для указанного свойства. А методы __defineGetter__() и __defineSetter__() позволяют определить метод чтения или записи:

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

6.8. Атрибуты объекта

 Сделать закладку на этом месте книги

Все объекты имеют атрибуты prototype, class и extensible. Все эти атрибуты описываются в подразделах ниже; в них также рассказывается, как получать и изменять значения атрибутов (если это возможно).

6.8.1. Атрибут prototype

 Сделать закладку на этом месте книги

Атрибут prototype объекта определяет объект, от которого наследуются свойства. (Дополнительные сведения о прототипах и наследовании прототипов приводятся в разделах 6.1.3 и 6.2.2.) Этот атрибут играет настолько важную роль, что обычно мы будем говорить о нем как о «прототипе объекта о», а не как об «атрибуте prototype объекта о». Кроме того, важно понимать, что когда в программном коде встречается ссылка prototype, она обозначает обычное свойство объекта, а не атрибут prototype.

Атрибут prototype устанавливается в момент создания объекта. В разделе 6.1.3 уже говорилось, что для объектов, созданных с помощью литералов, прототипом является Object.prototype. Прототипом объекта, созданного с помощью оператора new, является значение свойства prototype конструктора. А прототипом объекта, созданного с помощью Object.сreate(), становится первый аргумент этой функции (который может иметь значениеnull).

Стандартом ECMAScript 5 предусматривается возможность определить прототип любого объекта, если передать его методу Object.getPrototypeOf(). В ECMAScript 3 отсутствует эквивалентная функция, но зачастую определить прототип объекта о можно с помощью выражения о.constructor.prototype. Объекты, созданные с помощью оператора new, обычно наследуют свойство constructor, ссылающееся на функцию-конструктор, использованную для создания объекта. И как уже говорилось выше, функции-конструкторы имеют свойство prototype, которое определяет прототип объектов, созданных с помощью этого конструктора. Подробнее об этом рассказывается в разделе 9.2, где также объясняется, почему этот метод определения прототипа объекта не является достаточно надежным. Обратите внимание, что объекты, созданные с помощью литералов объектов или Object.сгеate(), получают свойство constructor, ссылающееся на конструктор Object(). Таким образом, constructor.prototype ссылается на истинный прототип для литералов объектов, но обычно это не так для объектов, создан


убрать рекламу






ных вызовом Object.create().

Чтобы определить, является ли один объект прототипом (или звеном в цепочке прототипов) другого объекта, следует использовать метод isPrototypeOf(). Чтобы узнать, является ли р прототипом о, нужно записать выражение р.isPrototypeOf(о). Например:


var р = {х:1}; // Определить объект-прототип.

var о = Object.сreate(p); // Создать объект с этим прототипом.

р.isPrototypeOf(o) // => true: о наследует р

Object.prototype.isPrototypeOf(р) // => true: р наследует Object.prototype


Обратите внимание, что isPrototypeOf() по своему действию напоминает оператор instanceof (раздел 4.9.4).

В реализации JavaScript компании Mozilla (первоначально созданной в Netscape) значение атрибута prototype доступно через специальное свойство __proto__, которое можно использовать напрямую для определения и установки прототипа любого объекта. Использование свойства __proto__ ухудшает переносимость: оно отсутствует (и, вероятно, никогда не появится) в реализациях броузеров IE или Opera, хотя в настоящее время оно поддерживается броузерами Safari и Chrome. Версии Firefox, реализующие стандарт ECMAScript 5, все еще поддерживают свойство __proto__, но не позволяют изменять прототип нерасширяемых объектов.

6.8.2. Атрибут class

 Сделать закладку на этом месте книги

Атрибут class объекта - это строка, содержащая информацию о типе объекта. Ни в ECMAScript 3, ни в ECMAScript 5 не предусматривается возможность изменения этого атрибута и предоставляются лишь косвенные способы определения его значения. По умолчанию метод toString() (наследуемый от Object.prototype) возвращает строку вида:


[object class]


Поэтому, чтобы определить класс объекта, можно попробовать вызвать метод toString() этого объекта и извлечь из результата подстроку с восьмого по предпоследний символ. Вся хитрость состоит в том, что многие методы наследуют другие, более полезные реализации метода toString(), и чтобы вызвать нужную версию toString(), необходимо выполнить косвенный вызов с помощью метода Function.саll() (раздел 8.7.3). В примере 6.4 определяется функция, возвращающая класс любого объекта, переданного ей.

Пример 6.4. Функция classoff() 


function classof(o) {

  if (о === null) return "Null";

  if (o === undefined) return "Undefined";

  return Object.prototype.toString.call(o).slice(8,-1);

}


Этой функции classof() можно передать любое значение, допустимое в языке JavaScript. Числа, строки и логические значения действуют подобно объектам, когда относительно них вызывается метод toString(), а значения null и undefined обрабатываются особо. (В ECMAScript 5 особая обработка не требуется.) Объекты, созданные с помощью встроенных конструкторов, таких как Array и Date, имеют атрибут class, значение которого совпадает с именами их конструкторов. Объекты среды выполнения обычно также получают осмысленное значение атрибута class, однако это зависит от реализации. Объекты, созданные с помощью литералов или вызовом Object.сreate, получают атрибут class со значением «Object». Если вы определите свой конструктор, все объекты, созданные с его помощью, получат атрибут class со значением «Object»: нет никакого способа установить иное значение в атрибуте class для собственных классов объектов:


classof(null)      // => "Null"

classof(1)         // => "Number"

classof("")        // => "String"

classof(false)     // => "Boolean"

classof({})        // => "Object"

classof([])        // => "Array"

classof(/./)       // => "Regexp"

classof(new DateO) // => "Date"

classof(window)    // => "Window" (объект клиентской среды выполнения)

function f() {};   // Определение собственного конструктора

classof(new f());  // => "Object"


6.8.3. Атрибут extensible

 Сделать закладку на этом месте книги

Атрибут extensible объекта определяет, допускается ли добавлять в объект новые свойства. В ECMAScript 3 все встроенные и определяемые пользователем объекты неявно допускали возможность расширения, а расширяемость объектов среды выполнения определялась каждой конкретной реализацией. В ECMAScript 5 все встроенные и определяемые пользователем объекты являются расширяемыми, если они не были преобразованы в нерасширяемые объекты, а расширяемость объектов среды выполнения по-прежнему определяется каждой конкретной реализацией.

Стандарт ECMAScript 5 определяет функции для получения и изменения признака расширяемости объекта. Чтобы определить, допускается ли расширять объект, его следует передать методу Object.isExtensible(). Чтобы сделать объект нерасширяемым, его нужно передать методу Object.preventExtensions(). Обратите внимание, что после того как объект будет сделан нерасширяемым, его нельзя снова сделать расширяемым. Отметьте также, что вызов preventExtensions() оказывает влияние только на расширяемость самого объекта. Если новые свойства добавить в прототип нерасширяемого объекта, нерасширяемый объект унаследует эти новые свойства.

Назначение атрибута extensible заключается в том, чтобы дать возможность «фиксировать» объекты в определенном состоянии, запретив внесение изменений. Атрибут объектов extensible часто используется совместно с атрибутами свойств configurable и writable, поэтому в ECMAScript 5 определяются функции, упрощающие одновременную установку этих атрибутов.

Метод Object.seal() действует подобно методу Object.preventExtensions(), но он не только делает объект нерасширяемым, но и делает все свойства этого объекта недоступными для настройки. То есть в объект нельзя будет добавить новые свойства, а существующие свойства нельзя будет удалить или настроить. Однако существующие свойства, доступные для записи, по-прежнему могут быть изменены.

После вызова Object.seal() объект нельзя будет вернуть в прежнее состояние. Чтобы определить, вызывался ли метод Object.seal() для объекта, можно вызвать метод Object.isSealed().

Метод Object.freeze() обеспечивает еще более жесткую фиксацию объектов. Помимо того, что он делает объект нерасширяемым, а его свойства недоступными для настройки, он также делает все собственные свойства с данными доступными только для чтения. (Это не относится к свойствам объекта с методами доступа, обладающими методами записи; эти методы по-прежнему будут вызываться инструкциями присваивания.) Чтобы определить, вызывался ли метод Object.freeze() объекта, можно вызвать метод Object.isFrozen().

Важно понимать, что Object.seal() и Object.freeze() воздействуют только на объект, который им передается: они не затрагивают прототип этого объекта. Если в программе потребуется полностью зафиксировать объект, вам, вероятно, потребуется зафиксировать также объекты в цепочке прототипов.

Все методы, Object.preventExtensions(), Object.seal() и Object.freeze(), возвращают переданный им объект, а это означает, что их можно использовать во вложенных вызовах:


// Создать нерасширяемый объект с ненастраиваемыми свойствами, с жестко

// зафиксированным прототипом и свойством, недоступным для перечисления

var о = Object.seal(Object.create(Object.freeze({x:1}),

                 {у: {value: 2, writable: true}})):


6.9. Сериализация объектов

 Сделать закладку на этом месте книги

Сериализация объектов - это процесс преобразования объектов в строковую форму представления, которая позднее может использоваться для их восстановления. Для сериализации и восстановления объектов JavaScript стандартом ЕСМА-Script 5 предоставляются встроенные функции JSON.stringify() и JSON.parse(). Эти функции используют формат обмена данными JSON. Название JSON происходит от «JavaScript Object Notation» (форма записи объектов JavaScript), а синтаксис этой формы записи напоминает синтаксис литералов объектов и массивов в языке JavaScript:


о = {х:1, у:{z:[false.null,""]}}: // Определить испытательный объект

s = JSON.stringify(o); // s == '{"х":1,"у":{"z":[false,null,'"]}}'

p = JSON.parse(s); // P - глубокая копия объекта о


Базовые реализации этих функций в ECMAScript 5 очень точно повторяют общедоступные реализации в ECMAScript 3, доступные в https://json.org/json2.js. С практической точки зрения это совершенно одинаковые реализации, и эти функции стандарта ECMAScript 5 можно использовать в ECMAScript 3, подключив указанный выше модуль json2.js.

Синтаксис формата JSON является лишь подмножеством синтаксиса языка JavaScript и не может использоваться для представления всех возможных значений, допустимых в JavaScript. Поддерживаются и могут быть сериализованы и восстановлены: объекты, массивы, строки, конечные числовые значения, true, false и null. Значения NaN, Infinity и -Infinity сериализуются в значение null. Объекты Date сериализуются в строки с датами в формате ISO (смотрите описание функции Date.toJSON()), но JSON.parse() оставляет их в строковом представлении и не восстанавливает первоначальные объекты Date. Объекты Function, RegExp и Error и значение undefined не могут быть сериализованы или восстановлены. ФункцияJSON.stringify() сериализует только перечислимые собственные свойства объекта. Если значение свойства не может быть сериализовано, это свойство просто исключается из строкового представления. Обе функции, JSON.stringify() и JSON.parse(), принимают необязательный второй аргумент, который можно использовать для настройки процесса сериализации и/или восстановления, например, посредством определения списка свойств, подлежащих сериализации, или функции преобразования значений во время сериализации. В справочном разделе приводится полное описание этих функций.

6.10. Методы класса Object

 Сделать закладку на этом месте книги

Как описывалось выше, все объекты в языке JavaScript (за исключением тех, что явно созданы без прототипа) наследуют свойства от Object.prototype. Эти наследуемые свойства являются первичными методами и представляют особый интерес для программистов на JavaScript, потому что доступны повсеместно. Мы уже познакомились с методами hasOwnProperty(), propertylsEnumerable() и isPrototy-peOf(). (И мы уже охватили достаточно много статических функций, определяемых конструктором Object, таких как Object.create() и Object.getPrototypeOf().) В этом разделе описывается несколько универсальных методов объектов, которые определены в Object.prototype и предназначены для переопределения в других, более специализированных классах.

6.10.1. Метод toString()

 Сделать закладку на этом месте книги

Метод toString() не требует аргументов; он возвращает строку, каким-либо образом представляющую значение объекта, для которого он вызывается. Интерпретатор JavaScript вызывает этот метод объекта во всех тех случаях, когда ему требуется преобразовать объект в строку. Например, это происходит, когда используется оператор + для конкатенации строки с объектом, или при передаче объекта методу, требующему строку.

Метод toString() по умолчанию не очень информативен (однако его удобно использовать для определения класса объекта, как было показано в разделе 6.8.2). Например, следующий фрагмент просто записывает в переменную s строку "[object Object]":


var s = { x:1, у:1 }.toString( );


Этот метод по умолчанию не отображает особенно полезной информации, поэтому многие классы определяют собственные версии метода toString(). Например, когда массив преобразуется в строку, мы получаем список элементов массива, каждый из которых преобразуется в строку, а когда в строку преобразуется функция, мы получаем исходный программный код этой функции. Эти специализированные версии метода toString() описываются в справочном руководстве. Смотрите, например, описание методов Array.toString(), Date.toString() и Function.toString().

В разделе 9.6.3 описывается, как можно переопределить метод toString() для своих собственных классов.

6.10.2. Метод toLocaleString()

 Сделать закладку на этом месте книги

В дополнение к методу toString() все объекты имеют метод toLocaleString(). Назначение последнего состоит в получении локализованного строкового представления объекта. По умолчанию метод toLocaleString(), определяемый классом Object, никакой локализации не выполняет; он просто вызывает метод toString() и возвращает полученное от него значение. Классы Date и Number определяют собственные версии метода toLocaleString(), возвращающие строковые представления чисел и дат в соответствии с региональными настройками. Класс Array определяет версию метода toLocaleString(), действующую подобно методу toString() за исключением того, что он форматирует элементы массива вызовом их метода toLocaleString(), а не toString().

6.10.3. Метод toJSON()

 Сделать закладку на этом месте книги

В действительности Object.prototype не определяет метод toJSON(), но метод JS0N.stringify() (раздел 6.9) пытается отыскать и использовать метод toJSON() любого объекта, который требуется сериализовать. Если объект обладает этим методом, он вызывается и сериализации подвергается возвращаемое значение, а не исходный объект. Примером может служить метод Date.toJSON().

6.10.4. Метод valueOf()

 Сделать закладку на этом месте книги

Метод valueOf() во многом похож на метод toString(), но вызывается, когда интерпретатору JavaScript требуется преобразовать объект в значение какого-либо простого типа, отличного от строки, - обычно в число. Интерпретатор JavaScript вызывает этот метод автоматически, если объект используется в контексте значения простого типа. Метод valueOf() по умолчанию не выполняет ничего, что представляло бы интерес, но некоторые встроенные классы объектов переопределяют метод valueOf() (например, Date.valueOf()). В разделе 9.6.3 описывается, как можно переопределить метод valueOf() в собственных типах объектов.


7

Массивы

 Сделать закладку на этом месте книги

Массив - это упорядоченная коллекция значений. Значения в массиве называются элементами, и каждый элемент характеризуется числовой позицией в массиве, которая называется индексом. Массивы в языке JavaScript являются нети-пизированными: элементы массива могут иметь любой тип, причем разные элементы одного и того же массива могут иметь разные типы. Элементы массива могут даже быть объектами или другими массивами, что позволяет создавать сложные структуры данных, такие как массивы объектов и массивы массивов. Отсчет индексов массивов в языке JavaScript начинается с нуля и для них используются 32-битные целые числа: первый элемент массива имеет индекс 0, а наибольший возможный индекс имеет значение 4294967294 (232-2), т.е. максимально возможный размер массива составляет 4294967295 элементов. Массивы в JavaScript являются динамическими: они могут увеличиваться и уменьшаться в размерах по мере необходимости; нет необходимости объявлять фиксированные размеры массивов при их создании или повторно распределять память при изменении их размеров. Массивы в JavaScript могут быть разреженными: не требуется, чтобы массив содержал элементы с непрерывной последовательностью индексов - в массивах могут отсутствовать элементы с некоторыми индексами. Все массивы в JavaScript имеют свойство length. Для неразреженных массивов это свойство определяет количество элементов в массиве. Для разреженных массивов значение length больше числа всех элементов в массиве.

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

Массивы наследуют свои свойства от прототипа Array.prototype, который определяет богатый набор методов манипулирования массивами, о которых рассказывается в разделах 7.8 и 7.9. Большинство из этих методов являются универсальными, т. е. они могут применяться не только к истинным массивам, но и к любым объектам, «похожим на массивы». Объекты, похожие на массивы, будут рассматриваться в разделе 7.11. В ECMAScript 5 строки ведут себя как массивы символов, и мы обсудим такое их поведение в разделе 7.12.

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

 Сделать закладку на этом месте книги

Легче всего создать массив с помощью литерала, который представляет собой простой список разделенных запятыми элементов массива в квадратных скобках. Например:


var empty = []; // Пустой массив

var primes = [2, 3, 5, 7, 11]; // Массив с пятью числовыми элементами

var misc = [ 1.1, true, "а", ]; // 3 элемента разных типов + завершающая запятая


Значения в литерале массива не обязательно должны быть константами - это могут быть любые выражения:


var base = 1024;

var table = [base, base+1, base+2, base+3];


Литералы массивов могут содержать литералы объектов или литералы других массивов:


var b = [[1,{х:1, у:2}], [2, {х:3, у:4}]];


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


var count = [1,,3]; // Элементы с индексами 0 и 2. count[1] => undefined

var undefs =[,,]; // Массив без элементов, но с длиной, равной 2


Синтаксис литералов массивов позволяет вставлять необязательную завершающую запятую, т.е. литерал [,,] соответствует массиву с двумя элементами, а не с тремя.

Другой способ создания массива состоит в вызове конструктора Аггау(). Вызвать конструктор можно тремя разными способами:

• Вызвать конструктор без аргументов:


var а = new Array();


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

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


var а = new Array(10);


В этом случае будет создан пустой массив указанной длины. Такая форма вызова конструктора Array() может использоваться для предварительного распределения памяти под массив, если заранее известно количество его элементов. Обратите внимание, что при этом в массиве не сохраняется никаких значений и даже свойства-индексы массива с именами «0», «1» и т. д. в массиве не определены.

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


var а = new Array(5, 4, 3, 2, 1, "testing, testing");


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

7.2. Чтение и запись элементов массива

 Сделать закладку на этом месте книги

Доступ к элементам массива осуществляется с помощью оператора [ ]. Слева от скобок должна присутствовать ссылка на массив. Внутри скобок должно находиться произвольное выражение, возвращающее неотрицательное целое значение. Этот синтаксис пригоден как для чтения, так и для записи значения элемента массива. Следовательно, допустимы все приведенные далее JavaScript-инструкции:


var а = ["world"]; // Создать массив с одним элементом

var value = а[0]; // Прочитать элемент 0

а[1] = 3.14; // Записать значение в элемент 1

і = 2;

а[і] = 3; // Записать значение в элемент 2

а[і + 1] = "hello"; // Записать значение в элемент 3

а[а[і]] = а[0]; // Прочитать элементы 0 и 2, записать значение в элемент 3


Напомню, что массивы являются специализированной разновидностью объектов. Квадратные скобки, используемые для доступа к элементам массива, действуют точно так же, как квадратные скобки, используемые для доступа к свойствам объекта. Интерпретатор JavaScript преобразует указанные в скобках числовые индексы в строки - индекс 1 превращается в строку "1" , - а затем использует строки как имена свойств. В преобразовании числовых индексов в строки нет ничего особенного: то же самое можно проделывать с обычными объектами:


о = {}; // Создать простой объект

о[1] = "one"; // Индексировать его целыми числами


Особенность массивов состоит в том, что при использовании имен свойств, которые являются неотрицательными целыми числами, не превышающими 232-2, массивы автоматически определяют значение свойства length. Например, выше был создан массив а с единственным элементом. Затем были присвоены значения его элементам с индексами 1, 2 и 3. В результате этих операций значение свойства length массива изменилось:


a.length // => 4


Следует четко отличать индексы в массиве от имен свойств объектов. Все индексы являются именами свойств, но только свойства с именами, представленными целыми числами в диапазоне от 0 до 232-2 являются индексами. Все массивы являются объектами, и вы можете добавлять к ним свойства с любыми именами. Однако если вы затрагиваете свойства, которые являются индексами массива, массивы реагируют на это, обновляя значение свойства length при необходимости.

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


а[-1.23] = true; // Будет создано свойство с именем "-1.23"

а["1000"] = 0; // 1001-й элемент массива

а[1.000] // Элемент с индексом 1. То же. что и а[1]


То обстоятельство, что индексы массива являются всего лишь особой разновидностью имен свойств объекта, означает, что для массивов JavaScript отсутствует понятие ошибки «выхода за границы». Попытка получить значение любого несуществующего свойства любого объекта не рассматривается как ошибка, в этом случае просто возвращается значение undefined. То же относится и к массивам:


а = [true, false]; // Этот массив имеет элементы с индексами 0 и 1

а[2] // => undefined. Нет элемента с таким индексом.

а[-1] // => undefined. Нет свойства с таким именем.


Поскольку массивы фактически являются объектами, они могут наследовать элементы от своих прототипов. В ECMAScript 5 массивы могут даже иметь элементы, определяющие методы чтения и записи (раздел 6.6). Если массив наследует элементы или элементы в нем имеют методы доступа, доступ к такому массиву не оптимизируется интерпретатором: время доступа к элементам такого массива будет сопоставимо с временем поиска обычных свойств объекта.

7.3. Разреженные массивы

 Сделать закладку на этом месте книги

Разреженным называется массив, индексы элементов которого не образуют непрерывную последовательность чисел, начиная с 0. Обычно свойство length массива определяет количество элементов в массиве. В разреженном массиве значение свойства length больше количества элементов. Разреженный массив можно создать с помощью конструктора Аггау() или путем присваивания значения элементу с индексом, большим, чем текущая длина массива.


а = new Array(5); // Нет элементов, но a.length имеет значение 5.

а = []; // Создаст пустой массив со значением length = 0.

а[1000] = 0; // Добавит один элемент, но установит дл


убрать рекламу






ину равной 1001.


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

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

Обратите внимание, что литералы с пропущенными значениями (когда в определении подряд следуют запятые, например [1, ,3]) создают разреженные массивы, в которых пропущенные элементы просто не существуют:


var a1 = [,]; // Массив без элементов с длиной, равной 1

var а2 = [undefined]; // Массив с одним неопределенным элементом

О in а1 // => false: а1 не имеет элемента с индексом О

О in а2 // => true: а2 имеет элемент с индексом 0 и со значением undefined


Некоторые старые реализации (такие как Firefox 3) некорректно вставляли элементы со значением undefined на место пропущенных элементов. В этих реализациях литерал [1,,3] был эквивалентен литералу [1, undefined,3].

7.4. Длина массива

 Сделать закладку на этом месте книги

Любой массив имеет свойство length, и это свойство отличает массивы от обычных объектов JavaScript. Для плотных (т.е. неразреженных) массивов свойство length определяет количество элементов в массиве. Его значение на единицу больше самого большого индекса в массиве:


[].length // => 0: массив не имеет элементов

['а','Ь','с'].length // => 3: наибольший индекс равен 2, длина равна 3


Для разреженных массивов значение свойства length больше числа элементов, и все, что можно сказать в этом случае, - это то, что значение свойства length гарантированно будет превышать индекс любого элемента в массиве. Или, говоря иначе, массивы (разреженные или нет) никогда не будут содержать элемент, индекс которого будет больше или равен значению свойства length массива. Для поддержки этого свойства массивы проявляют две особенности поведения. Первая была описана выше: если присвоить значение элементу массива, индекс і которого больше или равен текущему значению свойства length, в свойство length записывается значение i+1.

Вторая особенность в поведении, обеспечивающем работу свойства length, заключается в том, что при присваивании свойству length неотрицательного целого числа n, меньшего, чем его текущее значение, все элементы массива с индексами, большими или равными значению n, удаляются из массива:


а = [1,2,3,4,5]; // Создать массив с пятью элементами.

a.length =3; // теперь массив а содержит элементы [1,2,3].

a.length = 0; // Удалит все элементы, а - пустой массив [].

a.length = 5; // Длина равна 5, но элементы отсутствуют, подобно Аггау(5)


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

В ECMAScript 5 свойство length массива можно сделать доступным только для чтения, с помощью Object.defineProperty() (раздел 6.7):


а = [1,2,3]; // Создать массив а с тремя элементами.

Object.defineProperty(a, ‘length", // Сделать свойство length

      {writable: false}); // доступным только для чтения,

a.length =0; //а не изменится.


Аналогично, если сделать элемент массива ненастраиваемым, его нельзя будет удалить. Если элемент нельзя будет удалить, то и свойство length не может быть установлено в значение, меньшее или равное индексу ненастраиваемого элемента. (Смотрите раздел 6.7, а также описание методов Object.seal() и Object.freeze() в разделе 6.8.3.)

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

 Сделать закладку на этом месте книги

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


а = [] // Создать пустой массив.

а[0] = "zero"; // И добавить элементы.

а[1] = "one";


Для добавления одного или более элементов в конец массива можно также использовать метод push():


а = []; // Создать пустой массив

a.push("zero") // Добавить значение в конец, а = ["zero"]

a.push("one", "two") // Добавить еще два значения, а = ["zero", "one", "two"]


Добавить элемент в конец массива можно также, присвоив значение элементу а[а.length]. Для вставки элемента в начало массива можно использовать метод unshift() (описывается в разделе 7.8), при этом существующие элементы в массиве смещаются в позиции с более высокими индексами.

Удалять элементы массива можно с помощью оператора delete, как обычные свойства объектов:


а = [1.2.3];

delete а[1]; // теперь в массиве а отсутствует элемент с индексом 1

1 in а   // => false: индекс 1 в массиве не определен

a.length // => 3: оператор delete не изменяет свойство length массива


Удаление элемента напоминает (но несколько отличается) присваивание значения undefined этому элементу. Обратите внимание, что применение оператора delete к элементу массива не изменяет значение свойства length и не сдвигает вниз элементы с более высокими индексами, чтобы заполнить пустоту, оставшуюся после удаления элемента. После удаления элемента массив превращается в разреженный массив.

Кроме того, как уже было показано выше, имеется возможность удалять элементы в конце массива простым присваиванием нового значения свойству length. Массивы имеют метод рор() (противоположный методу push()), который уменьшает длину массива на 1 и возвращает значение удаленного элемента. Также имеется метод shift() (противоположный методу unshift()), который удаляет элемент в начале массива. В отличие от оператора delete, метод shift() сдвигает все элементы вниз на позицию ниже их текущих индексов. Методырор() и shift() описываются в разделе 7.8 и в справочном разделе.

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

7.6. Обход элементов массива

 Сделать закладку на этом месте книги

Наиболее часто для обхода элементов массива используется цикл for (раздел 5.5.3):


var keys = Object.keys(o); // Получить массив имен свойств объекта о

var values = [] // Массив для сохранения значений свойств

for(var і = 0; і < keys.length; i++) { // Для каждого элемента в массиве

  var key = keys[і]; // Получить имя свойства по индексу

  values[i] = о[key]; // Сохранить значение в массиве values

}


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


for(var і = 0, len = keys.length; і < len; i++) {

  // тело цикла осталось без изменений

}


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


for(var і = 0; і < a.length; i++) {

if (!a[і]) continue; // Пропустить null, undefined и несуществ. элементы // тело цикла

}


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


for(var і = 0; і < a.length; i++) {

if (a[і] === undefined) continue; // Пропустить undefined + несуществ. эл.

  // тело цикла

}


Наконец, если необходимо пропустить только несуществующие элементы, а элементы со значением undefined обрабатывать как обычные элементы, проверку можно записать так:


for(var і = 0; і < a.length; i++) {

if (!(i in a)) continue ; // Пропустить несуществующие элементы

  // тело цикла

}


Для обхода разреженных массивов можно также использовать цикл for/in (раздел 5.5.4). Этот цикл присваивает имена перечислимых свойств (включая индексы массива) переменной цикла. Отсутствующие индексы в итерациях не участвуют:


for(var index in sparseArray) {

  var value = sparseArray[index];

  // Далее следуют операции с индексами и значениями

}


Как отмечалось в разделе 6.5, цикл for/in может возвращать имена унаследованных свойств, такие как имена методов, добавленных в Array.prototype. По этой причине не следует использовать цикл for/in для обхода массивов, не предусмотрев дополнительной проверки для фильтрации нежелательных свойств. Для этого можно было бы использовать, например, такие проверки:


for(var і in а) {

  if (!a.hasOwnProperty(i)) continue; // Пропустить унаследованные свойства

  // тело цикла

}

for(var і in а) {

  // Пропустить і, если оно не является целым неотрицательным числом

  if (String(Math.floor(Math.abs(Number(i)))) !== і) continue;

}


Спецификация ECM AScript допускает возможность обхода свойств объекта в цикле for/in в любом порядке. Обычно реализации обеспечивают обход индексов массивов в порядке возрастания, но это не гарантируется. В частности, если массив имеет и свойства объекта, и элементы массива, имена свойств могут возвращаться в порядке их создания, а не в порядке возрастания числовых значений. Разные реализации по-разному обрабатывают эту ситуацию, поэтому, если для вашего алгоритма порядок выполнения итераций имеет значение, вместо цикла for/in лучше использовать обычный цикл for.

Стандарт ECMAScript 5 определяет множество новых методов, позволяющих выполнять итерации по элементам массивов в порядке возрастания индексов и передавать их функции, определяемой пользователем. Наиболее типичным представителем этих методов является метод forEach():


var data = [1,2,3,4,5]; // Этот массив требуется обойти

var sumOfSquares =0; // Требуется вычислить сумму квадратов элементов

data.forEach(function(x){ // Передать каждый элемент этой функции

   sumOfSquares += х*х; // прибавить квадрат к сумме

});

sumOfSquares // =>55 : 1+4+9+16+25


forEach() и другие родственные методы, предназначенные для выполнения итераций, позволяют использовать при работе с массивами простой и мощный стиль функционального программирования. Они описываются в разделе 7.9, и еще раз мы вернемся к ним в разделе 8.8, когда будем рассматривать приемы функционального программирования.

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

 Сделать закладку на этом месте книги

JavaScript не поддерживает «настоящие» многомерные массивы, но позволяет неплохо имитировать их при помощи массивов из массивов. Для доступа к элементу данных в массиве массивов достаточно дважды использовать оператор []. Например, предположим, что переменная matrix - это массив массивов чисел. Каждый элемент matrix[x] - это массив чисел. Для доступа к определенному числу в массиве можно использовать выражение matrix[x][y]. Ниже приводится конкретный пример, где двумерный массив используется в качестве таблицы умножения:


// Создать многомерный массив

var table = new Array(10); // В таблице 10 строк

for(var і = 0; і < table.length; i++)

  table[i] = new Array(10); // В каждой строке 10 столбцов


// Инициализировать массив

for(var row = 0; row < table.length; row++) {

  for(col = 0; col < table[row].length; col++) {

    table[row][col] = row*col;

  }

}

// Расчет произведения 5*7 с помощью многомерного массива

var product = table[5][7]; // 35


7.8. Методы класса Array

 Сделать закладку на этом месте книги

Стандарт ECMAScript 3 определяет в составе Array.prototype множество удобных функций для работы с массивами, которые доступны как методы любого массива. Эти методы будут представлены в следующих подразделах. Более полную информацию можно найти в разделе Array в справочной части по базовому языку JavaScript. Стандарт ECMAScript 5 определяет дополнительные методы для выполнения итераций по массивам - эти методы рассматриваются в разделе 7.9.

7.8.1. Метод join()

 Сделать закладку на этом месте книги

Метод Array.join() преобразует все элементы массива в строки, объединяет их и возвращает получившуюся строку. В необязательном аргументе методу можно передать строку, которая будет использоваться для отделения элементов в строке результата. Если строка-разделитель не указана, используется запятая. Например, следующий фрагмент дает в результате строку «1,2,3»:


var а = [1, 2, 3]; // Создать новый массив с указанными тремя элементами

a.join(); // => "1,2,3"

a.join(" "); // => "1 2 З"

a. join(""); // => "123"

var b = new Аггау(10); // Массив с длиной, равной 10, и без элементов

b. join("-') // => "---------" строка из 9 дефисов



Метод Array.join() является обратным по отношению к методу String.split(), создающему массив путем разбиения строки на фрагменты.

7.8.2. Метод reverse()

 Сделать закладку на этом месте книги

Метод Array.reverse() меняет порядок следования элементов в массиве на обратный и возвращает переупорядоченный массив. Перестановка выполняется непосредственно в исходном массиве, т. е. этот метод не создает новый массив с переупорядоченными элементами, а переупорядочивает их в уже существующем массиве. Например, следующий фрагмент, где используются методы reverse() и join(), дает в результате строку "3,2,1":


var а = [1,2,3];

a.reverse().join(); // => "3,2,1”: теперь а = [3,2,1]


7.8.3. Метод sort()

 Сделать закладку на этом месте книги

Метод Array.sort() сортирует элементы в исходном массиве и возвращает отсортированный массив. Если метод sort() вызывается без аргументов, сортировка выполняется в алфавитном порядке (для сравнения элементы временно преобразуются в строки, если это необходимо):


var а = new Array("banana", "cherry", "apple");

a.sort();

var s = a.join(", ”); // s == "apple, banana, cherry"


Неопределенные элементы переносятся в конец массива.

Для сортировки в каком-либо ином порядке, отличном от алфавитного, методу sort() можно передать функцию сравнения в качестве аргумента. Эта функция устанавливает, какой из двух ее аргументов должен следовать раньше в отсортированном списке. Если первый аргумент должен предшествовать второму, функция сравнения должна возвращать отрицательное число. Если первый аргумент должен следовать за вторым в отсортированном массиве, то функция должна возвращать число больше нуля. А если два значения эквивалентны (т. е. порядок их следования не важен), функция сравнения должна возвращать 0. Поэтому, например, для сортировки элементов массива в числовом порядке можно сделать следующее:


var а = [33, 4, 1111, 222];

a.sortO;               // Алфавитный порядок: 1111, 222, 33, 4

a.sort(function(a,b) { // Числовой порядок: 4, 33, 222, 1111

    return a-b;         // Возвращает значение < 0, 0 или > 0

  }); //в зависимости от порядка сортировки а и b

a.sort(function(a,b) {return b-a}); // Обратный числовой порядок




Обратите внимание, насколько удобно использовать в этом фрагменте неименованную функцию. Функция сравнения используется только здесь, поэтому нет необходимости давать ей имя.

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


а = ['ant', 'Bug', 'cat', 'Dog']

a.sort(); // сортировка с учетом регистра символов: [ 'Bug', 'Dog', 'ant', 'cat' ]

a.sort(function(s,t) { // Сортировка без учета регистра символов

    var а = s.toLowerCase();

    var b = t.toLowerCase();

    if (a < b) return -1;

    if (a > b) return 1;

    return 0;

  }); //=>['ant','Bug','cat'.'Dog']



7.8.4. Метод concat()

 Сделать закладку на этом месте книги

Метод Array.concat() создает и возвращает новый массив, содержащий элементы исходного массива, для которого был вызван метод concat(), и значения всех аргументов, переданных методу concat(). Если какой-либо из этих аргументов сам является массивом, его элементы добавляются в возвращаемый массив. Следует, однако, отметить, что рекурсивного превращения массива из массивов в одномерный массив не происходит. Метод concat() не изменяет исходный массив. Ниже приводится несколько примеров:


var а = [1,2,3];

a.concat(4,5) // Вернет [1,2,3,4,5]

а.concat([4,5]); // Вернет [1,2,3,4,5]

a.concat([4,5],[6,7]) // Вернет [1,2,3,4,5,6,7]

a.concat(4, [5,[6,7]]) // Вернет [1,2,3,4,5,[6,7]]


7.8.5. Метод slice()

 Сделать закладку на этом месте книги

Метод Array.slice() возвращает фрагмент  или подмассив, указанного массива. Два аргумента метода определяют начало и конец возвращаемого фрагмента. Возвращаемый массив содержит элемент, номер которого указан в первом аргументе, плюс все последующие элементы, вплоть до (но не включая) элемента, номер которого указан во втором аргументе. Если указан только один аргумент, возвращаемый массив содержит все элементы от начальной позиции до конца массива. Если какой-либо из аргументов имеет отрицательное значение, он определяет номер элемента относительно конца массива. Так, аргументу -1 соответствует последний элемент массива, а аргументу -3 - третий элемент массива с конца. Вот несколько примеров:


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

a.slice(0,3); // Вернет [1,2,3]

а.slice(3); // Вернет [4,5]

а.slice(1,-1); // Вернет [2,3,4]

a.slice(-3,-2); // Вернет [3]


7.8.6. Метод splice()

 Сделать закладку на этом месте книги

Метод Array.splice() - это универсальный метод, выполняющий вставку или удаление элементов массива. В отличие от методов slice() и concat(), метод splice() изменяет исходный массив, относительно которого он был вызван. Обратите внимание, что методы splice() и slice() имеют очень похожие имена, но выполняют совершенно разные операции.

Метод splice() может удалять элементы из массива, вставлять новые элементы или выполнять обе операции одновременно. Элементы массива при необходимости смещаются, чтобы после вставки или удаления образовывалась непрерывная последовательность. Первый аргумент метода splice() определяет позицию в массиве, начиная с которой будет выполняться вставка и/или удаление. Второй аргумент определяет количество элементов, которые должны быть удалены (вырезаны) из массива. Если второй аргумент опущен, удаляются все элементы массива от указанного до конца массива. Метод splice() возвращает массив удаленных элементов или (если ни один из элементов не был удален) пустой массив. Например:


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

a.splice(4);   // Вернет [5,6,7,8]; а = [1,2,3,4]

a.splice(1,2); // Вернет [2,3]; а = [1,4]

a.splice(1,1); // Вернет [4]; а = [1]


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


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

a.splice(2,0,'а','b');   // Вернет []; а = [1,2,’а','b',3,4,5]

a.splice(2,2, [1,2], 3); // Вернет ['a','b']; а = [1,2, [1,2],3,3,4,5]


Обратите внимание, что, в отличие от concat(), метод splice() вставляет массивы целиком, а не их элементы.

7.8.7. Методы push() и рор()

 Сделать закладку на этом месте книги

Методы push() и рор() позволяют работать с массивами как со стеками. Метод push() добавляет один или несколько новых элементов в конец массива и возвращает его новую длину. Метод pop() выполняет обратную операцию - удаляет последний элемент массива, уменьшает длину массива и возвращает удаленное им значение. Обратите внимание, что оба эти метода изменяют исходный массив, а не создают его модифицированную копию. Комбинация push() и рор() позволяет на основе массива реализовать стек с дисциплиной обслуживания «первым вошел - последним вышел». Например:


var stack = [];  // стек: []

stack.push(1,2): // стек: [1,2] Вернет 2

stack.pop();     // стек: [1] Вернет 2

stack.push(3);   // стек: [1,3] Вернет 2

stack.pop();     // стек: [1] Вернет 3

stack.push([4,5]); // стек: [1,[4,5]] Вернет 2

stack.рор()      // стек: [1] Вернет [4,5]

stack.рор();     // стек: [] Вернет 1



7.8.8. Методы unshift() и shift()

 Сделать закладку на этом месте книги

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


var а = []; // а:[]

a.unshift(1); // а:[1] Вернет: 1

a.unshift(22); // а:[22,1] Вернет: 2

a.shift(); // а:[1] Вернет: 22

a.unshift(3,[4,5]); // а:[3,[4,5],1] Вернет: 3

a.shift(); // а:[[4,5], 1 ] Вернет: 3

a.shift(); // а:[1] Вернет: [4,5]

a.shift(); // а:[] Вернет: 1



Обратите внимание на поведение метода unshift() при вызове с несколькими аргументами. Аргументы вставляются не по одному, а все сразу (как в случае с методом splice()). Это значит, что в результирующем массиве они будут следовать в том же порядке, в котором были указаны в списке аргументов. Будучи вставленными по одному, они бы расположились в обратном порядке.

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

 Сделать закладку на этом месте книги

Массивы, как и любые другие объекты в JavaScript, имеют метод toString(). Для массива этот метод преобразует каждый его элемент в строку (вызывая в случае необходимости методы toString() элементов массива) и выводит список этих строк через запятую. Примечательно, что результат не включает квадратные скобки или какие-либо другие разделители вокруг значений массива. Например:


[1,2,3].toString() //Получается '1,2,3'

["а", "Ь", "с"]. toString() // Получается а,Ь,с'

[1, [2, 'с']].toString() //Получается '1,2,с'


Обратите внимание, что toString() возвращает ту же строку, что и метод join() при вызове его без аргументов.

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

7.9. Методы класса Array, определяемые стандартом ECMAScript 5

 Сделать закладку на этом месте книги

Стандарт ECMAScript 5 определяет девять новых методов массивов, позволяющих выполнять итерации, отображение, фильтрацию, проверку, свертку и поиск. Все эти методы описываются в следующих далее подразделах.

Однако, прежде чем перейти к изучению особенностей, следует сделать некоторые обобщения, касающиеся методов массивов в ECMAScript 5. Во-первых, большинство описываемых ниже методов принимают функцию в первом аргументе и вызывают ее для каждого элемента (или нескольких элементов) массива. В случае разреженных массивов указанная функция не будет вызываться для несуществующих элементов. В большинстве случаев указанной функции передаются три аргумента: значение элемента массива, индекс элемента и сам массив. Чаще всего вам необходим будет только первый аргумент, а второй и третий аргументы можно просто игнорировать. Большинство методов массивов, введенных стандартом ECMAScript 5, которые в первом аргументе принимают функцию, также принимают второй необязательный аргумент. Если он указан, функция будет вызываться, как если бы она была методом этого второго аргумента. То есть второй аргумент будет доступен функции, как значение ключевого слова this. Значение, возвращаемое функцией, играет важную роль, но разные методы обрабатывают его по-разному. Ни один из методов массивов, введенных стандартом ECMAScript 5, не изменяет исходный массив. Разумеется, функция, передаваемая этим методам, может модифицировать исходный массив.

7.9.1. Метод forEach()

 Сделать закладку на этом месте книги

Метод forEach() выполняет обход элементов массива и для каждого из них вызывает указанную функцию. Как уже говорилось выше, функция передается методу forEach() в первом аргументе. При вызове этой функции метод forEach() будет передавать ей три аргумента: значение элемента массива, индекс элемента и сам массив. Если вас интересует только значение элемента, можно написать функцию с одним параметром - дополнительные аргументы будут игнорироваться:


var data = [1,2,3,4,5]; // Массив, элементы которого будут суммироваться

// Найти сумму элементов массива

var sum =0; // Начальное значение суммы 0

data.forEach(function(value) { sum += value; }); // Прибавить значение к sum

sum // => 15


// Увеличить все элементы массива на 1

data.forEach(function(v, і, а) { а[і] = v + 1; });

data // => [2,3,4,5,6]


Обратите внимание, что метод fогEach() не позволяет прервать итерации, пока все элементы не будут переданы функции. То есть отсутствует эквивалент инструкции break, которую можно использовать с обычным циклом for. Если потребуется прервать итерации раньше, внутри функции можно возбуждать исключение, а вызов forEach() помещать в блок try. Ниже демонстрируется функция foreach(), вызывающая метод forEach() внутри такого блока try. Если функция, которая передается функции foreach(), возбудит исключение foreach.break, цикл будет прерван преждевременно:


function foreach(a,f,t) {

  try {

    a.forEach(f,t);

  } catch(e) {

    if (e === foreach.break)

      return;

    else throw e;

  }

}

foreach.break = new Error("StopIteration");


7.9.2. Метод map()

 Сделать закладку на этом месте книги

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


а = [1, 2, 3];

b = a.map(function(x) { return х*х; }); // b = [1, 4, 9]


Метод map() вызывает функцию точно так же, как и метод forEach(). Однако функция, передаваемая методу map(), должна возвращать значение. Обратите внимание, что map() возвращает новый массив: он не изменяет исходный массив. Если исходный массив является разреженным, возвращаемый массив также будет разреженным: он будет иметь ту же самую длину и те же самые отсутствующие элементы.

7.9.3. Метод filter()

 Сделать закладку на этом месте книги

Метод filter() возвращает массив, содержащий подмножество элементов исходного массива. Передаваемая ему функция должна быть функцией-предикатом, т.е. должна возвращать значение true или false. Метод filter() вызывает функцию точно так же, как методы forEach() и map(). Если возвращается true или значение, которое может быть преобразовано в true, переданный функции элемент считается членом подмножества и добавляется в массив, возвращаемый методом. Например:


а = [5, 4, 3, 2, 1];

smallvalues = a.filter(function(x) { return х < 3 }); // [2, 1]

everyother = a.filter(function(x,і) { return і%2==0 }); // [5, 3, 1]


Обратите внимание, что метод filter() пропускает отсутствующие элементы в разреженных массивах и всегда возвращает плотные массивы. Чтобы уплотнить разреженный массив, можно выполнить следующие действия:


var dense = sparse.filter(function() { return true; });


А чтобы уплотнить массив и удалить из него все элементы со значениями undefined и null, можно использовать метод filter(), как показано ниже:


а = a.filter(function(x) { return х !== undefined && х != null; });


7.9.4. Методы every() и some()

 Сделать закладку на этом месте книги

Методы every() и some() являются предикатами массива: они применяют указанную функцию-предикат к элементам массива и возвращают true или false. Метод every() напоминает математический квантор всеобщности V: он возвращает true, только если переданная вами функция-предикат вернула true для всех элементов массива:


а = [1,2,3,4,5];

a.every(function(x) { return х < 10; }) // => true: все значения < 10.

a.every(function(x) { return х % 2 === 0; }) // => false: не все четные.


Метод some() напоминает математический квантор существования 3: он возвращает true, если в массиве имеется хотя бы один элемент, для которого функция-предикат вернет true, а значение false возвращается методом, только если функция-предикат вернет false для всех элементов массива:


а = [1,2,3,4, 5];

a.some(function(x) { return х%2===0; }) // => true: имеются четные числа,

a.some(isNaN) // => false: нет нечисловых элементов.


Обратите внимание, что оба метода, every() и some(), прекращают обход элементов массива, как только результат становится известен. Метод some() возвращает true, как только функция-предикат вернет true, и выполняет обход всех элементов массива, только если функция-предикат всегда возвращает false. Метод every() является полной противоположностью: он возвращает false, как только функция-предикат вернет false, и выполняет обход всех элементов массива, только если функция-предикат всегда возвращает true. Кроме того, отметьте, что в соответствии с правилами математики для пустого массива метод everу() возвращает true, а метод some() возвращает false.

7.9.5. Методы reduce() и reduceRight()

 Сделать закладку на этом месте книги

Методы reduce() и reduceRight() объединяют элементы массива, используя указанную вами функцию, и возвращают единственное значение. Это типичная операция в функциональном программировании, где она известна также под названием «свертка». Примеры ниже помогут понять суть этой операции:


var а = [1,2,3,4,5]

var sum = a.reduce(function(x,у) { return х+у }, 0); // Сумма значений

var product = a.reduce(function(x,у) { return х*у }, 1); // Произвел, значений

var max = a.reduce(function(x,у) { return (х>у)?х:у; }); // Наибольш. значение


Метод reduce() принимает два аргумента. В первом передается функция, которая выполняет операцию свертки. Задача этой функции - объединить некоторым способом или свернуть два значения в одно и вернуть свернутое значение. В примерах выше функции выполняют объединение двух значений, складывая их, умножая и выбирая наибольшее. Во втором (необязательном) аргументе передается начальное значение для функции.

Функции, передаваемые методу reduce(), отличаются от функций, передаваемых методам forEach() и map(). Знакомые уже значение, индекс и массив передаются им во втором, третьем и четвертом аргументах. А в первом аргументе передается накопленный результат свертки. При первом вызове в первом аргументе функции передается начальное значение, переданное методу reduce() во втором аргументе. Во всех последующих вызовах передается значение, полученное в результате предыдущего вызова функции. В первом примере, из приведенных выше, функция свертки сначала будет вызвана с аргументами 0 и 1. Она сложит эти числа и вернет 1. Затем она будет вызвана с аргументами 1 и 2 и вернет 3. Затем она вычислит 3+3=6, затем 6+4=10 и, наконец, 10+5=15. Это последнее значение 15 будет возвращено методом reduce().

Возможно, вы обратили внимание, что в третьем вызове, в примере выше, методу reduce() передается единственный аргумент: здесь не указано начальное значение. Когда метод reduce() вызывается без начального значения, как в данном случае, в качестве начального значения используется первый элемент массива. Это означает, что при первом вызове функции свертки будут переданы первый и второй элементы массива. В примерах вычисления суммы и произведения точно так же можно было бы опустить аргумент с начальным значением.

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

Метод reduceRight() действует точно так же, как и метод reduce(), за исключением того, что массив обрабатывается в обратном порядке, от больших индексов к меньшим (справа налево). Это может потребоваться, если операция свертки имеет ассоциативность справа налево, например:


var а = [2, 3, 4]

// Вычислить 2^(3^4). Операция возведения в степень имеет ассоциативность справа налево

var big = a.reduceRight(function(accumulator,value) {

                     return Math.pow(value,accumulator);

             });


Обратите внимание, что ни reduce(), ни reduceRight() не принимают необязательный аргумент, определяющий значение this внутри функции свертки. Его место занял необязательный аргумент с начальным значением. Если потребуется вызывать функцию свертки как метод конкретного объекта, можно воспользоваться методом Function.bind().

Следует отметить, что методы every() и some(), описанные выше, являются своеобразной разновидностью операции свертки массива. Однако они отличаются отreduce() тем, что стремятся завершить обход массива как можно раньше и не всегда проверяют значения всех его элементов.

В примерах, представленных до сих пор, для простоты использовались числовые массивы, но методы reduce() и reduceRight() могут использоваться не только для математических вычислений. Взгляните на функцию union() в примере 6.2. Она вычисляет «объединение» двух объектов и возвращает новый объект, имеющий свойства обоих. Эта функция принимает два объекта и возвращает другой объект, т. е. она действует как функция свертки, поэтому ее можно использовать с методом reduce() и обобщить операцию создания объединения произвольного числа объектов:


var objects = [{х:1}, {у:2}, {z:3}];

var merged = objects.reduce(union); // => {x:1, y:2, z:3}


Напомню, что, когда два объекта имеют свойства с одинаковыми именами, функция union() использует значение свойства второго аргумента, т.е. reduce() и reduceRight() могут давать разные результаты при использовании с функцией union():


var objects = [{х:1,а:1}, {у:2,а:2}, {z:3,а:3}]:

var leftunion = objects.reduce(union); // {x:1, y:2, z:3. a:3}

var rightunion = objects.reduceRight(union); // {x:1, y:2, z:3, a:1}


7.9.6. Методы indexOf() и lastlndexOf()

 Сделать закладку на этом месте книги

Методы indexOf() и lastlndexOf() отыскивают в массиве элемент с указанным значением и возвращают индекс первого найденного элемента или -1, если элемент с таким значением отсутствует. Метод indexOf() выполняет поиск от начала массива к концу, а метод lastlndexOf() - от конца к началу.


а = [0.1,2.1,0];

a. indexOf(1) // => 1: а[1] = 1

a.lastlndexOf(1) // => 3: а[3] = 1

a.index0f(3) // => -1: нет элемента со значением 3


В отличие от других методов, описанных в этом разделе, методы indexOf() и lastlndexOf() не принимают функцию в виде аргумента. В первом аргументе им передается искомое значение. Второй аргумент является необязательным: он определяет индекс массива, с которого следует начинать поиск. Если опустить этот аргумент, метод indexOf() начнет поиск с начала массива, а метод lastlndexOf() - с конца. Во втором аргументе допускается передавать отрицательные значения, которые интерпретируются как смещение относительно конца массива, как в методе splice(): значение -1, например, соответствует последнему элементу массива.

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

// Отыскивает все вхождения значения х в массив и возвращает

// массив индексов найденных совпадений

function findall(a. х) {

  var results = [], // Возвращаемый массив индексов

  len = a.length, // Длина массива, где выполняется поиск

  pos = 0; // Начальная позиция поиска

  while(pos < len) { // Пока остались непроверенные элементы...

    pos = a.indexOf(x, pos); // Искать

    if (pos === -1) break; // Если ничего не найдено, поиск завершен.

    results.push(pos); // Иначе - сохранить индекс в массиве

    pos = pos +1; //И продолжить поиск со следующего элемента

  }

  return results; // Вернуть массив индексов

}


Обратите внимание, что строки также имеют методы indexOf() и lastlndexOf(), которые действуют подобно методам массивов.

7.10. Тип Array

 Сделать закладку на этом месте книги

На протяжении этой главы мы не раз имели возможность убедиться, что массивы являются объектами, обладающими особыми чертами поведения. Получая неизвестный объект, иногда бывает полезно проверить, является он массивом или нет. Сделать это в реализации ECMAScript 5 можно с помощью функции Array. isArray():


Array.isArray([]) // => true

Array.isArray({}) // => false


Однако до выхода стандарта ECMAScript 5 отличить массивы от других объектов было удивительно сложно. Оператор typeof никак не помогает в этом: для массивов он возвращает строку "object" (и для всех других объектов, кроме функций). В простых случаях можно использовать оператор instanceof:


[] instanceof Array // => true

({}) instanceof Array // => false


Проблема применения оператора instanceof состоит в том, что в веб-броузерах может быть открыто несколько окон или фреймов. Каждое окно или фрейм имеет собственное окружение JavaScript, с собственным глобальным объектом. А каждый глобальный объект имеет собственное множество функций-конструкторов. Поэтому объект из одного фрейма никогда не будет определяться как экземпляр конструктора в другом фрейме. Даже при том, что путаница между фреймами возникает довольно редко, тем не менее этого вполне достаточно, чтобы считать оператор instanceof ненадежным средством определения принадлежности к массивам.

Решение заключается в том, чтобы выполнить проверку атрибута class (раздел 6.8.2) объекта. Для массивов этот атрибут всегда будет иметь значение «Array», благодаря чему в реализации ECMAScript 3 функцию isArray() можно определить так:


var isArray = Function.isArray || function(o) {

  return typeof о === "object" &&

        Object.prototype.toString.call(o) === "[object Array]";

};


Фактически именно такая проверка атрибута class выполняется в функции Array.isArray(), определяемой стандартом ECMAScript 5. Прием определения класса объекта с помощью Object.prototype.toString() был описан в разделе 6.8.2 и продемонстрирован в примере 6.4.

7.11. Объекты, подобные массивам

 Сделать закладку на этом месте книги

Как мы уже видели, массивы в языке JavaScript обладают некоторыми особенностями, отсутствующими в других объектах:

• Добавление нового элемента вызывает автоматическое обновление свойства length.

• Уменьшение значения свойства length вызывает усечение массива.

• Массивы наследуют множество удобных методов от Array.prototype.

• Атрибут class массивов имеет значение «Array».

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

Такие объекты, «подобные массивам», иногда используются для решения практических задач, и хотя с ними нельзя работать через методы массивов или ожидать специфического поведения свойства length, все же можно организовать перебор свойств объекта теми же программными конструкциями, которые используются при работе с настоящими массивами. Оказывается, что значительное число алгоритмов для работы с массивами вполне пригодно для работы с объектами, подобными массивам. Это особенно верно, если используемые алгоритмы не изменяют массивы или хотя бы не затрагивают его свойство length.

В следующем фрагменте создается обычный объект и к нему добавляются дополнительные свойства, которые превращают его в объект, подобный массиву, после чего производится перебор «элементов» получившегося псевдомассива:


var а = {}; // Для начала создать обычный пустой объект

// Добавить свойства, которые сделают его похожим на массив

var і = 0;

while(i < 10) {

  a[i] = і * і;

  і++;

}

a.length = і;

// Теперь можно обойти свойства объекта, как если бы он был настоящим массивом

var total = 0;

for(var j = 0; j < a.length; j++) total += a[j];


Объект Arguments, который описывается в разделе 8.3.2, является объектом, подобным массиву. В клиентском языке JavaScript такие объекты возвращаются многими методами объектной модели документа (DOM), такими как метод document. getElementsByTagName(). Следующая функция проверяет, является ли объект подобным массиву:


// Определяет, является ли о объектом, подобным массиву. Строки и функции имеют

// числовое свойство length, но они исключаются проверкой typeof.

// В клиентском JavaScript текстовые узлы D0M имеют числовое свойство length

// и, возможно, должны быть исключены дополнительной проверкой o.nodeType != 3.

function isArrayLike(o) {

  if (о &&                   // о не null, не undefined и т. д.

    typeof о === "object" && //о - объект

    isFinite(o.length) &&    // о.length - конечное число

    о.length >= 0 &&         // о.length - положительное

    о.length===Math.floor(o.length) && // о.length - целое

    о.length < 4294967296)   // о.length < 2~32


    return true; // Значит, объект о подобен массиву

  else

    return false; // Иначе - нет

}


В разделе 7.12 будет показано, что строки в ECMAScript 5 ведут себя подобно массивам (и некоторые броузеры обеспечивали возможность обращения к символам в строке по индексам еще до выхода ECMAScript 5). Однако проверки на подобие массивам, такие как приведенная выше, для строк обычно возвращают false -с ними лучше работать как со строками, чем как с массивами.

Методы массивов в языке JavaScript преднамеренно были сделаны достаточно универсальными, чтобы их можно было использовать не только с настоящими массивами, но и с объектами, подобными массивам. В ECMAScript 5 все методы массивов являются универсальными. В ECMAScript 3 универсальными также являются все методы, за исключением toString() и toLocaleString(). (К исключениям также относится метод concat(): несмотря на то что его можно применять к объектам, подобным массивам, он некорректно разворачивает объекты в возвращаемый массив.) Поскольку объекты, подобные массивам, не наследуют свойства от Array.prototype, к ним нельзя напрямую применить методы массивов. Однако их можно вызывать косвенно, с помощью метода Function.call():


var а = {"О":"а", ”2":"с”, length:3}; // Объект, подобный массиву

Array.prototype.join.call(a, "+") // => "a+b+c"

Array.prototype.slice.call(a, 0) // => ["a"."b","с"]: копия, настоящий массив

Array.prototype.map.call(a, function(x) { return x.toUpperCase();}) // => ["А","В","C"]:


Мы уже встречались с таким использованием метода саll() в разделе 7.10, где описывался метод isArray(). Метод саll() объектов класса Function детально рассматривается в разделе 8.7.3.

Методы массивов, определяемые в ECMAScript 5, были введены в Firefox 1.5. Поскольку они имели универсальную реализацию, в Firefox также были введены версии этих методов в виде функций, объявленных непосредственно в конструкторе Array. Если использовать эти версии методов, примеры выше можно переписать так:


var а = {"О":"а", "1":"Ь", "2":"с", length;3}; // Объект, подобный массиву

Array.join(a, "+")

Array.slice(a, 0)

Array.map(a, function(x) { return x.toUpperCase(); })


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


Array.join = Array.join || function(a,sep) {

  return Array.prototype.join.call(a,sep);


убрать рекламу






};

Array.slice = Array.slice || function(a,from,to) {

  return Array.prototype.slice.call(a,from,to);

};

Array.map = Array.map || function(a, f, thisArg) {

  return Array.prototype.map.call(a, f, thisArg);

}

7.12. Строки как массивы

 Сделать закладку на этом месте книги

В ECMAScript 5 (и во многих последних версиях броузеров, включая IE8, появившихся до выхода стандарта ECMAScript 5) строки своим поведением напоминают массивы, доступные только для чтения. Вместо метода charAt() для обращения к отдельным символам можно использовать квадратные скобки:


var s = test;

s.charAt(0) // => "t"

s[1] // => "e"


Оператор typeof для строк все так же возвращает «string», а если строку передать методу Array.isArray(), он вернет false.

Основное преимущество, которое дает поддержка индексирования строк, - это возможность заменить вызов метода charAt() квадратными скобками и получить более краткий, удобочитаемый и, возможно, более эффективный программный код. Однако тот факт, что строки своим поведением напоминают массивы, означает также, что к ним могут применяться универсальные методы массивов. Например:


s = "JavaScript"

Array.prototype.join.call(s, " ") // => "J a v a S с r і p t"

Array.prototype.filter.call(s, // Фильтровать символы строки

  function(x) {

    return x.match(/[~aeiou]/); // Совпадение только с согласными

  }).join("") // => "JvScrpt"


Имейте в виду, что строки являются неизменяемыми значениями, поэтому при работе с ними как с массивами их следует интерпретировать как массивы, доступные только для чтения. Такие методы массивов, как push(), sort(), reverse() и splice(), изменяют исходный массив и не будут работать со строками. Однако попытка изменить строку с помощью метода массива не вызовет ошибку: строка просто не изменится.


8

Функции

 Сделать закладку на этом месте книги

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

Если функция присваивается свойству объекта, она называется методом объекта. Когда функция вызывается посредством объекта, этот объект становится контекстом вызова, или значением ключевого слова this. Функции, предназначенные для инициализации вновь созданных объектов, называются конструкторами. Конструкторы были описаны в разделе 6.1, и мы вернемся к ним в главе 9.

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

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

8.1. Определение функций

 Сделать закладку на этом месте книги

Определение функций выполняется с помощью ключевого слова function, которое может использоваться в выражениях определения функций (раздел 4.3) или в инструкциях объявления функций (раздел 5.3.2). В любом случае определение функции начинается с ключевого слова function, за которым указываются следующие компоненты:

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

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

• Пара фигурных скобок с нулем или более инструкций JavaScript внутри. Эти инструкции составляют тело функции: они выполняются при каждом вызове функции.

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


Именование функций

В качестве имени функции может использоваться любой допустимый идентификатор. Старайтесь выбирать функциям достаточно описательные, но не длинные имена. Искусство сохранения баланса между краткостью и информативностью приходит с опытом. Правильно подобранные имена функций могут существенно повысить удобочитаемость (а значит, и простоту сопровождения) ваших программ.

Чаще всего в качестве имен функций выбираются глаголы или фразы, начинающиеся с глаголов. По общепринятому соглашению имена функций начинаются со строчной буквы. Если имя состоит из нескольких слов, в соответствии с одним из соглашений они отделяются друг от друга символом подчеркивания, примерно так: like_this(), по другому соглашению все слова, кроме первого, начинаются с прописной буквы, примерно так: likeThis(). Имена функций, которые, как предполагается, реализуют внутреннюю, скрытую от посторонних глаз функциональность, иногда начинаются с символа подчеркивания.

В некоторых стилях программирования или в четко определенных программных платформах бывает полезно давать наиболее часто используемым функциям очень короткие имена. Примером может служить библиотека jQuery клиентского JavaScript (описываемая в главе 19), в которой широко используется функция с именем $() (да-да, просто знак доллара). (В разделе 2.4 уже говорилось, что в идентификаторах JavaScript помимо алфавитно-цифровых символов допускается использовать знаки доллара и подчеркивания.)

*********************************************************


Пример 8.1. Определения JavaScript-функций  

// Выводит имена и значения всех свойств объекта о. Возвращает undefined,

function printprops(o) {

  for(var p in o)

    console.log(p + ": " + o[p] + "\n");

}


// Вычисляет Декартово расстояние между точками (х1,у1) и (х2,у2).

function distance(x1, у1. х2, у2) {

  var dx = х2 - х1; var dy = у2 - у1;

  return Math.sqrt(dx*dx + dy*dy);

}


// Рекурсивная функция (вызывающая сама себя), вычисляющая факториал

// Напомню, что х! - это произведение х и всех положительных целых чисел, меньше X.

function factorial(x) {

  if (х <= 1) return 1;

  return x * factorial(x-1);

}


// Следующее выражение определяет функцию, вычисляющую квадрат аргумента.

// Обратите внимание, что она присваивается переменной

var square = function(x) { return x*x; }


// Выражения определения функций могут иметь имена, что позволяет

// производить рекурсивные вызовы.

var f = function fact(x) { if (x <= 1) return 1; else return x*fact(x-1); };


// Выражения определения функций могут также использоваться в качестве

// аргументов других выражений:

data.sort(function(a,b) { return a-b: }):


// Выражения определения функций иногда могут тут же вызываться:

var tensquared = (function(x) {return x*x;}(10));


Обратите внимание, что в выражениях определения функций имя функции может отсутствовать. Инструкция объявления функции фактически объявляет переменную и присваивает ей объект функции. Выражение определения функции, напротив, не объявляет переменную. Однако в выражениях определения допускается указывать имя функции, как в функции вычисления факториала выше, которое может потребоваться в теле функции для вызова себя самой. Если выражение определения функции включает имя, данное имя будет ссылаться на объект функции в области видимости этой функции. Фактически имя функции становится локальной переменной, доступной только в теле функции. В большинстве случаев имя функции не требуется указывать в выражениях определения, что делает определения более компактными. Особенно удобно использовать выражения для определения однократно используемых функций, как в последних двух примерах выше.

Как описывалось в разделе 5.3.2, инструкции объявления функций «поднимаются» в начало сценария или вмещающей их функции, благодаря чему объявленные таким способом функции могут вызываться в программном коде выше объявления. Это не относится к функциям, которые определяются в виде выражений: чтобы вызвать функцию, необходимо иметь возможность сослаться на нее, однако нельзя сослаться на функцию, которая определяется с помощью выражения, пока она не будет присвоена переменной. Объявления переменных также поднимаются вверх (раздел 3.10.1), но операции присваивания значений этим переменным не поднимаются, поэтому функции, определяемые в виде выражений, не могут вызываться до того, как они будут определены.

Обратите внимание, что большинство (но не все) функций в примере 8.1 содержат инструкцию return (раздел 5.6.4). Инструкция return завершает выполнение функции и выполняет возврат значения своего выражения (если указано) вызывающей программе. Если выражение в инструкции return отсутствует, она возвращает значение undefined. Если инструкция return отсутствует в функции, интерпретатор просто выполнит все инструкции в теле функции и вернет вызывающей программе значение undefined.

Большинство функций в примере 8.1 вычисляют некоторое значение, и в них инструкция return используется для возврата этого значения вызывающей программе. Функция printprops() несколько отличается в этом смысле: ее работа заключается в том, чтобы вывести имена свойств объекта. Ей не нужно возвращать какое-либо значение, поэтому в функции отсутствует инструкция return. Функция printprops() всегда будет возвращать значение undefined. (Функции, не имеющие возвращаемого значения, иногда называются процедурами.)

8.1.1. Вложенные функции

 Сделать закладку на этом месте книги

В JavaScript допускается вложение определений функций в другие функции. Например:


function hypotenuse(a, b) {

  function square(x) { return x*x; }

  return Math.sqrt(square(a) + square(b));

}


Особый интерес во вложенных функциях представляют правила видимости переменных: они могут обращаться к параметрам и переменным, объявленным во вмещающей функции (или функциях). Например, в определении выше внутренняя функция square() может читать и изменять параметры а и Ь, объявленные во внешней функции hypotenuse(). Эти правила видимости, действующие для вложенных функций, играют важную роль, и мы еще вернемся к ним в разделе 8.6.

Как отмечалось в разделе 5.3.2, инструкции объявления функций в действительности не являются настоящими инструкциями, и спецификация ECMAScript допускает использовать их только в программном коде верхнего уровня. Они могут появляться в глобальном программном коде или внутри других функций, но они не могут находиться внутри циклов, условных инструкций, инструкций try/catch/finally или with.[11] Обратите внимание, что эти ограничения распространяются только на объявления функций в виде инструкции function. Выражения определения функций могут присутствовать в любом месте в программе на языке JavaScript.

8.2. Вызов функций

 Сделать закладку на этом месте книги

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

• как функции,

• как методы,

• как конструкторы и

• косвенно, с помощью их методов саll() и аррlу().

8.2.1. Вызов функций

 Сделать закладку на этом месте книги

Вызов функций как функций или как методов, выполняется с помощью выражения вызова (раздел 4.5). Выражение вызова состоит из выражения обращения к функции, которое возвращает объект функции, и следующими за ним круглыми скобками со списком из нуля или более выражений-аргументов, разделенных запятыми, внутри. Если выражение обращения к функции является выражением обращения к свойству - если функция является свойством объекта или элементом массива - тогда выражение вызова является выражением вызова метода. Этот случай будет описан ниже. В следующем фрагменте демонстрируется несколько примеров выражений вызова обычных функций:


printprops({х:1});

var total = distance(0,0,2,1) + distanced, 1,3, 5);

var probability = factorial(5)/factorial(13);


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

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

При вызове функции в ECMAScript 3 и в нестрогом режиме ECMAScript 5 контекстом вызова (значением this) является глобальный объект. Однако в строгом режиме контекстом вызова является значение undefined.

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


// Определение и вызов функции, которая выясняет действующий режим работы,

var strict = (function() { return !this; }());



Составление цепочек вызовов методов

Когда методы возвращают объекты, появляется возможность использовать значение, возвращаемое одним методом, как часть последующих вызовов. Это позволяет создавать последовательности («цепочки», или «каскады») вызовов методов в одном выражении. При работе с библиотекой jQuery (глава 19), например, часто можно встретить такие инструкции:


// Отыскать все заголовки, отобразить их в значения атрибутов id,

// преобразовать в массив и отсортировать

$( ":header").map(function() { return this.id )).get().sort();


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

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


shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();


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

8.2.2. Вызов методов

 Сделать закладку на этом месте книги

Метод - это не что иное, как функция, которая хранится в виде свойства объекта. Если имеется функция f и объект о, то можно определить метод объекта о с именем m, как показано ниже:


о.m = f;


После этого можно вызвать метод m() объекта о:


о.m();


Или, если метод m() принимает два аргумента, его можно вызвать так:


о.m(х, у);


Строка выше является выражением вызова: она включает выражение обращения к функции о.m и два выражения-аргумента, х и у. Выражение обращения к функции в свою очередь является выражением обращения к свойству (раздел 4.4), а это означает, что функция вызывается как метод, а не как обычная функция.

Аргументы и возвращаемое значение при вызове метода обрабатываются точно так же, как при вызове обычной функции. Однако вызов метода имеет одно важное отличие: контекст вызова. Выражение обращения к свойству состоит из двух частей: объекта (в данном случае о) и имени свойства (m). В подобных выражениях вызова методов объект о становится контекстом вызова, и тело функции получает возможность ссылаться на этот объект с помощью ключевого слова this. Например:


var calculator = { // Литерал объекта

  ореrand1: 1,

  operand2: 1,

  add: function() {

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

    // ключевое слово this.

    this.result = this.operandl + this.operand2:

  }

};

calculator.add(); // Вызвать метод, чтобы вычислить 1+1.

calculator.result // => 2


Чаще всего при вызове методов используется форма обращения к свойствам с помощью оператора точки, однако точно так же можно использовать форму обращения к свойствам с помощью квадратных скобок. Например, оба следующих выражения являются выражениями вызова методов:


о["m’'](х,у); // Другой способ записать это выражение: о.m(х.у).

a[0](z) // Тоже вызов метода (предполагается, что а[0] - это функция).


Выражения вызова методов могут включать более сложные выражения обращения к свойствам:


customer.surname.toUpperCase(): // Вызвать метод объекта customer.surname

f().m();         // Вызвать метод m() возвращаемого значения функции f()


Методы и ключевое слово this занимают центральное место в парадигме объектно-ориентированного программирования. Любая функция, используемая как метод, фактически получает неявный аргумент - объект, относительно которого она была вызвана. Как правило, методы выполняют некоторые действия с объектом, и синтаксис вызова метода наглядно отражает тот факт, что функция оперирует объектом. Сравните следующие две строки:


rect.setSize(width, height);

setRectSize(rect, width, height);


Гипотетически функции, вызывающиеся в этих двух строках, могут производить абсолютно идентичные действия над объектом rect (гипотетическим), но синтаксис вызова метода в первой строке более наглядно демонстрирует, что в центре внимания находится объект rect.

Обратите внимание: this - это именно ключевое слово, а не имя переменной или свойства. Синтаксис JavaScript не допускает возможность присваивания значений элементу this.

В отличие от переменных, ключевое слово this не имеет области видимости, и вложенные функции не наследуют значение this от вызывающей функции. Если вложенная функция вызывается как метод, значением this является объект, относительно которого был сделан вызов. Если вложенная функция вызывается как функция, то значением this будет либо глобальный объект (в нестрогом режиме), либо undefined (в строгом режиме). Распространенная ошибка полагать, что во вложенной функции, которая вызывается как функция, можно использовать this для получения доступа к контексту внешней функции. Если во вложенной функции необходимо иметь доступ к значению this внешней функции, это значение следует сохранить в переменной, находящейся в области видимости внутренней функции. Для этой цели часто используется переменная с именем self. Например:


var о = { // Объект о.

  m: function() { // Метод m объекта.

    var self = this; // Сохранить значение this в переменной,

    console.log(this === о); // Выведет "true": this - это объект о.

    f(); // Вызвать вспомогательную ф-цию f().

    function f() { // Вложенная функция f

      console.log(this === о); // "false": this - глоб. об. или undefined

      console.log(self === o); // "true": self - знач, this внеш. ф-ции.

    }

  }

};

o.m(); // Вызвать метод m объекта о.


В примере 8.5 (раздел 8.7.4) демонстрируется более практичный способ использования идиомы var self=this.

8.2.3. Вызов конструкторов

 Сделать закладку на этом месте книги

Если вызову функции или метода предшествует ключевое слово new, следовательно, это вызов конструктора. (Вызовы конструкторов обсуждались в разделах 4.6 и 6.1.2, а более подробно конструкторы будут рассматриваться в главе 9.) Вызов конструктора отличается от вызова обычной функции или метода особенностями обработки аргументов, контекстом вызова и возвращаемым значением.

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


var о = new Object();

var о = new Object;


Вызов конструктора создает новый пустой объект, наследующий свойство prototype конструктора. Назначение функции-конструктора - инициализировать объект, и этот вновь созданный объект передается конструктору как контекст вызова, благодаря чему функция-конструктор может ссылаться на него с помощью ключевого слова this. Обратите внимание, что вновь созданный объект передается как контекст вызова, даже если вызов конструктора выглядит как вызов метода. То есть в выражении new о.m() контекстом вызова будет вновь созданный объект, а не объект о.

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

8.2.4. Косвенный вызов

 Сделать закладку на этом месте книги

Функции в языке JavaScript являются объектами и подобно другим объектам имеют свои методы. В их числе есть два метода, call() и аррlу(), выполняющие косвенный вызов функции. Оба метода позволяют явно определить значение this для вызываемой функции, что дает возможность вызывать любую функцию как метод любого объекта, даже если фактически она не является методом этого объекта. Кроме того, обоим методам можно передать аргументы вызова. Метод саll() позволяет передавать аргументы для вызываемой функции в своем собственном списке аргументов, а метод apply() принимает массив значений, которые будут использованы как аргументы. Подробнее о методах call() и аррlу() рассказывается в разделе 8.7.3.

8.3. Аргументы и параметры функций

 Сделать закладку на этом месте книги

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

8.3.1. Необязательные аргументы

 Сделать закладку на этом месте книги

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


убрать рекламу






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


// Добавить в массив а перечислимые имена свойств объекта о и вернуть его.

// Если аргумент а не не был передан, создать и вернуть новый массив,

function getPropertyNames(o, /* необязательный */ а) {

  if (а === undefined) а = []; // Если массив не определен, создать новый

  for(var property in о) a.push(property);

  return а;

}

// Эта функция может вызываться с 1 или 2 аргументами:

var а = getPropertyNames(o); // Получить свойства объекта о в новом массиве

getPropertyNames(p,а); // добавить свойства объекта р в этот массив


Вместо инструкции if в первой строке этой функции можно использовать оператор || следующим образом:


а = а || [];


В разделе 4.10.2 говорилось, что оператор || возвращает первый аргумент, если он имеет истинное значение, и в противном случае возвращает второй аргумент. В данном примере, если во втором аргументе будет передан какой-либо объект, функция будет использовать его. Но если второй аргумент отсутствует (или в нем будет передано значение null), будет использоваться вновь созданный массив.

Обратите внимание, что при объявлении функций необязательные аргументы должны завершать список аргументов, чтобы их можно было опустить. Программист, который будет писать обращение к вашей функции, не сможет передать второй аргумент и при этом опустить первый: он будет вынужден явно передать в первом аргументе значение undefined. Обратите также внимание на комментарий /* необязательный */ в определении функции, который подчеркивает тот факт, что параметр является необязательным.

8.3.2. Списки аргументов переменной длины: объект Arguments

 Сделать закладку на этом месте книги

Если число аргументов в вызове функции превышает число имен параметров, функция лишается возможности напрямую обращаться к неименованным значениям. Решение этой проблемы предоставляет объект Arguments. В теле функции идентификатор arguments ссылается на объект Arguments, присутствующий в вызове. Объект Arguments - это объект, подобный массиву (раздел 7.11), позволяющий извлекать переданные функции значения по их номерам, а не по именам.

Предположим, что была определена функция f, которая требует один аргумент, х. Если вызвать эту функцию с двумя аргументами, то первый будет доступен внутри функции по имени параметра х или как arguments[0]. Второй аргумент будет доступен только как arguments[1]. Кроме того, подобно настоящим массивам, arguments имеет свойство length, определяющее количество содержащихся элементов. То есть в теле функции f, вызываемой с двумя аргументами, arguments, length имеет значение 2.

Объект Arguments может использоваться с самыми разными целями. Следующий пример показывает, как с его помощью проверить, была ли функция вызвана с правильным числом аргументов, - ведь JavaScript этого за вас не сделает:


function f(x, у, z)

{

  // Сначала проверяется, правильное ли количество аргументов передано

  if (arguments.length != 3) {

    throw new Error("функция f вызвана c ” + arguments.length +

        "аргументами, а требуется 3.");

  }

  // А теперь сам код функции...

}


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

Объект Arguments иллюстрирует важную возможность JavaScript-функций: они могут быть написаны таким образом, чтобы работать с любым количеством аргументов. Следующая функция принимает любое число аргументов и возвращает значение самого большого из них (аналогично ведет себя встроенная функция Math.max()):


function max(/*...*/)

{

  var m = Number.NEGATIVE.INFINITY;

  // Цикл по всем аргументам, поиск и сохранение наибольшего из них

  for(var і = 0; і < arguments.length; i++)

    if (arguments[i] > max) max = arguments[i];

  // Вернуть наибольшее значение return max;

}


var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); // => 10000


Функции, подобные этой и способные принимать произвольное число аргументов, называются функциями с переменным числом аргументов (ivariadic functions, variable arity functions или varargs functions). Этот термин возник вместе с появлением языка программирования С.

Обратите внимание, что функции с переменным числом аргументов не должны допускать возможность вызова с пустым списком аргументов. Будет вполне разумным использовать объект arguments[] при написании функции, ожидающей получить фиксированное число обязательных именованных аргументов, за которыми может следовать произвольное число необязательных неименованных аргументов.

Не следует забывать, что arguments фактически не является массивом - это объект Arguments. В каждом объекте Arguments имеются пронумерованные элементы массива и свойство length, но с технической точки зрения это не массив. Лучше рассматривать его как объект, имеющий некоторые пронумерованные свойства. Подробнее об объектах, подобных массивам, рассказывается в разделе 7.11.

У объекта Arguments есть одна очень необычная особенность. Когда у функции имеются именованные параметры, элементы массива объекта Arguments при выполнении в нестрогом режиме являются синонимами параметров, содержащих аргументы функции. Массив arguments[] и имена параметров - это два разных средства обращения к одним и тем же переменным. Изменение значения аргумента через имя аргумента меняет значение, извлекаемое через массив arguments[]. Изменение значения аргумента через массив arguments[] меняет значение, извлекаемое по имени аргумента. Например:


function f(x) {

  console.log(x); // Выведет начальное значение аргумента

  arguments[0] = null; // При изменении элемента массива изменяется х!

  console.log(x); // Теперь выведет "null"

}


Определенно, это не совсем то поведение, которое можно было бы ожидать от настоящего массива. В этом случае arguments[0] и х могли бы ссылаться на одно и то же значение, но изменение одной ссылки не должно оказывать влияния на другую.

Эта особенность в поведении объекта Arguments была ликвидирована в строгом режиме, предусматриваемом стандартом ECMAScript 5. Кроме того, в строгом режиме имеется еще несколько отличий. В нестрогом режиме arguments - это всего лишь обычный JavaScript-идентификатор, а не зарезервированное слово. В строгом режиме не допускается использовать имя arguments в качестве имени параметра или локальной переменной функции и отсутствует возможность присваивать значения элементам arguments.

8.3.2.1. Свойства callee и caller

 Сделать закладку на этом месте книги

Помимо элементов своего массива объект Arguments определяет свойства callee и caller. При попытке изменить значения этих свойств в строгом режиме ECMAScript 5 гарантированно возбуждается исключение ТуреЕrror. Однако в нестрогом режиме стандарт ECMAScript утверждает, что свойство callee ссылается на выполняемую в данный момент функцию. Свойство caller не является стандартным, но оно присутствует во многих реализациях и ссылается на функцию, вызвавшую текущую. Свойство caller можно использовать для доступа к стеку вызовов, а свойство callee особенно удобно использовать для рекурсивного вызова неименованных функций:


var factorial = function(x) {

  if (x <= 1) return 1; return x * arguments.callee(x-1);

};

8.3.3. Использование свойств объекта в качестве аргументов

 Сделать закладку на этом месте книги

Когда функция имеет более трех параметров, становится трудно запоминать правильный порядок их следования. Чтобы предотвратить ошибки и избавить программиста от необходимости заглядывать в документацию всякий раз, когда он намеревается вставить в программу вызов такой функции, можно предусмотреть возможность передачи аргументов в виде пар имя/значение в произвольном порядке. Чтобы реализовать такую возможность, при определении функции следует предусмотреть передачу объекта в качестве единственного аргумента. Благодаря такому стилю пользователи функции смогут передавать функции объект, в котором будут определяться необходимые пары имя/значение. В следующем фрагменте приводится пример такой функции, а также демонстрируется возможность определения значений по умолчанию для опущенных аргументов:


// Скопировать length элементов из массива from в массив to.

// Копирование начинается с элемента from_start в массиве from

// и выполняется в элементы, начиная с to_start в массиве to.

// Запомнить порядок следования аргументов такой функции довольно сложно.

function аггаусору(/* массив */ from, /* индекс */ from_start,

        /* массив */ to, /* индекс */ to_start,

        /* целое */ length)

{

  // здесь находится реализация функции

}


// Эта версия функции чуть менее эффективная, но не требует запоминать порядок следования

// аргументов, а аргументы from_start и to_start по умолчанию принимают значение 0.

function easycopy(args) {

  arraycopy(args.from,

  args.from_start || 0,

  // Обратите внимание, как назначаются args.to,

  // значения по умолчанию

  args.to_start || 0, args.length);

}

// Далее следует пример вызова функции easycopy():

var а = [1,2,3,4], b = [];

easycopy({from: a, to: b, length: 4});


8.3.4. Типы аргументов

 Сделать закладку на этом месте книги

В языке JavaScript параметры функций объявляются без указания их типов, а во время передачи значений функциям не производится никакой проверки их типов. Вы можете сделать свой программный код самодокументируемым, выбирая описательные имена для параметров функций и включая описание типов аргументов в комментарии, как это сделано в только что рассмотренном примере функции аггаусору(). Для необязательных аргументов в комментарий можно добавлять слово «необязательный» («optional»). А если функция может принимать произвольное число аргументов, можно использовать многоточие:


function max(/* число... */) { /* тело функции */ }


Как отмечалось в разделе 3.8, при необходимости JavaScript выполняет преобразование типов. Таким образом, если определить функцию, которая ожидает получить строковый аргумент, а затем вызвать ее с аргументом какого-нибудь другого типа, значение аргумента просто будет преобразовано в строку, когда функция пытается обратиться к нему как к строке. В строку может быть преобразовано любое простое значение, и все объекты имеют методы toString() (правда, не всегда полезные); тем самым устраняется вероятность появления ошибки.

Однако такой подход может использоваться не всегда. Вернемся к методу аггаусору(), продемонстрированному выше. Он ожидает получить массив в первом аргументе. Любое обращение к функции окажется неудачным, если первым аргументом будет не массив (или, возможно, объект, подобный массиву). Если функция должна вызываться чаще, чем один-два раза, следует добавить в нее проверку соответствия типов аргументов. Гораздо лучше сразу же прервать вызов функции в случае передачи аргументов ошибочных типов, чем продолжать выполнение, которое потерпит неудачу с сообщением об ошибке, запутывающим ситуацию. Ниже приводится пример функции, выполняющей проверку типов. Обратите внимание, что она использует функцию isArrayLike() из раздела 7.11:


// Возвращает сумму элементов массива (или объекта, подобного массиву) а.

// Все элементы массива должны быть числовыми, при этом значения null

// и undefined игнорируются,

function sum(a) {

  if (isArrayLike(a)) {

    var total = 0;

    for(var і = 0; і < a.length; і++) { // Цикл по всем элементам

      var element = a[і];

      if (element == null) continue; // Пропустить null и undefined

      if (isFinite(element))

        total += element;

      else throw new Error("sum(): все элементы должны быть числами");

    }

    return total;

  }

  else throw new Error("sum(): аргумент должен быть массивом");

}


Этот метод sum() весьма строго относится к проверке типов входных аргументов и генерирует исключения с достаточно информативными сообщениями, если типы входных аргументов не соответствуют ожидаемым. Тем не менее он остается достаточно гибким, обслуживая наряду с настоящими массивами объекты, подобные массивам, и игнорируя элементы, имеющие значения null и undefined.

JavaScript - чрезвычайно гибкий и к тому же слабо типизированный язык, благодаря чему можно писать функции, которые достаточно терпимо относятся к количеству и типам входных аргументов. Далее приводится метод flexsum(), реализующий такой подход (и, вероятно, являющийся примером другой крайности). Например, он принимает любое число входных аргументов и рекурсивно обрабатывает те из них, которые являются массивами. Вследствие этого он может принимать переменное число аргументов или массив аргументов. Кроме того, он прилагает максимум усилий, чтобы преобразовать нечисловые аргументы в числа, прежде чем сгенерировать исключение:


function flexisum(a) { var total = 0;

  for(var і = 0; і < arguments.length; i++) {

    var element = arguments[i], n;

    if (element == null) continue; // Игнорировать null и undefined

    if (isArray(element)) // Если аргумент - массив

      n = flexisum.apply(this. element); // вычислить сумму рекурсивно

    else

      if (typeof element === "function") // Иначе, если это функция...

        n = Number(element()); // вызвать и преобразовать,

      else

        n = Number(element); // Иначе попробовать преобразовать

    if (isNaN(n)) // Если не удалось преобразовать в число, возбудить искл.

      throw Error("flexisum(): невозможно преобразовать " + element + в число");

    total += n; // Иначе прибавить n к total

  }

  return total;

}

8.4. Функции как данные

 Сделать закладку на этом месте книги

Самые важные особенности функций заключаются в том, что они могут определяться и вызываться. Определение и вызов функции - это синтаксические средства JavaScript и большинства других языков программирования. Однако в JavaScript функции - это не только синтаксические конструкции, но и данные, а это означает, что они могут присваиваться переменным, храниться в свойствах объектов или элементах массивов, передаваться как аргументы функциями и т. д.[13]

Чтобы понять, как функции в JavaScript могут быть одновременно синтаксическими конструкциями и данными, рассмотрим следующее определение функции:


function square(x) { return х*х; }


Это определение создает новый объект функции и присваивает его переменной square. Имя функции действительно нематериально - это просто имя переменной, которая ссылается на объект функции. Функция может быть присвоена другой переменной, и при этом работать так же, как и раньше:


var s = square; // Теперь s ссылается на ту же функцию, что и square

square(4); // => 16

s(4); // => 16


Функции могут быть также присвоены не только глобальным переменным, но и свойствам объектов. В этом случае их называют методами:


var о = {square: function(x) { return х*х; }}; // Литерал объекта

var у = о.square(16); // у = 256


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


var а = [function(x) { return х*х; }, 20]; // Литерал объекта

а[0](а[1]); // => 400


Синтаксис вызова функции в последнем примере выглядит необычно, однако это вполне допустимый вариант применения выражения вызова!

В примере 8.2 демонстрируется, что можно делать, когда функции выступают в качестве данных. Хотя пример может показаться вам несколько сложным, комментарии объясняют, что происходит.

Пример 8.2. Использование функций как данных 


// Определения нескольких простых функций

function add(x.y) { return х + у; }

function subtract(x,у) { return х - у; }

function multiply(x,у) { return х * у; }

function divide(x,y) { return x / у; }


// Эта функция принимает одну из предыдущих функций

// в качестве аргумента и вызывает ее с двумя операндами

function operate(operator, operand1, operand2) {

  return operator(operand1, operand2);

}


// Так можно вызвать эту функцию для вычисления выражения (2+3)+(4*5):

var і = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));


// Ради примера реализуем эти функции снова, на этот раз

// с помощью литералов функций внутри литерала объекта,

var operators = {

  add: function(x,у) { return x+y; },

  subtract: function(x,y) { return x-y; },

  multiply: function(x,y) { return x*y; },

  divide: function(x,y) { return x/y; },

  pow: Math.pow // Можно использовать даже предопределенные функции

}


// Эта функция принимает имя оператора, отыскивает оператор в объекте,

// а затем вызывает его с указанными операндами.

// Обратите внимание на синтаксис вызова функции оператора,

function operate2(operation, operand1, operand2) {

  if (typeof operators[operation] === "function")

    return operators[operation](operand1. operand2);

  else throw "неизвестный оператор":

}


// Вычислить значение ("hello" + " " + "world"):

var j = operate2("add", "hello", operate2("add", " ", "world")):

// Использовать предопределенную функцию Math.pow():

var k = operate2("pow", 10, 2):


В качестве еще одного примера использования функций как значений рассмотрим метод Array.sort(). Он сортирует элементы массива. Существует много возможных порядков сортировки (числовой, алфавитный, по датам, по возрастанию, по убыванию и т. д.), поэтому метод sort() принимает в качестве необязательного аргумента функцию, которая сообщает о том, как выполнять сортировку. Эта функция делает простую работу: получает два значения, сравнивает их и возвращает результат, указывающий, какой из элементов должен быть первым. Этот аргумент-функция делает метод Array.sort() совершенно универсальным и бесконечно гибким - он может сортировать любой тип данных в любом мыслимом порядке. Примеры использования функции Array.sort() представлены в разделе 7.8.3.

8.4.1. Определение собственных свойств функций

 Сделать закладку на этом месте книги

Функции в языке JavaScript являются не простыми значениями, а особой разновидностью объектов, благодаря чему функции могут иметь свойства. Когда функции требуется «статическая» переменная, значение которой должно сохраняться между ее вызовами, часто оказывается удобным использовать свойство объекта функции, позволяющее не занимать пространство имен определениями глобальных переменных. Например, предположим, что надо написать функцию, возвращающую уникальное целое число при каждом своем вызове. Функция никогда не должна возвращать одно и то же значение дважды. Чтобы обеспечить это, функция должна запоминать последнее возвращенное значение и сохранять его между ее вызовами. Эту информацию можно было бы хранить в глобальной переменной, но это было бы нежелательно, потому что данная информация используется только этой функцией. Лучше сохранять ее в свойстве объекта Function. Вот пример функции, которая возвращает уникальное целое значение при каждом вызове:


// Инициализировать свойство counter объекта функции. Объявления функций

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

uniquelnteger.counter =0;

// Эта функция возвращает разные целые числа при каждом вызове.

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

function uniqueInteger() {

  return uniqueInteger.counter++; // Увеличить и вернуть свойство counter

}


Еще один пример, взгляните на следующую функцию factorial(), которая использует собственные свойства (интерпретируя себя как массив) для сохранения результатов предыдущих вычислений:


// Вычисляет факториалы и сохраняет результаты в собственных свойствах.

function factorial(n) {

  if (isFinite(n) && n>0 && n==Math.round(n)) { // Только конечные положительные целые

    if (!(n in factorial)) // Если не сохранялось ранее

      factorial[n] = n * factorial(n-1); // Вычислить и сохранить

  return factorial[n]; // Вернуть сохр. результат

  }

  else return NaN; // Для ошибочного аргумента

}

factorial[1] = 1; // Инициализировать кэш базовым случаем.

8.5. Функции как пространства имен

 Сделать закладку на этом месте книги

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

Предположим, например, что имеется модуль JavaScript, который можно использовать в различных JavaScript-программах (или, если говорить на языке клиентского JavaScript, в различных веб-страницах). Допустим, что в программном коде этого модуля, как в практически любом другом программном коде, объявляются переменные для хранения промежуточных результатов вычислений. Проблема состоит в том, что модуль будет использоваться множеством различных программ, и поэтому заранее неизвестно, будут ли возникать конфликты между переменными, создаваемыми модулем, и переменными, используемыми в программах, импортирующих этот модуль. Решение состоит в том, чтобы поместить программный код модуля в функцию и затем вызывать эту функцию. При таком подходе переменные модуля из глобальных превратятся в локальные переменные функции:


function mymodule() {

  // Здесь находится реализация модуля.

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

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

}

mymodule(); // Но не забудьте вызвать функцию!


Данный программный код объявляет единственную глобальную переменную: имя функции «mymodule». Если даже единственное имя это слишком много, можно определить и вызвать анонимную функцию в одном выражении:


(function() { // функция mymodule переписана как неименованное выражение

  // Здесь находится реализация модуля.

}()); // конец литерала функции и ее вызов.


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

Практическое применение приема создания


убрать рекламу






пространства имен демонстрируется в примере 8.3. Здесь определяется анонимная функция, возвращающая функцию extend(), подобную той, что была представлена в примере 6.2. Анонимная функция проверяет наличие хорошо известной ошибки в Internet Explorer и возвращает исправленную версию функции, если это необходимо. Помимо этого, анонимная функция играет роль пространства имен, скрывающего массив с именами свойств.

Пример 8.3. Функция extend(), исправленная, если это необходимо 


// Определяет функцию extend, которая копирует свойства второго и последующих аргументов

// в первый аргумент. Здесь реализован обход ошибки в IE: во многих версиях IE цикл for/in

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

// его прототипа является неперечислимым. Это означает, что такие свойства,

// как toString, обрабатываются некорректно, если явно не проверять их.

var extend = (function() { // Присвоить значение, возвращаемое этой функцией

  // Сначала проверить наличие ошибки, прежде чем исправлять ее.

  for(var р in {toString:null}) {

    // Если мы оказались здесь, значит, цикл for/in работает корректно

    // и можно вернуть простую версию функции extend()

    return function extend(o) {

      for(var і = 1; і < arguments.length; i++) {

        var source = arguments[i];

        for(var prop in source) o[prop] = source[prop];

      }

      return o;

    };

  }

  // Если мы оказались здесь, следовательно, цикл for/in не перечислил

  // свойство toString тестового объекта. Поэтому необходимо вернуть версию extend(),

  // которая явно проверяет неперечислимость свойств прототипа Object.prototype.

  // Список свойств, которые необходимо проверить

  var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty",

  "isPrototypeOf". "propertylsEnumerable". "toLocaleString"];

  return function patched_extend(o) {

    for(var і = 1; і < arguments.length; i++) {

      var source = arguments[i];

      // Скопировать все перечислимые свойства

      for(var prop in source) o[prop] = source[prop];

      // А теперь проверить специальные случаи свойств

      for(var j = 0; j < protoprops.length; j++) {

        prop = protoprops[j];

        if (source.hasOwnProperty(prop)) o[prop] = source[prop];

      }

    }

    return o;

  };

}0);


8.6. Замыкания

 Сделать закладку на этом месте книги

Как и в большинстве языков программирования, в JavaScript используются лексические области видимости. Это означает, что при выполнении функций действуют области видимости переменных, которые имелись на момент их определения, а не на момент вызова. Для реализации лексической области видимости внутренняя информация о состоянии объекта функции в языке JavaScript должна включать не только программный код функции, но еще и ссылку на текущую цепочку областей видимости. (Прежде чем продолжить чтение этого раздела, вам, возможно, следует повторно прочитать сведения об областях видимости переменных и цепочках областей видимости в разделах 3.10 и 3.10.3.) Такая комбинация объекта функции и области видимости (множества связанных переменных), в которой находятся переменные, используемые вызываемой функцией, в литературе по информационным технологиям называется замыканием}

Технически все функции в языке JavaScript образуют замыкания: они являются объектами и имеют ассоциированные с ними цепочки областей видимости. Большинство функций вызываются внутри той же цепочки областей видимости, которая действовала на момент определения функции, и в этой ситуации факт образования замыкания не имеет никакого значения. Интересные особенности замыканий начинают проявляться, когда их вызов производится в другой цепочке областей видимости, отличной от той, что действовала на момент определения. Чаще всего это происходит, когда объект вложенной функции возвращается функцией, вмещающей ее определение. Существует множество мощных приемов программирования, вовлекающих такого рода вложенные функции-замыкания, и их использование довольно широко распространено в программировании на языке JavaScript. Замыкания могут выглядеть малопонятными при первом знакомстве, однако вам необходимо хорошо понимать их, чтобы чувствовать себя уверенно при их использовании. 14 

Первый шаг к пониманию замыканий - знакомство с правилами лексической области видимости, действующими для вложенных функций. Взгляните на следующий пример (который напоминает пример в разделе 3.10):


var scope = "global scope”; // Глобальная переменная

function checkscope() {

  var scope = "local scope"; // Локальная переменная

  function f() { return scope; } // Вернет значение локальной переменной scope

  return f();

}

checkscope() // => "local scope"



Реализация замыканий

Понять суть замыканий будет совсем несложно, если усвоить правило лексической области видимости: во время выполнения функции используется цепочка областей видимости, которая действовала в момент ее определения. Однако некоторые программисты испытывают сложности при освоении замыканий, потому что не до конца понимают особенности реализации. Известно, думают они, что локальные переменные, объявленные во внешней функции, прекращают свое существование после выхода из внешней функции, но тогда как вложенная функция может использовать цепочку областей видимости, которая больше не существует? Если вы задавали себе такой вопрос, значит, у вас наверняка есть опыт работы с низкоуровневыми языками программирования, такими как С, и аппаратными архитектурами, использующими стек: если локальные переменные размещать на стеке, они действительно прекращают свое существование после завершения функции.

Но вспомните определение цепочки областей видимости из раздела 3.10.3. Там она описывалась как список объектов, а не стек. Каждый раз, когда интерпретатор JavaScript вызывает функцию, он создает новый объект для хранения локальных переменных этой функции, и этот объект добавляется в цепочку областей видимости. Когда функция возвращает управление, этот объект удаляется из цепочки. Если в программе нет вложенных функций и нет ссылок на этот объект, он будет утилизирован сборщиком мусора. Если в программе имеются вложенные функции, тогда каждая из этих функций будет владеть ссылкой на свою цепочку областей видимости, а цепочка будет ссылаться на объекты с локальными переменными. Если объекты вложенных функций существуют только в пределах своих внешних функций, они сами будут утилизированы сборщиком мусора, а вместе с ними будут утилизированы и объекты с локальными переменными, на которые они ссылались. Но если функция определяет вложенную функцию и возвращает ее или сохраняет в свойстве какого-либо объекта, то образуется внешняя ссылка на вложенную функцию. Такой объект вложенной функции не будет утилизирован сборщиком мусора, и точно так же не будет утилизирован объект с локальными переменными, на который она ссылается.

*********************************************************


Функция checkscope() объявляет локальную переменную и вызывает функцию, возвращающую значение этой переменной. Должно быть совершенно понятно, почему вызов checkscope() возвращает строку «local scope». Теперь немного изменим пример. Сможете ли вы сказать, какое значение вернет этот фрагмент?


var scope = "global scope"; // Глобальная переменная

function checkscope() {

  var scope = "local scope"; // Локальная переменная

  function f() { return scope; } // Вернет значение локальной переменной scope

  return f;

}

checkscope()() // Какое значение вернет этот вызов?


В этой версии пара круглых скобок была перемещена из тела функции checkscope() за ее пределы. Вместо вызова вложенной функции и возврата ее результата checkscope() теперь просто возвращает сам объект вложенной функции. Что произойдет, если вызвать вложенную функцию (добавив вторую пару скобок в последней строке примера) из-за пределов функции, в которой она определена?

Напомню главное правило лексической области видимости: при выполнении функции в языке JavaScript используется цепочка областей видимости, действовавшая на момент ее определения. Вложенная функция f() была определена в цепочке видимости, где переменная scope связана со значением «local scope». Эта связь остается действовать и при выполнении функции f, независимо от того, откуда был произведен ее вызов. Поэтому последняя строка в примере выше вернет «local scope», а не «global scope». Проще говоря, эта особенность является самой удивительной и мощной чертой замыканий: они сохраняют связь с локальными переменными (и параметрами) внешней функции, где они были определены.

В разделе 8.4.1 был приведен пример функции uniquelnteger(), в которой используется свойство самой функции для сохранения следующего возвращаемого значения. Недостаток такого решения состоит в том, что ошибочный или злонамеренный программный код может сбросить значение счетчика или записать в него нечисловое значение, вынудив функцию uniquelnteger() нарушить обязательство возвращать «уникальное» или «целочисленное» значение. Замыкания запирают локальные переменные в объекте вызова функции и могут использовать эти переменные для хранения частной информации. Ниже показано, как можно реализовать функцию uniquelnteger() с использованием замыкания:


var uniquelnteger = (function() { // Определение и вызов

  var counter =0; // Частное значение для функции ниже

  return function() { return counter++; };

}());


Внимательно изучите этот пример, чтобы понять, как он действует. На первый взгляд, первая строка выглядит как инструкция присваивания функции переменной uniquelnteger. Фактически же это определение и вызов функции (как подсказывает открывающая круглая скобка в первой строке), поэтому в действительности переменной uniquelnteger присваивается значение, возвращаемое функцией. Если теперь обратить внимание на тело функции, можно увидеть, что она возвращает другую функцию. Именно этот объект вложенной функции и присваивается переменной uniquelnteger. Вложенная функция имеет доступ к переменным в ее области видимости и может использовать переменную counter, объявленную во внешней функции. После возврата из внешней функции никакой другой программный код не будет иметь доступа к переменной counter: вложенная функция будет обладать исключительным правом доступа к ней.

Скрытые переменные, такие как counter, не являются исключительной собственностью единственного замыкания: в одной и той же внешней функции вполне возможно определить две или более вложенных функций, которые будут совместно использовать одну цепочку областей видимости. Рассмотрим следующий пример:


function counter() { var n = 0;

  return {

    count: function() { return n++; },

    reset: function() { n = 0; }

  };

}

var c = counter(), d = counter(); // Создать два счетчика

c.count()         // => 0

d.count()         // => 0: они действуют независимо

с.reset() // методы reset() и count() совместно

// используют одну переменную

c.count() // => 0: сброс счетчика с

d.count() // => 1: не оказывает влияния на счетчик d


Функция counter() возвращает объект «счетчика». Этот объект имеет два метода: count(), возвращающий следующее целое число, и reset(), сбрасывающий счетчик в начальное состояние. В первую очередь следует запомнить, что два метода совместно используют одну и ту же частную переменную n. Во-вторых, каждый вызов функции counter() создает новую цепочку областей видимости и новую скрытую переменную. То есть, если вызвать функцию counter() дважды, она вернет два объекта-счетчика с различными скрытыми переменными. Вызов методов count() и reset() одного объекта-счетчика не оказывает влияния на другой.

Важно отметить, что описанный прием образования замыканий можно использовать в комбинации с приемом определения свойств с методами доступа. Следующая версия функции counter() является вариацией примера, представленного в разделе 6.6, но здесь для хранения скрытой информации вместо обычного свойства объекта используются замыкания:


function counter(n) { // Аргумент n функции - скрытая переменная

  return {

    // Метод чтения свойства возвращает и увеличивает переменную счетчика,

    get count() { return n++; },

    // Метод записи в свойство не позволяет уменьшать значение n

    set count(m) {

      if (m >= n)

        n = m;

      else throw Error( "значение счетчика нельзя уменьшить");

    }

  };

}

var с = counter(1000);

с.count // => 1000

с.count // => 1001

с.count = 2000

с.count // => 2000

с.count = 2000 // => Ошибка!


Обратите внимание, что эта версия функции counter() не объявляет локальную переменную. Для сохранения информации она просто использует параметр n, доступный обоим методам доступа к свойству. Это позволяет программе, вызывающей counter(), определять начальное значение скрытой переменной.

В примере 8.4 демонстрируется обобщение приема совместного использования скрытой информации в замыканиях. Этот пример определяет функцию addPrivateProperty(), которая в свою очередь определяет скрытую переменную и две вложенные функции для чтения и записи значения этой переменной. Она добавляет эти вложенные функции как методы указанного вами объекта:


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


// Эта функция добавляет методы доступа к свойству с заданным именем объекта о.

// Методы получают имена вида get<name> и set<name>. Если дополнительно предоставляется

// функция проверки, метод записи будет использовать ее для проверки значения

// перед сохранением. Если функция проверки возвращает false,

// метод записи генерирует исключение.

//

// Необычность такого подхода заключается в том, что значение свойства,

// доступного методам, сохраняется не в виде свойства объекта о, а в виде

// локальной переменной этой функции. Кроме того, методы доступа также определяются

// внутри этой функции и потому получают доступ к этой локальной переменной.

// Это означает, что значение доступно только этим двум методам и не может быть

// установлено или изменено иначе, как методом записи,

function addPrivateProperty(o, name, predicate) {

  var value; // Это значение свойства

  // Метод чтения просто возвращает значение.

  о["get" + name] = function() { return value; };

  // Метод записи сохраняет значение или возбуждает исключение,

  // если функция проверки отвергает это значение.

  o["set" + name] = function(v) {

    if (predicate && !predicate(v))

             throw Error("set" + name + недопустимое значение + v);

    else

      value = v;

  };

}


// Следующий фрагмент демонстрирует работу метода addPrivateProperty().

var о = {}; // Пустой объект

// Добавить к свойству методы доступа с именами getName() и setName()

// Обеспечить допустимость только строковых значений

addPrivateProperty(o, "Name", function(x) { return typeof x == "string"; });

o.setName("Frank"); // Установить значение свойства

console.log(o.getName()); // Получить значение свойства

о.setName(0); // Попробовать установить значение свойства неверного типа


Мы увидели несколько примеров, когда замыкания определяются в одной и той же цепочке областей видимости и совместно используют одну локальную пере¬менную или переменные. Важно знать и уметь пользоваться этим приемом, но но менее важно уметь распознавать ситуации, когда замыкания получают перемен¬ную в совместное использование по ошибке. Рассмотрим следующий пример:


// Эта функция возвращает функцию, которая всегда возвращает v

function constfunc(v) { return function() { return v; }; }

// Создать массив функций-констант:

var funcs = [];

for(var і = 0; і < 10; i++) funcs[i] = constfunc(i);

// Функция в элементе массива с индексом 5 возвращает 5.

funcs[5]() // => 5


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


// Возвращает массив функций, возвращающих значения 0-9

function constfuncs() { var funcs = [];

  for(var і = 0; і < 10; i++)

    funcs[i] = function() { return i; };

  return funcs;

}

var funcs = constfuncs();

funcs[5]() // Что вернет этот вызов?


Функция выше создает 10 замыканий и сохраняет их в массиве. Замыкания образуются в одном и том же вызове функции, поэтому все они получат доступ к переменной і. Когда constfuncs() вернет управление, переменная і будет иметь значение 10, и все 10 замыканий будут совместно использовать это значение. Таким образом, все функции в возвращаемом массиве будут возвращать одно и то же значение, что совсем не то, чего мы пытались добиться. Важно помнить, что цепочка областей видимости, связанная с замыканием, не фиксируется. Вложенные функции не создают частные копии области видимости и не фиксируют значения переменных.

Кроме того, при создании замыканий следует помнить, что this - это ключевое слово, а не переменная. Как отмечалось выше, каждый вызов функции получает свое значение this, и замыкание не имеет доступа к значению this внешней функции, если внешняя функция не сохранит его в переменной:


var self = this; // Сохранить значение this в переменной для использования

// во вложенной функции.


То же относится и к объекту arguments. Это не ключевое слово, но он автоматически создается при каждом вызове функции. Поскольку замыкания при вызове получают собственный объект arguments, они не могут обращаться к массиву аргументов внешней функции, если внешняя функция не сохранит этот массив в переменной с другим именем:


var outerArguments = arguments; // Сохранить для использования во вложенных функциях


В примере 8.5, далее в этой главе, определяется замыкание, использующее эти приемы для получения доступа к значениям this и arguments внешней функции.

8.7. Свойства и методы функций и конструктор Function

 Сделать закладку на этом месте книги

Мы видели, что в JavaScript-программах функции могут использоваться как значения. Оператор typeOf возвращает для функций строку «function», однако в действительности функции в языке JavaScript - это особого рода объекты. А раз функции являются объектами, то они имеют свойства и методы, как любые другие объекты. Существует даже конструктор Function(), который создает новые объекты функций. В следующих подразделах описываются свойства и методы функций, а также конструктор Function(). Кроме того, информация обо всем этом приводится в справочном разделе.

8.7.1. Свойство length

 Сделать закладку на этом месте книги

В теле функции свойство arguments.length определяет количество аргументов, переданных функции. Однако свойство length самой функции имеет иной смысл. Это свойство, доступное только для чтения, возвращает количество аргументов, которое функция ожидает получить, - число объявленных параметров.

В следующем фрагменте определяется функция с именем check(), получающая массив аргументов arguments от другой функции. Она сравнивает свойство arguments.length (число фактически переданных аргументов) со свойством arguments. callee.length (число ожидаемых аргументов), чтобы определить, передано ли функции столько аргументов, сколько она ожидает. Если значения не совпадают, генерируется исключение. За функцией check() следует тестовая функция f(), демонстрирующая порядок использования функции check():


// Эта функция использует arguments.callee, поэтому она

// не будет работать в строгом режиме,

function check(args) {

  var actual = args.length; // Фактическое число аргументов

  var expected = args.callee.length; // Ожидаемое число аргументов

  if (actual !== expected) // Если не совпадают, генерируется исключение

    throw new Еrror("ожидается: " + expected + получено " + actual);

}

function f(x, у, z) {

  // Проверить число ожидаемых и фактически переданных аргументов.

  check(arguments);

  // Теперь выполнить оставшуюся часть функции как обычно

  return х + у + z;

}

8.7.2. Свойство prototype

 Сделать закладку на этом месте книги

Любая функция имеет свойство prototype, ссылающееся на объект, известный как объект прототипа. Каждая функция имеет свой объект прототипа. Когда функция используется в роли конструктора, вновь созданный объект наследует свойства этого объекта прототипа. Прототипы и свойство prototype обсуждались в разделе 6.1.3, и мы еще раз вернемся к этим понятиям в главе 9.

8.7.3. Методы call() и apply()

 Сделать закладку на этом месте книги

Методы саll() и аррlу() позволяют выполнять косвенный вызов функции (раздел 8.2.4), как если бы она была методом некоторого другого объекта. (Мы уже использовали метод саll() в примере 6.4 для вызова Object.prototype.toString относительно объекта, класс которого необходимо было определить.) Первым аргументом обоим методам, саll() и аррlу(), передается объект, относительно которого вызывается функция; этот аргумент определяет контекст вызова и становится значением ключевого слова this в теле функции. Чтобы вызвать функцию f() (без аргументов) как метод объекта о, можно использовать любой из методов, саll() или аррlу():


f.call(о);

f.apply(o);


Любой из этих способов вызова эквивалентен следующему фрагменту (где предполагается, что объект о не имеет свойства с именем m):


о.m = f; // Временно сделать f методом о.

о.m(); // Вызывать его без аргументов,

delete о.m; // Удалить временный метод.


В строгом режиме ECMAScript 5 первый аргумент методов саll() и apply() становится значением this, даже если это простое значение, null или undefined. В ECMAScript 3 и в нестрогом режиме значения null и undefined замещаются глобальным объектом, а простое значение - соответствующим объектом-оберткой.

Все остальные аргументы метода саll(), следующие за первым аргументом, определяющим контекст вызова, передаются вызываемой функции. Например, ниже показано, как можно передать функции f() два числа и вызвать ее, как если бы она была методом объекта о:


f.call(o, 1, 2);


Метод аррlу() действует подобно методу саll(), за исключением того, что аргументы для функции передаются в виде массива:


f.apply(o, [1,2]);


Если функция способна обрабатывать произвольное число аргументов, метод apply() может использоваться для вызова такой функции в контексте массива произвольной длины. Например, чтобы отыскать наибольшее число в массиве чисел, для передачи элементов массива функции Math.max() можно было бы использовать метод ар ply ():


var biggest = Math.max.apply(Math, array_of_numbers);


Обратите внимание, что мет