как использовать javascript Object.defineProperty

Я искал, как использовать Object.defineProperty метод, но ничего приличного не нашел.

Кто-то дал мне этот фрагмент кода:

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

Но я этого не понимаю. В основном, get - это то, что я не могу получить (каламбур). Как это работает?


person Math chiller    schedule 30.08.2013    source источник
comment
developer.mozilla.org/en-US/ docs/Web/JavaScript/Reference/ это отличный учебник.   -  person Martian2049    schedule 04.03.2018


Ответы (8)


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

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
comment
Эта часть ясна. 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 - person abu abu; 19.04.2018

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

person Paul    schedule 30.08.2013
comment
это похоже на функцию, для вызова которой вам не нужно использовать ()... Я не понимаю, в чем заключалась идея, когда они изобрели эту штуку. Функции полностью одинаковы: jsbin.com/bugipi/edit?js,console,output< /а> - person vsync; 06.08.2015

defineProperty — это метод объекта, который позволяет настраивать свойства в соответствии с некоторыми критериями. Вот простой пример с объектом сотрудника с двумя свойствами 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 на что-то другое, например

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

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

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

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

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

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

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

настраиваемый

если кто-то позже переопределил объект позже, например, enumerable в 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));

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

вы можете ограничить это поведение, установив для параметра configurable значение false.

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

person Code-EZ    schedule 27.05.2017
comment
Я понимаю, что у вас было это в вашем блоге и вы просто вставили его сюда, но, по крайней мере, знайте это на будущее: скриншоты не популярны на SO. Вы не можете скопировать код, чтобы попробовать его, и код не будет виден поисковыми системами или вспомогательными технологиями. - person Domino; 18.09.2017
comment
@JacqueGoupil Вы правы. Я обновлю, добавив код вместо снимка экрана. - person Code-EZ; 18.09.2017

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

person Cole Pilegard    schedule 30.08.2013

да больше никаких расширений функций для установщика и геттера установки это мой пример 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);
person Faizal Pribadi    schedule 30.07.2014

Object.defineProperty() - это глобальная функция. Она недоступна внутри функции, которая в противном случае объявляет объект. Вам придется использовать ее статически...

person ISONecroMAn    schedule 26.01.2015

Резюме:

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, дескриптор свойства может быть изменен, а свойство может быть удалено из объекта. Если параметр configurable равен 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
}

person Willem van der Veen    schedule 12.09.2018

Определяет новое свойство непосредственно в объекте или изменяет существующее свойство объекта и возвращает объект.

Примечание. Этот метод вызывается непосредственно в конструкторе объекта, а не в экземпляре типа объекта.

   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/

person Mano    schedule 30.07.2019