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 – это объектно-ориентированный язык программирования. В общих чертах это означает, что вместо глобальных функций для обработки значений различных типов типы сами могут определять методы для обработки значений. Например, чтобы отсортировать элементы массива a, необязательно передавать массив a функции sort(). Вместо этого можно просто вызвать метод sort() массива a:

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

Порядок определения методов описывается в главе 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.

Этот формат знаком программистам на языке Java как тип double. Этот же формат используется для представления чисел типа double в большинстве современных реализаций языков С и C++.

Этот формат способен представлять числа в диапазоне от ±1,7976931348623157 × 10308 до ±5 × 10324.

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

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

содержание 3.1.1. Целые литералы

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

0
3
10000000

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

0xff // 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. Литералы вещественных чисел

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

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

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

[цифры][.цифры][(E\e)[(+\-)]цифры]

Например:

3.14
2345.789
.333333333333333333
6.02e23 // 6.02 × 1023
1.4738223E-32 // 1.4738223 × 10-32

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

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

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

Math.pow(2,53) // => 9007199254740992: 2 в степени 53;
Math.round(.6) // => 1: округление до ближайшего целого;
Math.ceil(.6) // => 1: округление вверх ;
Math.floor(.6) // => 0: округление вниз;
Math.abs(-5) // => 5: абсолютное значение;
Math.max(x,y,z) // => Возвращает наибольший аргумент;
Math.max(5,7,6) // => 7;
Math.min(x,y,z) // Возвращает наименьший аргумент;
Math.min(5,7,6) // => 5;
Math.random() // => 0.15525387458616624: Псевдослучайное число х, где 0 <= х < 1.0;
Math.PI // => π = 3.141592653589793 : длина окружности / диаметр;
Math.sqrt(3) // => 1.7320508075688772: Корень квадратный из 3;
Math.pow(3, 1/3) // => 1.4422495703074083: Корень кубический из 3;
Math.sin(3.14/2) // => 0.9999996829318346: Тригонометрия: имеются также
 // Math.cos, Math.atan и другие;
Math.log(10) // => 2.302585092994046: Натуральный логарифм 10;
Math.log(100)/Math.LN10 // => 2: Логарифм 100 по основанию 10 (десятичный);
Math.log(512)/Math.LN2 // => 9: Логарифм 512 по по основанию 2;
Math.exp(3) // => 20.085536923187668: Math.E в кубе.

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

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

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

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

Деление на ноль не считается ошибкой в JavaScript: в этом случае просто возвращается бесконечность или отрицательная бесконечность:

 1/0    // => Infinity: «бесконечность»,
-1/0    // => -Infinity: «отрицательная бесконечность».

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

    0/0    // => NaN: «not-a-number»,
    (1/0)/(2/0)    // => NaN: «not-a-number»,
    Math.sqrt(-3) // => NaN: Корень квадратный из -3 «не число»;
    'раз'/'два'    // => NaN: «not-a-number».

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

Infinity// Переменная, доступная для чтения/записи,
 // инициализированная значением Infinity.
Number.POSITIVE_INFINITY// => Infinity, то же значение, доступное только для чтения.
1/0// => Infinity, то же самое значение.
Number.MAX_VALUE// Это выражение возвращает 1.7976931348623157e+308
Number.MAX_VALUE + 1// Это выражение также возвращает 1.7976931348623157e+308
Number.NEGATIVE_INFINITY// => -Infinity, возвращает отрицательную бесконечность.
-Infinity 
-1/0// => -Infinity, также возвращает отрицательную бесконечность.;
-Number.MAX_VALUE// => -1.7976931348623157e+308 ;
-Number.MAX_VALUE - 1// => -1.7976931348623157e+308, то же самое значение.
NaN// Переменная, доступная для чтения/записи,
 // инициализированная значением NaN.
Number.NaN// Свойство, доступное только для чтения, с тем же значением.
0/0// Возвращает NaN.
Number.MIN_VALUE// => 5e-324, наименьшее положительное значение типа Number.
Number.MIN_VALUE/2// => 0, Потеря значащих разрядов: возвращает 0
-Number.MIN_VALUE/2 === 0// => true, -Number.MIN_VALUE/2: отрицательный ноль
- 
-1/Infinity === 0// => true, -1/Infinity: также отрицательный ноль
-0 === 0// => true;

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

    ('раз'/'два')==('раз'/'два')    // => false.
    0==0    // => true.
    0/0==0/0    // => false.
    (1/0)/(2/0)==(1/0)/(2/0)    // => false.

