Опубликован: 10.12.2007 | Доступ: свободный | Студентов: 822 / 20 | Оценка: 5.00 / 5.00 | Длительность: 58:33:00
Лекция 5:

Скрипты

5.3.3. Принципы обработки

Синтаксис определяет не только внешний вид кода, но и то, как интерпретатор JavaScript будет обрабатывать ваш скрипт. В этом языке используется несколько совершенно новых концепций.

5.3.3.1. Приоритет операторов

Приоритет операторов указан в таблице 5.1. Правила вычислений слева направо и справа налево примерно такие же, как и в C, C++ и Java.

5.3.3.2. Передача аргументов

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

5.3.3.3. Преобразование типов

JavaScript автоматически преобразовывает данные между всеми простыми типами и объектами Number, String и Boolean. Преобразование типов выполняется так, чтобы выражение могло быть вычислено в любом случае. Каждый объект в JavaScript обладает методами toNumber() и toString(), которые можно задействовать по мере надобности. Приведение типов не требуется; оно выполняется в соответствии с расширенным набором правил стандарта ECMA-262. Суть этих правил можно выразить в следующих двух:

  • Правило 1: Не следует предполагать, что преобразование сработает при превращении строки в число.
  • Правило 2: Не следует пользоваться бинарными операторами над объектами, чьи типы точно не известны.

Правило 1 существует потому, что содержимое строки может быть некорректным числовым литералом. Тогда JavaScript вернет значение NaN и, в худшем случае, может аварийно завершить работу:

var str = "123stop45";
var x = str * 3; // str не число.

Аварийное завершение работы происходит только в случае синтаксических ошибок или ошибок во время исполнения. Чтобы уберечься от таких вещей, необходимо явно использовать функции parseInt() и parseFloat():

var x = parseInt("123stop45") * 3;

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

5.3.3.4. Область видимости

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

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

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

В C и C++ переменные, объявленные в середине области видимости, действительны далее, начиная с этого места. В JavaScript переменные, объявленные в середине функции (или где-нибудь в области глобальной видимости), действительны для всей области видимости. Следующий фрагмент кода, некорректный для C/C++, корректен в JavaScript:

function f() {
  alert(x); // выдаст "undefined"
  var x = 42;
  alert(x); // выдаст "42";
}
f();

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

Инструкция with в JavaScript временно добавляет объекты в цепочку областей видимости. В листинге 5.5 показано, что функция toString() используется несколько раз и каждый раз - для нового объекта. В то же время переменная myflag всегда находится в объекте window, так как ни у одного из этих объектов нет свойства myflag.

// цепочка областей видимости = window
var myflag = "Test String";
var x = toString(); // "[object ChromeWindow]"
with (document) {
  // цепочка областей видимости = document, window
  x = toString(); // "[object HTMLDocument]"
  x = new Object;
  with (x) 	{
    // цепочка областей видимости = x, document, window
    var y = toString(); // "[object Object]"
    var x = myflag; // window.myflag
  }
}
Листинг 5.5. Пример работы цепочки областей видимости в JavaScript

Цепочки областей видимости - дальние родственницы таблиц виртуальных функций в C++, реализующих наследование, но размеры цепочек областей видимости не фиксированы, и эти цепочки не реализуют наследование объектов в привычном понимании. Для программиста цепочки областей видимости полезны, но о них обычно не стоит задумываться.

5.3.3.5. Стеки и сборка мусора

В C, C++ и Java для данных используются два варианта размещения в памяти: в стеке или в свободной памяти (или куче). Стек используется автоматически по необходимости, а свободная память используется при любом вызове new или malloc(). В JavaScript нет стека, по крайней мере, нет стека, с которым программист мог бы работать. Память всегда выделяется из кучи. Ни для элементов массивов, ни для аргументов функций не гарантируется последовательное размещение. Стеки используются для реализации передачи аргументов функциям в Mozilla SpiderMonkey, но этот механизм не виден программисту на JavaScript.

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

5.3.3.6. Вычисления во время выполнения

JavaScript обладает возможностью интерпретировать новый код во время выполнения. Самый удобный способ сделать это - применить метод eval() глобального объекта. eval() может использоваться для выполнения любого корректного кода JavaScript:

var x = 5;
var code = "x = x + 1;";
eval(code);                 // x = 6
x = x + 1;                  // x = 7

Другие аналогичные методы не связаны с интерпретацией напрямую. В Mozilla это методы setTimeout() и setInterval(), очень ограниченные parseInt(), parseFloat() и URL javascript:. Более подробно о механизме интерпретации нового кода во время выполнения рассказано в разделе "Использование XPCOM-компонентов".

5.3.3.7. Замкнутые выражения

JavaScript поддерживает замкнутые выражения. Это демонстрируется в следующем примере:

function accumulate(x) {
  return function (y) { return x + y };
}
var subtotal = accumulate(2);
var total = subtotal(3); // total == 5

Каким будет возвращаемое значение при вызове анонимной функции? Если бы при завершении области действия функции accumulate() значение аргумента x очищалось, она бы не участвовала в вычислениях при вызове subtotal().

Решение проблемы - замкнутые выражения. Замкнутое выражение - это набор данных, которые нужно сохранить и по завершении функции. Упрощенно это копия переменных из данной области действия, ссылающихся на другие объекты. Эти копии возвращаются, следовательно, данные, на которые они ссылаются, не теряют свою последнюю ссылку, когда исходные объекты очищаются. Таким образом, по завершении области действия сборщик мусора обходит эту группу созданных объектов стороной. Замкнутые выражения для программиста прозрачны. Их наличие имеет смысл для любого языка, поддерживающего вычисления во время выполнения.

5.3.3.8. Цепочки прототипов

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

На самом деле у каждого объекта в JavaScript, включая те, которые сами выступают как прототипы, есть объект-прототип с именем __proto__. Поэтому следующий код совершенно корректен:

MyConstructor.prototype.__proto__.value = 5;

Такой набор прототипов, указанный явно или нет, называется цепочкой прототипов. Эта цепочка заканчивается объектом Object.prototype, у которого нет собственного прототипа. Все звенья этой цепочки добавляют свойства к конечному создаваемому объекту. Сама цепочка представляет собой упорядоченный список звеньев, именно в этом порядке свойства будут добавляться. Одним из следствий является эффект затенения, когда свойства, добавленные позже, могут переписывать значения добавленных раньше свойств с теми же именами.

С помощью прототипов можно реализовывать объектно-ориентированное наследование. Самый просто способ это сделать - заменить прототип новым "базовым классом", то есть новым объектом-прототипом. Как видно из листинга 5.4, это можно сделать так:

function Family() { };          // Какой-то конструктор.
Family.prototype = new Parents; // Новый базовый "класс".

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

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