Propriedades e métodos de acesso em JavaScript
Para intermediar o acesso às propriedades de uma classe nos padrões mais recentes do JavaScript, foram adicionadas funcionalidades de métodos de acesso, conhecidos como get
e set
. Vamos primeiro considerar um problema que podemos encontrar:
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
A classe Person
define duas propriedades, name
(nome) e age
(idade), cujos valores podemos obter ou definir. Mas, e se passarmos valores incorretos? No exemplo acima, o valor negativo é passado para a propriedade age
, mas a idade não pode ser negativa.
Para resolver essa situação, podemos definir um campo privado para a idade
, que só pode ser acessado dentro da própria classe. Para obter ou definir seu valor, criamos métodos especiais:
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
Agora, a idade é armazenada em um campo privado ageValue
. Ao definir a idade no método setAge
, o valor passado é verificado e a definição ocorre apenas se o valor for adequado. O método getAge
retorna o valor desse campo.
No entanto, há outra solução, que é usar os métodos de acesso get
e set
:
// definição do campo privado
#field;
set field(value) {
this.#field= value;
}
get field() {
return this.#field;
}
Ambos os métodos têm o mesmo nome e geralmente intermediam o acesso a algum campo privado. O método set
é usado para definir, recebendo um novo valor como parâmetro. Podemos então executar uma série de ações durante a definição.
O método get
é usado para obter o valor, onde podemos definir alguma lógica ao retornar o valor.
A seguir, vamos reescrever o exemplo anterior usando get
e set
:
class Person {
#ageValue = 1;
constructor(name, age) {
this.name = name;
this.age = age;
}
set age(value) {
console.log(`Valor passado: ${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);
Note que o trabalho com métodos de acesso é feito da mesma forma que com propriedades comuns. Assim, para obter o valor e exibi-lo no console, usamos:
console.log(tom.age);
Ao acessar tom.age
, o método get
é acionado, que retorna o valor do campo ageValue
.
E ao executar:
tom.age = -15;
o método set
é acionado, recebendo o valor passado (-15) como parâmetro. Então, dentro do método set
, decidimos se esse valor será definido ou não.
Propriedades Disponíveis apenas para Leitura
Embora os métodos get
e set
tenham sido aplicados anteriormente, permitindo tanto obter quanto definir o valor de um campo, na prática podemos usar apenas um deles. Por exemplo, podemos manter apenas o método get
, tornando a propriedade acessível apenas para leitura.
Por exemplo, vamos modificar o exemplo anterior e fazer a propriedade name
disponível apenas para leitura:
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"; // Isso não terá efeito
console.log(tom.name); // Tom - o valor não mudou
Nesse caso, ao invés de uma propriedade pública name
, um campo privado #name
é definido. Ele só pode ser definido internamente, o que fazemos no construtor da classe. No entanto, externamente, ele só pode ser lido através do método get
. Por isso, qualquer tentativa de definir a propriedade:
tom.name = "Bob";
não resultará em nada.
Propriedades Disponíveis apenas para Escrita
Podemos também fazer uma propriedade disponível apenas para escrita, mantendo apenas o método set
. Por exemplo, vamos adicionar uma nova propriedade id
, que será acessível apenas para escrita:
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; // definindo o valor da propriedade id
tom.print(); // id: 55 name: Tom age: 37
console.log(tom.id); // undefined - o valor da propriedade id não pode ser obtido
Aqui, o valor do campo privado #id
é definido, mas como não há método get
para essa propriedade, ao tentar obter o valor de id
, obtemos undefined
:
console.log(tom.id); // undefined - o valor da propriedade id não pode ser obtido
Propriedades sem Acesso a Aampos
Vale notar que os métodos get
e set
não necessariamente precisam acessar campos privados ou não privados. Eles também podem ser propriedades calculadas. Por exemplo:
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
Neste caso, a propriedade de leitura fullName
retorna efetivamente a combinação de duas propriedades - firstName
e lastName
.
De forma similar, podemos definir uma propriedade para escrita:
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
Neste caso, o método set da propriedade fullName
recebe uma string e, usando o método split baseado em espaço, divide-a em um array de substrings separadas por um espaço. Assim, esperamos que algo como "Tom Smith" seja passado e, após a divisão por espaço, a propriedade firstName
receberá o valor "Tom", enquanto a propriedade lastName
receberá "Smith". É importante notar que, para simplificação e demonstração, não estamos considerando situações excepcionais, como passar uma string vazia ou uma string que não se divide em duas partes, etc.
Ao receber um novo valor:
tom.fullName = "Tomas Jefferson";
O método set o dividirá por espaço, e o primeiro elemento do array será atribuído à propriedade firstName
, e o segundo, à propriedade lastName
.