Книга JavaScript Подробное руководство. Автор: Дэвид Флэнаган

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

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

При написании программ на JavaScript используется набор символов Unicode.

16-разрядная кодировка Unicode обеспечивает представление практически любого письменного языка

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

JavaScript чувствительный к регистру

Заметим, однако, что язык HTML, в отличие от JavaScript, не чувствителен к регистру. По причине близкой связи HTML и клиентского JavaScript это различие может привести к путанице.

Символы-разделители и переводы строк

JavaScript игнорирует пробелы, табуляции и переводы строк, присутствующие между лексемами в программе.

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

Точка с запятой служит для отделения инструкций друг от друга. Однако в JavaScript точку с запятой можно не ставить, если каж дая инструкция помещается в отдельной строке

Комментарии

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

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

/*

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

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

*/

Литералы

Литерал – это значение, указанное непосредственно в тексте программы

Например

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

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

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

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

Идентификаторы

Идентификатор - это просто имя

Идентификатор в JS это имя переменных, функций, а также меток некоторых циклов.

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

Идентификаторы не может совпадать с названием ключевого слова.

Например, break, else, finally, function, return и т.д

Типы данных и значения

ВОДА -- Компьютерные программы работают, манипулируя значениями (values), такими как число 3,14 или текст «Hello World». Типы значений, которые могут быть представлены и обработаны в языке программирования, известны как типы данных (data types), и одной из наиболее фундаментальных характеристик языка программирования является поддерживаемый им набор типов данных.

3 элементарных типа данных: Числа, Строки, Логические значения

2 тривиальных типа данных: null и undefined, каждый из которых определяет только одно значение.

1 составной тип данных: объект

Объект - коллекция значений.

Объекты в JS это неупорядоченная и упорядоченная коллекция (например, массив упорядоченная).

То что внизу, тоже являются объектами. - Массив - Функция - Специальные объекты или даже классы, например, Date, RegExp и Error

Объект и массив имеет один тип, но ведут себя по разному.

Функции ведут себя не так, как другие виды объектов, и в JavaScript определен специальный синтаксис для работы с ними.

Числа

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

Диапазон значений от ±1,7976931348623157 × 10^308 до ±5 × 10^-324.

Числовой литерал - число находящееся непосредственно в коде JavaScript программы

Строки

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

Преобразование чисел в строки

Для явного преобразования числа в строку используется функция String():

var string_value = String(number);

Еще один способ преобразования числа в строку заключается в вызове метода toString():

string_value = number.toString( );

Метод toFixed() преобразует число в строку и отображает определенное число знаков после десятичной точки

Метод toExponential(), который преобразует число в экспоненциальное представление с одним знаком перед точкой и с заданным числом десятичных знаков после точки.

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

var n = 123456.789; n.toFixed(0); // "123457"

n.toFixed(2); // "123456.79"

n.toExponential(1); // "1.2e+5"

n.toExponential(3); // "1.235e+5"

n.toPrecision(4); // "1.235e+5" n.toPrecision(7); // "123456.8"

Преобразование строк в числа

Можно так

var number = string_value - 0;

var number = Number(string_value); - минус подхода в том что он очень строг. Используется только для преобразования десятичных чисел и появление не числовых символов не допускается.

Методы parseInt() и parseFloat() преобразуют и возвращают числа, стоящие в начале строки. Игнорят нецифровые символы расположенные вслед за числом.

parseInt - целочисленное преобразование. Если строка начинается с "0x" переводит в шестнадцатеричное число. Второй аргумент, основание системы счисления.

parseInt("3 слепых мышки"); // Вернет 3

parseInt("0xFF"); // Вернет 255

parseFloat - как целые так и вещественные числа.

Если методы parseInt() и parseFloat() не могут выполнить преобразование, то возвращают NaN

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

TRUE

любое число кроме 0 и NaN

не пустая строка

объект, функция, массив если они не равны null

FALSE

null и undefined

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

Явное преобразование

var x_as_boolean = Boolean(x);

Неявное

var x_as_boolean = !!x;

Функции

Функция – это фрагмент исполняемого кода.

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

