4 Выражения и операторы

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

Наиболее типичный способ конструирования сложных выражений из более простых выражений заключается в использовании операторов. Операторы объединяют значения своих операндов (обычно двух) некоторым способом и вычисляют новое значение. Простейшим примером может служить оператор умножения *. Выражение x * y вычисляется как произведение значений выражений x и y. Иногда для простоты мы говорим, что оператор возвращает значение вместо «вычисляет» значение.

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

содержание 4.1. Первичные выражения содержание

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

Ниже приводятся некоторые из зарезервированных слов JavaScript, являющихся первичными выражениями:

Мы познакомились со значениями true, false и null в разделах 3.3 и 3.4. В отличие от других ключевых слов, this не является константой – в разных местах программы оно может возвращать разные значения. Ключевое слово this используется в объектно-ориентированном программировании. Внутри метода this возвращает объект, относительно которого был вызван метод.

Наконец, третьим типом первичных выражений являются ссылки на переменные:

Когда в программе встречается идентификатор, интерпретатор JavaScript предполагает, что это имя переменной и пытается отыскать ее значение. Если переменной с таким именем не существует, возвращается значение undefined. Однако в строгом режиме, определяемом стандартом ECMAScript 5, попытка получить значение несуществующей переменной оканчивается исключением ReferenceError.

содержание 4.2. Инициализаторы объектов и массивов содержание

Инициализаторы объектов и массивов – это выражения, значениями которых являются вновь созданные объекты и массивы. Эти выражения-инициализаторы иногда называют «литералами объектов» и «литералами массивов». Однако, в отличие от истинных литералов, они не являются первичными выражениями, потому что включают множество подвыражений, определяющих значения свойств и элементов. Инициализаторы массивов имеют более простой синтаксис, поэтому мы рассмотрим их в первую очередь.

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

Выражения в инициализаторе массива, определяющие значения его элементов, сами могут быть инициализаторами массивов, благодаря чему имеется возможность создавать вложенные массивы:

var matrix = [[1,2,3], [4,5,6], [7,8,9]];

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

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

var sparseArray = [1,,,,5];

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

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

Литералы объектов могут быть вложенными. Например:

