Свойства и методы доступа
Для опосредования доступа к свойствам класса в последних стандартах JavaScript была добавлена поддержка методов доступа - get
и set
. Сначала рассмотрим проблему, с которой мы можем столкнуться:
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
}
const tom = new Person("Tom", 37);
// 37
console.log(tom.age);
tom.age = -15;
// -15
console.log(tom.age);
Класс 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);
// 37
console.log(tom.getAge());
tom.setAge(-15);
// 37
console.log(tom.getAge());
Теперь возраст хранится в приватном поле 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);
// Tom
console.log(tom.name);
// Это ничего не даст
tom.name = "Bob";
// Tom - значение не изменилось
console.log(tom.name);
В данном случае вместо общедоступного свойства 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);
// id: 1 name: Tom age: 37
tom.print();
// устанавливаем значение свойства id
tom.id = 55;
// id: 55 name: Tom age: 37
tom.print();
// undefined - значение свойства id нельзя получить
console.log(tom.id);
Здесь определено свойство id
, которое устанавливает значение приватного поля #id
. Но поскольку метода get
для этого свойства не определено, то при попытке получить значение свойства id
, мы получим undefined
:
// undefined - значение свойства id нельзя получить
console.log(tom.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");
// Tom Smith
console.log(tom.fullName);
В данном случае свойство для чтения 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");
// Tom Smith
console.log(tom.fullName);
tom.fullName = "Tomas Jefferson";
// Jefferson
console.log(tom.lastName);
В данном случае метод set
свойства fullName
в качестве параметра получает некоторую строку и с помощью ее метода split
разбивает по пробелу и получает массив подстрок, которые были разделены пробелом. То есть, теоретически мы рассчитываем, что будет передано что-то наподобие Tom Smith
, а после разделения по пробелу свойство firstName
получит значение Tom
, а свойтсво lastName
- значение Smith
. Стоит отметить, что для простоты и целй демонстрации здесь мы не рассматриваем исключительные ситуации, когда передается пустая строка или строка, которая не делится по пробелу на две части и т.д.
В итоге при получении нового значения:
tom.fullName = "Tomas Jefferson";
Метод set
разобьет его по пробелу, и первый элемент массива будет передан свойству firstName
, а второй - свойству lastName
.