Это значит, что функции могут храниться в переменных, массивах и объектах, а также передаваться в качестве аргументов другим функциям.

ПОДРОБНЕЕ О ФУНКЦИЯХ БУДЕТ РАССКАЗЫВАТЬСЯ ПОЗЖЕ

Объекты

Объект – это коллекция именованных значений, которые обычно называют свойствами (properties) объекта.

Массивы

Массив (array), как и объект, представляет собой коллекцию значений. Если каждое значение, содержащееся в объекте, имеет имя, то в массиве каждое значение имеет номер, или индекс.

Значение null

null - говорит об отсутствии объекта

null - false // Если использовать в логическом контексте

null - 0 // в числовом

null - "null" // в строковом

Значение undefined

undefined возвращается при обращении к объявленной переменной без значения, либо к свойству объекта, которого не существует.

var a;

a // undefined

var b = {};

b.name // undefined

Объект Date

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

Объекты Error

При возникновении ошибки интерпретатор генерирует объект Error

Каждый объект имеет свойство message.

Типы объектов Error

Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError и URIError.

Объекты-обертки для элементарных типов данных

ВАЖНО ПОНИМАТЬ

Почему мы можем вызвать метод у строки типа "Строка". Например, так s.length

Дело в том, что для каждого из трех базовых типов данных определен соответствующий класс объектов (Number, String и Boolean). Эти классы представляют собой «обертки» для базовых типов данных.

Когда мы используем строку в объектном контексте (s.length), JavaScript создает внутри себя объект-обертку для строкового значения. Этот объект String используется вместо базового строкового значения.

То же самое, верно и для других базовых типов и соответствующих им объектов-оберток;

Строки автоматически преобразуются в объекты String, когда это требуется и наоборот.

Преобразование объектов в значения элементарных типов

Порядок преобразования объекта в числовое значение

1 - Сначала вызывается метод valueOf() (по умолчанию наследуется от Object). valueOf не возвращает значение элементарного типа.

2 - Вызывается toString() с последующим преобразованием строки в число

В случае массивом, метод toString() преобразует элементы массива в строки и возвращает результат операции конкатенации этих строк. Пустой массив преобразуется в пустую строку, что в результате дает число 0.

Есть одно исключение из правил: когда с оператором + используется объект Date, преобразование сразу начинается с вызова метода toString(). Это обусловлено тем обстоятельством, что объект Date обладает собственными реализациями методов toString() и valueOf().

По значению или по ссылке

Манипулировать данными можно тремя способами

Первый способ – это копирование данных

Второй способ – передать значение функции или методу

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

Переменные

Переменная – это имя, связанное со значением.

Типизация переменных

JavaScript - нетипизированный (untyped) язык.

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

Объявление переменных

Переменные, объявленные с помощью инструкции var, называются долговременными (permanent): попытка удалить их с помощью оператора delete приве дет к ошибке.

Повторные и опущенные объявления

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

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

Область видимости переменной

Область видимости (scope) переменной – это та часть программы, для которой эта переменная определена.

Внутри тела функции локальная переменная имеет преимущество перед гло бальной переменной с тем же именем.

Определения функций могут быть вложенными. Каждая функция имеет собст венную локальную область видимости.

Неопределенные и неинициализированные переменные

В JavaScript имеется два вида неопределенных переменных.

1 - й – неопределенная пе ременная, которая нигде не объявлена. Доступ у ней приведет к ошибке

2 - й - неинициализированная переменная, которая была объявлена, но значение ей нигде не присваивалось. Достеп к ней вернет её значение по умолчанию - undefined.

Элементарные и ссылочные типы

Типы данных делятся на два группы: элементарные и ссылочные.

Элементарные типы - числа, логические значения, а также значения null и undefined.

Ссылочные типы - объекты, массивы и функции.

Элементарный тип имеет фиксированный размер. Например, число занимает во семь байтов, а логическое значение может быть представлено всего одним битом.

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

Ссылка – это не само значение, но она сообщает переменной, где это значение можно найти.

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

Со строками вот какая фигня.

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

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

Сборка мусора