var rectangle = { upperLeft: { x: 2, y: 2 }, lowerRight: { x: 4, y: 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 имеется два способа обращения к свойствам:

выражение . идентификатор
выражение [ выражение ]

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

Независимо от способа обращения к свойству первым вычисляется выражение, стоящее перед . или [. Если значением этого выражения является null или undefined, возбуждается исключение TypeError, потому что эти два значения в JavaScript не имеют свойств. Если значение выражения не является объектом (или массивом), оно будет преобразовано в объект (раздел 3.6). Если за первым выражением следует точка и идентификатор, интерпретатор попытается отыскать значение свойства с именем, совпадающим с идентификатором, которое и станет значением всего выражения. Если за первым выражением следует другое выражение в квадратных скобках, интерпретатор вычислит второе выражение и преобразует его в строку. В этом случае значением всего выражения станет значение свойства, имя которого совпадает со строкой. В любом случае если свойство с указанным именем не существует, значением выражения обращения к свойству станет значение undefined.

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

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

содержание 4.5. Выражения вызова содержание

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

При вычислении выражения вызова первым вычисляется выражение, возвращающее функцию, а затем вычисляются выражения аргументов и создается список значений аргументов. Если значением выражения, возвращающего функцию, не является вызываемый объект, возбуждается исключение ТуреЕггог. (Все функции являются вызываемыми объектами. Объекты среды выполнения также могут быть вызываемыми, даже если они не являются функциями. Это отличие рассматривается в разделе 8.7.7.) Далее значения аргументов присваиваются в порядке их следования именам параметров, которые указаны в определении функции, после чего выполняется тело функции. Если внутри функции используется инструкция return, возвращающая некоторое значение, это значение становится значением выражения вызова. В противном случае выражение вызова возвращает значение undefined. Полное описание механизма вызова функций, включая описание того, что происходит, когда количество выражений аргументов не совпадает с количеством параметров в определении функции, вы найдете в главе 8.

Все выражения для вызовов функций включают пару круглых скобок и выражение перед открывающей круглой скобкой. Если последнее является выражением обращения к свойству, такой вызов называется вызовом метода. При вызове метода объект или массив, к свойству которого производится обращение, становится значением параметра this, доступного в теле функции во время его выполнения. Это обеспечивает поддержку парадигмы объектно-ориентированного программирования, согласно которой функции (в ООП обычно называются «методами») получают возможность манипулировать объектом, частью которого они являются. Подробности приводятся в главе 9.

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

содержание 4.6. Выражения создания объектов содержание

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

new Object()
new Point(2,3)
.

Если в выражении создания объекта функции-конструктору не передается ни одного аргумента, пустую пару круглых скобок можно опустить:

new Object
new Point(Date.

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

Более подробно конструкторы описываются в главе 9.

содержание 4.7. Обзор операторов содержание

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

Таблица 4.1. Операторы JavaScript

Обратите внимание, что большинство операторов обозначаются символами пунктуации, такими как + и =, а некоторые – ключевыми словами, например delete и instanceof. И ключевые слова, и знаки пунктуации обозначают обычные операторы, просто первые имеют менее лаконичный синтаксис.

Операторы в табл. 4.1 перечислены в порядке их приоритетов. Операторы, перечисленные первыми, имеют более высокий приоритет. Операторы в ячейках с одинаком цветом имеют одинаковый приоритет. Столбец «A» в этой таблице содержит ассоциативность оператора (либо L – слева направо, либо R – справа налево), а столбец «N» определяет количество операндов. В столбце «Типы значений» указаны ожидаемые типы операндов и (после символа  → ) тип результата, возвращаемого оператором. В подразделах, следующих за таблицей, описываются концепции приоритетов, ассоциативности и типов операндов. Вслед за этим приводится обсуждение самих операторов.

содержание 4.7.1. Количество операндов

Операторы могут быть разбиты на категории по количеству требуемых им операндов. Большинство JavaScript-операторов, таких как оператор умножения *, являются двухместными. Такие операторы объединяют два выражения в одно, более сложное. То есть эти операторы работают с двумя операндами. JavaScript поддерживает также несколько унарных операторов, которые преобразуют одно выражение в другое, более сложное. Оператор - в выражении -x является унарным оператором, выполняющим смену знака операнда x. И, наконец, 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 = x + y * z;

Оператор умножения * имеет более высокий приоритет по сравнению с оператором сложения +, поэтому умножение выполняется раньше сложения. Оператор присваивания = имеет наименьший приоритет, поэтому присваивание выполняется после завершения всех операций в правой части.

Приоритет операторов может быть переопределен с помощью скобок. Чтобы сложение в предыдущем примере выполнялось раньше, надо написать:

w = (x + y) * z;

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

typeof my.functions[x](y)

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

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

содержание 4.7.6. Ассоциативность операторов

В табл. 4.1 в столбце «A» указана ассоциативность операторов. Значение L указывает на ассоциативность слева направо, а значение R – на ассоциативность справа налево. Ассоциативность оператора определяет порядок выполнения операций с одинаковым приоритетом. Ассоциативность слева направо означает, что операции выполняются слева направо. Например, оператор вычитания имеет ассоциативность слева направо, поэтому следующие два выражения эквивалентны:

w = x - y - z;
w = ((x - y) - z);

С другой стороны, выражения

x = ~-y;
w = x = y = z;
q = a?b:c?d:e?f:g;

эквивалентны следующим выражениям:

x = ~(-y);
w = (x = (y = z));
q = a?b:(c?d:(e?f:g));

Причина в том, что унарные операторы, операторы присваивания и условные тернарные операторы имеют ассоциативность справа налево.

содержание 4.7.7. Порядок вычисления

Приоритет и ассоциативность операторов определяют порядок их выполнения в сложных выражениях, но они не оказывают влияния на порядок вычислений в подвыражениях. Выражения в языке JavaScript всегда вычисляются слева направо. Например, в выражении w = x + y* z первым будет вычислено подвыражение w, затем x, y и z. После этого будет выполнено умножение значений y и z, затем сложение со значением x и результат будет присвоен переменной или свойству, определяемому выражением w. Добавляя в выражения круглые скобки, можно изменить относительный порядок выполнения операций умножения, сложения и присваивания, но нельзя изменить общий порядок вычислений слева направо. Порядок вычисления имеет значение, только когда выражение имеет побочные эффекты, оказывающие влияние на значения других выражений. Если выражение x увеличивает значение переменной, используемой в выражении z, тогда тот факт, что x вычисляется раньше, чем z, имеет большое значение.

содержание 4.8. Арифметические выражения содержание

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

Основными арифметическими операторами являются * (умножение), / (деление), % (деление по модулю: остаток от деления), + (сложение) и -- (вычитание). Как уже отмечалось, оператор + будет рассматриваться в отдельном разделе. Другие основные четыре оператора просто определяют значения своих операндов, преобразуют их значения в числа, если это необходимо, и вычисляют произведение, частное, остаток или разность значений. Нечисловые операнды, которые не могут быть преобразованы в числа, преобразуются в значение NaN. Если какой-либо из операндов имеет (или преобразуется в) значение NaN, результатом операции также будет значение NaN.

Оператор / делит первый операнд на второй. Если вам приходилось работать с языками программирования, в которых целые и вещественные числа относятся к разным типам, вы могли бы ожидать получить целый результат от деления одного целого числа на другое целое число. Однако в языке JavaScript все числа являются вещественными, поэтому все операции деления возвращают вещественный результат: выражение 5/2 вернет , а не 2. Деление на ноль возвращает положительную или отрицательную бесконечность, тогда как выражение 0/0 возвращает ; ни в одном из этих случаев не возбуждается исключение.

Оператор % производит деление по модулю первого операнда на второй. Иными словами, он возвращает остаток от целочисленного деления первого операнда на второй. Знак результата определяется знаком первого операнда. Например, выражение 5 % 2 вернет , а выражение -5 % 2 вернет .

Несмотря на то что оператор по модулю обычно применяется к целым числам, он также может оперировать вещественными значениями. Например, выражение 6.5 % 2.1 вернет (0.2).

содержание 4.8.1. Оператор +

Двухместный оператор + складывает числовые операнды или выполняет конкатенацию строковых операндов:

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

Формально оператор + использует следующий алгоритм работы:

• Если значением любого из операндов является объект, он преобразуется в простое значение с использованием алгоритма преобразования объекта в простое значение, описанного в разделе 3.8.3: объекты Date преобразуются с помощью их метода toString(), а все остальные объекты преобразуются с помощью метода valueOf(), если он возвращает простое значение. Однако большинство объектов не имеют метода valueOf(), поэтому они также преобразуются с помощью метода toString().
• Если после преобразования объекта в простое значение любой из операндов оказывается строкой, другой операнд также преобразуется в строку и выполняется операция конкатенации.
• В противном случае оба операнда преобразуются в числа (или в NaN) и выполняется операция сложения.

Например:

Наконец, важно отметить, что, когда оператор + применяется к строкам и числам, он может нарушать ассоциативность. То есть результат может зависеть от порядка, в каком выполняются операции. Например:

В первом выражении отсутствуют скобки, и оператор + имеет ассоциативность слева направо, благодаря чему сначала выполняется сложение двух чисел, а их сумма объединяется со строкой. Во втором выражении порядок выполнения операций изменен с помощью скобок: число 2 объединяется со строкой, давая в результате новую строку. А затем число 1 объединяется с новой строкой, что дает окончательный результат.

содержание 4.8.2. Унарные арифметические операторы

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

Унарный плюс (+)

Оператор унарного плюса преобразует свой операнд в число (или в NaN) и возвращает преобразованное значение. При использовании с числовым операндом он не выполняет никаких действий. Унарный минус (–) Когда – используется как унарный оператор, он преобразует свой операнд в число, если это необходимо, и затем изменяет знак результата.

Инкремент (++)

Оператор ++инкрементирует (т. е. увеличивает на единицу) свой единственный операнд, который должен быть левосторонним выражением (переменной, элементом массива или свойством объекта). Оператор преобразует свой операнд в число, добавляет к этому числу 1 и присваивает результат сложения обратно переменной, элементу массива или свойству.

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

Обратите внимание, что выражение ++x не всегда возвращает тот же результат, что и выражение x = x + 1. Оператор ++ никогда не выполняет операцию конкатенации строк: он всегда преобразует свой операнд в число и увеличивает его. Если x является строкой «1», выражение ++x вернет число , тогда как выражение x + 1 вернет строку «».

Отметьте также, что из-за автоматического добавления точки с запятой в языке 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 & 0x00FF даст в результате число 0x0034.

Поразрядное ИЛИ (|)

Оператор | выполняет операцию «логическое ИЛИ» над каждым битом своих целочисленных аргументов. Бит результата устанавливается, если соответствующий бит установлен хотя бы в одном операнде. Например, выражение 0x1234 | 0x00FF даст в результате 0x12FF.

Поразрядное исключающее ИЛИ (^)

Оператор ^ выполняет логическую операцию «исключающее ИЛИ» над каждым битом своих целочисленных аргументов. Исключающее ИЛИ означает, что должен быть истинен либо первый операнд, либо второй, но не оба сразу. Бит результата устанавливается, если соответствующий бит установлен в одном (но не в обоих) из двух операндов. Например, выражение 0xFF00 ^ 0xF0F0 даст в результате 0x0FF0.

Поразрядное НЕ (~)

Оператор ~ представляет собой унарный оператор, указываемый перед своим единственным целым операндом. Он выполняет инверсию всех битов операнда. Из-за способа представления целых со знаком в JavaScript применение оператора ~ к значению эквивалентно изменению его знака и вычитанию 1. Например, выражение ~0x0f даст в результате 0xFFFFFFF0, или .

Сдвиг влево (<<)

Оператор << сдвигает все биты в первом операнде влево на количество позиций, указанное во втором операнде, который должен быть целым числом в диапазоне от 0 до 31. Например, в операции а << 1 первый бит в а становится вторым битом, второй бит становится третьим и т. д. Новым первым битом становится ноль, значение 32-го бита теряется. Сдвиг значения влево на одну позицию эквивалентен умножению на 2, на две позиции – умножению на 4 и т. д. Например, выражение 7 << 2 даст в результате .

Сдвиг вправо с сохранением знака (>>)

Оператор >> сдвигает все биты своего первого операнда вправо на количество позиций, указанное во втором операнде (целое между 0 и 31). Биты, сдвинутые за правый край, теряются. Самый старший бит не изменяется, чтобы сохранить знак результата. Если первый операнд положителен, старшие биты результата заполняются нулями; если первый операнд отрицателен, старшие биты результата заполняются единицами. Сдвиг значения вправо на одну позицию эквивалентен делению на 2 (с отбрасыванием остатка), сдвиг вправо на две позиции эквивалентен делению на 4 и т. д. Например, выражение 7 >> 1 даст в результате , а выражение -7 >> 1 даст в результате .

Сдвиг вправо с заполнением нулями (>>>)

Оператор >>> аналогичен оператору >>, за исключением того, что при сдвиге старшие разряды заполняются нулями, независимо от знака первого операнда. Например, выражение -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 никогда не бывает идентичным никакому значению, даже самому себе! Чтобы проверить, является ли значение x значением NaN, следует использовать выражение x !== x. Только если x имеет значение 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().

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

Наконец, обратите внимание, что операторы <= (меньше или равно) и >= (больше или равно) для определения «равенства» двух значений не используют операторы равенства или идентичности. Оператор «меньше или равно» определяется просто как «не больше», а оператор «больше или равно» – как «не меньше». Единственное исключение имеет место, когда один из операндов представляет собой значение NaN (или преобразуется в него). В этом случае все четыре оператора сравнения возвращают false.

содержание 4.9.3. Оператор in

Оператор in требует, чтобы левый операнд был строкой или мог быть преобразован в строку. Правым операндом должен быть объект. Результатом оператора будет значение true, если левое значение представляет собой имя свойства объекта, указанного справа. Например:

содержание 4.9.4. Оператор instanceof

Оператор instanceof требует, чтобы левым операндом был объект, а правым – имя класса объектов. Результатом оператора будет значение true, если объект, указанный слева, является экземпляром класса, указанного справа. В противном случае результатом будет false. В главе 9 рассказывается, что классы объектов в языке JavaScript определяются инициализировавшей их функцией-конструктором. Следовательно, правый операнд оператора instanceof должен быть именем функции-конструктора. Например:

Обратите внимание, что все объекты являются экземплярами класса Object. Определяя, является ли объект экземпляром класса, оператор instanceof принимает во внимание и «суперклассы». Если левый операнд instanceof не является объектом, instanceof возвращает false. Если правый операнд не является функцией, возбуждается исключение TypeError.

Чтобы понять, как действует оператор instanceof, необходимо познакомиться с таким понятием, как «цепочка прототипов». Это – механизм наследования в JavaScript; он описывается в разделе 6.2.2. Чтобы вычислить значение выражения o instanceof f, интерпретатор JavaScript определяет значение f.prototype и затем пытается отыскать это значение в цепочке прототипов объекта o . В случае успеха объект o считается экземпляром класса f (или суперкласса класса f), и оператор возвращает true. Если значение f.prototype отсутствует в цепочке прототипов объекта o, то объект o не является экземпляром класса f, и оператор instanceof возвращает false.

содержание 4.10. Логические выражения содержание

Логические операторы &&, || и ! используются для выполнения операций булевой алгебры и часто применяются в сочетании с операторами отношений для объединения двух выражений отношений в одно более сложное выражение. Эти операторы описываются в подразделах, следующих ниже. Чтобы понять, как они действуют, вам может потребоваться еще раз прочитать о концепции «истинности» и «ложности» значений в разделе 3.3.

содержание 4.10.1. Логическое И (&&)

Условно говоря, оператор && действует на трех уровнях. На самом простом уровне, когда в операции участвуют логические операнды, оператор && выполняет операцию «логическое И» над двумя значениями: он возвращает true тогда и только тогда, когда оба операнда имеют значение true. Если один или оба операнда имеют значение false, оператор возвращает false.

Оператор && часто используется для объединения двух выражений отношений:

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

Но оператор && не требует, чтобы его операнды были логическими значениями. Напомню, что все значения в языке JavaScript являются либо «истинными», либо «ложными». (Подробности в разделе 3.3. Ложными значениями являются false, null, undefined, 0, -0, NaN и "". Все другие значения, включая все объекты, являются истинными.) На втором уровне оператор && действует как логическое И для истинных и ложных значений. Если оба операнда являются истинными, оператор возвращает истинное значение. В противном случае, когда один или оба операнда являются ложными, возвращается ложное значение. В языке JavaScript все выражения и инструкции, использующие логические значения, будут также работать с истинными или ложными значениями, поэтому тот факт, что оператор && не всегда возвращает true или false, на практике не вызывает никаких проблем.

Обратите внимание, что в предыдущем абзаце говорилось, что оператор возвращает «истинное значение» или «ложное значение», но при этом не уточнялось, какое именно значение возвращается. Для этого нам необходимо перейти на третий, заключительный уровень оператора &&. Свою работу оператор начинает с вычисления первого операнда – выражения слева. Если выражение слева возвращает ложное значение, значением всего выражения также должно быть ложное значение, поэтому оператор && просто возвращает значение слева и не вычисляет выражение справа.

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

Важно понимать, что оператор && может не вычислять выражение правого операнда. В примере выше переменная p имеет значение null, поэтому попытка вычислить выражение p.x привела бы к ошибке ТуреЕггог. Но здесь задействован оператор &&, благодаря чему выражение p.x вычисляется, только если p будет содержать истинное значение – не null или undefined.

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

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

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

содержание 4.10.2. Логическое ИЛИ (||)

Оператор || выполняет операцию «логическое ИЛИ» над двумя операндами. Если один или оба операнда имеют истинное значение, он возвращает истинное значение. Если оба операнда имеют ложные значения, он возвращает ложное значение. Хотя оператор || чаще всего применяется просто как оператор «логическое ИЛИ», он, как и оператор &&, ведет себя более сложным образом. Его работа начинается с вычисления первого операнда, выражения слева. Если значение этого операнда является истинным, возвращается истинное значение. В противном случае оператор вычисляет второй операнд, выражение справа, и возвращает значение этого выражения.

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

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

Этот прием часто используется в функциях для определения значений по умолчанию параметров:

содержание 4.10.3. Логическое НЕ (!)

Оператор ! является унарным оператором, помещаемым перед одиночным операндом. Он используется для инверсии логического значения своего операнда. Например, если переменная x имеет истинное значение, то выражение !x возвращает значение false . Если x имеет ложное значение, то выражение !x возвращает значение false.

В отличие от операторов && и || оператор ! преобразует свой операнд в логическое значение (используя правила, описанные в главе 3) перед тем, как инвертировать его. Это означает, что оператор ! всегда возвращает true или false что всегда можно преобразовать любое значение x в его логический эквивалент, дважды применив этот оператор: !!x (раздел 3.8.2).

Будучи унарным, оператор ! имеет высокий приоритет и тесно связан с операндом. Если вам потребуется инвертировать значение выражения, такого как р && q, необходимо будет использовать круглые скобки:
!(р && q). В булевой алгебре есть две теоремы, которые можно выразить на языке JavaScript:

содержание 4.11. Выражения присваивания содержание

Для присваивания значения переменной или свойству в языке JavaScript используется оператор =. Например:

Левым операндом оператора = должно быть левостороннее выражение: переменная, элемент массива или свойство объекта. Правым операндом может быть любое значение любого типа. Значением оператора присваивания является значение правого операнда. Побочный эффект оператора = заключается в присваивании значения правого операнда переменной или свойству, указанному слева, так что при последующих обращениях к переменной или свойству будет получено это значение.

Чаще всего выражения присваивания используются как самостоятельные инструкции; тем не менее иногда можно увидеть, как выражение присваивания включается в более сложные выражения. Например, в одном выражении можно совместить операции присваивания и проверки значения:

(a = b) == 0

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

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

i = j = k = 0; // Инициализировать 3 переменные значением 0
Результат:

содержание 4.11.1. Присваивание с операцией

Помимо обычного оператора присваивания = в языке JavaScript поддерживается несколько других операторов, объединяющих присваивание с некоторой другой операцией. Например, оператор += выполняет сложение и присваивание. Следующее выражение:

total += sales_tax

эквивалентно выражению:

total = total + sales_tax

Как можно было ожидать, оператор += работает и с числами, и со строками. Для числовых операндов он выполняет сложение и присваивание, а для строковых – конкатенацию и присваивание.

Из подобных ему операторов можно назвать -=,*=,&= и др. Все операторы присваивания с операцией перечислены в табл. 4.2.

Таблица 4.2. Операторы присваивания

В большинстве случаев выражение:

a op = b

где op означает оператор, эквивалентно выражению:

a = a op b.

В первой строке выражение a вычисляется только один раз. Во второй – дважды. Эти выражения отличаются, только если подвыражение a содержит операции, имеющие побочные эффекты, такие как вызов функции или оператор инкремента. Например, следующие два выражения присваивания неэквивалентны:

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() (когда она вызывается таким способом) является то обстоятельство, что она использует окружение программного кода, вызвавшего ее. То есть она будет отыскивать значения переменных и определять новые переменные и функции, как это делает локальный программный код. Если функция определит локальную переменную x и затем вызовет eval("x"), она получит значение локальной переменной. Вызов eval("x = 1") изменит значение локальной переменной. А если выполнить вызов eval("var y = 3;"), будет объявлена новая локальная переменная y. Точно так же можно определять новые локальные функции:

eval("function f() { return x + 1; }");

Если вызвать функцию eval() из программного кода верхнего уровня, она, разумеется, будет оперировать глобальными переменными и глобальными функциями.

Обратите внимание, что программный код в строке, передаваемой функции eval(), должен быть синтаксически осмысленным – эту функцию нельзя использовать, чтобы вставить фрагмент программного кода в вызывающую функцию. Например, бессмысленно писать вызов eval("return;"), потому что инструкция return допустима только внутри функций, а тот факт, что программный код в строке использует то же самое окружение, что и вызывающая функция, не делает его частью этой функции. Если программный код в строке может расцениваться как самостоятельный сценарий (пусть и очень короткий, такой как x = 0), его уже можно будет передавать функции eval(). В противном случае eval() возбудит исключение SyntaxError.

содержание 4.12.2. Использование eval() в глобальном контексте

Способность функции eval() изменять локальные переменные представляет значительную проблему для оптимизаторов JavaScript. Для ее решения некоторые интерпретаторы просто уменьшают степень оптимизации всех функций, вызывающих eval(). Однако как быть интерпретатору JavaScript, когда в сценарии определяется псевдоним функции eval() и выполняется ее вызов по другому имени? Чтобы облегчить жизнь разработчикам интерпретаторов JavaScript, стандарт ECMAScript 3 требует, чтобы такая возможность в интерпретаторах была запрещена. Если функция eval() вызывается под любым другим именем, отличным от «eval», она должна возбуждать исключение EvalError.

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

Стандарт ECMAScript 5 отменяет возбуждение исключения Eval Error и стандартизует поведение eval(), сложившееся де-факто. «Прямой вызов» – это вызов функции eval() по ее непосредственному имени «eval» (которое все больше начинает походить на зарезервированное слово). Прямые вызовы eval() используют окружение вызывающего контекста. Любые другие вызовы – косвенные вызовы – в качестве окружения используют глобальный объект и не могут получать, изменять или определять локальные переменные или функции. Это поведение демонстрируется в следующем фрагменте:

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

До появления версии IE9 Internet Explorer отличался от других браузеров: функция eval(), вызванная под другим именем, выполняла переданный ей программный код не в глобальном контексте. (Однако она не возбуждала исключение Eval Error: программный код просто выполнялся ею в локальном контексте.) Но 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, и иногда он так и называется – «тернарный оператор». Этот оператор обычно записывается как ?:, хотя в программах он выглядит по-другому. Он имеет три операнда, первый предшествует символу ?, второй- между ? и :, третий – после :. Используется он следующим образом:

x > 0 ? x : -x // Абсолютное значение x.

Пример:

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

Тот же результат можно получить с помощью инструкции 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(i) .

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

Для всех объектных типов и типов массивов результатом оператора typeof является строка «object», поэтому он может быть полезен только для определения принадлежности значения к объектному или к простому типу. Чтобы отличить один класс объектов от другого, следует использовать другие инструменты, такие как оператор instance of (раздел 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» для них, хотя они ведут себя как функции. В версии IE9 клиентские методы были реализованы как обычные объекты-функции базового языка. Подробнее об отличиях между истинными функциями и вызываемыми объектами рассказывается в разделе 8.7.7.

содержание 4.13.3. Оператор delete

Унарный оператор delete выполняет попытку удалить свойство объекта или элемент массива, определяемый операндом.

(Программистам на C++ следует обратить внимание, что delete в JavaScript совершенно не похож на delete в C++. В JavaScript освобождение памяти выполняется сборщиком мусора автоматически, и беспокоиться о явном освобождении памяти не надо. Поэтому в операторе delete в стиле C++, удаляющем объекты без остатка, нет необходимости.)

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

Внимание: удаленное свойство или элемент массива не просто получает значение undefined. После удаления свойства оно прекращает свое существование. Попытка прочитать значение несуществующего свойства возвратит значение undefined, но вы можете проверить фактическое наличие свойства с помощью оператора in (раздел 4.9.3). Операция удаления элемента массива оставляет в массиве «дырку» и не изменяет длину массива. В результате получается разреженный массив.

Оператор delete требует, чтобы его операнд был левосторонним выражением. Если операнд не является левосторонним выражением, оператор не будет выполнять никаких действий и вернет значение true. В противном случае delete попытается удалить указанное левостороннее выражение. В случае успешного удаления значения левостороннего выражения оператор delete вернет значение true. Не все свойства могут быть удалены: некоторые встроенные свойства из базового и клиентского языков JavaScript устойчивы к операции удаления. Точно так же не могут быть удалены пользовательские переменные, объявленные с помощью инструкции var. Кроме того, невозможно удалить функции, объявленные с помощью инструкции function, а также объявленные параметры функций.

В строгом режиме, определяемом стандартом ECMAScript 5, оператор delete возбуждает исключение SyntaxError, если его операндом является неквалифицированный идентификатор, такой как имя переменной, функции или параметра функции: он может оперировать только операндами, которые являются выражениями обращения к свойству (раздел 4.4). Кроме того, строгий режим определяет, что оператор delete должен возбуждать исключение Туре Error, если запрошено удаление ненастраиваемого свойства (раздел 6.7). В обычном режиме в таких случаях исключение не возбуждается, и оператор delete просто возвращает false, чтобы показать, что операнд не был удален.

Ниже приводится несколько примеров использования оператора delete:

1

2 "use strict";

delete o;

SyntaxError: applying the 'delete' operator to an unqualified name is deprecated

3

4 "use strict";

delete x_ // в строгом режиме возбуждает исключение.
SyntaxError: applying the 'delete' operator to an unqualified name is deprecated
Используйте "delete this.x_"

5

6 "use strict";

С оператором delete мы снова встретимся в разделе 6.3.

содержание 4.13.4. Оператор void

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

Чаще всего этот оператор применяется в клиентском JavaScript, в адресах URL вида JavaScript:, где он позволяет вычислить выражение ради его побочных действий, не отображая в браузере вычисленное значение. Например, оператор void можно использовать в HTML-теге <а>:

<а href="javascript:void window.open();">Открыть новое окно</а>

Эта разметка HTML была бы более очевидна, если бы вместо URL javascript: применялся обработчик события onclick, где в использовании оператора void нет никакой необходимости.

содержание 4.13.5. Оператор «запятая» (,)

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

i = 0, j = l, k = 2;

вернет значение 2 и практически эквивалентна строке:

i = 0; j = 1; k = 2;

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