Это означает, что нельзя использовать проверку х == NaN, чтобы определить, является значение переменной x значением NaN. Чтобы определить, является ли значение переменной x значением NaN, следует выполнять проверку
х != х:
    ('раз'/'два') != ('раз'/'два')    // => true.

Эта проверка вернет true тогда и только тогда, когда x имеет значение NaN. Аналогичную проверку можно выполнить с помощью функции isNaN(). Она возвращает true, если аргумент имеет значение NaN или если аргумент является нечисловым значением, таким как строка или объект. Родственная функция isFinite() возвращает true, если аргумент является числом, отличным от NaN, Infinity или -Infinity.

isNaN('раз'/'два') // => true;

isFinite('раз'/'два') // => false.

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

var zero = 0;// => 0. Обычный ноль
var negz = -0;// => 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 x = .3 - .2;// тридцать копеек минус двадцать копеек
var у = .2 - .1;// двадцать копеек минус 10 копеек
x == y// => false: получились два разных значения!
x == .1// => false: .3-.2 не равно .1
y == .1// => true: .2-.1 равно .1

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

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

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

содержание 3.1.5. Дата и время

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

var then = new Date(2010,0,1);// Первый день первого месяца 2010 года
Fri Jan 01 2010 00:00:00 GMT+0300 (Москва, стандартное время)
var later = new Date(2010,0,1,17,10,30);// Та же дата, в 17:10:30 локального времени
Fri Jan 01 2010 17:10:30 GMT+0300 (Москва, стандартное время)
var now = new Date();// Текущие дата и время
// Mon Oct 22 2018 22:42:23 GMT+0300 (Москва, стандартное время)
now.toUTCString()// Mon, 22 Oct 2018 19:42:23 GMT (текущие дата и время по UTC)
var elapsed = now - then;// => 277944143925
// -разность дат: интервал в миллисекундах.
later.getFullYear()// => 2010
later.getMonth()// => 0: счет месяцев начинается с нуля
later.getDate()// => 1: счет дней начинается с единицы
later.getDay()// => 5: день недели. 0 - воскр., 5 - пятн.
later.getHours()// => 17 часов локального времени
later.getUTCHours()// => 14 -часы по UTC; зависит от часового пояса
later.toString()// => Fri Jan 01 2010 17:10:30 GMT+0300 (Москва, стандартное время)
later.toUTCString()// => Fri, 01 Jan 2010 14:10:30 GMT
later.toLocaleDateString()// => 01.01.2010
later.toLocaleTimeString()// => 17:10:30
later.toISOString()// => 2010-01-01T14:10:30.000Z

содержание 3.2. Текст содержание

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

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

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

var p = "\u03c0";// π - это 1 символ с 16-битным кодовым пунктом 0x03c0
var e = "\ud835\udc52"// 𝑒 - это 1 символ с 17-битным кодовым пунктом 0x1d452
p.length// => 1: p содержит единственный 16-битный элемент
e.length// => 2: в кодировке UTF-16 символ е определяется двумя
e.length// 16-битными значениями: "𝑒"

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

содержание 3.2.1. Строковые литералы

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

""  // Это пустая строка: в ней ноль символов
'testing'
"3.14"
'name="myform"'
"Вы предпочитаете книги издательства O'Reilly, не правда ли?"
"В этом строковом литерале\nдве строки"
"π - это отношение длины окружности к ее диаметру"

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

"две\nстроки" // Строковый литерал, представляющий две строки,
"Roses are red,\n\Violets are blue.\n\I'm schizophrenic,\n\And so am I.";
// Строковый литерал, представляющий четыре строки,
// с тегом <pre> это выглядит так:
 две
строки

Roses are red, Violets are blue. I'm schizophrenic, And so am I.
"одна\
длинная\
строка"
одна 
длинная 
строка  // один длинный строковый литерал, записанный в трех строках. Только в ECMAScript 5

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

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

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

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

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

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

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

