Поскольку вы задали похожий вопрос , давайте рассмотрим его шаг за шагом. Это немного дольше, но это может сэкономить вам гораздо больше времени, чем я потратил на написание этого:
Свойство - это функция ООП, предназначенная для чистого разделения клиентского кода. Например, в каком-то интернет-магазине у вас могут быть такие объекты:
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 ). Проблема в том, что большинство участников ведут себя как общие переменные, просто скидка здесь требует особого внимания. Но хороший дизайн требует инкапсуляции каждого элемента данных, чтобы сохранить код расширяемым. Так что вам нужно добавить много кода, который ничего не делает. Это тоже плохой дизайн, шаблонный антипаттерн . Иногда вы не можете просто рефакторизовать поля для методов позже (код eshop может стать большим или какой-то сторонний код может зависеть от старой версии), поэтому шаблон здесь меньше зла. Но все же это зло. Вот почему свойства были введены во многих языках. Вы можете сохранить оригинальный код, просто превратив член скидки в собственность с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 стандартные свойства (элемент данных с геттером и сеттером, описанным выше) определяются дескриптором доступа (по ссылке, которая у вас есть в вашем вопросе). Исключительно, вы можете использовать дескриптор данных (так что вы не можете использовать т.е. значение и установить одно и то же свойство):
- дескриптор доступа = get + set (см. пример выше)
- get должен быть функцией; его возвращаемое значение используется при чтении свойства; если не указан, по умолчанию не определено , что ведет себя как функция, которая возвращает неопределенный
- set должен быть функцией; его параметр заполняется RHS при присвоении значения свойству; если не указан, по умолчанию не определено , что ведет себя как пустая функция
- дескриптор данных = значение + доступный для записи (см. пример ниже)
- значение по умолчанию не определено ; если значение доступно для записи , настройки и перечисления (см. ниже), свойство ведет себя как обычное поле данных
- доступный для записи - по умолчанию false ; если не верно , свойство доступно только для чтения; попытка записи игнорируется без ошибок *!
Оба дескриптора могут иметь следующие члены:
- настраиваемый - по умолчанию false ; если не истина, свойство не может быть удалено; попытка удаления игнорируется без ошибок *!
- enumerable - по умолчанию false ; если true, он будет повторен в
for(var i in theObject)
; если false, он не будет повторяться, но все еще доступен как общедоступный
* если не в строгом режиме - в этом случае 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 (yourObject) такой же, как и выше, и свойства не могут быть изменены (фактически устанавливаются
writable: false
для всех свойств с дескриптором данных). На доступное для записи свойство Setter не влияет (так как у него его нет). Замораживание является мелким : это означает, что если свойство является объектом, его свойства НЕ заморожены (если вы хотите, вы должны выполнить что-то вроде «глубокого замораживания», аналогично клонированию с глубоким копированием ). Используйте,Object.isFrozen(<yourObject>)
чтобы обнаружить это.
Вам не нужно беспокоиться об этом, если вы напишите несколько забавных строк. Но если вы хотите написать код игры (как вы упомянули в связанном вопросе), вам следует позаботиться о хорошем дизайне. Попробуйте гуглить что-нибудь об антипаттернах и запахе кода . Это поможет вам избежать таких ситуаций, как «О, мне нужно полностью переписать мой код снова!» , это может сэкономить месяцы отчаяния, если вы хотите много кодировать. Удачи.