Hay dos tipos de propiedades de objetos.

El primer tipo son las propiedades de datos. Ya sabemos cómo trabajar con ellas. Todas las propiedades que hemos estado utilizando hasta ahora eran propiedades de datos.

El segundo tipo de propiedades es algo nuevo. Son las propiedades accesorias. Son esencialmente funciones que se ejecutan al obtener y establecer un valor, pero que parecen propiedades normales para un código externo.

Getters y setters

Las propiedades accesorias están representadas por métodos «getter» y «setter». En un literal de objeto se denotan por get y 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 }};

El getter funciona cuando se lee obj.propName, el setter – cuando se asigna.

Por ejemplo, tenemos un objeto user con name y surname:

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

Ahora queremos añadir una propiedad fullName, que debería ser "John Smith". Por supuesto, no queremos copiar-pegar la información existente, por lo que podemos implementarla como un accessor:

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

Desde fuera, una propiedad accessor parece una normal. Esa es la idea de las propiedades accesorias. No llamamos a user.fullName como una función, la leemos normalmente: el getter se ejecuta entre bastidores.

A partir de ahora, fullName sólo tiene un getter. Si intentamos asignar user.fullName=, habrá un error:

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

Arreglémoslo añadiendo un 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, tenemos una propiedad «virtual» fullName. Es legible y escribible.

Descriptores del accesorio

Los descriptores de las propiedades del accesorio son diferentes de los de las propiedades de los datos.

Para las propiedades del accesorio, no hay value ni writable, sino que hay funciones get y set.

Es decir, un descriptor accesorio puede tener:

  • get – una función sin argumentos, que funciona cuando se lee una propiedad,
  • set – una función con un argumento, que se llama cuando se establece la propiedad,
  • enumerable – igual que para las propiedades de datos,
  • configurable – igual que para las propiedades de datos.

Por ejemplo, para crear un accesorio fullName con defineProperty, podemos pasar un descriptor con get y 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

Tenga en cuenta que una propiedad puede ser un accesorio (tiene get/set métodos) o una propiedad de datos (tiene un value), no ambos.

Si intentamos suministrar tanto get como value en el mismo descriptor, habrá un error:

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

Mejores getters/setters

Los getters/setters se pueden utilizar como envoltorios sobre los valores de las propiedades «reales» para tener más control sobre las operaciones con ellos.

Por ejemplo, si queremos prohibir nombres demasiado cortos para user, podemos tener un setter name y mantener el valor en una propiedad 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...

Así, el nombre se almacena en la propiedad _name, y el acceso se realiza mediante getter y setter.

Técnicamente, el código externo es capaz de acceder al nombre directamente utilizando user._name. Pero hay una convención ampliamente conocida de que las propiedades que comienzan con un guión bajo "_" son internas y no deben ser tocadas desde fuera del objeto.

Usar por compatibilidad

Una de las grandes utilidades de los accessors es que permiten tomar el control sobre una propiedad de datos «normal» en cualquier momento sustituyéndola por un getter y un setter y retocar su comportamiento.

Imagina que empezamos a implementar objetos de usuario usando las propiedades de datos name y age:

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

…Pero tarde o temprano, las cosas pueden cambiar. En lugar de age podemos decidir almacenar birthday, porque es más preciso y conveniente:

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

¿Ahora qué hacer con el código antiguo que todavía utiliza la propiedad age?

Podemos tratar de encontrar todos esos lugares y arreglarlos, pero eso lleva tiempo y puede ser difícil de hacer si ese código es utilizado por muchas otras personas. Y además, age es algo agradable de tener en user, ¿no?

Mantengámoslo.

Añadir un getter para age resuelve el 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

Ahora el código antiguo también funciona y tenemos una bonita propiedad adicional.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.