Learn JavaScript

Object.defineProperties() - метод для настройки нескольких свойств объекта в JavaScript

Object.defineProperties() создаёт или перенастраивает сразу несколько свойств объекта.

Если Object.defineProperty() работает с одним свойством, то этот метод делает то же самое для набора свойств за один вызов.

Синтаксис

Object.defineProperties(obj, descriptors)

obj - объект, который нужно изменить.

descriptors - объект, где ключи это имена свойств, а значения - их дескрипторы.

Что делает Object.defineProperties()

Метод берёт объект с описанием свойств и применяет эти настройки к целевому объекту.

const user = {};

Object.defineProperties(user, {
  name: {
    value: "Anna",
    enumerable: true,
  },
  age: {
    value: 25,
    enumerable: true,
  },
});

console.log(user); // { name: "Anna", age: 25 }

Что возвращает метод

Object.defineProperties() возвращает тот же самый объект.

const user = {};

const result = Object.defineProperties(user, {
  name: {
    value: "Anna",
  },
});

console.log(result === user); // true

Это важно: метод не создаёт новый объект, а изменяет существующий.

Объект дескрипторов

Главная особенность метода в том, что второй аргумент - это не просто объект со значениями, а объект с дескрипторами.

const user = {};

Object.defineProperties(user, {
  name: {
    value: "Anna",
    writable: true,
    enumerable: true,
    configurable: true,
  },
});

Для каждого свойства можно задать те же настройки, что и в Object.defineProperty().

value, writable, enumerable, configurable

Для обычных свойств можно использовать:

  • value
  • writable
  • enumerable
  • configurable

Пример:

const settings = {};

Object.defineProperties(settings, {
  theme: {
    value: "dark",
    writable: true,
    enumerable: true,
    configurable: true,
  },
  version: {
    value: 1,
    writable: false,
    enumerable: true,
    configurable: false,
  },
});

По умолчанию многие флаги равны false

Это тот же подводный камень, что и у Object.defineProperty().

Если не указать writable, enumerable и configurable, они будут false.

const user = {};

Object.defineProperties(user, {
  name: {
    value: "Anna",
  },
});

user.name = "Ivan";

console.log(user.name); // "Anna"
console.log(Object.keys(user)); // []

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

Getter и setter

Через Object.defineProperties() тоже можно создавать accessor properties.

const user = {
  firstName: "Anna",
  lastName: "Smith",
};

Object.defineProperties(user, {
  fullName: {
    get() {
      return `${this.firstName} ${this.lastName}`;
    },
    set(value) {
      const [first, last] = value.split(" ");
      this.firstName = first;
      this.lastName = last;
    },
    enumerable: true,
    configurable: true,
  },
});

console.log(user.fullName); // "Anna Smith"

Нельзя смешивать value/writable и get/set

Для одного свойства нужно выбрать один тип дескриптора:

  • либо обычный через value
  • либо accessor property через get и set

Такой вариант недопустим:

Object.defineProperties(obj, {
  name: {
    value: "Anna",
    get() {
      return "Ivan";
    },
  },
});

Object.defineProperties() и Object.defineProperty()

Эти методы очень похожи:

  • Object.defineProperty() работает с одним свойством
  • Object.defineProperties() работает сразу с несколькими
const user = {};

Object.defineProperty(user, "name", {
  value: "Anna",
});
const user = {};

Object.defineProperties(user, {
  name: {
    value: "Anna",
  },
  age: {
    value: 25,
  },
});

Если нужно одно свойство, обычно проще первый метод.

Если нужно несколько - второй удобнее.

Проверка через Object.getOwnPropertyDescriptors()

После настройки свойств очень удобно смотреть результат через:

const descriptors = Object.getOwnPropertyDescriptors(user);

Так можно сразу увидеть, какие флаги и геттеры/сеттеры реально установились.

Точное клонирование объекта

Object.defineProperties() часто используют вместе с Object.getOwnPropertyDescriptors().

const clone = Object.defineProperties(
  {},
  Object.getOwnPropertyDescriptors(source),
);

Это полезно, когда нужно скопировать:

  • неперечисляемые свойства
  • getter/setter
  • настройки writable, enumerable, configurable

Такой способ точнее, чем Object.assign() или spread.

Массивы тоже работают

Массив - это тоже объект, поэтому метод работает и с ним.

const arr = ["a", "b"];

Object.defineProperties(arr, {
  0: {
    value: "x",
  },
});

console.log(arr); // ["x", "b"]

Но с массивами таким способом обычно работают осторожно.

Когда это полезно

Object.defineProperties() удобно использовать, когда:

  • нужно настроить сразу несколько свойств объекта
  • нужно добавить сразу несколько getter/setter
  • нужно восстановить объект из набора дескрипторов
  • нужно сделать точную настройку свойств за один вызов

Частые ошибки

Думать, что второй аргумент - это просто объект значений

const user = {};

Object.defineProperties(user, {
  name: "Anna",
});

Для каждого свойства нужен именно дескриптор.

Забывать, что writable/enumerable/configurable по умолчанию false

const user = {};

Object.defineProperties(user, {
  name: {
    value: "Anna",
  },
});

console.log(Object.keys(user)); // []

Это частый источник путаницы.

Думать, что метод создаёт новый объект

const user = {};

const result = Object.defineProperties(user, {
  name: {
    value: "Anna",
  },
});

console.log(result === user); // true

Метод меняет существующий объект.

Быстрая памятка

  • Object.defineProperties() создаёт или перенастраивает сразу несколько свойств
  • метод возвращает тот же самый объект
  • второй аргумент - это объект дескрипторов
  • через него можно задавать value, writable, enumerable, configurable, get, set
  • по умолчанию многие флаги будут false

Коротко

Object.defineProperties() - это способ настроить сразу несколько свойств объекта за один вызов. Главное, что стоит запомнить: метод работает через дескрипторы, а не через обычные значения, и особенно полезен там, где нужно точно контролировать поведение нескольких свойств сразу.