Для опосредования доступа к свойствам класса в последних стандартах JavaScript была добавлена поддержка методов доступа - get и set. Сначала рассмотрим проблему, с которой мы можем столкнуться:
class Person{ constructor(name, age){ this.name = name; this.age = age; } } const tom = new Person("Tom", 37); console.log(tom.age); // 37 tom.age = -15; console.log(tom.age); // -15
Класс Person определяет два свойства - name
(имя) и age
(возраст человека), значения которых мы можем получить или установить. Но что если
мы передадим некорректные значения? Так, в примере выше свойству age передается отрицательное число, но возраст не может быть отрицательным.
Чтобы выйти из этой ситуации, мы можем определить приватное поле age, к которому можно было бы обратиться только из текущего класса. А для получения или установки его значения создать специальные методы:
class Person{ #ageValue = 1; constructor(name, age){ this.name = name; this.setAge(age); } getAge(){ return this.#ageValue; } setAge(value){ if(value>0 && value < 110) this.#ageValue = value; } } const tom = new Person("Tom", 37); console.log(tom.getAge()); // 37 tom.setAge(-15); console.log(tom.getAge()); // 37
Теперь возраст хранится в приватном поле ageValue
. При его установке в методе setAge()
проверяется переданное значение. И
установка происходит, если только передано корректное значение. А метод getAge()
возвращает значение этой переменной.
Но есть и другое решение - применение методов доступа get и set.
// определение приватного поля #field; set field(value){ this.#field= value; } get field(){ return this.#field; }
Оба метода - get
и set
имеют одинаковые названия. Как правило, они опосредуют доступ к некоторому приватному полю.
Метод set предназначен для установки. Он принимает в качестве параметра новое значение. Далее в методе set
мы можем выполнить ряд действий при установке.
Метод get предназначен для получения значения. Здесь мы можем определить какую-нибудь логику при возвращении значения.
Так, перепишем предыдущий пример с использованием get и set:
class Person{ #ageValue = 1; constructor(name, age){ this.name = name; this.age = age; } set age(value){ console.log(`Передано ${value}`); if(value>0 && value < 110) this.#ageValue = value; } get age(){ return this.#ageValue; } } const tom = new Person("Tom", 37); console.log(tom.age); tom.age = -15; console.log(tom.age);
Стоит отметить, что работа с методами доступа производится также, как с обычными свойствами. Так, для получения значения и вывода на консоль применяется вызов:
console.log(tom.age);
а не
console.log(tom.age());
То есть при обращении tom.age
фактически будет срабатывать метод get, который возвратит значение поля ageValue
.
А при вызове
tom.age = -15;
будет срабатывать метод set, который получит передаваемое ему значение (здесь число -15) через единственный параметр. И далее в самом методе set
мы можем решить, надо ли устанавливать
это значение.
Выше применялись оба метода get
и set
, соответственно значение поля можно было и получить, и установить. Однако в реальност мы можем использовать только один из них.
Например, мы можем оставить только метод get и тем самым сделать свойство доступным только для чтения.
Например, в изменим пример выше и сделаем свойство name
доступным только для чтения:
class Person{ #age = 1; #name; constructor(name, age){ this.#name = name; this.age = age; } //set name(value){ this.#name = value; } get name(){ return this.#name; } set age(value){ if(value>0 && value < 110) this.#age = value; } get age(){ return this.#age; } } const tom = new Person("Tom", 37); console.log(tom.name); // Tom tom.name = "Bob"; // Это ничего не даст console.log(tom.name); // Tom - значение не изменилось
В данном случае вместо общедоступного свойства name
определена приватное поле #name
. Его можно установить только из внутри класса, что мы и делаем в конструкторе класса.
Однако из вне его можно только прочитать с помощью метода get
. Поэтому попытка установки свойства
tom.name = "Bob";
ни к чему не приведет
Также мы можем сделать свойство доступным только для записи, оставив только метод set. Например, добавим новое свойство id, которое будет доступно только для записи:
class Person{ #id; constructor(name, age, id){ this.name = name; this.age = age; this.id = id; } set id(value){ this.#id = value;} print(){ console.log(`id: ${this.#id} name: ${this.name} age: ${this.age}`); } } const tom = new Person("Tom", 37, 1); tom.print(); // id: 1 name: Tom age: 37 tom.id = 55; // устанавливаем значение свойства id tom.print(); // id: 55 name: Tom age: 37 console.log(tom.id); // undefined - значение свойства id нельзя получить
Здесь определено свойство id
, которое устанавливает значение приватного поля #id
. Но поскольку метода get
для этого свойства не определено,
то при попытке получить значение свойства id, мы получим undefined
:
console.log(tom.id); // undefined - значение свойства id нельзя получить
Стоит отметить, что методы get
и set
необязательно должны обращаться к приватным или неприватным полям. Это могут быть и вычисляемые свойства. Например:
class Person{ constructor(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } get fullName(){ return `${this.firstName} ${this.lastName}` } } const tom = new Person("Tom", "Smith"); console.log(tom.fullName); // Tom Smith
В данном случае свойство для чтения fullName
возращает фактически объединение двух свойств - firstName
и lastName
.
Подобным образом можно определить и свойство для записи:
class Person{ constructor(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } get fullName(){ return `${this.firstName} ${this.lastName}` } set fullName(value){ [this.firstName, this.lastName] = value.split(" "); } } const tom = new Person("Tom", "Smith"); console.log(tom.fullName); // Tom Smith tom.fullName = "Tomas Jefferson"; console.log(tom.lastName); // Jefferson
В данном случае метод set
свойства fullName
в качестве параметра получает некоторую строку и с помощью ее метода split
разбивает по пробелу и получает массив подстрок, которые были разделены пробелом. То есть, теоретически мы рассчитываем,
что будет передано что-то наподобие "Tom Smith", а после разделения по пробелу свойство firstName
получит значение "Tom", а свойтсво
lastName
- значение "Smith". Стоит отметить, что для простоты и целй демонстрации здесь мы не рассматриваем исключительные ситуации,
когда передается пустая строка или строка, которая не делится по пробелу на две части и т.д.
В итоге при получении нового значения
tom.fullName = "Tomas Jefferson";
Метод set
разобьет его по пробелу, и первый элемент массива будет передан свойству firstName
, а второй - свойству lastName
.