примет вид

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

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

Таблица 3.1. Управляющие последовательности JavaScript


Последовательность Представляемый символ
\0 Символ NUL (\u0000)
\b «Забой» (\u0008)
\t Горизонтальная табуляция (\u0009)
\n Перевод строки (\u000A)
\v Вертикальная табуляция (\u000B)
\f Перевод страницы (\u000C)
\r Возврат каретки (\u000D)
\" Двойная кавычка (\u0022)
\' Одинарная кавычка (\u0027)
\\ Обратный слэш (\u005C)
\xXX Символ Latin-1, заданный двумя шестнадцатеричными цифрами XX
\uXXXX Символ Unicode, заданный четырьмя шестнадцатеричными цифрами XXXX

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

содержание 3.2.3. Работа со строками

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

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

greeting = "Добро пожаловать на мою домашнюю страницу," + " " + name;
// Получается строка "Добро пожаловать на мою домашнюю страницу, Адам".

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

s.length
greeting.length// => 47.

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

var msg = "hello, world"// Начнем с того же текста.
msg.charAt(0)// => h: первый символ.
msg.charAt(msg.length-1)// => d: последний символ.
msg.substring(1,4)// => ell: 2-й, 3-й и 4-й символы.
msg.slice(1,4)// => ell: то же самое
msg.slice(-3)// => rld: последние 3 символа
msg.indexOf("l")// => 2: позиция первого символа l.
msg.lastIndexOf("l")// => 10: позиция последнего символа l.
msg.indexOf("l",3)// => 3: позиция первого символа "l", следующего
// за 3 символом в строке
msg.split(", ")// => hello,world => [Hello,world] разбивает на подстроки
msg.replace("H", "h")// => hello, world: замещает все вхождения подстроки
msg.toUpperCase()// => HELLO, WORLD

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

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

