- 9-JS OOP: Getters And Setters In Javascript Classes
- Why do we need Getters and Setters?
- Example of usage
- But why not just use the property member directly?
- Combining multiple properties when using getters
- Is it practical in our real world projects?
- 🎨 Conclusion
- ☕♨️ Buy me a Coffee ♨️☕
- 😍 Join our community
- 📚 Bonus Content 📚
- Свойства — геттеры и сеттеры
- Геттеры и сеттеры
- Дескрипторы свойств доступа
- Умные геттеры/сеттеры
- Использование для совместимости
9-JS OOP: Getters And Setters In Javascript Classes
Getters and setters are methods that are used to get and set the value of a property member in a class. So a getter method is a method that is prefixed with get keyword, and it allows us to get a certain value when a user tries to access that getter property. An example of a getter method:
class Human private _name: string = 'Hasan'; get name() return this._name; > >
Here we defined get name() this is a getter method, it allows us to return whatever we want, in our case we’re going to return our private property _name . Any getter method, does not take any parameters, and it returns a value. On the other hand, a setter method is a method that is prefixed with set keyword, and it allows us to set a certain value when a user tries to set that setter property. An example of a setter method:
class Human private _name: string = 'Hasan'; set name(name: string) this._name = name; > >
Here we defined set name(name: string) this is a setter method, it allows us to set whatever we want, in our case we’re going to set our private property _name . Any setter method, takes a parameter, which is the value that we want to assign and it does not return a value.
Why do we need Getters and Setters?
Getters and setters are used to get and set the value of a property member in a class, this will give us more control over the value of the property member.
Example of usage
class Human _name = ''; get name() return this._name; > set name(value) this._name = value; > > const me = new Human(); me.name = 'Hasan'; console.log(me.name);
Here we have a class called Human that has a property member called _name and we have a getter and a setter for this property member.
But why not just use the property member directly?
Well, we can use the property member directly but we will lose the control over the value of the property member. For example, we want to add birthDate property which allows the user define his/her birth date, but in the mean time, we want to calculate the age of the user based on the birth date.
class Human _name = ''; _birthDate = ''; get name() return this._name; > set name(value) this._name = value; > get birthDate() return this._birthDate; > set birthDate(value) this._birthDate = value; > > const me = new Human(); me.name = 'Hasan'; me.birthDate = '1990-01-01'; console.log(me.name); console.log(me.birthDate);
Nothing new here, we just added the birthDate property member and we have a getter and a setter for it. Now let’s add a new property member called age and we will calculate the age based on the birthDate property member.
class Human _name = ''; _birthDate = ''; _age = 0; get name() return this._name; > set name(value) this._name = value; > get birthDate() return this._birthDate; > set birthDate(value) this._birthDate = value; // Calculate the age based on the birth date const birthDate = new Date(value); const today = new Date(); this._age = today.getFullYear() - birthDate.getFullYear(); > get age() return this._age; > > const me = new Human(); me.name = 'Hasan'; me.birthDate = '1990-01-01'; console.log(me.name); // Hasan console.log(me.birthDate); // 1990-01-01 console.log(me.age); // 33
This is where getters and setters are useful, we can calculate the age based on the birth date and we can control the value of the property member. We can also use getters and setters to validate the value of the property member.
class Human _name = ''; _birthDate = ''; _age = 0; get name() return this._name; > set name(value) this._name = value; > get birthDate() return this._birthDate; > set birthDate(value) if (typeof value !== 'string') throw new Error('Birth date must be a string'); > this._birthDate = value; // Calculate the age based on the birth date const birthDate = new Date(value); const today = new Date(); this._age = today.getFullYear() - birthDate.getFullYear(); > get age() if (! this._age) throw new Error('Age is not available, please set the birth date first'); > return this._age; > > const me = new Human(); me.name = 'Hasan'; console.log(me.name); // Hasan me.birthDate = 1990; // Error: Birth date must be a string console.log(me.age); // Error: Age is not available, please set the birth date first
In that sense, we checked the data type of the birthDate property member and we also checked if the age property member is available or not.
Combining multiple properties when using getters
Another way to use getter methods is to combine multiple properties when using getters. For example, we’ve the firstName and lastName properties, and we want to combine them when we use the fullName property.
class Human _firstName = ''; _lastName = ''; get firstName() return this._firstName; > set firstName(value) this._firstName = value; > get lastName() return this._lastName; > set lastName(value) this._lastName = value; > get fullName() return `$this._firstName> $this._lastName>`; > > const me = new Human(); me.firstName = 'Hasan'; me.lastName = 'Khan'; console.log(me.fullName); // Hasan Khan
Is it practical in our real world projects?
The answer is Yes of course, we can use getters and setters in our real world projects, not so much but we can use them, for example that fullName property is easier to use in our code, especially if we are working with list of keys and we just want to loop over these keys to get its values and display it.
const keys = ['firstName', 'lastName', 'fullName', 'birthDate', 'age']; for (const key of keys) console.log(me[key]); >
So i won’t get bothered by checking if the key is a method or a property member, i can just use it directly.
🎨 Conclusion
In this article, we learned about getters and setters in javascript, we learned why we need them and how we can use them.
☕♨️ Buy me a Coffee ♨️☕
If you enjoy my articles and see it useful to you, you may buy me a coffee, it will help me to keep going and keep creating more content.
😍 Join our community
📚 Bonus Content 📚
Свойства — геттеры и сеттеры
Первый тип это свойства-данные (data properties). Мы уже знаем, как работать с ними. Все свойства, которые мы использовали до текущего момента, были свойствами-данными.
Второй тип свойств мы ещё не рассматривали. Это свойства-аксессоры (accessor properties). По своей сути это функции, которые используются для присвоения и получения значения, но во внешнем коде они выглядят как обычные свойства объекта.
Геттеры и сеттеры
Свойства-аксессоры представлены методами: «геттер» – для чтения и «сеттер» – для записи. При литеральном объявлении объекта они обозначаются get и set :
let obj = < get propName() < // геттер, срабатывает при чтении obj.propName >, set propName(value) < // сеттер, срабатывает при записи obj.propName = value >>;
Геттер срабатывает, когда obj.propName читается, сеттер – когда значение присваивается.
Например, у нас есть объект user со свойствами name и surname :
А теперь добавим свойство объекта fullName для полного имени, которое в нашем случае «John Smith» . Само собой, мы не хотим дублировать уже имеющуюся информацию, так что реализуем его при помощи аксессора:
let user = < name: "John", surname: "Smith", get fullName() < return `$$`; > >; alert(user.fullName); // John Smith
Снаружи свойство-аксессор выглядит как обычное свойство. В этом и заключается смысл свойств-аксессоров. Мы не вызываем user.fullName как функцию, а читаем как обычное свойство: геттер выполнит всю работу за кулисами.
На данный момент у fullName есть только геттер. Если мы попытаемся назначить user.fullName= , произойдёт ошибка:
let user = < get fullName() < return `. `; >>; user.fullName = "Тест"; // Ошибка (у свойства есть только геттер)
Давайте исправим это, добавив сеттер для user.fullName :
let user = < name: "John", surname: "Smith", get fullName() < return `$$`; >, set fullName(value) < [this.name, this.surname] = value.split(" "); >>; // set fullName запустится с данным значением user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper
В итоге мы получили «виртуальное» свойство fullName . Его можно прочитать и изменить.
Дескрипторы свойств доступа
Дескрипторы свойств-аксессоров отличаются от «обычных» свойств-данных.
Свойства-аксессоры не имеют value и writable , но взамен предлагают функции get и set .
То есть, дескриптор аксессора может иметь:
- get – функция без аргументов, которая сработает при чтении свойства,
- set – функция, принимающая один аргумент, вызываемая при присвоении свойства,
- enumerable – то же самое, что и для свойств-данных,
- configurable – то же самое, что и для свойств-данных.
Например, для создания аксессора fullName при помощи defineProperty мы можем передать дескриптор с использованием get и set :
let user = < name: "John", surname: "Smith" >; Object.defineProperty(user, 'fullName', < get() < return `$$`; >, set(value) < [this.name, this.surname] = value.split(" "); >>); alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname
Ещё раз заметим, что свойство объекта может быть либо свойством-аксессором (с методами get/set ), либо свойством-данным (со значением value ).
При попытке указать и get , и value в одном дескрипторе будет ошибка:
// Error: Invalid property descriptor. Object.defineProperty(<>, 'prop', < get() < return 1 >, value: 2 >);
Умные геттеры/сеттеры
Геттеры/сеттеры можно использовать как обёртки над «реальными» значениями свойств, чтобы получить больше контроля над операциями с ними.
Например, если мы хотим запретить устанавливать короткое имя для user , мы можем использовать сеттер name для проверки, а само значение хранить в отдельном свойстве _name :
let user = < get name() < return this._name; >, set name(value) < if (value.length < 4) < alert("Имя слишком короткое, должно быть более 4 символов"); return; >this._name = value; > >; user.name = "Pete"; alert(user.name); // Pete user.name = ""; // Имя слишком короткое.
Таким образом, само имя хранится в _name , доступ к которому производится через геттер и сеттер.
Технически, внешний код всё ещё может получить доступ к имени напрямую с помощью user._name , но существует широко известное соглашение о том, что свойства, которые начинаются с символа «_» , являются внутренними, и к ним не следует обращаться из-за пределов объекта.
Использование для совместимости
У аксессоров есть интересная область применения – они позволяют в любой момент взять «обычное» свойство и изменить его поведение, поменяв на геттер и сеттер.
Например, представим, что мы начали реализовывать объект user , используя свойства-данные имя name и возраст age :
function User(name, age) < this.name = name; this.age = age; >let john = new User("John", 25); alert( john.age ); // 25
…Но рано или поздно всё может измениться. Взамен возраста age мы можем решить хранить дату рождения birthday , потому что так более точно и удобно:
function User(name, birthday) < this.name = name; this.birthday = birthday; >let john = new User("John", new Date(1992, 6, 1));
Что нам делать со старым кодом, который использует свойство age ?
Мы можем попытаться найти все такие места и изменить их, но это отнимает время и может быть невыполнимо, если код используется другими людьми. И кроме того, age – это отличное свойство для user , верно?
Добавление геттера для age решит проблему:
function User(name, birthday) < this.name = name; this.birthday = birthday; // возраст рассчитывается из текущей даты и дня рождения Object.defineProperty(this, "age", < get() < let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); >>); > let john = new User("John", new Date(1992, 6, 1)); alert( john.birthday ); // доступен как день рождения alert( john.age ); // . так и возраст
Теперь старый код тоже работает, и у нас есть отличное дополнительное свойство!