<b> // Возвращает строковое представление диапазона</b>
<b> toString: function() { return "(" + this, from + "..." + this, to + ")"; }</b>
<b>};</b>
<b>// Ниже приводится пример использования объекта range.</b>
<b>var r = new Range(1.3); // Создать новый объект range</b>
<b>r.includes(2); // => true: число 2 входит в диапазон</b>
<b>r.foreach(console.log); // Выведет 1 2 3</b>
<b>console.log(r); // Выведет (1...3)</b>
Имеет смысл детально сравнить примеры 9.1 и 9.2 и отметить различия между этими двумя способами определения классов. Во-первых, обратите внимание, что при преобразовании в конструктор фабричная функция
<b>range()</b>
была переименована в
<b>Range().</b>
Это обычное соглашение по оформлению: функции-конструкторы в некотором смысле определяют классы, а имена классов начинаются с заглавных символов. Имена обычных функций и методов начинаются со строчных символов.
Далее отметьте, что конструктор
<b>Range()</b>
вызывается (в конце примера) с ключевым словом
<b>new</b>
, тогда как фабричная функция
<b>range()</b>
вызывается без него. В примере 9.1 для создания нового объекта использовался вызов обычной функции (раздел 8.2.1), а в примере 9.2 - вызов конструктора (раздел 8.2.3). Поскольку конструктор
<b>Range()</b>
вызывается с ключевым словом
<b>new</b>
, отпадает необходимость вызывать функцию
<b>inherit()</b>
или предпринимать какие-либо другие действия по созданию нового объекта. Новый объект создается автоматически перед вызовом конструктора и доступен в конструкторе как значение
<b>this</b>
. Конструктору
<b>Range()</b>
остается лишь инициализировать его. Конструкторы даже не должны возвращать вновь созданный объект. Выражение вызова конструктора автоматически создает новый объект, вызывает конструктор как метод этого объекта и возвращает объект. Тот факт, что вызов конструктора настолько отличается от вызова обычной функции, является еще одной причиной, почему конструкторам принято давать имена, начинающиеся с заглавного символа. Конструкторы предназначены для вызова в виде конструкторов, с ключевым словом
<b>new</b>
, и обычно при вызове в виде обычных функций они не способны корректно выполнять свою работу. Соглашение по именованию конструкторов, обеспечивающее визуальное отличие имен конструкторов от имен обычных функций, помогает программистам не забывать использовать ключевое слово
<b>new</b>
.
Еще одно важное отличие между примерами 9.1 и 9.2 заключается в способе именования объекта-прототипа. В первом примере прототипом было свойство
<b>range.methods</b>
. Это было удобное, описательное имя, но в значительной мере произвольное. Во втором примере прототипом является свойство
<b>Range.prototype</b>
, и это имя является обязательным. Выражение вызова конструктора
<b>Range()</b>
автоматически использует свойство
<b>Range.prototype</b>
как прототип нового объекта
<b>Range</b>
.
Наконец, обратите также внимание на одинаковые фрагменты примеров 9.1 и 9.2: в обоих классах методы объекта range определяются и вызываются одинаковым способом.
9.2.1. Конструкторы и идентификация класса
Как видите, объект-прототип играет чрезвычайно важную роль в идентификации класса: два объекта являются экземплярами одного класса, только если они наследуют один и тот же объект-прототип. Функция-конструктор, инициализирующая свойства нового объекта, не является определяющей: два конструктора могут иметь свойства
<b>prototype</b>
, ссылающиеся на один объект-прототип. В этом случае оба конструктора будут создавать экземпляры одного и того же класса.
Хотя конструкторы не играют такую же важную роль в идентификации класса, как прототипы, тем не менее конструкторы выступают в качестве фасада класса. Например, имя конструктора обычно используется в качестве имени класса. Так, принято говорить, что конструктор
<b>Range()</b>
создает объекты класса
<b>Range</b>
. Однако более важным применением конструкторов является их использование в операторе
<b>instanceof</b>
при проверке принадлежности объекта классу. Если имеется объект r, и необходимо проверить, является ли он объектом класса
<b>Range</b>
, такую проверку можно выполнить так:
<b>r instanceof Range // вернет true, если r наследует Range.prototype</b>
В действительности оператор
<b>instanceof</b>
не проверяет, был ли объект r инициализирован конструктором
<b>Range</b>
. Он проверяет, наследует ли этот объект свойство
<b>Range.prototype</b>
. Как бы то ни было, синтаксис оператора
<b>instanceof</b>
закрепляет использование конструкторов в качестве идентификаторов классов. Мы еще встретимся с оператором
<b>instanceof</b>
далее в этой главе.
9.2.2. Свойство constructor
В примере 9.2 свойству
<b>Range.prototype</b>
присваивался новый объект, содержащий методы класса. Хотя было удобно определить методы как свойства единственного объекта-литерала, но при этом совершенно не было необходимости создавать новый объект. Роль конструктора в языке JavaScript может играть любая функция, поскольку выражению вызова конструктора необходимо лишь свойство
<b>рrototype</b>
. Следовательно, любая функция (кроме функций, возвращаемых методом
<b>Function.bind()</b>
в ECMAScript 5) автоматически получает свойство
<b>prototype</b>
. Значением этого свойства является объект, который имеет единственное неперечислимое свойство
<b>constructor</b>
. Значением свойства
<b>constructor</b>
является объект функции:
<b>var F = function() {}; </b>
<b>// Это объект функции.</b>