Encapsulamento de Propriedades usando Getters e Setters em JavaScript
Encapsulamento é um dos conceitos-chave da programação orientada a objetos e envolve esconder o estado de um objeto para evitar acesso direto externo, a fim de manter a integridade dos dados. Por padrão, todas as propriedades dos objetos são públicas e acessíveis, e podemos acessá-las de qualquer lugar no programa.
function User(uName, uAge) {
  this.name = uName;
  this.age = uAge;
  this.print = function () {
    console.log(`Name: ${this.name}  Age: ${this.age}`);
  };
}
const tom = new User("Tom", 39);
tom.age = 11500;
tom.print(); // Name: Tom  Age: 11500No entanto, esse método de acesso pode ser indesejável. Por exemplo, na situação acima, a propriedade age, que representa a idade, pode ser atribuída a valores muito diferentes, inclusive inválidos.
Mas podemos esconder isso de acesso externo. Para isso, a propriedade é definida como uma variável local/constante:
function User(uName, uAge) {
  this.name = uName;
  let _age = uAge;
  this.print = function () {
    console.log(`Name: ${this.name}  Age: ${_age}`);
  };
}
const tom = new User("Tom", 39);
tom._age = 11500;
tom.print(); // Name: Tom  Age: 39No construtor do User, a variável local _age é declarada em vez da propriedade age:
let _age = uAge;Geralmente, os nomes das variáveis locais em construtores começam com um sublinhado. Embora essa variável também possa receber dados dos parâmetros do construtor e ser usada em funções dentro do construtor, não é possível acessá-la de fora:
tom._age = 11500;Aqui, para o objeto tom, define-se uma nova propriedade chamada, como a variável _age. Mas essa propriedade _age não afetará a variável local _age, como podemos ver pela saída do console do método print.
Getters e Setters
Como vimos, escondemos o valor da idade na variável local _age, mas às vezes ainda é necessário algum acesso, por exemplo, para o mesmo console de saída ou mudança. Nesse caso, podemos definir métodos de acesso especiais: getter (para obter o valor) e setter (para alterar o valor).
function User(uName, uAge) {
  this.name = uName;
  let _age = uAge;
  // getter - retorna o valor da variável
  this.getAge = function () {
    return _age;
  };
  // setter - define o valor da variável
  this.setAge = function (age) {
    if (age > 0 && age < 110) {
      // se a idade for maior que 0 e menor que 110
      _age = age;
    } else {
      console.log("Valor inválido");
    }
  };
  this.print = function () {
    console.log(`Name: ${this.name}  Age: ${_age}`);
  };
}
const tom = new User("Tom", 39);
// obtém o valor
console.log(tom.getAge()); // 39
// define um novo valor
tom.setAge(22);
console.log(tom.getAge()); // 22
tom.setAge(11500); // Valor inválido
console.log(tom.getAge()); // 22Para trabalhar com a idade do usuário externamente, são definidos dois métodos. O método getAge() é destinado a obter o valor da variável _age. Este método também é chamado de getter. O segundo método é setAge, que também é chamado de setter, é destinado a definir o valor da variável _age.
A vantagem dessa abordagem é que temos maior controle sobre o acesso ao valor de _age. Por exemplo, podemos verificar algumas condições concomitantes, como no caso acima, onde se verifica o tipo de valor (deve ser um número) e o próprio valor (a idade não pode ser inferior a 0).
Vale destacar que o JavaScript também oferece construções especiais para a criação de getters e setters: get e set, respectivamente. No entanto, no contexto de funções construtoras, eles não fazem muito sentido, então serão considerados mais adiante.