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