Оскільки ви задали подібне запитання , давайте зробимо це покроково. Це трохи довше, але це може заощадити набагато більше часу, ніж я витратив на написання цього:
Властивість - це функція OOP, призначена для чіткого розділення коду клієнта. Наприклад, у деяких електронних магазинах у вас можуть бути такі об’єкти:
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 стандартні властивості (член даних із getter та setter, описаними вище) визначаються дескриптором доступу (у посиланні, яке ви маєте у своєму запитанні). Виключно ви можете використовувати дескриптор даних (тому ви не можете використовувати тобто значення та встановити на одній властивості):
- дескриптор accessor = get + set (див. приклад вище)
- get повинен бути функцією; його повернене значення використовується при читанні властивості; якщо не вказано, за замовчуванням не визначено , що поводиться як функція, що повертає невизначено
- набір повинен бути функцією; його параметр заповнюється RHS при призначенні значення властивості; якщо не вказано, за замовчуванням не визначено , що поводиться як порожня функція
- дескриптор даних = значення + запис (див. приклад нижче)
- значення за замовчуванням невизначене ; якщо записані , налаштовані та перелічені (див. нижче) вірні, властивість поводиться як звичайне поле даних
- для запису - false за замовчуванням; якщо неправда , властивість читається лише; спроба запису ігнорується без помилки *!
Обидва дескриптори можуть мати цих членів:
- конфігурується -замовчуванням брехня ; якщо це неправда, властивість не можна видалити; спроба видалення ігнорується без помилки *!
- перелічувальний - за замовчуванням false ; якщо це правда, вона буде повторена
for(var i in theObject)
; якщо помилково, воно не буде повтореним, але воно все ще доступне як публічне
* за винятком суворого режиму - у такому випадку 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) запобігає доданню нових властивостей у вашObject . Використовуйте,
Object.isExtensible(<yourObject>)
щоб перевірити, чи використовувався метод на об'єкті. Профілактика неглибока (читайте нижче).
- Object.seal (yourObject), як і вище, і властивості не можна видалити (ефективно встановлюється
configurable: false
на всі властивості). ВикористовуйтеObject.isSealed(<yourObject>)
для виявлення цієї функції на об'єкті. Печатка неглибока (читайте нижче).
- Object.freeze (yourObject), як і вище, і властивості не можна змінювати (ефективно встановлює
writable: false
всі властивості з дескриптором даних). Властивість для запису Setter не впливає (оскільки вона не має). Заморожування є неглибоким : це означаєщо якщо властивість об'єкт, його властивості не заморожені (якщо ви хочете, ви повинні виконати щощось на зразок «глибокої заморозки», схоже на глибоку копію - клонування ). ВикористовуйтеObject.isFrozen(<yourObject>)
для його виявлення.
Вам не потрібно зациклюватися на цьому, якщо ви пишете лише кілька рядків весело. Але якщо ви хочете кодувати гру (як ви згадали у зв'язаному питанні), вам слід дуже подбати про хороший дизайн. Спробуйте в Google гугл щось про анти-візерунки та запах коду . Це допоможе вам уникнути ситуацій на кшталт "О, мені потрібно повністю переписати свій код ще раз!" , це може заощадити місяці відчаю, якщо ви хочете багато кодувати. Удачі.