Existem dois tipos de propriedades de objectos.

O primeiro tipo são propriedades de dados. Nós já sabemos como trabalhar com eles. Todas as propriedades que temos usado até agora eram propriedades de dados.

O segundo tipo de propriedades é algo novo. São propriedades de acesso. São essencialmente funções que executam na obtenção e definição de um valor, mas parecem propriedades regulares para um código externo.

Getters e setters

Acessórios são representados pelos métodos “getter” e “setter”. Em um objeto literal elas são representadas por get e set:

let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};

O getter funciona quando obj.propName é lido, o setter – quando ele é atribuído.

Por exemplo, temos um objecto user com name e surname:

let user = { name: "John", surname: "Smith"};

Agora queremos adicionar uma propriedade fullName, que deve ser "John Smith". Claro que não queremos copiar-colar informação existente, para que possamos implementá-la como um acessor:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith

Do exterior, uma propriedade acessor parece ser uma propriedade normal. Essa é a ideia das propriedades acessórias. Nós não chamamos user.fullName como uma função, nós lemos normalmente: o getter corre nos bastidores.

Como de agora, fullName tem apenas um getter. Se tentarmos atribuir user.fullName=, haverá um erro:

let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)

Vamos corrigi-lo adicionando um setter para user.fullName:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { = value.split(" "); }};// set fullName is executed with the given value.user.fullName = "Alice Cooper";alert(user.name); // Alicealert(user.surname); // Cooper

Como resultado, temos uma propriedade “virtual” fullName. Ela é legível e gravável.

Descritores de acesso

Descritores para propriedades de acesso são diferentes daqueles para propriedades de dados.

Para propriedades de acesso, não há value ou writable, mas em vez disso há get e set funções.

>

Ou seja, um descritor de acessor pode ter:

  • get – uma função sem argumentos, que funciona quando uma propriedade é lida,
  • set – uma função com um argumento, que é chamada quando a propriedade é definida,
  • enumerable – o mesmo que para propriedades de dados,
  • configurable – o mesmo que para propriedades de dados.

Por exemplo, para criar um acessor,fullName com defineProperty, podemos passar um descritor com get e set:

let user = { name: "John", surname: "Smith"};Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { = value.split(" "); }});alert(user.fullName); // John Smithfor(let key in user) alert(key); // name, surname

Por favor note que uma propriedade pode ser ou um acessor (tem get/set métodos) ou uma propriedade de dados (tem um value), e não ambas.

Se tentarmos fornecer ambos get e value no mesmo descritor, haverá um erro:

// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});

Aganhadores/setters mais inteligentes

Getters/setters podem ser usados como invólucros sobre valores de propriedades “reais” para ganhar mais controle sobre as operações com eles.

Por exemplo, se quisermos proibir nomes demasiado curtos para user, podemos ter um setter name e manter o valor numa propriedade separada _name:

let user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; }};user.name = "Pete";alert(user.name); // Peteuser.name = ""; // Name is too short...

Então, o nome é guardado na propriedade _name, e o acesso é feito via getter e setter.

Tecnicamente, o código externo é capaz de acessar o nome diretamente usando user._name. Mas há uma convenção amplamente conhecida que propriedades começando com um underscore "_" são internas e não devem ser tocadas de fora do objeto.

Usando para compatibilidade

Um dos grandes usos dos acessores é que eles permitem assumir o controle sobre uma propriedade de dados “regular” a qualquer momento, substituindo-a por um getter e um setter e ajustar seu comportamento.

Imagine que começamos a implementar objetos de usuário usando propriedades de dados name e age:

function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25

…Mas mais cedo ou mais tarde, as coisas podem mudar. Em vez de age podemos decidir armazenar birthday, porque é mais preciso e conveniente:

function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));

Agora o que fazer com o código antigo que ainda usa age propriedade?

Podemos tentar encontrar todos esses lugares e consertá-los, mas isso leva tempo e pode ser difícil de fazer se esse código for usado por muitas outras pessoas. E além disso, age é bom ter em user, certo?

Servamos.

Adicionar um getter para age resolve o problema:

function User(name, birthday) { this.name = name; this.birthday = birthday; // age is calculated from the current date and 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 ); // birthday is availablealert( john.age ); // ...as well as the age

Agora o código antigo também funciona e temos uma bela propriedade adicional.

Deixe uma resposta

O seu endereço de email não será publicado.