<b> }</b>
<b> // Создать перечислимые, ненастраиваемые свойства с методами доступа</b>
<b> Object.defineProperties(this, {</b>
<b> from: {getigetFrom, </b>
<b> set:setFrom, </b>
<b> enumerable:true,</b>
<b> configurable:false},</b>
<b> to: { get: getTo,</b>
<b> set: setTo,</b>
<b> enumerable:true,</b>
<b> configurable:false }</b>
<b> });</b>
<b>}</b>
<b>// Настройка объекта-прототипа осталась такой же, как и в предыдущих примерах.</b>
<b>// Обращение к методам экземпляров чтения свойств from и to выполняется так,</b>
<b>// как если бы они были простыми свойствами.</b>
<b>Range.prototype = hideProps({</b>
<b> constructor: Range,</b>
<b> includes: function(x) {</b>
<b> return this.from <= x && x <= this.to: },</b>
<b> foreach: function(f) {</b>
<b> for(var x=Math.ceil(this.from);x<=this.to;x++) f(x);},</b>
<b> toString: function() { return "(" + this, from + ”..." + this, to + ")"; }</b>
<b>});</b>
9.8.4. Предотвращение расширения класса
Возможность расширения классов за счет добавления новых методов в объект-прототип обычно рассматривается как характерная особенность языка JavaScript. Стандарт ECMAScript 5 позволяет при желании предотвратить такую возможность. Функция
<b>Object.preventExtensions()</b>
делает объект нерасширяемым (раздел 6.8.3) - в такой объект невозможно добавить новые свойства. Функция
<b>Object.seal()</b>
идет еще дальше: она не только предотвращает добавление новых свойств, но и делает все имеющиеся свойства ненастраиваемыми, предотвращая возможность их удаления. (Однако ненастраиваемое свойство по-прежнему может быть доступно для записи и по-прежнему может быть преобразовано в свойство, доступное только для чтения.) Чтобы предотвратить возможность расширения объекта
<b>Object.prototype</b>
, можно просто записать:
<b>Object.seal(Object.prototype);</b>
Другая динамическая особенность языка JavaScript - возможность замены методов объекта:
<b>var original_sort_method = Array.prototype.sort;</b>
<b>Array.prototype.sort = function() {</b>
<b> var start = new Date();</b>
<b> original_sort_method.apply(this, arguments);</b>
<b> var end = new Date();</b>
<b> console.log("Сортировка массива заняла " + (end - start) +</b>
<b> " миллисекунд.");</b>
<b>};</b>
Предотвратить такую замену можно, объявив методы экземпляров доступными только для чтения. Сделать это можно с помощью вспомогательной функции
<b>freezeProps(),</b>
объявленной выше. Другой способ добиться этого эффекта заключается в использовании функции
<b>Object.freeze(),</b>
которая выполняет те же действия, что и функция
<b>Object.seal(),</b>
и дополнительно делает все свойства ненастраиваемыми и доступными только для чтения.
Свойства, доступные только для чтения, обладают одной особенностью, о которой необходимо помнить при работе с классами. Если объект
<b>о</b>
наследует свойство
<b>р</b>
, доступное только для чтения, попытка присвоить значение свойству
<b>о.р</b>
будет завершаться неудачей без создания нового свойства в объекте
<b>о</b>
. Если потребуется переопределить унаследованное свойство, доступное только для чтения, можно воспользоваться функциями
<b>Object.defineProperty(), Object.defineProperties()</b>
или
<b>Object.create(),</b>
чтобы создать новое свойство. Это означает, что, когда методы экземпляров класса делаются доступными только для чтения, это существенно осложняет возможность их переопределения в подклассах.
На практике обычно не требуется блокировать возможность изменения объектов-прототипов таким способом, но в некоторых случаях предотвращение расширения объектов может оказаться полезным. Вспомните фабричную функцию
<b>enumeration()</b>
из примера 9.7. Она сохраняет все экземпляры перечислений в свойствах объекта-прототипа и в свойстве-массиве
<b>values</b>
конструктора. Эти свойства и массив играют роль официального перечня экземпляров перечислений, и их определенно имеет смысл зафиксировать, чтобы исключить возможность добавления новых экземпляров и изменения или удаления существующих. Для этого достаточно добавить в функцию
<b>enumeration()</b>
следующие строки:
<b>Object.freeze(enumeration.values);</b>
<b>Object.freeze(enumeration);</b>
Обратите внимание, что применение функции
<b>Object.freeze()</b>
к типу перечисления исключает возможность использования свойства
<b>objectId</b>
, как было показано в примере 9.17. Решение этой проблемы состоит в том, чтобы прочитать значение свойства
<b>objectId</b>
(вызвать соответствующий метод чтения и установить внутреннее свойство) перечисления только один раз, перед тем как его зафиксировать.