Los getters y setters son funciones o métodos utilizados para obtener y establecer los valores de las variables. El concepto getter-setter es común en la programación informática: casi todos los lenguajes de programación de alto nivel vienen con un conjunto de sintaxis para implementar getters y setters, incluyendo JavaScipt.
Lea también: 4 Useful JavaScript Statements You Should Know
En este post, veremos qué son los getters setters, y cómo crearlos y utilizarlos en JavaScript.
- Getters-setters y encapsulación
- Crear getters y setters
- 1. Con métodos
- 2. Con palabras clave
- ¿Cuál es la mejor manera?
- Prevención de sobrescritura
- Operaciones dentro de los getters y setters
- Protege los datos con getters y setters
- Datos desprotegidos
- 1. Alcance de bloque
- 2. Alcance de la función
- 3. Protección de datos sin scoping
- ¿Cuándo deberías usar getters y setters?
Getters-setters y encapsulación
La idea de getters y setters siempre se menciona junto con la encapsulación. La encapsulación puede entenderse de dos maneras.
En primer lugar, es la configuración del trío getters-setters de datos para acceder y modificar esos datos. Esta definición es útil cuando hay que realizar algunas operaciones, como la validación, sobre los datos antes de guardarlos o visualizarlos-los getters y setters proporcionan el hogar perfecto para ello.
En segundo lugar, hay una definición más estricta según la cual la encapsulación se realiza para ocultar los datos, para hacerlos inaccesibles desde otro código, excepto a través de los getters y setters. De esta forma no acabamos sobrescribiendo accidentalmente datos importantes con algún otro código del programa.
Crear getters y setters
1. Con métodos
Dado que los getters y setters son básicamente funciones que obtienen/cambian un valor, hay más de una forma de crearlos y utilizarlos. La primera forma es:
var obj = { foo: 'this is the value of foo', getFoo: function() { return this.foo; }, setFoo: function(val) { this.foo = val; }}console.log(obj.getFoo());// "this is the value of foo"obj.setFoo('hello');console.log(obj.getFoo());// "hello"
Esta es la forma más sencilla de crear getters y setters. Hay una propiedad foo
y hay dos métodos: getFoo
y setFoo
para devolver y asignar un valor a esa propiedad.
2. Con palabras clave
Una forma más «oficial» y robusta de crear getters y setters es utilizando las palabras clave get
y set
.
Para crear un getter, se coloca la palabra clave get
delante de una declaración de función que servirá como método getter, y se utiliza la palabra clave set
de la misma forma para crear un setter. La sintaxis es la siguiente:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; }}console.log(obj.foo);// "this is the value of foo"obj.foo = 'hello';console.log(obj.foo);// "hello"
Note que los datos sólo pueden ser almacenados bajo un nombre de propiedad (fooVal
) que es diferente del nombre de los métodos getter-setter (foo
) porque una propiedad que contiene el getter-setter no puede contener los datos también.
¿Cuál es la mejor manera?
Si eliges crear getters y setters con palabras clave, puedes utilizar el operador de asignación para establecer los datos y el operador de punto para obtenerlos, de la misma manera que accederías/establecerías el valor de una propiedad normal.
Sin embargo, si eliges la primera forma de codificar getters y setters, tienes que llamar a los métodos setter y getter usando la sintaxis de llamada a función porque son funciones típicas (nada especial como las creadas usando las palabras clave get
y set
).
Además, existe la posibilidad de que acabes asignando accidentalmente algún otro valor a las propiedades que contenían esos métodos getter-setter y los pierdas por completo. Algo de lo que no tienes que preocuparte en el último método.
Así que puedes ver por qué dije que la segunda técnica es más robusta.
Prevención de sobrescritura
Si por alguna razón prefieres la primera técnica, haz que las propiedades que contienen los métodos getter-setter sean de sólo lectura creándolas con Object.defineProperties
. Las propiedades creadas mediante Object.defineProperties
, Object.defineProperty
y Reflect.defineProperty
se configuran automáticamente en writable: false
lo que significa que son de sólo lectura:
/* Overwrite prevention */var obj = { foo: 'this is the value of foo'};Object.defineProperties(obj, { 'getFoo': { value: function () { return this.foo; } }, 'setFoo': { value: function (val) { this.foo = val; } }});obj.getFoo = 66;// getFoo is not going to be overwritten!console.log(obj.getFoo());// "this is the value of foo"
Operaciones dentro de los getters y setters
Una vez que hayas introducido los getters y setters, puedes seguir adelante y realizar operaciones sobre los datos antes de cambiarlos o devolverlos.
En el código siguiente, en la función getter se concatenan los datos con una cadena antes de ser devueltos, y en la función setter se realiza una validación de si el valor es un número o no antes de actualizar n
.
var obj = { n: 67, get id() { return 'The ID is: ' + this.n; }, set id(val) { if (typeof val === 'number') this.n = val; }}console.log(obj.id);// "The ID is: 67"obj.id = 893;console.log(obj.id);// "The ID is: 893"obj.id = 'hello';console.log(obj.id);// "The ID is: 893"
Protege los datos con getters y setters
Hasta ahora, hemos cubierto el uso de getters y setters en el primer contexto de encapsulación. Pasemos al segundo, es decir, cómo ocultar los datos del código exterior con la ayuda de getters y setters.
Datos desprotegidos
La configuración de getters y setters no significa que los datos sólo puedan ser accedidos y modificados a través de esos métodos. En el siguiente ejemplo, se cambia directamente sin tocar los métodos getter y setter:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; }}obj.fooVal = 'hello';console.log(obj.foo);// "hello"
No usamos el setter sino que cambiamos directamente los datos (fooVal
). Los datos que establecimos inicialmente dentro de obj
han desaparecido. Para evitar que esto ocurra (accidentalmente), necesitas alguna protección para tus datos. Puedes añadir eso limitando el alcance de donde están disponibles tus datos. Usted puede hacer que por cualquiera de bloque de alcance o función de alcance.
1. Alcance de bloque
Una forma es utilizar un alcance de bloque dentro del cual se definirán los datos utilizando la palabra clave let
que limita su alcance a ese bloque.
Un alcance de bloque se puede crear colocando su código dentro de un par de llaves. Siempre que cree un ámbito de bloque asegúrese de dejar un comentario encima pidiendo que se dejen las llaves, para que nadie quite las llaves por error pensando que son unas llaves extra redundantes en el código o añada una etiqueta al ámbito de bloque.
/* BLOCK SCOPE, leave the braces alone! */{let fooVal = 'this is the value of foo';var obj = { get foo() { return fooVal; }, set foo(val) { fooVal = val } }}fooVal = 'hello';// not going to affect the fooVal inside the blockconsole.log(obj.foo);// "this is the value of foo"
Cambiar/crear fooVal
fuera del bloque no afectará al fooVal
referido dentro de los getters setters.
2. Alcance de la función
La forma más común de proteger los datos con el alcance es mantener los datos dentro de una función y devolver un objeto con los getters y setters de esa función.
function myobj(){ var fooVal = 'this is the value of foo'; return { get foo() { return fooVal; }, set foo(val) { fooVal = val } }}fooVal = 'hello';// not going to affect our original fooValvar obj = myobj();console.log(obj.foo);// "this is the value of foo"
El objeto (con el getter-setter foo()
dentro de él) devuelto por la función myobj()
se guarda en obj
, y luego obj
se utiliza para llamar al getter y setter.
3. Protección de datos sin scoping
También hay otra manera de proteger los datos de ser sobrescritos sin limitar su alcance. La lógica que subyace es la siguiente: ¿cómo puedes cambiar un dato si no sabes cómo se llama?
Si los datos tienen un nombre de variable/propiedad no tan fácilmente reproducible, lo más probable es que nadie (ni siquiera nosotros mismos) vaya a acabar sobrescribiéndolo asignando algún valor a ese nombre de variable/propiedad.
var obj = { s89274934764: 'this is the value of foo', get foo() { return this.s89274934764; }, set foo(val) { this.s89274934764 = val; }}console.log(obj.foo);// "this is the value of foo"
Veamos, esa es una forma de solucionar las cosas. Aunque el nombre que elegí no es realmente bueno, también puedes usar valores o símbolos aleatorios para crear nombres de propiedades como lo propone Derick Bailey en esta entrada del blog. El objetivo principal es mantener los datos ocultos a otro código y dejar que un par getter-setter acceda a ellos/los actualice.
¿Cuándo deberías usar getters y setters?
Ahora viene la gran pregunta: ¿empiezas a asignar getters y setters a todos tus datos ahora?
Si estás ocultando datos, entonces no hay otra opción.
Pero si tus datos son vistos por otro código está bien, ¿todavía necesitas usar getters setters sólo para agruparlos con el código que realiza algunas operaciones en ellos? Yo diría que sí. El código se acumula muy pronto. Crear micro unidades de datos individuales con su propio getter-setter te proporciona cierta independencia para trabajar sobre dichos datos sin afectar a otras partes del código.
Lee también: 10 razones por las que necesitas optimizar el código