<b> var props = (arguments.length == 1) // Если один аргумент,</b>
<b> ? Object.getOwnPropertyNames(o) // изменить все свойства,</b>
<b> : Array.prototype.splice.call(arguments, 1);</b>
<b> // иначе только указанные</b>
<b> props.forEach(function(n) { // Делает каждое свойство ненастраиваемым</b>
<b> // и доступным только для чтения</b>
<b> // Пропустить ненастраиваемые свойства</b>
<b> if (!Object.getOwnPropertyDescriptor(o,n).configurable) return:</b>
<b> Object.defineProperty(o, n, { writable: false, configurable: false });</b>
<b> }):</b>
<b> return о; // Чтобы можно было продолжить работу с объектом о</b>
<b>}</b>
<b>// Делает неперечислимыми указанные (или все) свойства объекта о,</b>
<b>// если они доступны для настройки,</b>
<b>function hideProps(o) {</b>
<b> var props = (arguments.length == 1) // Если один аргумент,</b>
<b> ? Object.getOwnPropertyNames(o) // изменить все свойства,</b>
<b> : Array.prototype.splice.call(arguments, 1);</b>
<b> // иначе только указанные</b>
<b> props.forEach(function(n) { // Скрыть каждое от цикла for/in</b>
<b> // Пропустить ненастраиваемые свойства</b>
<b> if (!Object.getOwnPropertyDescriptor(o,n).configurable) return:</b>
<b> Object.defineProperty(o, n, { enumerable: false });</b>
<b> }):</b>
<b> return o;</b>
<b>}</b>
Функции
<b>Object.defineProperty()</b>
и
<b>Object.defineProperties()</b>
могут использоваться и для создания новых свойств, и для изменения атрибутов уже существующих свойств. При создании новых свойств все опущенные атрибуты по умолчанию принимают значение
<b>false</b>
. Однако при изменении атрибутов уже существующих свойств опущенные атрибуты не изменяются. Например, в функции
<b>hideProps()</b>
выше указывается только атрибут
<b>enumerable</b>
, потому что функция должна изменять только его.
С помощью этих двух функций можно писать определения классов с использованием преимуществ ECMAScript 5, без существенного изменения привычного стиля определения классов. В примере 9.20 приводится определение неизменяемого класса
<b>Range</b>
, в котором используются наши вспомогательные функции.
Пример 9.20. Более простое определение неизменяемого класса
<b>function Range(from, to) { // Конструктор неизменяемого класса Range</b>
<b> this.from = from;</b>
<b> this.to = to;</b>
<b> freezeProps(this); // Сделать свойства неизменяемыми</b>
<b>}</b>
<b>Range.prototype = hideProps({ // Определить неперечислимые свойства прототипа</b>
<b> constructor: Range,</b>
<b> includes: function(x) { return this.from <= x && x <= this.to; },</b>
<b> foreach: function(f) {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.3. Сокрытие данных объекта
В разделе 9.6.6 и в примере 9.10 было показано, как можно использовать переменные и аргументы функции-конструктора для сокрытия данных объекта, создаваемого этим конструктором. Недостаток этого приема заключается в том, что в ECMAScript 3 допускается возможность замещения методов доступа к этим данным. Стандарт ECMAScript 5 позволяет обеспечить более надежное сокрытие частных данных за счет определения методов доступа к свойствам, которые не могут быть удалены. Этот способ демонстрируется в примере 9.21.
Пример 9.21. Класс Range со строго инкапсулированными границами
<b>// Эта версия класса Range является изменяемой, но она следит за своими</b>
<b>// границами, обеспечивая выполнение условия from <= to.</b>
<b>function Range(from, to) {</b>
<b> // Проверить соблюдение условия при создании</b>
<b> if (from > to) throw new Error("Range: значение from должно быть <= to");</b>
<b> // Определение методов доступа, которые следят за соблюдением условия</b>
<b> function getFrom() { return from; }</b>
<b> function getTo() { return to; }</b>
<b> function setFrom(f) { // He позволяет устанавливать значение from > to</b>
<b> if (f <= to) from = f;</b>
<b> else throw new Error("Range: значение from должно быть <= to");</b>
<b> }</b>
<b> function setTo(t) { // He позволяет устанавливать значение to < from</b>
<b> if (t >= from) to = t;</b>
<b> else throw new Error("Range: значение to должно быть >= from");</b>