як використовувати javascript Object.defineProperty


183

Я роздивився, як використовувати Object.definePropertyметод, але не зміг знайти нічого пристойного.

Хтось дав мені цей фрагмент коду :

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

Але я цього не розумію. В основному, getце те, чого я не можу отримати (каламбур призначений). Як це працює?


1
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… це чудовий підручник тут.
Martian2049

Відповіді:


499

Оскільки ви задали подібне запитання , давайте зробимо це покроково. Це трохи довше, але це може заощадити набагато більше часу, ніж я витратив на написання цього:

Властивість - це функція 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 гугл щось про анти-візерунки та запах коду . Це допоможе вам уникнути ситуацій на кшталт "О, мені потрібно повністю переписати свій код ще раз!" , це може заощадити місяці відчаю, якщо ви хочете багато кодувати. Удачі.


Ця порція зрозуміла. function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount>80) _discount = 80; } }); } 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
abu abu

27

get- це функція, яка викликається при спробі прочитати значення player.health, наприклад у:

console.log(player.health);

Це фактично не сильно відрізняється від:

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());

Встановлено протилежне значення get, яке буде використано при призначенні значення. Оскільки немає сетера, здається, що призначати здоров'я гравця не передбачається:

player.health = 5; // Doesn't do anything, since there is no set function defined

Дуже простий приклад:

var player = {
  level: 5
};

Object.defineProperty(player, "health", {
  get: function() {
    return 10 + (player.level * 15);
  }
});

console.log(player.health); // 85
player.level++;
console.log(player.health); // 100

player.health = 5; // Does nothing
console.log(player.health); // 100


це просто як функція, яку вам не потрібно насправді використовувати ()для виклику ... Я не розумію, яка була ідея, коли вони винайшли цю річ. Функції абсолютно однакові: jsbin.com/bugipi/edit?js,console,output
vsync

15

defineProperty - це метод на Object, який дозволяє налаштувати властивості для задоволення деяких критеріїв. Ось простий приклад із об'єктом співробітника з двома властивостями firstName & lastName та додайте два властивості, замінивши метод toString на об'єкті.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
employee.toString=function () {
    return this.firstName + " " + this.lastName;
};
console.log(employee.toString());

Ви отримаєте вихід у вигляді: Джеймел Мойден

Я збираюся змінити той самий код, використовуючи на об'єкті defineProperty

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(employee.toString());

Перший параметр - це ім'я об'єкта, а потім другий параметр - це ім'я властивості, яку ми додаємо, в нашому випадку це toString, а потім останній параметр - об’єкт json, який має значення, який буде функцією, і три параметри, записані, перелічувані і налаштовується. Я просто декларував все як правдиве.

Якщо ви запустите приклад, ви отримаєте результат як: Джеймел Мойден

Давайте розберемося, навіщо нам потрібні три властивості, такі як записаний, перелічуваний та настроюваний.

записаний

Однією з дуже дратівливих частин javascript є, якщо ви, наприклад, змінили властивість toString на щось інше

введіть тут опис зображення

якщо запустити це ще раз, все стає перервою. Давайте змінимо текст, записаний на помилковий. Якщо знову запустити те саме, ви отримаєте правильний вихід, як "Джеймел Мойден". Ця властивість не зможе пізніше перезаписати цю властивість.

численні

якщо ви надрукуєте всі ключі всередині об'єкта, ви зможете побачити всі властивості, включаючи toString.

console.log(Object.keys(employee));

введіть тут опис зображення

якщо ви встановите численні значення false, ви можете приховати властивістьString від усіх інших. Якщо запустити це ще раз, ви отримаєте firstName, lastName

настроюється

якщо хтось пізніше переосмислив об'єкт на наступний, наприклад, перелічує значення true та запустить його. Ви можете бачити, що властивістьString прийшло знову.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: false,
    enumerable: false,
    configurable: true
});

//change enumerable to false
Object.defineProperty(employee, 'toString', {

    enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));

введіть тут опис зображення

ви можете обмежити цю поведінку, встановивши налаштовується на false.

Оригінальне посилання на цю інформацію з мого особистого блогу


1
Я розумію, що ви це мали у своєму блозі та просто вставили його сюди, але принаймні знайте це на майбутнє: екранні екрани не користуються популярністю на SO. Ви не можете скопіювати код пасти, щоб спробувати його, і код не буде розглядатися пошуковими системами чи допоміжними технологіями.
Доміно

@JacqueGoupil Ви маєте рацію. Я буду оновлювати додавання коду замість знімка екрана
Code-EZ

3

В основному, definePropertyце метод, який бере 3 параметри - об'єкт, властивість та дескриптор. У цьому конкретному дзвінку відбувається те, що "health"властивість playerоб'єкта присвоюється в 10 плюс 15 разів вище рівня об'єкта гравця.


0

так, не більше функцій, що розширюються для налаштування налаштувань і getter, це мій приклад Object.defineProperty (obj, ім'я, функція)

var obj = {};
['data', 'name'].forEach(function(name) {
    Object.defineProperty(obj, name, {
        get : function() {
            return 'setter & getter';
        }
    });
});


console.log(obj.data);
console.log(obj.name);

0

Object.defineProperty () - це глобальна функція. Він не доступний всередині функції, яка оголошує об'єкт інакше. Вам доведеться використовувати його статично ...


0

Підсумок:

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
});

Object.definePropertyвикористовується для створення нової властивості на об’єкті програвача. Object.definePropertyце функція, яка споконвічно присутня в середовищі виконання JS і приймає такі аргументи:

Object.defineProperty(obj, prop, descriptor)

  1. Об'єкт , на якому ми хочемо , щоб визначити нову властивість
  2. Ім'я нового властивості ми хочемо визначити
  3. об’єкт дескриптора

Об'єкт дескриптора - цікава частина. Тут ми можемо визначити наступні речі:

  1. Налаштовується <boolean> : Якщо true дескриптор властивості може бути змінений, а властивість може бути видалена з об'єкта. Якщо настроюваним є falseвластивості дескриптора, які передаються, Object.definePropertyнеможливо змінити.
  2. Запис <boolean> : Якщо trueвластивість може бути перезаписана за допомогою оператора призначення.
  3. Численні <boolean> : Якщо true властивість можна повторити в for...inциклі. Також при використанні Object.keysфункції клавіша буде присутня. Якщо властивість така, falseвони не повторяться за допомогою for..inциклу та не відображатимуться під час використання Object.keys.
  4. get <function> : Функція, яка викликається, коли потрібна властивість. Замість того, щоб давати пряме значення, ця функція називається, а повернене значення задається як значення властивості
  5. set <function> : Функція, яка викликається, коли присвоюється властивість. Замість встановлення прямого значення ця функція викликається, а повернене значення використовується для встановлення значення властивості.

Приклад:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}


0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font


0

Визначає нову властивість безпосередньо на об'єкті або модифікує існуючу властивість на об'єкті та повертає об’єкт.

Примітка: Цей метод ви викликаєте безпосередньо в конструкторі Object, а не на екземплярі типу Object.

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

введіть тут опис зображення

Просте пояснення щодо визначення властивості.

Приклад коду: https://jsfiddle.net/manoj_antony32/pu5n61fs/


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.