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
, и обычно при вызове в виде обычных функций они не способны корректно выполнять свою работу. Соглашение по именованию конструкторов, обеспечивающее визуальное отличие имен конструкторов от имен обычных функций, помогает программистам не забывать использовать ключевое слово
new
.
Еще одно важное отличие между примерами 9.1 и 9.2 заключается в способе именования объекта-прототипа. В первом примере прототипом было свойство
range.methods
. Это было удобное, описательное имя, но в значительной мере произвольное. Во втором примере прототипом является свойство
Range.prototype
, и это имя является обязательным. Выражение вызова конструктора
Range
автоматически использует свойство
Range.prototype
как прототип нового объекта
Range
.
Наконец, обратите также внимание на одинаковые фрагменты примеров 9.1 и 9.2: в обоих классах методы объекта range определяются и вызываются одинаковым способом.
9.2.1. Конструкторы и идентификация класса
Как видите, объект-прототип играет чрезвычайно важную роль в идентификации класса: два объекта являются экземплярами одного класса, только если они наследуют один и тот же объект-прототип. Функция-конструктор, инициализирующая свойства нового объекта, не является определяющей: два конструктора могут иметь свойства
prototype
,
ссылающиеся на один объект-прототип. В этом случае оба конструктора будут создавать экземпляры одного и того же класса.
Хотя конструкторы не играют такую же важную роль в идентификации класса, как прототипы, тем не менее конструкторы выступают в качестве фасада класса. Например, имя конструктора обычно используется в качестве имени класса. Так, принято говорить, что конструктор
Range
создает объекты класса
Range
. Однако более важным применением конструкторов является их использование в операторе
instanceof
при проверке принадлежности объекта классу. Если имеется объект r, и необходимо проверить, является ли он объектом класса
Range
, такую проверку можно выполнить так:
r instanceof Range // вернет true, если r наследует Range.prototype
В действительности оператор
instanceof
не проверяет, был ли объект r инициализирован конструктором
Range
. Он проверяет, наследует ли этот объект свойство
Range.prototype
. Как бы то ни было, синтаксис оператора
instanceof
закрепляет использование конструкторов в качестве идентификаторов классов. Мы еще встретимся с оператором
instanceof
далее в этой главе.
9.2.2. Свойство constructor
В примере 9.2 свойству
Range.prototype
присваивался новый объект, содержащий методы класса. Хотя было удобно определить методы как свойства единственного объекта-литерала, но при этом совершенно не было необходимости создавать новый объект. Роль конструктора в языке JavaScript может играть любая функция, поскольку выражению вызова конструктора необходимо лишь свойство
рrototype
. Следовательно, любая функция (кроме функций, возвращаемых методом
Function.bind
в ECMAScript 5) автоматически получает свойство
prototype
. Значением этого свойства является объект, который имеет единственное неперечислимое свойство
constructor
. Значением свойства
constructor
является объект функции:
var F = function {};
// Это объект функции.
var р = F.prototype;
// Это объект-прототип, связанный с ней.
var c = p.constructor;
// Это функция, связанная с прототипом.
c === F
// => true: F.prototype.constructor === F для всех функций
Наличие предопределенного объекта-прототипа со свойством
constructor
означает, что объекты обычно наследуют свойство
constructor
, которое ссылается на их конструкторы. Поскольку конструкторы играют роль идентификаторов классов, свойство
constructor
определяет класс объекта:
var о = new F; // Создать объект класса F
о.constructor === F // => true: свойство constructor определяет класс