Ссылочные типы могут быть очень большими. Поскольку строки, объекты и массивы не имеют фиксированного размера, место для их хранения должно выделяться динамически. В JavaScript, реализована технология, называемая сборкой мусора (garbage collection). Интерпретатор JavaScript мо жет обнаружить, что объект никогда более не будет использоваться программой и занятая им па мять может быть освобождена.

Переменные как свойства

Переменные в JavaScript принципиально не отличаются от свойств объекта.

Контексты исполнения в JavaScript

Начиная исполнять функцию, интерпретатор JavaScript создает для нее новый контекст исполнения (execution context)

Следующий обзац точно помню спрашиавли на собеседовании, поэтому нужно запомнить.

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

JavaScript код в одном фрейме может ссылаться на другой фрейм с помощью выражения pa rent.frames[1], а на глобальную переменную x в первом фрейме можно сослаться из второго фрейма с помощью выражения parent.frames[0].x.

Еще об области видимости переменных

В JavaScript с каждым контекстом исполнения связана цепочка областей видимости (scope chain), представляющая собой список, или цепочку, объектов.

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

Выражения

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

1.7 // Числовой литерал

"JavaScript is fun!" // Строковый литерал

true // Логический литерал

null // Литерал значения null

/java/ // Литерал регулярного выражения

{ x:2, y:2 } // Объектный литерал

[2,3,5,7,11,13,17,19] // Литерал массива

function(x){return x*x;} // Функциональный литерал

i // Переменная i

sum // Переменная sum

Значение выражения -литерала – это просто значение самого литерала. Значение выражения- переменной – это значение, содержащееся в переменной, или значе ние, на которое переменная ссылается.

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

Операторы могут быть разбиты на категории по количеству требуемых им опе рандов: Унаврные, двухместные, тернатрые

Операторы равенства

Равенство (==) и идентичность (===)

При определении идентичности двух значений оператор === следует правилам:

- Два значения идентичны, только если оба они представляют собой числа, имеют одинаковые значения и не являются значением NaN (NaN не чему не равен, даже самому себе)

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

- Если оба значения представляют собой логические значения true или false, то они идентичны.

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

- Если оба значения равны null или undefined, то они идентичны.

Следующие правила применяются для определения равенства при помощи опе ратора ==:

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

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

- Если одно значение равно null, а другое – undefined, то они равны.

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

- Если какое -либо значение равно true, оно преобразуется в 1 и сравнение выполняется снова. Если какое- либо значение равно false, оно преобразу ется в 0 и сравнение выполняется снова.

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

- Любые другие комбинации значений не являются равными.

Неравенство (!=) и неидентичность (!==)

Операторы != и !== выполняют проверки, в точности противоположные операто рам == и ===

Операторы отношения < > <= >=

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

Оператор in

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

Оператор instanceof

Левый операнд объект, правый имя класса объекта.

Строковые операторы

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

Операторы <, <=, > и >= сравнивают две строки и определяют, в каком порядке они следуют друг за другом. Сравнение основано на алфавитном порядке.

Логические операторы

&& - логическое И

|| - логическое ИЛИ

! - логическое НЕ

Поразрядные операторы

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

Операторы присваивания (=)
Условный оператор (?:)

Условный оператор – это единственный тернарный оператор (с тремя операнда ми) в JavaScript и иногда он так и называется – «тернарный оператор».

Оператор typeof

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

Результатом оператора

typeof ЧИСЛО "number",

typeof СТРОКА "string"

typeof TRUE/FALSE "boolean"

typeof ОБЪЕКТ/МАССИВ/NULL "object"

Операнд typeof можно заключить в скобки typeof (ОПЕРАНД)

Оператор создания объекта (new)

Оператор new создает новый объект и вызывает функцию конструктор для его инициализации

Оператор delete

Унарный оператор delete выполняет попытку удалить свойство объекта, эле мент массива или переменную, указанную в его операнде.1 Он возвращает true, если удаление прошло успешно, и false в противном случае. Не все переменные и свойства могут быть удалены – некоторые встроенные свойства из базового и клиентского языков JavaScript устойчивы к операции удаления. Кроме того, не могут быть удалены переменные, определенные пользователем с помощью инструкции var. Если оператор delete вызывается для несуществующего свойст ва, он возвращает true.

