Hay dos tipos de propiedades de objetos.
El primer tipo son las propiedades de los datos . Ya sabemos cómo trabajar con ellos. Todas las propiedades que hemos estado usando hasta ahora eran propiedades de datos.
El segundo tipo de propiedad es algo nuevo. Es una propiedad accesoria . Son esencialmente funciones que se ejecutan al obtener y establecer un valor, pero parecen propiedades normales para un código externo.
Las propiedades de los accesorios están representadas por los métodos "getter" y "setter". En un objeto literal, se denotan mediante get
y set
:
dejar objeto = { obtener nombreprop() { // getter, el código ejecutado al obtener obj.propName }, establecer nombre de propiedad (valor) { // setter, el código ejecutado al configurar obj.propName = valor } };
El captador funciona cuando se lee obj.propName
, el definidor, cuando se asigna.
Por ejemplo, tenemos un objeto user
con name
y surname
:
dejar usuario = { nombre: "Juan", apellido: "Smith" };
Ahora queremos agregar una propiedad fullName
, que debería ser "John Smith"
. Por supuesto, no queremos copiar y pegar información existente, por lo que podemos implementarla como acceso:
dejar usuario = { nombre: "Juan", apellido: "Smith", obtener nombre completo() { return `${este.nombre} ${este.apellido}`; } }; alerta(usuario.nombrecompleto); // Juan Smith
Desde fuera, una propiedad de acceso parece una normal. Esa es la idea de las propiedades de acceso. No llamamos user.fullName
como función, lo leemos normalmente: el captador se ejecuta detrás de escena.
A partir de ahora, fullName
sólo tiene un captador. Si intentamos asignar user.fullName=
, habrá un error:
dejar usuario = { obtener nombre completo() { devolver `...`; } }; usuario.fullName = "Prueba"; // Error (la propiedad solo tiene un captador)
Arreglemoslo agregando un configurador para user.fullName
:
dejar usuario = { nombre: "Juan", apellido: "Smith", obtener nombre completo() { return `${este.nombre} ${este.apellido}`; }, establecer nombre completo (valor) { [este.nombre, este.apellido] = valor.split(" "); } }; // set fullName se ejecuta con el valor dado. usuario.fullName = "Alice Cooper"; alerta(nombre.usuario); // Alicia alerta(usuario.apellido); // tonelero
Como resultado, tenemos una propiedad "virtual" fullName
. Es legible y escribible.
Los descriptores de las propiedades de acceso son diferentes de los de las propiedades de datos.
Para las propiedades de acceso, no hay value
ni writable
, sino que hay funciones get
y set
.
Es decir, un descriptor de acceso 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 descriptor de acceso fullName
con defineProperty
, podemos pasar un descriptor con get
y set
:
dejar usuario = { nombre: "Juan", apellido: "Smith" }; Object.defineProperty(usuario, 'nombre completo', { conseguir() { return `${este.nombre} ${este.apellido}`; }, establecer (valor) { [este.nombre, este.apellido] = valor.split(" "); } }); alerta(usuario.nombrecompleto); // Juan Smith for(dejar ingresar usuario) alert(key); // nombre, apellido
Tenga en cuenta que una propiedad puede ser un accesor (tiene métodos get/set
) o una propiedad de datos (tiene un value
), no ambos.
Si intentamos proporcionar tanto get
como value
en el mismo descriptor, se producirá un error:
// Error: Descriptor de propiedad no válido. Objeto.defineProperty({}, 'prop', { conseguir() { regresar 1 }, valor: 2 });
Los captadores/definidores se pueden utilizar como envoltorios sobre valores de propiedades "reales" para obtener más control sobre las operaciones con ellos.
Por ejemplo, si queremos prohibir nombres demasiado cortos para user
, podemos tener un name
de establecimiento y mantener el valor en una propiedad separada _name
:
dejar usuario = { obtener nombre() { devolver this._name; }, establecer nombre (valor) { if (valor.longitud < 4) { alert("El nombre es demasiado corto, necesita al menos 4 caracteres"); devolver; } this._name = valor; } }; nombre.usuario = "Pete"; alerta(nombre.usuario); // Pete nombre.usuario = ""; // El nombre es demasiado corto...
Entonces, el nombre se almacena en la propiedad _name
y el acceso se realiza mediante getter y setter.
Técnicamente, el código externo puede acceder al nombre directamente utilizando user._name
. Pero existe una convención ampliamente conocida de que las propiedades que comienzan con un guión bajo "_"
son internas y no deben tocarse desde fuera del objeto.
Uno de los grandes usos de los descriptores de acceso es que permiten tomar control sobre una propiedad de datos "normal" en cualquier momento reemplazándola con un captador y un definidor y modificando su comportamiento.
Imagine que comenzamos a implementar objetos de usuario usando name
y age
de las propiedades de datos:
función Usuario(nombre, edad) { this.nombre = nombre; this.age = edad; } let john = nuevo Usuario("John", 25); alerta( juan.edad ); // 25
…Pero tarde o temprano, las cosas pueden cambiar. En lugar de age
podemos decidir almacenar birthday
, porque es más preciso y conveniente:
función Usuario (nombre, fecha de nacimiento) { this.nombre = nombre; this.cumpleaños = cumpleaños; } let john = nuevo Usuario("John", nueva Fecha(1992, 6, 1));
Ahora, ¿qué hacer con el código antiguo que todavía usa la propiedad age
?
Podemos intentar encontrar todos esos lugares y arreglarlos, pero eso lleva tiempo y puede ser difícil de hacer si muchas otras personas usan ese código. Y además, es bueno tener age
en user
, ¿verdad?
Mantengámoslo.
Agregar un captador de age
resuelve el problema:
función Usuario (nombre, fecha de nacimiento) { this.nombre = nombre; this.cumpleaños = cumpleaños; // la edad se calcula a partir de la fecha actual y el cumpleaños Object.defineProperty(esto, "edad", { conseguir() { let hoyAño = nueva Fecha().getFullYear(); volver hoyAño - this.birthday.getFullYear(); } }); } let john = nuevo Usuario("John", nueva Fecha(1992, 6, 1)); alerta( juan.cumpleaños ); // cumpleaños está disponible alerta( juan.edad ); // ...así como la edad
Ahora el código antiguo también funciona y tenemos una buena propiedad adicional.