как использовать 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

Поскольку вы задали похожий вопрос , давайте рассмотрим его шаг за шагом. Это немного дольше, но это может сэкономить вам гораздо больше времени, чем я потратил на написание этого:

Свойство - это функция ООП, предназначенная для чистого разделения клиентского кода. Например, в каком-то интернет-магазине у вас могут быть такие объекты:

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>)чтобы обнаружить это.

Вам не нужно беспокоиться об этом, если вы напишите несколько забавных строк. Но если вы хотите написать код игры (как вы упомянули в связанном вопросе), вам следует позаботиться о хорошем дизайне. Попробуйте гуглить что-нибудь об антипаттернах и запахе кода . Это поможет вам избежать таких ситуаций, как «О, мне нужно полностью переписать мой код снова!» , это может сэкономить месяцы отчаяния, если вы хотите много кодировать. Удачи.

Ян Турош
источник
Эта часть понятна. 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
Абу Абу
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());

Вы получите результат как: Jameel Moideen

Я собираюсь изменить тот же код с помощью 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, значение которого будет функцией и три записываемых параметра, перечислимые и настраиваемый. Прямо сейчас я просто объявил все как правда.

Если вы запустите пример, вы получите вывод: Jameel Moideen

Давайте разберемся, почему нам нужны три свойства: записываемое, перечисляемое и настраиваемое.

записываемый

Одна из самых раздражающих частей javascript - если вы измените свойство toString на что-то другое, например

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

если вы запустите это снова, все станет перерывом. Давайте изменим запись на ложь. Если запустить то же самое снова, вы получите правильный вывод как «Jameel Moideen». Это свойство предотвратит перезапись этого свойства позже.

перечислимый

если вы напечатаете все ключи внутри объекта, вы сможете увидеть все свойства, включая toString.

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

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

если вы установите enumerable в false, вы можете скрыть свойство toString от всех остальных. Если запустить это снова, вы получите firstName, lastName

конфигурируемый

если кто-то позже переопределит объект позже, например, перечислим в true и запустим его. Вы можете видеть, что свойство toString пришло снова.

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.

Первоначальная ссылка на эту информацию из моего личного блога

Код-EZ
источник
1
Я понял, что вы написали это в своем блоге и просто вставили сюда, но, по крайней мере, знаете об этом на будущее: скриншоты не популярны в SO. Вы не можете скопировать код, чтобы попробовать его, и этот код не будет виден поисковым системам или вспомогательным технологиям.
Домино
@JacqueGoupil Вы правы. Я обновлю, добавив код вместо скриншота
Code-EZ
3

По сути, definePropertyэто метод, который принимает 3 параметра - объект, свойство и дескриптор. То, что происходит в этом конкретном вызове, - это "health"свойство playerобъекта, которое назначается в 10 плюс 15 раз выше уровня объекта игрока.

Коул Пилегард
источник
0

да нет больше функции расширения для установки и получения это мой пример Object.defineProperty (obj, name, func)

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 () - это глобальная функция. Она не доступна внутри функции, которая иначе объявляет объект. Вам придется использовать его статически ...

ISONecroMAn
источник
0

Резюме:

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

Object.definePropertyиспользуется для создания нового свойства объекта player. Object.definePropertyэто функция, которая изначально присутствует в среде выполнения JS и принимает следующие аргументы:

Object.defineProperty(obj, prop, descriptor)

  1. Объект , на котором мы хотим , чтобы определить новое свойство
  2. Имя нового свойства мы хотим определить
  3. объект дескриптора

Объект дескриптора является интересной частью. Здесь мы можем определить следующие вещи:

  1. настраиваемый <boolean> : если true дескриптор свойства может быть изменен, а свойство может быть удалено из объекта. Если настраивается, falseдескриптор свойства, которые передаются, Object.definePropertyне могут быть изменены.
  2. Доступно для записи <boolean> : если trueсвойство может быть перезаписано с помощью оператора присваивания.
  3. Enumerable <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/

Mano
источник
0

Object.defineProperty(Array.prototype, "last", {
  get: function() {
    if (this[this.length -1] == undefined) { return [] }
    else { return this[this.length -1] }
  }
});

console.log([1,2,3,4].last) //returns 4

Джейсон Энтони Й.
источник