Поскольку вы задали похожий вопрос, давайте рассмотрим его шаг за шагом. Это немного длиннее, но может сэкономить вам гораздо больше времени, чем я потратил на написание этого:
Property — это объектно-ориентированная функция, предназначенная для четкого разделения клиентского кода. Например, в каком-то интернет-магазине у вас могут быть такие объекты:
function Product(name,price) {
this.name = name;
this.price = price;
this.discount = 0;
}
var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10); // {name:"T-shirt",price:10,discount:0}
Затем в своем клиентском коде (интернет-магазине) вы можете добавить скидки на свои товары:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Позже владелец интернет-магазина может понять, что скидка не может превышать, скажем, 80%. Теперь вам нужно найти КАЖДОЕ появление изменения скидки в коде клиента и добавить строку
if(obj.discount>80) obj.discount = 80;
Затем владелец интернет-магазина может изменить свою стратегию, например, если клиент является реселлером, максимальная скидка может составлять 90%. И вам нужно снова вносить изменения в нескольких местах, плюс вам нужно помнить об изменении этих строк каждый раз, когда меняется стратегия. Это плохой дизайн. Вот почему инкапсуляция является основным принципом ООП. Если бы конструктор был таким:
function Product(name,price) {
var _name=name, _price=price, _discount=0;
this.getName = function() { return _name; }
this.setName = function(value) { _name = value; }
this.getPrice = function() { return _price; }
this.setPrice = function(value) { _price = value; }
this.getDiscount = function() { return _discount; }
this.setDiscount = function(value) { _discount = value; }
}
Затем вы можете просто изменить методы getDiscount
(accessor) и setDiscount
(mutator). Проблема в том, что большинство элементов ведут себя как обычные переменные, вот только скидка требует особого внимания. Но хороший дизайн требует инкапсуляции каждого члена данных, чтобы код оставался расширяемым. Поэтому вам нужно добавить много кода, который ничего не делает. Это тоже плохой дизайн, шаблонный антишаблон. Иногда вы не можете просто преобразовать поля в методы позже (код интернет-магазина может стать большим или какой-то сторонний код может зависеть от старой версии), поэтому шаблон здесь меньшее зло. Но все же это зло. Вот почему свойства были введены во многие языки. Вы можете оставить исходный код, просто преобразовав член скидки в свойство с блоками get
и set
:
function Product(name,price) {
this.name = name;
this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
var _discount; // private member
Object.defineProperty(this,"discount",{
get: function() { return _discount; },
set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
});
}
// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called
Обратите внимание на предпоследнюю строку: ответственность за правильное значение скидки была перенесена из кода клиента (определение интернет-магазина) в определение продукта. Продукт отвечает за согласованность своих элементов данных. Хороший дизайн (грубо говоря), если код работает так же, как наши мысли.
Так много о свойствах. Но javascript отличается от чистых объектно-ориентированных языков, таких как C#, и кодирует функции по-другому:
В C# преобразование полей в свойства — это критическое изменение, поэтому общедоступные поля должны быть закодированы как Автоматически реализуемые свойства, если ваш код может использоваться в отдельно скомпилированном клиенте.
В Javascript стандартные свойства (элемент данных с геттером и сеттером, описанным выше) определяются дескриптором доступа (в ссылке, которая есть в вашем вопросе). Исключительно вы можете использовать дескриптор данных (поэтому вы не можете использовать, например, value и set для одного и того же свойства):
- accessor descriptor = get + set (see the example above)
- get must be a function; its return value is used in reading the property; if not specified, the default is undefined, which behaves like a function that returns undefined
- set должен быть функцией; его параметр заполняется RHS при присвоении значения свойству; если не указано, по умолчанию используется значение undefined, которое ведет себя как пустая функция.
- data descriptor = value + writable (see the example below)
- value default undefined; if writable, configurable and enumerable (see below) are true, the property behaves like an ordinary data field
- доступно для записи — по умолчанию false; если не true, свойство доступно только для чтения; попытка записи игнорируется без ошибки*!
Оба дескриптора могут иметь следующие члены:
- настраиваемый — по умолчанию false; если это не так, свойство не может быть удалено; попытка удаления игнорируется без ошибки*!
- перечисляемый — по умолчанию false; если true, повторение будет через
for(var i in theObject)
; если false, он не будет повторяться, но по-прежнему доступен как общедоступный
* если только в строгом режиме - в этом case JS останавливает выполнение с ошибкой TypeError, если она не обнаружена в блокировка try-catch
Чтобы прочитать эти настройки, используйте Object.getOwnPropertyDescriptor()
.
Учитесь на примере:
var o = {};
Object.defineProperty(o,"test",{
value: "a",
configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings
for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable
Если вы не хотите разрешать такие читы клиентскому коду, вы можете ограничить объект тремя уровнями ограничения:
- Object.preventExtensions(yourObject) предотвращает добавление новых свойств в yourObject. Используйте
Object.isExtensible(<yourObject>)
, чтобы проверить, использовался ли этот метод для объекта. Предотвращение поверхностно (см. ниже).
- Object.seal(yourObject) то же, что и выше, и свойства нельзя удалить (фактически устанавливает
configurable: false
для всех свойств). Используйте Object.isSealed(<yourObject>)
, чтобы обнаружить эту функцию на объекте. Печать неглубокая (см. ниже).
- Object.freeze(вашОбъект) то же, что и выше, и свойства не могут быть изменены (фактически устанавливает
writable: false
для всех свойств с дескриптором данных). Доступное для записи свойство сеттера не затрагивается (поскольку у него его нет). Заморозка неглубокая: это означает, что если свойство является Object, его свойства НЕ заморожены (если хотите, вы должны выполнить что-то вроде глубокой заморозки, похожее на глубокое копирование - клонирование). Используйте Object.isFrozen(<yourObject>)
, чтобы обнаружить его.
Вам не нужно заморачиваться с этим, если вы напишете всего несколько забавных строчек. Но если вы хотите закодировать игру (как вы упомянули в связанном вопросе), вам следует позаботиться о хорошем дизайне. Попробуйте поискать в Google что-нибудь об антипаттернах и запахе кода. Это поможет вам избежать таких ситуаций, как О, мне снова нужно полностью переписать мой код!, это может избавить вас от месяцев отчаяния, если вы хотите писать много кода. Удачи.
person
Jan Turoň
schedule
01.09.2013