msg[0] // => "h"
msg[msg.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 <==> ["1","2","3"] -массив всех совпадений
text.replace(pattern,"#");// => "testing: #, #, #"
text.split(/\D+/);// => ,1,2,3 <==> ["","1","2","3"]: разбить по нецифровым символам

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

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

a == 4

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

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

if (a == 4)
  b = b + 1;
else
  a = a + 1;

исходные значения: a = 4; b = 5;
новые значения: a = 4; b = 6;

исходные значения: a = 3; b = 6;
новые значения: a = 4; b = 6;

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

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

undefined
null
0
-0
NaN
""// пустая строка

Пример:

if (undefined) c = a; else c = a + b // c = 10;

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

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

if (o !== null) ... // => true;

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

if (o) ...

В первом случае тело инструкции if будет выполнено, только если значение переменной o не равно null. Во втором – ставится менее жесткое условие: тело инструкции if будет выполнено, только если o не содержит false или другое ложное значение (такое как null или undefined). Примеры:

o = undefined
if (o) c = c - (a + b)/2; else c = c + (a + b)/2; // c = 15
o = '';
if (o) c = c - (a + b)/2; else c = c + (a + b)/2; // c = 20
o = q;
if (o) c = c - (a + b)/2; else c = c + (a + b)/2; // c = 15;

Какая инструкция if больше подходит для вашей программы, зависит от того, какие значения могут присваиваться переменной o. Если в программе необходимо отличать значение 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 , являются признаком отсутствия значения и часто являются взаимозаменяемыми. Оператор равенства == считает их равными. (Чтобы отличать их в программе, можно использовать оператор идентичности ===).

null == undefined // => true
null === undefined // => false;

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

Значение 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 и JSON (раздел 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 объекта o, следует использовать инструкцию o.m().

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

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

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

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

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

var s = "test"; // Начальное строковое значение.
s.len = 4; // Установить его свойство.
var t = s.len; // (=> undefined) Теперь запросить значение свойства.

В начале этого фрагмента переменная t имеет значение undefined. Вторая строка создает временный объект String, устанавливает его свойство len равным 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 и B в примере выше, в обертываемые ими простые значения, но они не всегда ведут себя точно так же, как значения s, n и b. Оператор равенства == считает равными значения и соответствующие им объекты-обертки, но оператор идентичности === отличает их. Оператор typeof также обнаруживает отличия между простыми значениями и их объектами-обертками.

содержание 3.7. Неизменяемые простые значения и ссылки на изменяемые объекты содержание

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

var s = "hello";// Изначально имеется некоторый текст из строчных символов
s.toUpperCase();// Вернет "HELLO", но значение s при этом не изменится.
s // => "hello": оригинальная строка не изменилась.

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

Объекты отличаются от простых типов. Во-первых, они являются изменяемыми – их значения можно изменять:

var o = { х:1 }; // Начальное значение объекта
o.х = 2; // Изменить объект, изменив значение свойства
o.у = 3; // Изменить объект, добавив новое свойство
var a = [1,2,3];// Массивы также являются изменяемыми объектами
a[0] = 0; // Изменить значение элемента массив
a[3] = 4; // Добавить новый элемент

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

var o={x:1}, p = {х:1>;// Два объекта с одинаковыми свойствами
о === p // => false: разные объекты не являются равными
var a = [], b = [];// Два различных пустых массива.
a === b // => false: различные массивы не являются равными.

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

var a = []; // Переменная a ссылается на пустой массив.
var b = a; // Теперь b ссылается на тот же массив.
b[0] = 1; // Изменение массива с помощью ссылки в переменной b.
a[0] // => 1: изменение можно наблюдать в переменной a.
a === b // => true: a и b ссылаются на один и тот же объект,

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

var a = ['a','b','с']; // Копируемый массив
var b = []; // Массив, куда выполняется копирование
for(var i = 0; i < a.length; i++) { // Для каждого элемента в массиве a[]
  b[i] = a[i]; // Скопировать элемент a[] в b[]
} 

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

function equalArrays(a,b) {
  if (a.length != b.length) return false; // Массивы разной длины не равны
    for(var i = 0; i < a.length; i++) // Цикл по всем элементам
      if (a[i] !== b[i]) return false; // Если хоть один элемент
 // отличается, массивы не равны
return true; // Иначе они равны
}

equalArrays(a,b) // => true

содержание 3.8. Преобразование типов содержание

JavaScript может гибко преобразовывать один тип в другой. Мы уже могли убедиться в этом на примере логических значений: везде, где интерпретатор JavaScript ожидает получить логическое значение, можно указать значение любого типа, и JavaScript автоматически выполнит необходимое преобразование. Одни значения («истинные» значения) преобразуются в значение true, а другие («ложные») – в false. To же относится и к другим типам: если интерпретатор ожидает получить строку, он автоматически преобразует любое другое значение в строку. Если интерпретатор ожидает получить число, он попробует преобразовать имеющееся значение в число (в случае невозможности такого преобразования будет получено значение NaN). Например:

10 + " objects" // => "10 objects". Число 10 преобразуется в строку
"7" * "4" // => 28: обе строки преобразуются в числа
var n = 1 - "x"; // => NaN: строка "x" не может быть преобразована в число
var x = 1; // => 1: число
var n = 1 - "x"; // => NaN: строка "x" не может быть преобразована в число
var n = 1 - x; // => 0
n + " objects" // => 0 objects: NaN преобразуется в строку "NaN"

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

Преобразования одного простого типа в другой, показанные в табл. 3.2, выполняются относительно просто. Преобразование в логический тип уже обсуждалось в разделе 3.3. Преобразование всех простых типов в строку четко определено. Преобразование в число выполняется немного сложнее. Строки, которые могут быть преобразованы в числа, преобразуются в числа. В строке допускается наличие пробельных символов в начале и в конце, но присутствие других непробельных символов, которые не могут быть частью числа, при преобразовании строки в число приводят к возврату значения NaN). Некоторые особенности преобразования значений в числа могут показаться странными: значение true преобразуется в число 1, а значение false и пустая строка "" преобразуются в 0.

Преобразование простых типов в объекты также выполняется достаточно просто: значения простых типов преобразуются в соответствующие объекты-обертки как если бы вызывался конструктор String(), Number() или Boolean().
Исключение составляют значения null и undefined: любая попытка использовать их в контексте, где требуется объект, вместо преобразования будет приводить к возбуждению исключения TypeError.

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

Таблица 3.2. Преобразование типов в JavaScript

ЗначениеПреобразование в:
Строку Число Логическое значение Объект
undefined "undefined" NaN false возбуждается ошибка TypeError
null "null" 0false возбуждается ошибка TypeError
true "true" 1new Boolean(true)
false "false" 0new Boolean(false)
"" (пустая строка)  0false new StringC")
"1.2" (непустая строка, число)  1.2true new String("1.2")
"one" (не пустая строка, не число)  NaN true new String("one")
0"0" false new Number(O)
-0"0" false new Number(-O)
NaN "NaN"  false new Number(NaN)
Infinity "Infinity"  true new Number(Infinity)
-Infinity"-Infinity" true new Number(-Infinity)
1 (конечное, ненулевое) "1" true new Number(1)
{} (любой объект) см. разд. 3.8.3 см. раздел 3.8.3 true  
[] (пустой массив) ""0true  
[9] (1 числовой элемент) "9"9true  
['а'] (любой другой массив) используется метод join() NaN true  
function(){} (любая функция) см. разд. 3.8.3 NaN true  

содержание 3.8.1. Преобразования и равенство

Благодаря гибкости преобразований типов в JavaScript оператор равенства == также гибко определяет равенство значений. Например, все следующие сравнения возвращают true:

null == undefined // => true -Эти два значения считаются равными.
"0" == 0 // => true -Перед сравнением строка преобразуется в число.
0 == false // => true -Перед сравнением логич. значение преобразуется в число.
"0" == false // => true -Перед сравнением оба операнда преобразуются в числа.

В разделе 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 в объект возбуждается ошибка TypeError. Функция Object() в этом случае не возбуждает исключение, вместо этого она просто возвращает новый пустой объект.

Определенные операторы в языке JavaScript неявно выполняют преобразования и иногда могут использоваться для преобразования типов. Если один из операндов оператора + является строкой, то другой операнд также преобразуется в строку. Унарный оператор + преобразует свой операнд в число. А унарный оператор ! преобразует операнд в логическое значение и инвертирует его. Все это стало причиной появления следующих своеобразных способов преобразования типов, которые можно встретить на практике:

x + "" То же, что и String(x)
+x // То же, что и Number(x). Можно также встретить x - 0
!!x // То же, что и Boolean(x). Обратите внимание на два знака !
x = "_";  
!!x // => true.

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

Метод toString() класса Number принимает необязательный аргумент, определяющий основание системы счисления для преобразования. Если этот аргумент не определен, преобразование выполняется в десятичной системе счисления. Но вы можете производить преобразование в любой системе счисления (с основанием от 2 до 36). Например:

var n = 17;  
binary_string = n.toString(2); // Вернет "10001"
octal_string = "0" + n.toString(8); // Вернет "021"
hex_string = "0x" + n.toString(16); // Вернет "0x11"

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

var n = 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.235e+5
n.toPrecision(4); 1.235e+5
n.toPrecision(7); 123456.8
n.toPrecision(10); 123456.7890

Если передать строку функции преобразования Number(), она попытается разобрать эту строку как литерал целого или вещественного числа. Эта функция работает только с десятичными целыми числами и не допускает наличия в строке завершающих символов, не являющихся частью литерала числа. Функции parseInt() и parseFloat() (это глобальные функции, а не методы какого-либо класса) являются более гибкими. Функция parseInt() анализирует только целые числа, тогда как функция parseFloat() позволяет анализировать строки, представляющие и целые, и вещественные числа. Если строка начинается с последовательности «0x» или «0X», функция parseInt() интерпретирует ее как представление шестнадцатеричного числа.

(Согласно стандарту ECMAScript 3 функция parseInt() может выполнять преобразование строки, начинающейся с символа «0» (но не «0x» или «0X), в восьмеричное или десятичное число. Поскольку поведение функции четко не определено, следует избегать использования функции parseInt() для интерпретации строк, начинающихся с «0», или явно указывать основание системы счисления! В ECMAScript 5 функция parseInt() будет интерпретировать строки как восьмеричные числа, только если ей во втором аргументе явно указать основание 8 системы счисления.)

Обе функции, 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("a.1"); // => NaN

Функция parseInt() принимает второй необязательный аргумент, определяющий основание системы счисления для разбираемого числа. Допустимыми являются значения от 2 до 36. Например:

parseInt("11", 2); // => 3 (1*2 + 1)
parseInt("ff", 16); // => 255(15*16 + 15)
parseInt("zz", 36); // => 1295(35*36 + 35)
parseInt("077", 8); // => 63(7*8 + 7)
parseInt("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]"'

Многие классы определяют более специализированные версии метода 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) {f(x);}"
/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 d0 = new Date(1970,0,1);//=> "Thu Jan 01 1970 00:00:00 GMT+0300 (Москва, стандартное время)"
var d = new Date(2010,0,1);//=> "Fri Jan 01 2010 00:00:00 GMT+0300 (Москва, стандартное время)"
d0.valueOf()//=> "-10800000"
d0.valueOf() + 3h//=> "0" UTC
d.valueOf()//=> "1262293200000"
d - d0//=> "1262304000000"

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

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

При преобразовании объекта в число интерпретатор выполняет те же действия, но первым пытается применить метод valueOf():
• Если объект имеет метод valueOf(), возвращающий простое значение, интерпретатор преобразует (при необходимости) это значение в число и возвращает результат.
• Иначе, если объект имеет метод toString(), возвращающий простое значение, интерпретатор выполняет преобразование и возвращает полученное значение.
• В противном случае возбуждается исключение TypeError.

Описанный алгоритм преобразования объекта в число объясняет, почему пустой массив преобразуется в число 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) //=> "string": + преобразует дату в строку
typeof (now - 1) //=> "number": - выполнит преобразование объекта в число
now == now.toString()//=> "true": неявное и явное преобразование в строку
now > (now -1) //=> "true": > преобразует объект Date в число

содержание 3.9. Объявление переменных содержание

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

var i;
var sum;

Один раз использовав ключевое слово var, можно объявить несколько переменных:

var i, sum;

Объявление переменных можно совмещать с их инициализацией:

var message = "hello";
var i = 0, j = 0, k = 0;

Если начальное значение в инструкции var не задано, то переменная объявляется, но ее начальное значение остается неопределенным (undefined), пока не будет изменено программой.

Обратите внимание, что инструкция var также может включаться в циклы for и for/in (о которых рассказывается в главе 5), что позволяет объявлять переменную цикла непосредственно в самом цикле. Например:

for(var i = 0; i < 10; i++) console.log(i);
for(var i = 0, j=10; i < 10; i++,j--) console.log(i*j);
for(var p in o) console.log(p);

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

var i = 10;
i = "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; // => "local" -Вернет локальное значение, а не глобальное:
} 
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 checkscope() {
  var scope = "local scope"; // Локальная переменная
  function nested() {
    var scope = "nested scope"; // Вложенная область видимости локальных переменных
    return scope; // Вернет значение этой переменной scope
  }
  return nested();
}
checkscope() // => "nested scope"

