В процессе работы компьютерные программы манипулируют значениями, такими как число 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, которые не имеют методов.
null
и undefined
являются
неизменяемыми – не имеет даже смысла говорить об изменчивости, например, значения
числа. Строки можно представить себе как массивы символов, отчего можно
счесть, что они являются изменяемыми. Однако строки в JavaScript являются
неизменяемыми: строки предусматривают возможность обращения к символам по
числовым индексам, но в JavaScript отсутствует возможность изменить
существующую текстовую строку. Различия между изменяемыми и неизменяемыми
значениями будут рассматриваться ниже, в разделе 3.7.
В языке JavaScript значения достаточно свободно могут быть преобразованы из
одного типа в другой. Например, если программа ожидает получить строку, а вы
передаете ей число, интерпретатор автоматически преобразует число в строку.
Если вы укажете нелогическое значение там, где ожидается логическое,
интерпретатор автоматически выполнит соответствующее преобразование. Правила
преобразований описываются в разделе 3.8. Свобода преобразований типов
значений в JavaScript затрагивает и понятие равенства, и оператор == проверки на
равенство выполняет преобразование типов, как описывается в разделе 3.8.1.
Переменные в JavaScript не имеют типа: переменной может быть присвоено
значение любого типа и позднее этой же переменной может быть присвоено значение
другого типа. Объявление переменных выполняется с помощью ключевого слова
var. В языке JavaScript используются лексические области видимости.
Переменные, объявленные за пределами функции, являются глобальными переменными
и доступны из любой точки программы. Переменные, объявленные внутри
функции, находятся в области видимости функции и доступны только внутри этой
функции. Порядок объявления переменных и их видимость обсуждаются в
разделах 3.9
и 3.10.
В отличие от многих языков программирования, в 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),
не являющийся частью синтаксиса числовых литералов.
В JavaScript целые десятичные числа записываются как последовательность цифр. Например:
0 3 10000000Помимо десятичных целых литералов JavaScript распознает шестнадцатеричные значения (по основанию
16
). Шестнадцатеричные литералы начинаются
с последовательности символов «0x
» или «0X
», за которой следует строка
шестнадцатеричных цифр. Шестнадцатеричная цифра – это одна из цифр от 0
до 9
или
букв от a
(или A
) до f
(или F
), представляющих значения от 10
до 15
. Ниже
приводятся примеры шестнадцатеричных целых литералов:
0xff // 15*16 + 15 = (по основанию 10) 0xCAFE911Хотя стандарт ECMAScript не поддерживает представление целых литералов в восьмеричном формате (по основанию
8
), некоторые реализации JavaScript
допускают подобную возможность. Восьмеричный литерал начинается с цифры 0
,
за которой могут следовать цифры от 0
до 7
. Например:
0377 // 3*64 + 7*8 + 7 = (по основанию 10)Поскольку некоторые реализации поддерживают восьмеричные литералы, а некоторые нет, никогда не следует писать целый литерал с ведущим нулем, ибо нельзя сказать наверняка, как он будет интерпретирован данной реализацией – как восьмеричное число или как десятичное. В строгом (strict) режиме, определяемом стандартом ECMAScript 5 (раздел 5.7.3), восьмеричные литералы явно запрещены.
Литералы вещественных чисел должны иметь десятичную точку – при определении таких литералов используется традиционный синтаксис вещественных чисел. Вещественное значение представляется как целая часть числа, за которой следуют десятичная точка и дробная часть числа.
Литералы вещественных чисел могут также представляться в экспоненциальной нотации: вещественное число, за которым следует буква e (или E), а затем необязательный знак плюс или минус и целая экспонента. Такая форма записи обозначает вещественное число, умноженное на 10 в степени, определяемой значением экспоненты.
Ниже приводится более лаконичное определение синтаксиса:
[цифры][.цифры][(E\e)[(+\-)]цифры]Например:
3.14 2345.789 .333333333333333333 6.02e23 // 6.02 × 1023 1.4738223E-32 // 1.4738223 × 10-32
Обработка чисел в языке JavaScript выполняется с помощью арифметических
операторов. В число таких операторов входят: оператор сложения +
, оператор
вычитания -
, оператор умножения *
, оператор деления /
и оператор деления по
модулю %
(возвращает остаток от деления). Полное описание этих и других операторов
можно найти в главе 4.
Помимо этих простых арифметических операторов JavaScript поддерживает
более сложные математические операции, с помощью функций и констант,
доступных в виде свойств объекта Math
:
Math.pow(2,53) // => : 2 в степени 53; Math.round(.6) // => : округление до ближайшего целого; Math.ceil(.6) // => : округление вверх; Math.floor(.6) // => : округление вниз; Math.abs(-5) // => : абсолютное значение; Math.max(x,y,z) // Возвращает наибольший аргумент Math.max(5,7,6) // => ; Math.min(x,y,z) // Возвращает наименьший аргумент Math.min(5,7,6) // => ; Math.random() // => : Псевдослучайное число х, // где 0 <= х < 1.0; Math.PI // => π = : // длина окружности / диаметр; Math.sqrt(3) // => : Корень квадратный из 3; Math.pow(3, 1/3) // => : Корень кубический из 3; Math.sin(3.14/2) // => : Тригонометрия: // имеются также Math.cos, Math.atan и другие; Math.log(10) // => : Натуральный логарифм 10; Math.log(100)/Math.LN10 // => : Логарифм 100 по основанию 10 // (десятичный); Math.log(512)/Math.LN2 // => : Логарифм 512 по по основанию 2; Math.exp(3) // => : Math.E в кубе.
Полный перечень всех математических функций, поддерживаемых языком
JavaScript, можно найти в справочном разделе с описанием объекта Math
.
Арифметические операции в JavaScript не возбуждают ошибку в случае
переполнения, потери значащих разрядов или деления на ноль. Если результат
арифметической операции окажется больше самого большого представимого
значения (переполнение), возвращается специальное значение «бесконечность»,
которое в JavaScript обозначается как Infinity
.
Аналогично, если абсолютное значение отрицательного результата окажется больше
самого большого представимого значения, возвращается значение «отрицательная
бесконечность», которое обозначается как –Infinity
.
Эти специальные значения, обозначающие бесконечность, ведут себя именно так,
как и следовало ожидать: сложение, вычитание,
умножение или деление бесконечности на любое значение дают в результате
бесконечность (возможно, с обратным знаком).
Потеря значащих разрядов происходит, когда результат арифметической
операции оказывается ближе к нулю, чем минимально возможное значение. В этом
случае возвращается число 0
. Если потеря значащих разрядов происходит в
отрицательном результате, возвращается специальное значение, известное как
«отрицательный ноль». Это специальное значение практически ничем не отличается
от обычного нуля, и у программистов на JavaScript редко возникает
необходимость выделять его.
1/0 // => : «бесконечность», -1/0 // => : «отрицательная бесконечность».
Однако есть одно
исключение: операция деления нуля на ноль не имеет четко определенного
значения, поэтому в качестве результата такой операции возвращается специальное
значение «не число» (not-a-number), которое обозначается как NaN
. Значение NaN
возвращается также при попытке разделить бесконечность на бесконечность,
извлечь квадратный корень из отрицательного числа или выполнить
арифметическую операцию с нечисловыми операндами, которые не могут быть
преобразованы в числа:
0/0 // => : «not-a-number», (1/0)/(2/0) // => : «not-a-number», Math.sqrt(-3) // => : Корень квадратный из -3 «не число» 'раз'/'два' // => : «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 // => , то же значение, // доступное только для чтения. 1/0 // => , то же самое значение. Number.MAX_VALUE // Это выражение возвращает // Number.MAX_VALUE + 1 // Это выражение также возвращает // Number.NEGATIVE_INFINITY // => , возвращает отрицательную // бесконечность. -Infinity // => , -1/0 // => , также возвращает отрицательную // бесконечность.; -Number.MAX_VALUE // => -Number.MAX_VALUE - 1 // => , то же самое // значение. NaN // Переменная, доступная для чтения/записи, // инициализированная значением NaN. Number.NaN // Свойство, доступное только для чтения, // с тем же значением. 0/0 // Возвращает NaN. Number.MIN_VALUE // => , наименьшее положительное // значение типа Number. Number.MIN_VALUE/2 // => , Потеря значащих разрядов: возвращает 0 -Number.MIN_VALUE/2 === 0 // => , -Number.MIN_VALUE/2: // отрицательный ноль -1/Infinity === 0 // => , -1/Infinity: также отрицательный ноль -0 === 0 // =>
Значение «не число» в JavaScript обладает одной необычной особенностью: операция проверки на равенство всегда возвращает отрицательный результат, даже если сравнить его с самим собой:
Это означает, что нельзя использовать проверку
х == NaN
, чтобы определить,
является значение переменной x
значением NaN
.
Чтобы определить, является ли значение переменной x
значением NaN
, следует выполнять проверку
х != х: ('раз'/'два') != ('раз'/'два') // =>Эта проверка вернет
true
тогда
и только тогда, когда x
имеет значение NaN
. Аналогичную проверку можно
выполнить с помощью функции isNaN()
. Она возвращает true
, если аргумент имеет
значение NaN
или если аргумент является нечисловым значением, таким как строка
или объект. Родственная функция isFinite()
возвращает true
, если аргумент
является числом, отличным от NaN
, Infinity
или -Infinity
.
isNaN('раз'/'два') // => isFinite('раз'/'два') // =>Отрицательный ноль также имеет свои характерные особенности. В операциях сравнения (даже в операции строгой проверки на равенство) он признается равным положительному нулю, что делает эти два значения практически неотличимыми, за исключением случаев, когда они выступают в роли делителей:
var zero = 0; // => . Обычный ноль var negz = -0; // => . Отрицательный ноль zero === negz // => : ноль и отрицательный ноль равны 1/zero === 1/negz // => : Infinity и -Infinity не равны
Вещественных чисел существует бесконечно много, но формат представления
вещественных чисел в 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 y = .2 - .1; // двадцать копеек минус 10 копеек x == y // => : получились два разных значения! x == .1 // => : .3 - .2 не равно .1 y == .1 // => : .2 - .1 равно .1Из-за ошибок округления разность между аппроксимациями чисел
.3
и .2
оказалась не равной разности между аппроксимациями чисел .2
и .1
.
Эта проблема не является чем-то характерным для JavaScript: она
проявляется во всех языках программирования, где используется двоичное представление
вещественных чисел. Кроме того, обратите внимание, что значения x
и y
в
примере выше очень близки друг к другу и к истинному значению. Точность округления
вполне приемлема для большинства применений: проблема возникает лишь при
попытках проверить значения на равенство. В будущих версиях JavaScript может появиться поддержка десятичных чисел, лишенная описанных недостатков, связанных с округлением. Но до тех пор для важных финансовых расчетов предпочтительнее будет использовать масштабируемые целые числа. Например, финансовые расчеты можно производить в копейках, а не в долях рублей.
Помимо этих простых арифметических операторов JavaScript поддерживает
более сложные математические операции с помощью функций и констант,
доступных в виде свойств объекта Math
:
В базовом языке JavaScript имеется конструктор Date()
для создания объектов,
представляющих дату и время. Эти объекты Date
обладают методами для
выполнения простых вычислений с участием дат. Объект Date
не является
фундаментальным типом данных, таким как числа.
var then = new Date(2010, 0, 1); // Первый день первого месяца 2010 года // => var later = new Date(2010, 0, 1, 17, 10, 30); // Та же дата, в 17:10:30 локального времени // => var now = new Date(); // Текущие дата и время // => now.toUTCString() // => // (текущие дата и время по UTC) var elapsed = now - then; // => // -разность дат: интервал в миллисекундах. later.getFullYear() // => later.getMonth() // => : счет месяцев начинается с нуля later.getDate() // => : счет дней начинается с единицы later.getDay() // => : день недели. 0 - воскр., 5 - пятн. later.getHours() // => : часов локального времени later.getUTCHours() // => // -часы по UTC; зависит от часового пояса later.toString() // => later.toUTCString() // => later.toLocaleDateString() // => later.toLocaleTimeString() // => later.toISOString() // =>
Строка – это неизменяемая, упорядоченная последовательность 16-битных значений, каждое из которых обычно представляет символ Юникода. Строки в JavaScript являются типом данных, используемым для представления текста. Длина строки – это количество 16-битных значений, содержащихся в ней. Нумерация символов в строках (и элементов в массивах) в языке JavaScript начинается с нуля: первое 16-битное значение находится в позиции 0, второе – в позиции 1 и т. д. Пустая строка – это строка, длина которой равна 0. В языке JavaScript нет специального типа для представления единственного элемента строки. Для представления единственного 16-битного значения просто используется строка с длиной, равной 1.
Для представления символов Юникода в языке 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 // => : p содержит единственный 16-битный элемент e.length // => : в кодировке UTF-16 символ е определяется двумя // 16-битными значениями: "\ud835\udc52"Различные строковые методы, имеющиеся в языке JavaScript, манипулируют 16-битными значениями, а не символами. Они не предусматривают возможность специальной интерпретации суррогатных пар, не выполняют нормализацию строк и даже не проверяют, является ли строка последовательностью символов в кодировке UTF-16.
Чтобы включить литерал строки в JavaScript-программу, достаточно просто
заключить символы строки в парные одинарные или двойные кавычки ('
или "
).
Символы двойных кавычек могут содержаться в строках, ограниченных
символами одинарных кавычек, а символы одинарных кавычек – в строках,
ограниченных символами двойных кавычек. Ниже приводятся несколько примеров
строковых литералов:
"" // Это пустая строка: в ней ноль символов 'testing' "3.14" 'name="myform"' "Вы предпочитаете книги издательства O'Reilly, не правда ли?" "В этом строковом литерале\nдве строки" "π - это отношение длины окружности к ее диаметру"
В ECMAScript 3 строковые литералы должны записываться в одной строке
программы и не могут разбиваться на две строки. Однако в ECMAScript 5 строковые
литералы можно разбивать на несколько строк, заканчивая каждую строку,
кроме последней, символом обратного слэша (\
). Ни один из символов обратного
слэша, как и следующие за ними символы перевода строки, не будут включены
в строковый литерал. Чтобы включить в строковый литерал символ перевода
строки, следует использовать последовательность символов \n
(описывается ниже):
"две\nстроки" // Строковый литерал, представляющий две строки: "одна\ длинная\ строка"; // Одна строка, записанная в трех строках. // Только в ECMAScript 5:Обратите внимание, что ограничивая строку одинарными кавычками, необходимо проявлять осторожность в обращении с апострофами, употребляемыми в английском языке для обозначения притяжательного падежа и в сокращениях, как, например, в словах «can't» и «O'Reilly's». Поскольку апостроф и одиночная кавычка – это одно и то же, необходимо при помощи символа обратного слэша (
\
)
«экранировать» апострофы, расположенные внутри одиночных кавычек
(подробнее об этом – в следующем разделе). Программы на клиентском JavaScript часто содержат строки HTML-кода, а HTML-код, в свою очередь, часто содержит строки JavaScript-кода. Как и в JavaScript, в языке HTML для ограничения строк применяются либо одинарные, либо двойные кавычки. Поэтому при объединении JavaScript- и HTML-кода есть смысл придерживаться одного «стиля» кавычек для JavaScript, а другого – для HTML. В следующем примере строка «Спасибо» в JavaScript-выражении заключена в одинарные кавычки, а само выражение, в свою очередь, заключено в двойные кавычки как значение HTML-атрибута обработчика событий:
<button onclick="alert( 'Спасибо' )">Щелкни на мне</button>
Символ обратного слэша (\
) имеет специальное назначение в JavaScript-строках.
Вместе с символами, следующими за ним, он обозначает символ, не представимый
внутри строки другими способами. Например, \n
– это управляющая последовательность
(escape sequence), обозначающая символ перевода строки.
Другой пример, упомянутый выше, – это последовательность \'
, обозначающая
символ одинарной кавычки. Эта управляющая последовательность необходима
для включения символа одинарной кавычки в строковый литерал, заключенный
в одинарные кавычки. Теперь становится понятно, почему мы называем эти
последовательности управляющими – здесь символ обратного слэша позволяет
управлять интерпретацией символа одинарной кавычки. Вместо того чтобы отмечать
ею конец строки, мы используем ее как апостроф, например, строка:
"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
позволяет добавлять в многострочные строковые литералы символ обратного слэша
перед разрывом строки.
Одной из встроенных возможностей JavaScript является способность
конкатенировать строки. Если оператор +
применяется к числам, они складываются, а если
к строкам – они объединяются, при этом вторая строка добавляется в конец
первой. Например:
msg = "Hello, " + "world"; // Получается строка name = greeting = "Добро пожаловать на мою домашнюю страницу," + " " + name; // Получается строка
Для определения длины строки – количества содержащихся в ней 16-битных
значений – используется свойство строки length
. Например, длину строки s
можно получить следующим образом:
s.length
Кроме того, в дополнение к свойству length
строки имеют множество методов
(как обычно, более полную информацию ищите в справочном разделе):
var msg = // Начнем с того же текста. msg.charAt(0) // => : первый символ. msg.charAt(msg.length-1) // => : последний символ. msg.substring(1,4) // => : 2-й, 3-й и 4-й символы. msg.slice(1,4) // => : то же самое msg.slice(-3) // => : последние 3 символа msg.indexOf("l") // => : позиция первого символа l. msg.lastIndexOf("l") // => : позиция последнего символа l. msg.indexOf("l",3) // => : позиция первого символа "l", следующего // за 3 символом в строке msg.split(", ") // => => [Hello,world] разбивает на подстроки msg.replace("H", "h") // => : замещает все вхождения подстроки msg.toUpperCase() // =>
Не забывайте, что строки в JavaScript являются неизменяемыми. Такие методы,
как replace()
и toUpperCase()
, возвращают новые строки: они не изменяют строку,
относительно которой были вызваны.
В стандарте ECMAScript 5 строки могут интерпретироваться как массивы,
доступные только для чтения, и вместо использования метода charAt()
к отдельным
символам (16-битным значениям) строки можно обращаться с помощью индексов
в квадратных скобках:
msg[0] // => "h"
Веб-браузеры, основанные на движке Mozilla, такие как Firefox, уже давно предоставляют такую возможность. Большинство современных броузеров (заметным исключением из которых является IE) последовали за Mozilla еще до того, как эта особенность была утверждена в стандарте ECMAScript 5.
В языке 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) // => : имеется совпадение text.search(pattern) // => : позиция первого совпадения text.match(pattern) // => <==> ["1","2","3"] // - массив всех совпадений text.replace(pattern, "#") // => text.split(/\D+/) // => <==> ["","1","2","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
добавляется 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) ...
Оператор «не равно» !==
сравнит переменную o
со значением null
и вернет в
результате true
или false
. Однако вы можете опустить оператор сравнения и
положиться на тот факт, что null
является ложным значением, а объект – истинным:
if (o) ...
В первом случае тело инструкции if
будет выполнено, только если значение
переменной o
не равно null
. Во втором – ставится менее жесткое условие: тело
инструкции if
будет выполнено, только если o
не содержит false
или другое ложное
значение (такое как null
или undefined
). Примеры:
Какая инструкция if
больше подходит
для вашей программы, зависит от того, какие значения могут присваиваться
переменной o
. Если в программе необходимо отличать значение null
от 0 и "", то
следует использовать явную операцию сравнения.
Логические значения имеют метод toString()
, который можно использовать для
преобразования этих значений в строки «true»
или «false»
, но они не имеют
других полезных методов.
Несмотря на простоту прикладного интерфейса, в языке имеется три важных логических оператора.
Оператор &&
выполняет логическую операцию И. Он возвращает истинное
значение, только если оба операнда истинны – в противном случае он возвращает
ложное значение. Оператор ||
выполняет логическую операцию ИЛИ: он возвращает
истинное значение, если хотя бы один (или оба) из операндов является истинным,
и ложное значение – если оба операнда являются ложными. Наконец, унарный
оператор !
выполняет логическую операцию НЕ: он возвращает значение true
для
ложного операнда и false
– для истинного. Например:
if ((х == 0 && у == 0) || !(z == 0)) {
// х и у содержат значение 0 или z не равна нулю
}
Полное описание этих операторов приводится в разделе 4.10.
Ключевое слово 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
.
Выше описывались простые типы данных и значения языка
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.
Объекты в языке JavaScript являются составными значениями: они
представляют собой коллекции свойств, или именованных значений. Обращение к
свойствам мы будем выполнять с использованием точечной нотации. Свойства,
значениями которых являются функции, мы будем называть методами. Чтобы
вызвать метод m
объекта o
, следует использовать инструкцию o.m()
.
Мы уже видели, что строки обладают свойствами и методами:
// Использование свойств строки: var s = "hello world!"; // Строка var word = s.substring(s.indexOf(" ")+1, s.length); // =>
Однако строки не являются объектами, так почему же они обладают свойствами?
Всякий раз, когда в программе предпринимается попытка обратиться к свойству
строки s
, интерпретатор JavaScript преобразует строковое значение в объект, как
если бы был выполнен вызов new String(s)
. Этот объект наследует (раздел 6.2.2)
строковые методы и используется интерпретатором для доступа к свойствам.
После обращения к свойству вновь созданный объект уничтожается. (От
реализаций не требуется фактически создавать и уничтожать этот промежуточный
объект, но они должны вести себя так, как если бы объект действительно создавался
и уничтожался.)
Наличие методов у числовых и логических значений объясняется теми же
причинами: при обращении к какому-либо методу создается временный объект
вызовом конструктора Number()
или Boolean()
, после чего производится вызов метода
этого объекта. Значения null
и undefined
не имеют объектов-оберток: любые
попытки обратиться к свойствам этих значений будет вызывать ошибку ТуреError
.
Рассмотрим следующий фрагмент и подумаем, что происходит при его выполнении:
var s = "test"; // Начальное строковое значение. s.len = 4; // Установить его свойство len. var t = s.len; // Теперь запросить значение этого свойства (=> ).В начале этого фрагмента переменная
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
также
обнаруживает отличия между простыми значениями и их объектами-обертками.
Между простыми значениями (undefined, null
, логическими значениями, числами
и строками) и объектами (включая массивы и функции) в языке JavaScript
имеются фундаментальные отличия. Простые значения являются неизменяемыми:
простое значение невозможно изменить (или «трансформировать»). Это очевидно
для чисел и логических значений – нет никакого смысла изменять значение
числа. Однако для строк это менее очевидно. Поскольку строки являются массивами
символов, вполне естественно было бы ожидать наличие возможности изменять
символы в той или иной позиции в строке. В действительности JavaScript не
позволяет сделать это, и все строковые методы, которые, на первый взгляд,
возвращают измененную строку, на самом деле возвращают новое строковое значение.
Например:
var s = "hello"; // Изначально имеется некоторый текст из строчных символов s.toUpperCase(); // Вернет "", но значение s при этом не изменится. s // => "": оригинальная строка не изменилась.
Кроме того, величины простых типов сравниваются по значению: две величины
считаются одинаковыми, если они имеют одно и то же значение. Для чисел,
логических значений, null
и undefined
это выглядит очевидным: нет никакого другого
способа сравнить их. Однако для строк это утверждение не выглядит таким
очевидным. При сравнении двух строковых значений JavaScript считает их
одинаковыми тогда и только тогда, когда они имеют одинаковую длину и содержат
одинаковые символы в соответствующих позициях.
Объекты отличаются от простых типов. Во-первых, они являются изменяемыми – их значения можно изменять:
var o = { х:1 }; // Начальное значение объекта o.х = 2; // Изменить объект, изменив значение свойства o.у = 3; // Изменить объект, добавив новое свойство var a = [1,2,3]; // Массивы также являются изменяемыми объектами a[0] = 0; // Изменить значение элемента массив a[3] = 4; // Добавить новый элемент o = , a =
Объекты не сравниваются по значению: два объекта не считаются равными, даже если они будут иметь одинаковые наборы свойств с одинаковыми значениями. И два массива не считаются равными, даже если они имеют один и тот же набор элементов, следующих в том же порядке:
var o={x:1}, p = {х:1}; // Два объекта с одинаковыми свойствами o === p // => : разные объекты не являются равными var a = [], b = []; // Два различных пустых массива. a === b // => : различные массивы не являются равными.
Чтобы подчеркнуть отличие от простых типов JavaScript, объекты иногда называют ссылочными типами. Если следовать этой терминологии, значениями объектов являются ссылки, и можно сказать, что объекты сравниваются по ссылке: значения двух объектов считаются равными тогда и только тогда, когда они ссылаются на один и тот же объект в памяти.
var a = []; // Переменная a ссылается на пустой массив. var b = a; // Теперь b ссылается на тот же массив. b[0] = 1; // Изменение массива с помощью ссылки в переменной b. a[0] // => : изменение можно наблюдать в переменной a. a === b // => : 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[] } 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) // =>
JavaScript может гибко преобразовывать один тип в другой. Мы уже могли
убедиться в этом на примере логических значений: везде, где интерпретатор
JavaScript ожидает получить логическое значение, можно указать значение любого
типа, и JavaScript автоматически выполнит необходимое преобразование. Одни
значения («истинные» значения) преобразуются в значение true
, а другие
(«ложные») – в false
. То же относится и к другим типам: если интерпретатор ожидает
получить строку, он автоматически преобразует любое другое значение в строку,
а если ожидает число, попробует преобразовать
имеющееся значение в число (в случае невозможности такого преобразования будет
получено значение NaN
). Например:
10 + " objects" // => ““. Число 10 преобразуется в строку "7" * "4" // => : обе строки преобразуются в числа var n = 1 - "x"; // => : строка "x" не может преобразоваться в число var x = 1; // => : число, var n = 1 - x; // => var n = 1 - "x"; // => : строка "x" не может преобразоваться в число n + " objects" // => : NaN преобразуется в строку "NaN"В табл. 3.2 описывается, как в JavaScript выполняется преобразование значений из одного типа в другой. Жирным шрифтом в таблице выделены значения, соответствующие преобразованиям, которые могут преподносить сюрпризы. Пустые ячейки соответствуют ситуациям, когда преобразование не требуется и не выполняется.
Преобразования одного простого типа в другой, показанные в табл. 3.2,
выполняются относительно просто. Преобразование в логический тип уже обсуждалось
в разделе 3.3.
Преобразование всех простых типов в строку четко определено.
Преобразование в число выполняется немного сложнее. Строки, которые могут быть
преобразованы в числа, преобразуются в числа. В строке допускается наличие
пробельных символов в начале и в конце, но присутствие других непробельных
символов, которые не могут быть частью числа, при преобразовании строки в
число приводят к возврату значения NaN
). Некоторые особенности преобразования
значений в числа могут показаться странными: значение true
преобразуется
в число 1
, а значение false
и пустая строка "" преобразуются в 0
.
Преобразование простых типов в объекты также выполняется достаточно просто:
значения простых типов преобразуются в соответствующие объекты-обертки
как если бы вызывался конструктор String(), Number()
или Boolean()
.
Исключение составляют значения null
и undefined
: любая попытка использовать
их в контексте, где требуется объект, вместо преобразования будет приводить
к возбуждению исключения TypeError
.
Преобразование объектов в простые типы выполняется значительно сложнее и является темой обсуждения раздела 3.8.3.
Значение Преобразование в:
Строку Число Логическое значение Объект
undefined "undefined" NaN false возбуждается ошибка TypeError
null "null" 0 false возбуждается ошибка TypeError
true "true" 1 new Boolean(true)
false "false" 0 new Boolean(false)
"" (пустая строка) 0 false new StringC")
"1.2" (непустая строка, число) 1.2 true 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
[] (пустой массив) "" 0 true
[9] (1 числовой элемент) "9" 9 true
['а'] (любой другой массив) используется метод join() NaN true
function(){} (любая функция) см. разд. 3.8.3 NaN true
Благодаря гибкости преобразований типов в JavaScript оператор равенства ==
также гибко определяет равенство значений. Например, все следующие
сравнения возвращают true
:
null == undefined // => -Эти два значения считаются равными "0" == 0 // => -Перед сравнением строка преобразуется в число. 0 == false // => -Перед сравнением логическое значение // преобразуется в число. "0" == false // => -Перед сравнением оба операнда преобразуются // в числа.
В разделе 4.9.1
четко описывается, какие преобразования выполняет оператор ==
,
чтобы определить, являются ли два значения равными, и в этом же разделе
описывается оператор идентичности ===
, который не выполняет никаких
преобразований перед сравнением.
Имейте в виду, что возможность преобразования одного значения в другое не
означает равенства этих двух значений. Если, например, в логическом контексте
используется значение undefined
, оно будет преобразовано в значение false
. Но это
не означает, что undefined == false
. Операторы и инструкции JavaScript ожидают
получить значения определенных типов и выполняют преобразования в эти
типы. Инструкция if
преобразует значение undefined
в false
, но оператор ==
никогда
не пытается преобразовать свои операнды в логические значения.
Несмотря на то что многие преобразования типов JavaScript выполняет автоматически, иногда может оказаться необходимым выполнить преобразование явно или окажется предпочтительным выполнить явное преобразование, чтобы обеспечить ясность программного кода.
Простейший способ выполнить преобразование типа явно заключается в
использовании функций Boolean(), Number(), String()
и Object()
. Мы уже видели, как эти
функции используются в роли конструкторов объектов-оберток (раздел 3.6). При
вызове без оператора new они действуют как функции преобразования и
выполняют преобразования, перечисленные в табл. 3.2:
Number("3") // => String(false) // => "", или можно использовать false.toString() // => "" Boolean([]) // => Object(3) // => new Number()
Обратите внимание, что все значения, кроме null
или undefined
, имеют метод
toString()
, результатом которого обычно является то же значение, которое
возвращается функцией String()
. Кроме того, обратите внимание, что в табл. 3.2
отмечается, что при попытке преобразовать значение null
или undefined
в объект
возбуждается ошибка TypeError
. Функция Object()
в этом случае не возбуждает
исключение, вместо этого она просто возвращает новый пустой объект.
Определенные операторы в языке JavaScript неявно выполняют преобразования
и иногда могут использоваться для преобразования типов. Если один из
операндов оператора +
является строкой, то другой операнд также преобразуется в
строку. Унарный оператор +
преобразует свой операнд в число. А унарный оператор !
преобразует операнд в логическое значение и инвертирует его. Все это стало
причиной появления следующих своеобразных способов преобразования типов,
которые можно встретить на практике:
x + "" // То же, что и String(x), x + "" == String(x) => +x // То же, что и Number(x). Можно также встретить x - 0 !!x // То же, что и Boolean(x). Обратите внимание на два знака ! x = "_"; !!x // => , x = 0; !!x // =>Форматирование и парсинг чисел являются наиболее типичными задачами, решаемыми компьютерными программами, и потому в JavaScript имеются специализированные функции и методы, обеспечивающие более полный контроль над преобразованиями чисел в строки и строк в числа.
Метод toString()
класса Number
принимает необязательный аргумент,
определяющий основание системы счисления для преобразования. Если этот аргумент не
определен, преобразование выполняется в десятичной системе счисления. Но вы
можете производить преобразование в любой системе счисления (с основанием от
2 до 36). Например:
var n = 17; binary_string = n.toString(2) // Вернет "" octal_string = "0" + n.toString(8) // Вернет "" hex_string = "0x" + n.toString(16) // Вернет ""
При выполнении финансовых или научных расчетов может потребоваться
обеспечить преобразование чисел в строки с точностью до определенного числа
десятичных знаков или до определенного количества значащих разрядов или получать
представление чисел в экспоненциальной форме. Для подобных преобразований
чисел в строки класс Number
определяет три метода. Метод toFixed()
преобразует
число в строку, позволяя указывать количество десятичных цифр после запятой.
Он никогда не возвращает строки с экспоненциальным представлением чисел.
Метод toExponential()
преобразует число в строку в экспоненциальном
представлении, когда перед запятой находится единственный знак, а после запятой следует
указанное количество цифр (т. е. количество значащих цифр в строке получается
на одну больше, чем было указано при вызове метода). Метод toPrecision()
преобразует число в строку, учитывая количество заданных значащих разрядов. Если
заданное количество значащих разрядов оказывается недостаточным для
отображения всей целой части числа, преобразование выполняется в экспоненциальной
форме. Обратите внимание, что все три метода округляют последние цифры или
добавляют нули, если это необходимо. Взгляните на следующие примеры:
n = 123456.789; n.toFixed(0); n.toFixed(2); n.toFixed(5); n.toExponential(1); n.toExponential(3); n.toPrecision(4); n.toPrecision(7); n.toPrecision(10);
Если передать строку функции преобразования 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") // => parseFloat(" 3.14 meters") // => parseInt("-12.34") // => parseInt("0xFF") // => parseInt("0xff") // => parseInt("-0XFF") // => parseFloat(".1") // => parseInt("0.1") // => parseInt(".1") // => : целые числа не могут начинаться с "." parseInt("a.1") // => parseFloat("$72.47"); // => : числа не могут начинаться с "$"
Функция parseInt()
принимает второй необязательный аргумент, определяющий
основание системы счисления для разбираемого числа. Допустимыми являются
значения от 2 до 36. Например:
parseInt("11", 2); // => parseInt("ff", 16); // => parseInt("zz", 36); // => parseInt("077", 8); // => parseInt("077", 10); // =>
Преобразование объектов в логические значения выполняется очень просто: все
объекты (включая массивы и функции) преобразуются в значение true
. Это
справедливо и для объектов-оберток: результатом вызова new Boolean
(false
) является
объект, а не простое значение, поэтому он также преобразуется в значение true
.
Преобразование объекта в строку и преобразование объекта в число выполняется вызовом соответствующего метода объекта. Все осложняется тем, что объекты в языке JavaScript имеют два разных метода для выполнения преобразований, а также наличием нескольких специальных случаев, описываемых ниже. Обратите внимание, что правила преобразования объектов в строки и числа, описываемые здесь, применяются только к нативным¹ объектам. Объекты среды выполнения (например, определяемые веб-браузерами) могут предусматривать собственные алгоритмы преобразования в числа и строки.
Все объекты наследуют два метода преобразования. Первый,
toString()
, возвращает строковое представление объекта. По умолчанию метод
toString()
не возвращает ничего особенно интересного (хотя эта информация
иногда может оказаться полезной, как будет показано в примере 6.4):
({x:1, y:2}).toString() // => ""
Многие классы определяют более специализированные версии метода toString()
.
Например, метод toString()
класса Array
преобразует все элементы массива в
строки и объединяет результаты в одну строку, вставляя запятые между ними. Метод
toString()
класса Function
возвращает строковое представление функции,
зависящее от реализации. На практике обычно реализации преобразуют
пользовательские функции в строки с исходным программным кодом на языке JavaScript.
Класс Date
определяет метод toString()
, возвращающий строку с датой и временем
в удобочитаемом формате (который может быть разобран средствами JavaScript).
Класс RegExp
определяет метод toString()
, преобразующий объект RegExp
в строку,
которая выглядит как литерал регулярного выражения:
[1,2,3].toString() // => "" (function(x) {f(x);}).toString() // => "" /\d+/g.toString() // => "" new Date(2010,0,1).toString() // => ""
Другая функция преобразования объектов называется valueOf()
. Задача этого
метода определена не так четко: предполагается, что он должен преобразовать
объект в представляющее его простое значение, если такое значение существует.
Объекты по своей природе являются составными значениями, и большинство
объектов не могут быть представлены в виде единственного простого значения, поэтому
по умолчанию метод valueOf()
возвращает не простое значение, а сам объект.
Классы-обертки определяют методы valueOf()
, возвращающие обернутые простые
значения. Массивы, функции и регулярные выражения наследуют метод по
умолчанию. Вызов метода valueOf()
экземпляров этих типов возвращает сам объект.
Класс Date
определяет метод valueOf()
, возвращающий дату во внутреннем
представлении: количество миллисекунд, прошедших с 1 января 1970 года:
var d = new Date(2010,0,1); d // => " d.valueOf() // => "" var d0 = new Date(1970,0,1); d0; // => " d0.valueOf(); // => "" d0.valueOf() + 10800000; // => "" UTC var dd = d - d0; // => ""
Теперь, разобравшись с методами 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) // => "" // typeof (now +1) // => "": + преобразует дату в строку // typeof (now - 1) // => "": - выполнит преобразование // объекта в число now == now.toString() // => "": неявное и явное преобразование в строку now > (now -1) // => "": > преобразует объект Date в число
Прежде чем использовать переменную в 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";
С помощью инструкции var
можно объявить одну и ту же переменную несколько
раз. Если повторное объявление содержит инициализатор, то оно действует как
обычная инструкция присваивания.
Если попытаться прочитать значение необъявленной переменной, JavaScript
сгенерирует ошибку. В строгом режиме, предусмотренном стандартом ECMAScript 5,
(раздел 5.7.3),
ошибка также возбуждается при попытке присвоить значение
необъявленной переменной. Однако исторически и при выполнении не в строгом
режиме если присвоить значение переменной, не объявленной с помощью
инструкции var
, то JavaScript создаст эту переменную как свойство глобального объекта,
и она будет действовать практически так же (но с некоторыми отличиями,
описываемыми в разделе 3.10.2), как корректно
объявленная переменная. Это означает, что глобальные переменные можно не объявлять.
Однако это считается дурной привычкой и может явиться источником ошибок, поэтому всегда
старайтесь объявлять свои переменные с помощью var
.
Область видимости (scope) переменной – это та часть программы, для которой эта переменная определена. Глобальная переменная имеет глобальную область видимости – она определена для всей JavaScript-программы. В то же время переменные, объявленные внутри функции, определены только в ее теле. Они называются локальными и имеют локальную область видимости. Параметры функций также считаются локальными переменными, определенными только в теле этой функции.
Внутри тела функции локальная переменная имеет преимущество перед глобальной переменной с тем же именем. Если объявить локальную переменную или параметр функции с тем же именем, что у глобальной переменной, то фактически глобальная переменная будет скрыта:
var scope = "global"; // Объявление глобальной переменной function checkscope() { var scope = "local"; // Объявление локальной переменной с тем же именем return scope; // Вернет локальное значение, а не глобальное: } checkscope() // => ""
Объявляя переменные с глобальной областью видимости, инструкцию var
можно
опустить, но при объявлении локальных переменных всегда следует
использовать инструкцию var
. Посмотрите, что получается, если этого не сделать:
scope = "global"; // Объявление глобальной переменной, даже без var. function checkscope2() { scope = "local"; // Ой! Мы изменили глобальную переменную. myscope = "local"; // Неявно объявляется новая глоб. переменная. return [scope, myscope]; // Вернуть два значения: } checkscope2() // => : имеется побочный эффект! scope // => "": глобальная переменная изменилась, myscope // => "": нарушен порядок в глобальном пространстве имен.
Определения функций могут быть вложенными. Каждая функция имеет собственную локальную область видимости, поэтому может быть несколько вложенных уровней локальных областей видимости. Например:
var scope = "global scope"; // Глобальная переменная function checkscope() { var scope = "local scope"; // Локальная переменная function nested() { var scope = "nested scope"; // Вложенная область видимости // локальных переменных return scope; // Вернет значение этой переменной scope } return nested(); } checkscope() // => ""
В некоторых 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 не поддерживаются области видимости блоков, некоторые программисты стремятся объявлять все переменные в начале функции, а не рядом с местом, где они используются. Такой подход позволяет более точно отражать истинную область видимости переменных в программном коде.
При объявлении глобальной переменной в JavaScript в действительности
создается свойство глобального объекта (раздел 3.5). Если глобальная переменная
объявляется с помощью инструкции var
, создается ненастраиваемое свойство,
т. е. свойство, которое невозможно удалить с помощью оператора delete
.
Как уже отмечалось выше, если не используется строгий режим и необъявленной
переменной присваивается некоторое значение, интерпретатор JavaScript
автоматически создает глобальную переменную. Переменные, созданные таким
способом, становятся обычными, настраиваемыми свойствами глобального объекта,
и могут быть удалены:
var truevar = 1; // Правильно объявленная глобальная переменная, // неудаляемая. fakevar =2; // Создается удаляемое свойство глобального объекта. this.fakevar2 = 3; // То же самое. delete truevar // => : переменная не была удалена delete fakevar // => : переменная удалена delete this.fakevar2 // => : переменная удалена
Глобальные переменные в языке JavaScript являются свойствами глобального объекта, и такое положение вещей закреплено в спецификации ECMAScript. Это не относится к локальным переменным, однако локальные переменные можно представить как свойства объекта, ассоциированного с каждым вызовом функции. В спецификации ECMAScript 3 этот объект называется «объектом вызова» (call object), а в спецификации ECMAScript 5 он называется «записью с описанием окружения» (declarative environment record). Интерпретатор JavaScript позволяет ссылаться на глобальный объект с помощью ключевого слова this, но он не дает никакой возможности сослаться на объект, в котором хранятся локальные переменные. Истинная природа объектов, в которых хранятся локальные переменные, зависит от конкретной реализации и не должна заботить нас. Однако сам факт наличия объектов с локальными переменными имеет большое значение, и эта тема будет рассматриваться в следующем разделе.
JavaScript – это язык программирования с лексической областью видимости: область видимости переменной распространяется на строки с исходным программным кодом, для которых определена переменная. Глобальные переменные определены для всей программы в целом. Локальные переменные определены для всей функции, в которой они объявлены, а также для любых функций, вложенных в эту функцию.
Если считать локальные переменные свойствами некоторого объекта, зависящего
от реализации, то появляется возможность взглянуть на области видимости
переменных с другой стороны. Каждый фрагмент программного кода на JavaScript
(глобальный программный код или тело функции) имеет цепочку областей видимости,
ассоциированную с ним. Эта цепочка областей видимости представляет
собой список, или цепочку объектов, определяющих переменные, которые
находятся «в области видимости» данного фрагмента программного кода. Когда
интерпретатору требуется отыскать значение переменной x
(этот процесс
называется разрешением переменной), он начинает поиск с первого объекта в цепочке.
Если этот объект имеет свойство с именем x
, используется значение этого свойства.
Если первый объект не имеет свойства с именем x
, интерпретатор JavaScript
продолжает поиск в следующем объекте в цепочке. Если второй объект не имеет
свойства с именем x
, интерпретатор переходит к следующему объекту и т. д. Если
ни один из объектов в цепочке областей видимости не имеет свойства с именем x
,
то интерпретатор считает, что переменная x
находится вне области видимости
данного программного кода и возбуждает ошибку ReferenceError
.
Для программного кода верхнего уровня (т. е. для программного кода за пределами каких-либо функций) цепочка областей видимости состоит из единственного, глобального объекта. Для невложенных функций цепочка областей видимости состоит из двух объектов. Первым является объект, определяющий параметры и локальные переменные функции, а вторым – глобальный объект. Для вложенных функций цепочка областей видимости может содержать три и более объектов. Важно понимать, как создаются цепочки этих объектов. Определение функции фактически сохраняет ее область видимости в цепочке. Когда эта функция вызывается, интерпретатор создает новый объект, хранящий локальные переменные, и добавляет его к имеющейся цепочке, образуя новую, более длинную цепочку, представляющую область видимости вызываемой функции. Ситуация становится еще более интересной для вложенных функций, потому что каждый раз, когда вызывается внешняя функция, внутренняя функция объявляется заново. Поскольку для каждого вызова внешней функции создается новая цепочка, вложенные функции будут немного отличаться при каждом определении – при каждом вызове внешней функции программный код вложенной функции будет одним и тем же, но цепочка областей видимости, ассоциированная с этим программным кодом, будет отличаться.
Такой взгляд на цепочку областей видимости будет полезен для понимания
инструкции with
(раздел 5.7.1)
и чрезвычайно важен для понимания замыканий (раздел 8.6 ).