
- •12. Создание динамического наполнения страницы. Основы JavaScript
- •12.1. Основы языка JavaScript.
- •12.1.5.12. Внутренние функции
- •12.1.6. Встраивание в веб-страницы
- •12.1.6.1. Расположение внутри тега
- •12.1.6.2. Отделение от разметки
- •12.1.6.3. Вынесение в отдельный файл
- •12.1.6.4. Атрибуты тега script
- •12.1.7. Область применения
- •12.1.7.9. Прикладное программное обеспечение
- •12.1.10.1. Межсайтовые уязвимости
- •12.1.10.2. Ошибки в браузере, плагинах и расширениях
- •12.1.10.3. Ошибки реализации песочницы
- •12.2. Ключевые термины
- •12.3. Краткие итоги
12. Создание динамического наполнения страницы. Основы JavaScript
В данной лекции рассматриваются основы языка JavaScript.
Содержание
12.1. Основы языка JavaScript.
12.1.1. Определение
12.1.2. История
12.1.2.1. Начало
12.1.2.2. JavaScript
12.1.2.3. Популярность
12.1.3. Возможности языка
12.1.4. Семантика и синтаксис
12.1.4.1. Структура языка
12.1.4.1.1. Ядро
12.1.4.1.2. Объектная модель браузера
12.1.4.1.3. Объектная модель документа
12.1.5. Основной синтаксис
12.1.5.1. Обзор
12.1.5.2. Числа
12.1.5.3. Строки
12.1.5.4. Другие типы
12.1.5.5. Переменные
12.1.5.6. Операторы
12.1.5.7. Управляющие структуры
12.1.5.8. Объекты
12.1.5.9. Массивы
12.1.5.10. Функции
12.1.5.11. Пользовательские объекты
12.1.5.12. Внутренние функции
12.1.6. Встраивание в веб-страницы
12.1.6.1. Расположение внутри тега
12.1.6.2. Отделение от разметки
12.1.6.3. Вынесение в отдельный файл
12.1.6.4. Атрибуты тега script
12.1.7. Область применения
12.1.7.1. Веб-приложения
12.1.7.2. AJAX
12.1.7.3. Comet
12.1.7.4. Браузерные операционные системы
12.1.7.5. Пользовательские скрипты в браузере
12.1.7.6. Серверные приложения
12.1.7.7. Мобильные приложения
12.1.7.8. Виджеты
12.1.7.9. Прикладное программное обеспечение
12.1.7.10. Манипуляция объектами приложений
12.1.7.11. Офисные приложения
12.1.8. Библиотеки JavaScript
12.1.9. Поддержка браузерами
12.1.10. Безопасность
12.1.10.1. Межсайтовые уязвимости
12.1.10.2. Ошибки в браузере, плагинах и расширениях
12.1.10.3. Ошибки реализации песочницы
12.2. Ключевые термины
12.3. Краткие итоги
12.1. Основы языка JavaScript.
12.1.1. Определение
JavaScript – объектно-ориентированный скриптовый язык программирования [1, 2]. JavaScript обычно используется как встраиваемый язык для программного доступа к объектам приложений. Наиболее широкое применение находит в браузерах как язык сценариев для придания интерактивности веб-страницам.
Основные архитектурные черты [1, 2]:
динамическая типизация – прием, широко используемый в языках программирования и языках спецификации, при котором переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной, таким образом, в различных участках программы одна и та же переменная может принимать значения разных типов;
слабая типизация – компилятор генерирует код, обеспечивающий преобразование типов, а логическая корректность такого преобразования контролируется программистом, т.е. фактически значение переменной одного типа можно присвоить значению переменной другого почти без ограничений;
автоматическое управление памятью – специальный код, называемый сборщиком мусора (garbage collector), периодически освобождает память, удаляя объекты, которые уже не будут востребованы приложением – то есть производит сборку мусора;
прототипное программирование – стиль объектно-ориентированного программирования, при котором отсутствует понятие класса, а повторное использование (наследование) производится путем клонирования существующего экземпляра объекта – прототипа;
функции как объекты первого класса – функции в JavaScript могут использоваться без существенных ограничений, их можно передавать как параметры, использовать как переменные, у них могут быть свои функции (например, invoke(), delay()).
На JavaScript оказали влияние многие языки программирования. При разработке была цель сделать язык похожим на Java, но при этом легким для использования непрограммистами.
12.1.2. История
12.1.2.1. Начало
В 1992 году компания Nombas начала разработку встраиваемого скриптового языка Cmm (Си-минус-минус), который, по замыслу разработчиков, должен был стать достаточно мощным, чтобы заменить макросы, сохраняя при этом схожесть с Си, чтобы разработчикам не составляло труда изучить его [2]. Главным отличием от Си была работа с памятью. В новом языке все управление памятью осуществлялось автоматически: не было необходимости создавать буферы, объявлять переменные, осуществлять преобразование типов. В остальном языки были сильно похожи друг на друга: в частности, Cmm поддерживал стандартные функции и операторы Си. Cmm был переименован в ScriptEase, поскольку исходное название звучало слишком негативно, а упоминание в нем Си "отпугивало" людей.
На основе этого языка был создан продукт CEnvi. В конце ноября 1995 года Nombas разработала версию CEnvi, внедряемую в веб-страницы. Страницы, которые можно было изменять с помощью скриптового языка, получили название Espresso Pages – они демонстрировали использование скриптового языка для создания игры, проверки пользовательского ввода в формы и создания анимации. Espresso Pages позиционировались как демоверсия, призванная помочь представить, что случится, если в браузер будет внедрен язык Cmm. Работали они только в 16-битовом Netscape Navigator под управлением Windows.
12.1.2.2. JavaScript
Перед Бренданом Айхом, нанятым в компанию Netscape 4 апреля 1995 года, была поставлена задача внедрить язык программирования Scheme или что-то похожее в браузер Netscape. Поскольку требования были размыты, Айха перевели в группу, ответственную за серверные продукты, где он проработал месяц, занимаясь улучшением протокола HTTP. В мае разработчик был переброшен обратно в команду, занимающуюся клиентской частью (браузером), где он немедленно начал разрабатывать концепцию нового языка программирования. Менеджмент разработки браузера был убежден, что Netscape должен поддерживать язык программирования, встраиваемый в HTML-код страницы.
Помимо Брендона Айха в разработке участвовали один из основателей Netscape Communications Марк Андрисин и один из основателей Sun Microsystems Билл Джой: чтобы успеть закончить работы над языком к релизу браузера, компании заключили соглашение о сотрудничестве в разработке. Они ставили перед собой цель обеспечить "язык для склеивания" составляющих частей веб-ресурса: изображений, плагинов, Java-апплетов, который был бы удобен для веб-дизайнеров и программистов, не обладающих высокой квалификацией.
Первоначально язык назывался LiveScript и предназначался как для программирования на стороне клиента, так и для программирования на стороне сервера (там он должен был называться LiveWire). На синтаксис оказали влияние языки Си и Java, и, поскольку Java в то время было модным словом, 4 декабря 1995 года LiveScript переименовали в JavaScript, получив соответствующую лицензию у Sun. Анонс JavaScript со стороны представителей Netscape и Sun состоялся накануне выпуска второй бета-версии Netscape Navigator. В нем декларируется, что 28 лидирующих ИТ-компаний выразили намерение использовать в своих будущих продуктах JavaScript как объектный скриптовый язык с открытым стандартом.
В 1996 году компания Microsoft выпустила аналог языка JavaScript, названный JScript. Анонсирован этот язык был 18 июля 1996 года. Первым браузером, поддерживающим эту реализацию был Internet Explorer 3.0.
По инициативе компании Netscape была проведена стандартизация языка ассоциацией ECMA. Стандартизированная версия имеет название ECMAScript, описывается стандартом ECMA-262. Первой версии спецификации соответствовал JavaScript версии 1.1, а также языки JScript и ScriptEasy.
12.1.2.3. Популярность
В статье "Самый непонимаемый язык программирования в мире стал самым популярным в мире языком программирования" [3] Дуглас Крокфорд утверждает, что лидирующую позицию JavaScript занял в связи с развитием AJAX, поскольку браузер стал превалирующей системой доставки приложений. Он также констатирует растущую популярность JavaScript, то, что этот язык встраивается в приложения, отмечает значимость языка.
По данным Black Duck Software в разработке открытого программного обеспечения доля использования JavaScript выросла. 36 % проектов, релизы которых состоялись с августа 2008 по август 2009 гг., включают JavaScript, наиболее часто используемый язык программирования с быстрорастущей популярностью. 80 % открытого программного обеспечения использует Си, C++, Java, Shell и JavaScript. При этом JavaScript – единственный из этих языков, чья доля использования увеличилась (более чем на 2 процента, если считать в строках кода).
12.1.3. Возможности языка
JavaScript обладает рядом свойств объектно-ориентированного языка, но реализованное в языке прототипирование обуславливает отличия в работе с объектами по сравнению с традиционными объектно-ориентированными языками [2]. Кроме того, JavaScript имеет ряд свойств, присущих функциональным языкам – функции как объекты первого класса, объекты как списки, анонимные функции – что придает языку дополнительную гибкость.
Несмотря на схожий с Си синтаксис, JavaScript по сравнению с языком Си имеет коренные отличия:
объекты, с возможностью интроспекции;
функции как объекты первого класса;
автоматическое приведение типов;
автоматическая сборка мусора;
анонимные функции.
Однако в JavaScript отсутствуют и некоторые "полезные вещи":
модульная система: JavaScript не предоставляет возможности управлять зависимостями и изоляцией областей видимости;
стандартная библиотека: в частности, отсутствует интерфейс программирования приложений по работе с файловой системой, управлению потоками ввода/вывода, базовых типов для бинарных данных;
стандартные интерфейсы к веб-серверам и базам данных.
12.1.4. Семантика и синтаксис
Синтаксис языка JavaScript во многом напоминает синтаксис Си и Java, семантически же язык гораздо ближе к Self или Smalltalk.
В JavaScript:
все идентификаторы зависят от регистра;
в названиях переменных можно использовать буквы, подчеркивание, символ доллара, арабские цифры;
названия переменных не могут начинаться с цифры;
для оформления однострочных комментариев используются //, многострочные и внутристрочные комментарии начинаются с /* и заканчиваются */.
12.1.4.1. Структура языка
Структурно JavaScript можно представить в виде объединения трех четко различимых друг от друга частей [1, 2]:
ядро (ECMAScript);
объектная модель браузера (BOM);
объектная модель документа (DOM).
Если рассматривать JavaScript в отличных от браузера окружениях, то объектная модель браузера и объектная модель документа могут не поддерживаться.
Объектную модель документа иногда рассматривают как отдельную от JavaScript сущность, что согласуется с определением DOM как независимого от языка интерфейса документа.
12.1.4.1.1. Ядро
ECMAScript не является браузерным языком и на самом деле в нем не определяются методы ввода и вывода информации. Это скорее основа для построения скриптовых языков. Спецификация ECMAScript описывает типы данных, инструкции, ключевые и зарезервированные слова, операторы, объекты, регулярные выражения, не ограничивая авторов производных языков от расширения их новыми составляющими.
12.1.4.1.2. Объектная модель браузера
Объектная модель браузера – "браузероспецифичная" часть языка, являющаяся прослойкой между ядром и объектной моделью документа. Основное предназначение объектной модели браузера – управление окнами браузера и обеспечение их взаимодействия. Каждое из окон браузера представляется объектом window, центральным объектом BOM.
Помимо управления окнами, в рамках объектной модели браузера, браузерами обычно обеспечивается поддержка следующих сущностей:
управление фреймами;
поддержка задержки в исполнении кода и зацикливания с задержкой;
системные диалоги;
управление адресом открытой страницы;
управление информацией о браузере;
управление информацией о параметрах монитора;
ограниченное управление историей просмотра страниц;
поддержка работы с HTTP cookie.
12.1.4.1.3. Объектная модель документа
Объектная модель документа – интерфейс программирования приложений для HTML и XML-документов. Согласно DOM документу можно поставить в соответствие дерево объектов, обладающих рядом свойств, которые позволяют производить с ним различные манипуляции:
получение узлов;
изменение узлов;
изменение связей между узлами;
удаление узлов.
12.1.5. Основной синтаксис
12.1.5.1. Обзор
В JavaScript определены следующие типы [4]:
Number;
String;
Boolean;
Object:
Function;
Array;
Date;
RegExp;
Null;
Undefined;
Error.
12.1.5.2. Числа
Все числа в JavaScript согласно спецификации "64-битные двойной точности – формат IEEE 754". В JavaScript нет типа Integer, что может привести к неожиданным последствиям, например:
0.1 + 0.2 = 0.30000000000000004
Поддержаны стандартные арифметические операторы, такие как сложение, деление, остаток от деления и так далее. Здесь стоит вспомнить встроенные объект Math содержащий математические методы:
Math.sin(3.5);
d = Math.PI * r * r;
С использованием встроенной функции parseInt() можно преобразовать строку в число. Вторым параметром эта функция принимает базу, которую стоит всегда указывать во избежание казусов:
parseInt("123", 10) //результат – "123"
parseInt("010", 10) //результат – "10"
а если не указать базу то получим:
parseInt("010") //результат – "8"
Это произошло, потому что parseInt посчитало число восьмеричным из-за предшествующего 0.
Если функция не может преобразовать строку в число, она возвращает специальное значение NaN (сокращение от "Не Число"):
parseInt("Привет", 10) //результат – "NaN"
Результатом математической операции с NaN всегда будет NaN:
NaN + 5 //результат – "NaN"
Можно проверить значение на равенство NaN переменной с помощью встроенной функции isNaN():
isNaN(NaN) //результат – "true"
В JavaScript есть также значения Infinity и -Infinity:
1 / 0 //результат – "Infinity"
-1 / 0 //результат – "-Infinity"
12.1.5.3. Строки
Strings в JavaScript это последовательность символов. Для представления одного символа можно использовать строку единичной длины. Для получения длины строки и объектов есть свойство length:
"Привет".length //результат – "5"
Также строка имеет набор полезных методов:
"Привет".charAt(0) //результат – "П"
"Привет, мир!".replace("Привет", "Прощай") //результат – "Прощай, мир!"
"Привет".toUpperCase() //результат – "ПРИВЕТ"
12.1.5.4. Другие типы
JavaScript различает специальный тип и одноименное примитивное значение null – явное (программно-установленное) пустое значения, и значение undefined типа "undefined", которое говорит о том, что значение еще не было назначено:
typeof null //результат – "object"
typeof undefined //результат – "undefined"
В JavaScript можно объявить переменную не инициализируя ее. В этом случае значение переменной будет равно undefined.
В JavaScript есть логический тип с возможными значениями true и false (оба являются ключевыми словами в JavaScript). Любое значение может быть преобразовано к логическому в согласии со следующими правилами:
false, 0, пустая строка (""), NaN, null, и undefined трактуются как false;
все остальное как true.
Преобразование можно провести в явном виде с использованием функции Boolean():
Boolean("") //результат – "false"
Boolean(234) //результат – "true"
Эта возможность используется достаточно редко, поскольку такое преобразование производится автоматически в тех случаях, когда ожидается логическое значение, как в выражении if. Поэтому обычно просто говорят о "истинных значениях" и "ложных значениях", подразумевая, в какое из логических значений оно преобразуется.
Поддерживаются стандартные логические операторы && (логическое и), || (логическое или) и ! (логическое не).
12.1.5.5. Переменные
Переменная в JavaScript объявляется с использованием ключевого слова var:
var a;
var name = "Иван";
12.1.5.6. Операторы
В JavaScript есть стандартные математические операторы +, -, *, / и % (остаток от деления). Значения присваиваются оператором =, также есть операторы составного присваивания += и -=. Они позволяют записать короче часто встречающиеся выражения типа: x = x operator y:
x += 5
x = x + 5
Можно использовать ++ и -- для увеличения или уменьшения значения переменной на единицу. Помещать этот оператор можно как за символом переменной (i++) так и перед или (++i).
Оператор "+" применяется еще и для конкатенации строк:
"Привет, " + "мир!" //результат – "Привет, мир!"
При добавлении строки к числу последнее автоматически конвертируется в строку:
"3" + 4 + 5 //результат – "345"
3 + 4 + "5" //результат – "75"
Добавление пустой строки к чему-либо можно использовать, как преобразование к строке.
В JavaScript Comparisons есть следующие операторы сравнения: <, >, <= и >=. Их можно использовать и со строками и с числами. Сравнение значений уже несколько сложнее. Оператор двойного равенства производит преобразование типов, что может привести к несколько неожиданным результатам:
"Привет" == "Привет" //результат – "true"
1 == true //результат – "true"
Чтобы этого избежать следует использовать оператор тройного равенства, который учитывает типы:
1 === true //результат – "false"
true === true //результат – "true"
Есть также обратные операторы != и !== для двойного и тройного равенства соответственно.
12.1.5.7. Управляющие структуры
JavaScript имеет Си-подобные управляющие структуры. Условные операторы: if и else, из которых можно делать цепочки:
var name = "Котенок";
if (name == "Щенок") {
name += "!";
} else if (name == "Котенок") {
name += "!!";
} else {
name = "!" + name;
}
name == Котенок!!"
В JavaScript есть циклы while и do-while. Первый можно использовать для бесконечного цикла; второй в том случае если вы хотите, чтобы тело цикла выполнилось как минимум один раз:
while (true) {
// Бесконечный цикл!
}
do {
var input = get_input();
}
while (inputIsNotValid(input))
В JavaScript for такой же, как в C, C# и Java:
for (var i = 0; i < 5; i++) {
// будет выполнен 5 раз
}
Операторы && и || "закорачиваются", т.е. будет ли выполнен или нет второй операнд, зависит от значения первого. Это можно использовать для проверки значения объекта на null перед тем как пытаться прочитать его свойства:
var name = o && o.getName();
Или для установки значений по умолчанию:
var name = otherName || "default";
В JavaScript есть также тернарный оператор:
var allowed = (age > 18) ? "Да" : "Нет";
Конструкция switch может быть использована для разветвления на основании числового или строчного значения:
switch (action) {
case 'Рисовать':
drawit();
break;
case 'Есть':
eatit();
break;
default:
donothing();
}
Если забыть написать break, то выполнение "провалится" в следующий case:
switch (a) {
case 1: // проваливаемся
case 2:
eatit();
break;
default:
donothing();
}
Выражение default необязательно, его можно опустить.
Можно использовать "выражения" как в switch, так и в case; сравнение будет проводиться с использованием оператора тройного равенства ===:
switch(1 + 3):
case 2 + 2:
yay();
break;
default:
neverhappens();
}
12.1.5.8. Объекты
В JavaScript объекты это просто коллекции пар "ключ-значение". Таким образом, они несколько похожи на:
словари в Python;
хэши в Perl и Ruby;
хэш-таблицы в C и C++;
HashMaps в Java;
ассоциативные массивы в PHP.
Так как в JavaScript почти все является объектами (за исключением базовых типов), все JavaScript программы интенсивно используют хэш-таблицы. И они обладают высоким быстродействием.
"Ключ" должен быть в JavaScript строкой, в то время как значение может быть любым JavaScript типом – включая и сами объекты. Это позволяет строить достаточно сложные структуры данных.
Есть два способа создать объект:
var obj = new Object();
или
var obj = {};
Эти два способа абсолютно идентичны. Второй способ называется объектный литерал.
После создания объекта ему можно присвоить свойства двумя способами:
obj.name = "Иван"
var name = obj.name;
или
obj["name"] = "Иван";
var name = obj["name"];
Эти два варианта дают один и тот же результат. Во втором случае имя свойства передается, как строка и может быть вычислено динамически во время выполнения. Также через квадратные скобки можно установить и получить свойства с именами из зарезервированных в JavaScript ключевых слов.
obj.for = "Иван"; // Синтаксическая ошибка, т.к. 'for' является зарезервированным словом
obj["for"] = "Иван"; // Нет ошибки
Объектно-литеральный синтаксис может быть использован для определения свойств объекта в момент его создания:
var obj = {
name: "Петров",
"for": "Иван",
details: {
color: "orange",
size: 12
}
}
Доступ к свойствам можно выстраивать в цепь (т.е. вызывать свойства свойств и т.д.):
obj.details.color //результат – "orange"
obj["details"]["size"] //результат – "12"
Примечание: несколько подробнее объекты рассматриваются в разделе "Представление объектов в виде JSON" лекции 10.
12.1.5.9. Массивы
Массивы в JavaScript это специальный тип объектов. Они представляют собой почти рядовые объекты (числовые свойства доступны только через [] синтаксис), но имеют свойство "length", значение которого всегда больше на единицу наибольшего индекса в массиве.
Старый способ создания массивов:
var a = new Array();
a[0] = "собака";
a[1] = "кошка";
a[2] = "мышь";
a.length //результат – "3"
Более удобен "массивно-литеральный" синтаксис:
var a = ["собака", "кошка", "мышь"];
a.length //результат – "3"
Необходимо помнить, что array.length не всегда число элементов массива:
var a = ["собака", "кошка", "мышь"];
a[100] = "лиса";
a.length //результат – "101"
Длина массива это просто число на единицу большее наибольшего индекса.
Если запросить элемент с несуществующим индексом, то будет возвращено undefined:
typeof (a[90]) //результат – "undefined"
Обойти массив можно вот так:
for (var i = 0; i < a.length; i++) {
// работаем с a[i]
}
Но этот способ немного не эффективен, поскольку свойство length будет выбираться на каждом цикле.
Безопасный способ добавления элемента к концу массива:
a[a.length] = item; //тоже что и a.push(item);
Поскольку a.length на единицу больше наибольшего индекса, вы можно быть уверенным, что добавлен элемент в пустую позицию в конец массива.
Массивы имеют набор встроенных методов:
concat возвращает новый массив с элементами, добавленными к нему;
pop удаляет из массива и возвращает последний элемент;
push добавляет один или несколько элементов в конец массива (также как ar[ar.length]);
sort сортирует элементы в качестве необязательного параметра можно передать функцию;
splice позволяет удалить или заменить целый интервал элементов в массиве;
unshift добавляет элемент в начало массива.
12.1.5.10. Функции
Вместе с объектами функции играют ключевую роль в JavaScript. Вот объявление самой простой функции:
function add(x, y) {
var total = x + y;
return total;
}
В JavaScript функции могут принимать от 0 и больше именованных параметров. Тело функции может содержать любые выражения JavaScript и объявления внутренних переменных. Выражение return может быть использовано в любое время для прекращения выполнения функции и возврата значения. Если return отсутствует или не возвращает значения (пустой return), функция возвращает undefined.
Именованные параметры условны, т.е. можно вызвать функцию без параметров или с лишними параметрами. В первом случае значения параметров будут установлены в undefined:
add() //результат – "NaN", т.к. undefined нельзя складывать
Также можно передать аргументов больше, чем ожидает функция:
add(2, 3, 4) //результат – "5", т.к. складывает первые два параметра, а 4 игнорируется
Внутри тела функции доступна дополнительная переменная "arguments". Это "массиво-подобный" объект, в котором хранятся все переменные, переданные в функцию. Перепишем предыдущую функцию, чтобы она суммировала все параметры, которые ей передают:
function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}
И вызовем ее:
add(2, 3, 4, 5) //результат – "14"
Напишем функцию для подсчета среднего арифметического:
function avg() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
И вызовем ее:
avg(2, 3, 4, 5) //результат – "3.5"
Если мы захотим вычислить среднее по массиву, то можно переписать функцию, но можно воспользоваться уже существующей, поскольку в JavaScript функцию можно вызвать несколькими способами.
Первый это классический func(). Любую функцию можно вызвать также с помощью встроенного в Function метода apply(), который вызовет функцию с параметрами, переданными в виде массива в apply() вторым параметром:
avg.apply(null, [2, 3, 4, 5]) //результат – "3.5"
JavaScript позволяет создавать анонимные функции:
var avg = function() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
JavaScript позволяет функциям делать рекурсивные вызовы (т.е. вызывать самих себя). Это очень полезно, когда имеешь дело с древовидными структурами, например, такими как DOM:
function countChars(elm) {
if (elm.nodeType == 3) {
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
12.1.5.11. Пользовательские объекты
В классических объектно-ориентированных языках объект – это коллекция данных и методов, оперирующих этими данными [5].
Рассмотрим объект person с полями first и last name (имя и фамилия). Предположим, надо иметь два метода для различных отображений полного имени: как "first last" или как "last, first". Это можно сделать с использованием объекта и функций следующим образом:
function makePerson(first, last) {
return {
first: first,
last: last
}
}
function personFullName(person) {
return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
return person.last + ', ' + person.first
}
Тогда при вызове этих функций:
s = makePerson("Иван", "Петров");
personFullName(s) //результат – "Иван Петров"
personFullNameReversed(s) //результат – "Петров, Иван"
Но если необходимо как-то присоединять имена функций к объектам, то, как это делается в обычных объектно-ориентированных языках программирования, то можно написать следующий код:
function makePerson(first, last) {
return {
first: first,
last: last,
fullName: function() {
return this.first + ' ' + this.last;
},
fullNameReversed: function() {
return this.last + ', ' + this.first;
}
}
}
Тогда при вызове этих функций:
s = makePerson("Иван", "Петров")
s.fullName()//результат – "Иван Петров"
s.fullNameReversed()//результат – "Петров, Иван"
Здесь встречается ключевое словом "this". При использовании внутри функции "this" ссылается на текущий объект. Чему будет равно "this" на самом деле зависит от того как была вызвана функция. Если функция будет вызвана через точечную нотацию или квадратные скобки, тогда "this" указывает на этот объект. Если просто вызвать функцию, то "this" будет указывать на глобальный объект. Непонимание этого может привести к неприятным последствиям:
s = makePerson("Иван", "Петров")
var fullName = s.fullName;
fullName()//результат – "undefined undefined"
Когда вызывается fullName(), в роли "this" выступает глобальный объект. И поскольку не определены глобальные переменные first и last, для каждой из них будет получено undefined.
Можно использовать "this" для усовершенствования нашей функции makePerson:
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function() {
return this.first + ' ' + this.last;
}
this.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
}
var s = new Person("Иван", "Петров");
Здесь присутствует ключевое слово "new". "new" связан с "this". Он создает пустой объект и вызывает указанную функцию, внутри которой "this" указывает на этот новый объект. Функции, которые будут использоваться вместе с "new" называются "конструкторами". Обычно название таких функций начинается с заглавной буквы, и напоминает о том, что функция должна быть использована вместе с "new".
Однако до сих пор остались некоторые проблемы: каждый раз при создании объекта, необходимо создавать по две новых функции внутри него. Разделим эти функции между всеми объектами:
function personFullName() {
return this.first + ' ' + this.last;
}
function personFullNameReversed() {
return this.last + ', ' + this.first;
}
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = personFullName;
this.fullNameReversed = personFullNameReversed;
}
Теперь функции будут создаваться только один раз, а ссылки на них назначаются в конструкторе. Однако существует более хороший вариант:
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
Person.prototype это объект общий для всех объектов, созданных с помощью Person. Он входит в "prototype chain" (цепочку поиска): каждый раз, когда происходит попытка получить свойство объекта Person, которое у него отсутствует, JavaScript проверяет, нет ли такого свойства у Person.prototype. Таким образом, все, что будет присвоено Person.prototype становится доступно всем объектам порожденным конструктором Person, в том числе и через объект this.
Это необычайно мощное средство. В JavaScript можно модифицировать prototype (прототипы) объектов в любое время и в любом месте программы, что означает, что можно добавлять методы к объектам в процессе выполнения программы:
s = new Person("Иван", "Петров");
s.firstNameCaps(); //результат – "TypeError on line 1: s.firstNameCaps is not a function"
Person.prototype.firstNameCaps = function() {
return this.first.toUpperCase()
}
s.firstNameCaps() //результат – "ИВАН"
Как уже упоминалось, прототип является частью цепочки, в конце которой находится Object.prototype, у которого есть метод toString() – он вызывается, когда объект приводится к строке. И его можно переопределить для отладки:
var s = new Person("Иван", "Петров");
s //результат – "[object Object]"
Person.prototype.toString = function() {
return '<ФИО: ' + this.fullName() + '>';
}
s //результат – "<ФИО: Иван Петров>"
Теперь вернемся к рассмотрению первого параметр метода avg.apply().Первый параметр apply() это объект, на который будет ссылаться ключевое слово "this", если оно используется в теле вызываемой функции. Например, можно сделать тривиальную реализацию "new":
function trivialNew(constructor) {
var o = {}; // создаем объект
constructor.apply(o, arguments);
return o;
}
Это конечно не совсем "new", так как не создается прототипная цепочка (prototype chain).
Для apply() есть похожая функция call, которая также позволяет установить "this", но вместо массива параметров принимает параметры через запятую (как при обычном вызове):
function lastNameCaps() {
return this.last.toUpperCase();
}
var s = new Person("Иван", "Петров");
lastNameCaps.call(s);
// Тоже самое, что и:
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();