Существует два типа свойств объекта.
Первый вид — это свойства данных . Мы уже знаем, как с ними работать. Все свойства, которые мы использовали до сих пор, были свойствами данных.
Второй тип недвижимости – это нечто новое. Это свойство-аксессор . По сути, это функции, которые выполняются при получении и установке значения, но для внешнего кода выглядят как обычные свойства.
Свойства аксессора представлены методами «getter» и «setter». В литерале объекта они обозначаются get
и set
:
пусть объект = { получить propName() { // геттер, код, выполняемый при получении obj.propName }, установить propName(значение) { // установщик, код, выполняемый при установке obj.propName = value } };
Геттер срабатывает при чтении obj.propName
, сеттер — при его назначении.
Например, у нас есть объект user
с name
и surname
:
пусть пользователь = { имя: «Джон», фамилия: «Смит» };
Теперь мы хотим добавить свойство fullName
, которое должно быть "John Smith"
. Конечно, мы не хотим копировать и вставлять существующую информацию, поэтому можем реализовать ее как метод доступа:
пусть пользователь = { имя: «Джон», фамилия: «Смит», получить полное имя() { return `${this.name} ${this.surname}`; } }; оповещение(user.fullName); // Джон Смит
Внешне свойство-аксессор выглядит как обычное. В этом и заключается идея свойств-аксессуаров. Мы не вызываем user.fullName
как функцию, мы читаем ее как обычно: геттер работает за кулисами.
На данный момент у fullName
есть только геттер. Если мы попытаемся присвоить user.fullName=
, возникнет ошибка:
пусть пользователь = { получить полное имя() { вернуть `...`; } }; user.fullName = "Тест"; // Ошибка (свойство имеет только геттер)
Давайте исправим это, добавив установщик для user.fullName
:
пусть пользователь = { имя: «Джон», фамилия: «Смит», получить полное имя() { return `${this.name} ${this.surname}`; }, установить полное имя (значение) { [это.имя, эта.фамилия] = значение.split(" "); } }; // установка полного имени выполняется с заданным значением. user.fullName = "Элис Купер"; оповещение(имя_пользователя); // Алиса оповещение(пользователь.фамилия); // Купер
В результате у нас есть «виртуальное» свойство fullName
. Он доступен для чтения и записи.
Дескрипторы свойств средств доступа отличаются от дескрипторов свойств данных.
Для свойств аксессора нет value
или writable
, вместо этого есть функции get
и set
.
То есть дескриптор средства доступа может иметь:
get
– функция без аргументов, которая работает при чтении свойства,
set
– функция с одним аргументом, которая вызывается, когда свойство установлено,
enumerable
– то же, что и для свойств данных,
configurable
– то же, что и для свойств данных.
Например, чтобы создать метод доступа fullName
с помощью defineProperty
, мы можем передать дескриптор с помощью get
и set
:
пусть пользователь = { имя: «Джон», фамилия: «Смит» }; Object.defineProperty(user, 'fullName', { получать() { return `${this.name} ${this.surname}`; }, набор (значение) { [это.имя, эта.фамилия] = значение.split(" "); } }); оповещение(user.fullName); // Джон Смит for(введите пользователя) alert(key); // имя, фамилия
Обратите внимание, что свойство может быть либо средством доступа (имеет методы get/set
), либо свойством данных (имеет value
), но не тем и другим одновременно.
Если мы попытаемся передать get
и value
в одном и том же дескрипторе, возникнет ошибка:
// Ошибка: неверный дескриптор свойства. Object.defineProperty({}, 'prop', { получать() { вернуть 1 }, значение: 2 });
Геттеры/сеттеры можно использовать в качестве оберток над «реальными» значениями свойств, чтобы получить больше контроля над операциями с ними.
Например, если мы хотим запретить слишком короткие имена для user
, мы можем указать name
установщика и сохранить значение в отдельном свойстве _name
:
пусть пользователь = { получить имя() { верните это._имя; }, установить имя (значение) { если (значение.длина <4) { alert("Имя слишком короткое, должно быть не менее 4 символов"); возвращаться; } this._name = значение; } }; user.name = "Пит"; оповещение(имя_пользователя); // Пит имя пользователя = ""; // Имя слишком короткое...
Итак, имя хранится в свойстве _name
, а доступ осуществляется через геттер и сеттер.
Технически внешний код может получить доступ к имени напрямую, используя user._name
. Но существует широко известное соглашение, согласно которому свойства, начинающиеся с подчеркивания "_"
являются внутренними и их нельзя трогать снаружи объекта.
Одним из замечательных применений аксессоров является то, что они позволяют в любой момент взять под контроль «обычное» свойство данных, заменив его геттером и сеттером и настроив его поведение.
Представьте, что мы начали реализовывать пользовательские объекты, используя name
и age
свойств данных:
функция Пользователь(имя, возраст) { это.имя = имя; this.age = возраст; } пусть Джон = новый Пользователь("Джон", 25); оповещение(john.age); // 25
…Но рано или поздно все может измениться. Вместо age
мы можем решить сохранить birthday
, потому что это более точно и удобно:
функция Пользователь(имя, день рождения) { это.имя = имя; this.birthday = день рождения; } let john = new User("Джон", новая дата(1992, 6, 1));
Что теперь делать со старым кодом, который все еще использует свойство age
?
Мы можем попытаться найти все такие места и исправить их, но это требует времени и может оказаться затруднительным, если этот код будет использоваться многими другими людьми. И кроме того, age
— это приятно иметь в user
, не так ли?
Давайте сохраним это.
Добавление геттера age
решает проблему:
функция Пользователь(имя, день рождения) { это.имя = имя; this.birthday = день рождения; // возраст рассчитывается исходя из текущей даты и дня рождения Object.defineProperty(this, "возраст", { получать() { пусть TodayYear = новая дата().getFullYear(); вернуть сегодняшний год - this.birthday.getFullYear(); } }); } let john = new User("Джон", новая дата(1992, 6, 1)); оповещение(джон.день рождения); // день рождения доступен оповещение(john.age); // ...а также возраст
Теперь старый код тоже работает, и у нас появилось приятное дополнительное свойство.