Оператор «запятая»

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

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

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

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

Операторы доступа к массивам и объектам

Квадратные скобки ([]) и точка (.)

Оператор вызова функции

Круглые скобки ()

Инструкции

break - выход из самого внутрен него цикла инструкции switch или инструкции с име нем имя_метки

case - Метка для инструкции внут ри конструкции switch continue - Перезапуск самого внутрен него цикла или цикла, поме ченного меткой имя_метки default - Отметка инструкции по умолчанию внутри инструк ции switch do/while - Альтернатива циклу while Пустая инструкция (;) ничего не делает for - Простой цикл for/in - цикл по свойствам объекта function - объявление функции if/else - услованое использование фрагмента программы Метка - присваивание инструкции имени идентификатор return - возврат из функции или за дание возвращаемого функ цией значения, равным выражению switch - многопозиционное ветвле ние для инструкций, поме ченных метками case и default throw - генерация исключения try - перезват исключения var - объявление или инициализация переменной while - базовая конструкция для цикла

Объекты и массивы

Свойство constructor

В JavaScript любой объект имеет свойство constructor, которое ссылается на функ цию-конструктор, используемую для инициализации объекта.

var d = new Date( ); d.constructor == Date; // Равно true

if ((typeof o == "object") && (o.constructor == Date)) // Какие то действия с объектом Date...

Можно и так написать:

if ((typeof o == "object") && (o instanceof Date)) // Какие то действия с объектом Date...

Метод toString()

Метод toString() не требует аргументов; он возвращает строку, каким либо обра зом представляющую тип и/или значение объекта, для которого он вызывается. Интерпретатор JavaScript вызывает этот метод объекта во всех тех случаях, ко гда ему требуется преобразовать объект в строку.

Метод toString() по умолчанию не очень информативен. Например:

var s = { x:1, y:1 }.toString( ); // вернет "[object Object]"

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

Метод toLocaleString()

Класс Object в дополнение к методу toString() определяет метод toLocaleString()

Возвращает то же значение что и toString()

Классы Array, Date и Number определяют версии метода toLocaleString() и возвращают что-то другое

Метод valueOf()

Метод valueOf() во многом похож на метод toString(), но вызывается, когда ин терпретатору JavaScript требуется преобразовать объект в значение какого либо элементарного типа, отличного от строки, – обычно в число.

Метод hasOwnProperty()

Метод hasOwnProperty( "ИМЯ_СВОЙСТВА" ) // true если это свойство в объекте есть и оно не унаследовано и fasle если его там нет

var o = {}; o.hasOwnProperty("undef"); // false: свойство не определено o.hasOwnProperty("toString"); // false: toString – это унаследованное свойство Math.hasOwnProperty("cos"); // true: объект Math имеет свойство cos

Метод propertyIsEnumerable()

Метод propertyIsEnumerable("ИМЯ_СВОЙСТВА") - то же поведение что и в методу hasOwnProperty, но свойство должно ещё быть перечислимым, только тогда вернет true

var o = { x:1 }; o.propertyIsEnumerable("x"); // true: свойство существует и является перечислимым o.propertyIsEnumerable("y"); // false: свойство не существует o.propertyIsEnumerable("valueOf"); // false: свойство неперечислимое

Неперечислимыми обычно являются унаследованные свой ства

Метод isPrototypeOf(ИМЯ_ОБЪЕКТА)

var o = {}; Object.prototype.isPrototypeOf(o); // true: o.constructor == Object Object.isPrototypeOf(o); // false o.isPrototypeOf(Object.prototype); // false Function.prototype.isPrototypeOf(Object); // true: Object.constructor == Function

Массивы

new Array(10) - если вызвать с одним аргументом, то это будет длина массива. Все значения в нем будут равны undefined

Свойство length массива доступно как для чтения, так и для записи.

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

Метод toString() преобразует массив в строку

[1,2,3].toString() // Получается '1,2,3'

