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.