содержание 3.10.1. Область видимости функции и подъем

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

В следующем фрагменте переменные i, j и k объявляются в разных местах, но все они имеют одну и ту же область видимости – все три переменные доступны из любого места в теле функции:

function test(o) {
  var i = 0; // i определена в теле всей функции
  if (typeof о == "object") {
    var j = 0; // j определена везде, не только в блоке
    for(var k=0; k < 10; k++) { // k определена везде, не только в цикле
      console.log(k); // выведет числа от 0 до 9
    }  
    console.log(k); // 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. To есть функция выше эквивалентна реализации, приведенной ниже, в которой объявление переменной «поднято» в начало функции, а инициализация переменной выполняется там же, где и раньше:

function f() {
  var scope; // Объявление локальной переменной в начале функции
  console.log(scope); // Здесь она доступна, но имеет значение "undefined"
  scope = "local"; // Здесь она инициализируется и получает свое значение
  console.log(scope); // А здесь она имеет ожидаемое значение
} 

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

содержание 3.10.2. Переменные как свойства

При объявлении глобальной переменной в JavaScript в действительности создается свойство глобального объекта (раздел 3.5). Если глобальная переменная объявляется с помощью инструкции var, создается ненастраиваемое свойство, т. е. свойство, которое невозможно удалить с помощью оператора 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 ).