Ниже приводится пример использования объекта range.
var r = range(1,3);
// Создать новый объект range
r.includes(2);
// => true: число 2 входит в диапазон
r.foreach(console.log);
// Выведет 1 2 3
console.log(r);
// Выведет (1...3)
В примере 9.1 есть несколько интересных моментов, которые следует отметить особо. Здесь определяется фабричная функция
range,
которая используется для создания новых объектов
range
. Обратите внимание, что для хранения объекта-прототипа, определяющего класс, используется свойство
range.methods
функции
range.
В таком способе хранения объекта-прототипа нет ничего необычного. Во-вторых, отметьте, что функция
range
определяет свойства
from
и
to
для каждого объекта
range
. Эти не общие, не унаследованные свойства определяют уникальную информацию для каждого отдельного объекта range. Наконец, обратите внимание, что все общие, унаследованные методы, определяемые свойством
range.methods
, используют свойства
from
и
to
и ссылаются на них с помощью ключевого слова
this
, указывающего на объект, относительно которого вызываются эти методы. Такой способ использования this является фундаментальной характеристикой методов любого класса.
9.2. Классы и конструкторы
В примере 9.1 демонстрируется один из способов определения класса в языке JavaScript. Однако это не самый типичный способ, потому что он не связан с определением конструктора. Конструктор - это функция, предназначенная для инициализации вновь созданных объектов. Как описывалось в разделе 8.2.3, конструкторы вызываются с помощью ключевого слова
new
. Применение ключевого слова
new
при вызове конструктора автоматически создает новый объект, поэтому конструктору остается только инициализировать свойства этого нового объекта. Важной особенностью вызова конструктора является использование свойства
prototype
конструктора в качестве прототипа нового объекта. Это означает, что все объекты, созданные с помощью одного конструктора, наследуют один и тот же объект-прототип и, соответственно, являются членами одного и того же класса. В примере 9.2 демонстрируется, как можно было бы реализовать класс
range
, представленный в примере 9.1, не с помощью фабричной функции, а с помощью функции конструктора:
Пример 9.2. Реализация класса Range с помощью конструктора
// range2.js: Еще один класс, представляющий диапазон значений.
// Это функция-конструктор, которая инициализирует новые объекты Range.
// Обратите внимание, что она не создает и не возвращает объект.
// Она лишь инициализирует его.
function Range(from, to) {
// Сохранить начальное и конечное значения в новом объекте range.
//
Это не унаследованные свойства, и они являются уникальными для данного объекта,
this.from = from;
this.to = to;
}
// Все объекты Range наследуют свойства этого объекта.
// Обратите внимание, что свойство обязательно должно иметь имя "prototype".
Range.prototype = {
// Возвращает true, если х - объект класса range, в противном случае возвращает false
// Этот метод может работать не только с числовыми диапазонами, но также
// с текстовыми диапазонами и с диапазонами дат Date,
includes: function(x) { return this.from <= x && x <= this.to; },
// Вызывает f для каждого целого числа в диапазоне.
// Этот метод может работать только с числовыми диапазонами,
foreach: function(f) {
for(var х = Math.ceil(this.from); x <= this.to; x++) f(x);
},
// Возвращает строковое представление диапазона
toString: function { return "(" + this, from + "..." + this, to + ")"; }
};
// Ниже приводится пример использования объекта range.
var r = new Range(1.3); // Создать новый объект range
r.includes(2); // => true: число 2 входит в диапазон
r.foreach(console.log); // Выведет 1 2 3
console.log(r); // Выведет (1...3)
Имеет смысл детально сравнить примеры 9.1 и 9.2 и отметить различия между этими двумя способами определения классов. Во-первых, обратите внимание, что при преобразовании в конструктор фабричная функция
range
была переименована в
Range.
Это обычное соглашение по оформлению: функции-конструкторы в некотором смысле определяют классы, а имена классов начинаются с заглавных символов. Имена обычных функций и методов начинаются со строчных символов.
Далее отметьте, что конструктор
Range
вызывается (в конце примера) с ключевым словом
new
, тогда как фабричная функция
range
вызывается без него. В примере 9.1 для создания нового объекта использовался вызов обычной функции (раздел 8.2.1), а в примере 9.2 - вызов конструктора (раздел 8.2.3). Поскольку конструктор
Range
вызывается с ключевым словом
new
, отпадает необходимость вызывать функцию
inherit
или предпринимать какие-либо другие действия по созданию нового объекта. Новый объект создается автоматически перед вызовом конструктора и доступен в конструкторе как значение
this
. Конструктору
Range
остается лишь инициализировать его. Конструкторы даже не должны возвращать вновь созданный объект. Выражение вызова конструктора автоматически создает новый объект, вызывает конструктор как метод этого объекта и возвращает объект. Тот факт, что вызов конструктора настолько отличается от вызова обычной функции, является еще одной причиной, почему конструкторам принято давать имена, начинающиеся с заглавного символа. Конструкторы предназначены для вызова в виде конструкторов, с ключевым словом
new
, и обычно при вызове в виде обычных функций они не способны корректно выполнять свою работу. Соглашение по именованию конструкторов, обеспечивающее визуальное отличие имен конструкторов от имен обычных функций, помогает программистам не забывать использовать ключевое слово