["a", "b", "c"].toString() // Получается 'a,b,c'

[1, [2,'c']].toString() // Получается '1,2,c'

toString() возвращает ту же строку, что и метод join() при вызове его без аргументов.

Массивы в JavaScript являются особенными, потому что их свойство length обла дает особенным поведением:

• Значение этого свойства автоматически изменяется при добавлении к масси ву новых элементов.

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

Объект Argument является объектом, подоб ным массиву. В клиентском языке JavaScript такие объекты возвращают многие методы DOM, например метод document.getEle mentsByTagName().

Функции

Определения функций не могут находиться внут ри циклов или условных инструкций.

Объект Arguments определяет свойство callee, ссылающееся на исполняемую в данный момент функцию.

Пример вычисления факториала: function(x) { if (x <= 1) return 1; return x * arguments.callee(x 1); }

Свойство length доступное только для чтения возвра щает количество аргументов, которое функция ожидает получить

function check(args) { var actual = args.length; // Фактическое число аргументов var expected = args.callee.length; // Ожидаемое число аргументов return actual === expected; }

Любая функция имеет свойство prototype, ссылающееся на предопределенный объект-прототип

Методы apply и call() определены для всех функций. Первый агумент объект, остальные параметры передаваемые функции.

f.call(o, 1, 2);

Тоже самое можно было написать так: o.m = f; o.m(1,2); delete o.m;

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

Что просиходит когда интерпритатор вызывает функцию в JS

1 - Уста навливает область видимости в соответствии с цепочкой областей видимости, ко торая действовала на момент определения функции

2 - добавляет в начало цепочки новый объект, известный как объект вызова (activation object)

3 - В этот объект добавляется свойство arguments

4 - Добавляются именнованные аргументы, переменные объявленные с помощью var

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

// Эта функция возвращает другую функцию // От вызова к вызову изменяется область видимости, // в которой была определена вложенная функция function makefunc(x) { return function() { return x; } }

// Вызвать makefunc() несколько раз и сохранить результаты в массиве: var a = [makefunc(0), makefunc(1), makefunc(2)];

// Теперь вызвать функции и вывести полученные от них значения. // Хотя тело каждой функции остается неизменным, их области видимости // изменяются, и при каждом вызове они возвращают разные значения: alert(a[0]( )); // Выведет 0 alert(a[1]( )); // Выведет 1 alert(a[2]( )); // Выведет 2

// При каждом вызове возвращает разные значения uniqueID = function() { if (!arguments.callee.id) arguments.callee.id = 0; return arguments.callee.id++; };

Проблема заключается в том, что свойство uniqueID.id доступно за пределами функции и может быть установлено в значение 0. Для решения этой проблемы можно сохранять значение в замыкании, доступ к которому будет иметь только эта функция:

uniqueID = (function() { // Значение сохраняется в объекте вызова функции var id = 0; // Это частная переменная, сохраняющая свое // значение между вызовами функции // Внешняя функция возвращает вложенную функцию, которая имеет доступ // к этому значению. Эта вложенная функция сохраняется // в переменной uniqueID выше. return function() { return id++; }; // Вернуть и увеличить })(); // Вызов внешней функции после ее определения.

Конструктор Function() можно использовать для создания функции

var f = new Function("x", "y", "return x*y;"); тоже самое что и function f(x, y) { return x*y; }

Конструктор Function() принимает любое количество строковых аргументов. По следний аргумент – это тело функции.

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

var y = "глобальная"; function constructFunction() { var y = "локальная"; return new Function("return y"); // Не сохраняет локальный контекст! } // Следующая строка выведет слово "глобальная", потому что функция, // созданная конструктором Function(), не использует локальный контекст. // Если функция была определена как литерал, // эта строка вывела бы слово "локальная". alert(constructFunction()()); // Выводит слово "глобальная"

Классы, конструкторы и прототипы

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

Конструкторы

После new идет имя функции-конструктора и потом:

  • Оператор создает новый пустой объект без каких-либо свойств, затем
  • Устанавливает в этом объекте ссыл ку на ( прототип ) 1.
  • Вызывает функцию, передавая ей только что созданный объект в виде значения this
прототип в данном случае значение свойста prototype функции-конструктора. Все функции имеют свойство prototype, которое ини циализируется в момент определения функции. Начальным значением этого свойства является объект с единственным свойством. Это свойство называется constructor и значением его является ссылка на саму функцию- конструктор, с которой этот прототип ассоциируется. Любые свойства, добавленные к прототипу, автоматически стано вятся свойствами объектов, инициализируемых конструктором.

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

Чтобы определить собственный конструктор, доста точно написать функцию, добавляющую новые свойства к объекту, на который ссылается ключевое слово this.

// Определяем конструктор. // Обратите внимание, как инициализируется объект с помощью "this". function Rectangle(w, h) { this.width = w; this.height = h; } // Вызываем конструктор для создания двух объектов Rectangle. // Мы передаем ширину и высоту конструктору, чтобы можно было // правильно проинициализировать оба новых объекта. var rect1 = new Rectangle(2, 4); // rect1 = { width:2, height:4 }; var rect2 = new Rectangle(8.5, 11); // rect2 = { width:8.5, height:11 };

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

Прототипы и наследование

Все объекты в JavaScript содержат внутреннюю ссылку на объект, известный как прототип.

Любой объект в JavaScript наследует свойства своего прототипа.

Пример:

function Rectangle(w, h) { this.width = w; this.height = h; this.area = function( ) { return this.width * this.height; } }

Данный пример не является оптимальным т.к каждый созданный прямоугольник будет иметь три свойства. Свойст ва width и height могут иметь уникальные значения для каждого прямоугольни ка, но свойство area каждого отдельно взятого объекта Rectangle всегда будет ссылаться на одну и ту же функцию.

Другой пример более оптимальный.

// Функция -конструктор инициализирует те свойства, которые могут // иметь уникальные значения для каждого отдельного экземпляра. function Rectangle(w, h) { this.width = w; this.height = h; } // Прототип объекта содержит методы и другие свойства, которые должны // совместно использоваться всеми экземплярами этого класса. Rectangle.prototype.area = function() { return this.width * this.height; }

Объект- прототип – идеальное место для методов и других свойств -констант.

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

Свойства не копируются из объекта-прото типа в новый объект; они просто присутствуют, как если бы были свойствами этих объектов.

Это имеет два важных следствия:

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

Унаследованные свойства ничем не отличаются от обычных свойств объекта. Они поддаются перечислению в цикле for/in и могут проверяться с помощью опе ратора in. Отличить их можно только с помощью метода Object.hasOwnProperty(): var r = new Rectangle(2, 3); r.hasOwnProperty("width"); // true: width – непосредственное свойство "r" r.hasOwnProperty("area"); // false: area – унаследованное свойство "r" "area" in r; // true: area – свойство объекта "r"

Чтение и запись унаследованных свойств

У каждого класса имеется один объект -прототип с одним наборов свойств, но потенциально может существовать множество экземпляров класса, каждый из которых наследует свойства прототипа. Свойство прототипа может наследоваться многими объектами, поэтому интерпретатор JavaScript должен обеспечить фундаментальную асимметричность между чтением и записью значений свойств. Когда вы читаете свойство p объекта o, JavaScript сначала проверяет, есть ли у объекта o свойство с именем p. Если такого свойства нет, то проверяется, есть ли свойство с именем p в объекте-прототипе. Так работает наследование на базе прототипов.

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

Поэтому наследование свойств происходит только при чтении значений свойств, но не при их записи. Если вы устанавливаете свойство p в объекте o, который на следует это свойство от своего прототипа, происходит создание нового свойства непосредственно в объекте p. Теперь, когда объект o имеет собственное свойство с именем p, он больше не наследует значение p от прототипа. Когда вы читаете значение p, JavaScript сначала ищет его в свойствах объекта o. Так как он находит свойство p, определенное в o, ему не требуется искать его в объекте -прототипе, и JavaScript никогда не будет искать определенное в нем значение p. Мы иногда говорим, что свойство p «затеняет» (скрывает) свойство p объекта- прототипа. Наследование прототипов может показаться запутанным, но все вышеизложен ное хорошо иллюстрирует рис 2

Рис 2 Объекты и прототипы
Расширение встроенных типов

Встроенные классы, такие как String и Date, также имеют объекты-прототипы

Например, следующий фрагмент определяет новый метод, доступный всем объектам String:

// Возвращает true, если последним символом является значение аргумента c String.prototype.endsWith = function(c) { return (c == this.charAt(this.length 1)) }

Определив новый метод endsWith() в объекте прототипе String, мы сможем обра титься к нему следующим образом: var message = "hello world"; message.endsWith('h') // Возвращает false message.endsWith('d') // Возвращает true

Лучше не трогать прототипы встроенных объектов.
Никогда не следует добавлять свойства к объекту Object.prototype

Объектно ориентированный язык JavaScript

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

И JavaScript, и объектно-ориентированные языки, основывающиеся на классах, допускают наличие множества объектов одного класса. Мы часто говорим, что объект – это экземпляр класса. Таким образом, одновременно может существовать множество экземпляров любого класса.

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

Свойства экземпляра

Каждый объект имеет собственные копии свойств экземпляра. Другими слова ми, если имеется 10 объектов данного класса, то имеется и 10 копий каждого свойства экземпляра.

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

Методы экземпляра

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

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

В Java Script мы определяем метод экземпляра класса путем присваивания функции свойству объекта -прототипа в конструкторе. Так, все объекты, созданные данным конструктором, совместно используют унаследованную ссылку на функцию и могут вызывать ее.

Методы экземпляра и ключевое слово this

В Java и C++ область видимости методов экземпляров включает объект this. Так, например, метод area в Java может быть реализован проще:

return width * height;

Однако в JavaScript приходится явно вставлять ключевое слово this перед именами свойств:

return this.width * this.height;

Если вам покажется неудобным вставлять this перед каждым именем свойства экземпляра, можно воспользоваться инструкцией with, например:

Rectangle.prototype.area = function( ) { with(this) { return width*height; } }

Свойства класса

Свойство класса в Java – это свойство, связанное с самим классом, а не с каждым экземпляром этого класса. Независимо от того, сколько создано экземпляров класса, есть только одна копия каждого свойства класса.

Свойства класса имитируются в JavaScript простым определением свойства самой функции-конструктора. Например, свойство класса Rectangle.UNIT для хранения единичного прямоугольника с размера ми 1x1 можно создать так:

Rectangle.UNIT = new Rectangle(1,1);

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

Методы класса

Метод класса – это метод, связанный с классом, а не с экземпляром класса; он вызывается через сам класс, а не через конкретный экземпляр класса. Метод Date.parse() – это метод класса. Он всегда вызывается через объект конструктора Date, а не через конкретный экземпляр класса Date.

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

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

Пример: класс Circle

// Начнем с конструктора. function Circle(radius) { // r – свойство экземпляра, оно определяется // и инициализируется конструктором. this.r = radius; }

// Circle.PI – свойство класса, т. е. свойство функции конструктора. Circle.PI = 3.14159;

// Метод экземпляра, который рассчитывает площадь круга. Circle.prototype.area = function( ) { return Circle.PI * this.r * this.r; }

// Метод класса – принимает два объекта Circle и возвращает объект с большим радиусом. Circle.max = function(a,b) { if (a.r > b.r) return a; else return b; }

// Примеры использования каждого из этих полей: var c = new Circle(1.0); // Создание экземпляра класса Circle c.r = 2.2; // Установка свойства экземпляра r var a = c.area(); // Вызов метода экземпляра area() var x = Math.exp(Circle.PI); // Обращение к свойству PI класса для выполнения // расчетов var d = new Circle(1.2); // Создание другого экземпляра класса Circle var bigger = Circle.max(c,d); // Вызов метода класса max()

Частные члены

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

ПРИВЕТ

function ImmutableRectangle(w, h) { // Этот конструктор не создает свойства объекта, где может храниться // ширина и высота. Он просто определяет в объекте методы доступа // Эти методы являются замыканиями и хранят значения ширины и высоты // в своих цепочках областей видимости. this.getWidth = function() { return w; } this.getHeight = function() { return h; } }

// Обратите внимание: класс может иметь обычные методы в объекте-прототипе. ImmutableRectangle.prototype.area = function( ) { return this.getWidth( ) * this.getHeight( ); };

Общие методы класса Object

Метод toString()

Идея метода toString() состоит в том, что каждый класс объектов должен иметь собственное особое строковое представление и поэтому определять соответствую щий метод toString() для преобразования объектов в строковую форму

Метод valueOf()

Метод valueOf() во многом похож на метод toString(), но вызывается, когда Java Script требуется преобразовать объект в значение какого либо элементарного ти па, отличного от строкового – обычно в число.

Методы сравнения

Операторы сравнения в JavaScript сравнивают объекты по ссылке, а не по значе нию. Так, если имеются две ссылки на объекты, то выясняется, ссылаются они на один и тот же объект или нет, но не выясняется, обладают ли разные объекты оди наковыми свойствами с одинаковыми значениями.

Привер: Создание подкласса в JS

// Определение простого класса прямоугольников. // Этот класс имеет ширину и высоту и может вычислять свою площадь function Rectangle(w, h) { this.width = w; this.height = h; }

Rectangle.prototype.area = function( ) { return this.width * this.height; }

// Далее идет определение подкласса function PositionedRectangle(x, y, w, h) { // В первую очередь необходимо вызвать конструктор надкласса // для инициализации свойств width и height нового объекта. // Здесь используется метод call, чтобы конструктор был вызван // как метод инициализируемого объекта. // Это называется вызов конструктора по цепочке. Rectangle.call(this, w, h);

// Далее сохраняются координаты верхнего левого угла прямоугольника this.x = x; this.y = y; }

// Если мы будем использовать объект прототип по умолчанию, // который создается при определении конструктора PositionedRectangle(), // был бы создан подкласс класса Object.

// Чтобы создать подкласс класса Rectangle, необходимо явно создать // объект прототип. PositionedRectangle.prototype = new Rectangle();

// Мы создали объект прототип с целью наследования, но мы не собираемся // наследовать свойства width и height, которыми обладают все объекты // класса Rectangle, поэтому удалим их из прототипа. delete PositionedRectangle.prototype.width; delete PositionedRectangle.prototype.height;

// Поскольку объект прототип был создан с помощью конструктора // Rectangle(), свойство constructor в нем ссылается на этот // конструктор. Но нам нужно, чтобы объекты PositionedRectangle // ссылались на другой конструктор, поэтому далее выполняется // присваивание нового значения свойству constructor PositionedRectangle.prototype.constructor = PositionedRectangle;

// Теперь у нас имеется правильно настроенный прототип для нашего // подкласса, можно приступать к добавлению методов экземпляров. PositionedRectangle.prototype.contains = function(x,y) { return (x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height); }

Надклассы и подклассы

Изменение конструктора

Вызов переопределенных методов

Карусель с выявлением типа данных

function getType(x) { // Если значение x равно null, возвращается "null" if (x == null) return "null";

// Попробовать определить тип с помощью оператора typeof var t = typeof x;

// Если получен непонятный результат, вернуть его if (t != "object") return t;

// В противном случае, x – это объект. Вызвать метод toString() // по умолчанию и извлечь подстроку с именем класса. var c = Object.prototype.toString.apply(x); // В формате "[object class]" c = c.substring(8, c.length 1); // Удалить "[object" и "]"

// Если имя класса не Object, вернуть его. if (c != "Object") return c;

// Если получен тип "Object", проверить, может быть x // действительно принадлежит этому классу. if (x.constructor == Object) return c; // Тип действительно "Object"

// Для пользовательских классов извлечь строковое значение свойства // classname, которое наследуется от объекта прототипа if ("classname" in x.constructor.prototype && // наследуемое имя класса typeof x.constructor.prototype.classname == "string") // это строка return x.constructor.prototype.classname;

// Если определить тип так и не удалось, так и скажем об этом. return "<unknown type>"; }

Грубое определение типа

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