Getters e setters são funções ou métodos usados para obter e definir os valores das variáveis. O conceito getter-setter é comum na programação de computadores: quase todas as linguagens de programação de alto nível vêm com um conjunto de sintaxe para implementar getters e setters, incluindo JavaScipt.
Ler Também: 4 Declarações úteis em JavaScript que você deve saber
Neste post, vamos ver o que são os getters setters, e como criá-los e usá-los em JavaScript.
- Getters-setters e encapsulamento
- Criar getters e setters
- 1. Com métodos
- 2. Com palavras-chave
- Qual a melhor maneira?
- Prevenção de sobreescrita
- Operações dentro de getters e setters
- Proteger dados com getters e setters
- Dados desprotegidos
- 1. Block scoping
- 2. Função scoping
- 3. Proteção de dados sem scoping
- Quando você deve usar getters e setters?
Getters-setters e encapsulamento
A idéia de getters e setters é sempre mencionada em conjunto com o encapsulamento. O encapsulamento pode ser entendido de duas maneiras.
Primeiro, é a configuração do trio data-getters-setters para acessar e modificar esses dados. Esta definição é útil quando algumas operações, como a validação, têm de ser realizadas nos dados antes de os guardar ou visualizar – os getters e setters fornecem a casa perfeita para eles.
Segundo, há uma definição mais estrita segundo a qual o encapsulamento é feito para esconder dados, para torná-los inacessíveis a partir de outro código, excepto através dos getters e setters. Desta forma não acabamos por sobrepor acidentalmente dados importantes com algum outro código no programa.
Criar getters e setters
1. Com métodos
Os getters e setters são basicamente funções que buscam/alteram um valor, há mais de uma maneira de criá-los e usá-los. A primeira maneira é:
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 é a maneira mais simples de criar getters e setters. Há uma propriedade foo
e há dois métodos: getFoo
e setFoo
para retornar e atribuir um valor a essa propriedade.
2. Com palavras-chave
Uma forma mais “oficial” e robusta de criar getters e setters é usando as get
e set
palavras-chave.
Para criar um getter, coloque a palavra-chave get
na frente de uma declaração de função que servirá como método getter, e use a palavra-chave set
da mesma forma para criar um setter. A sintaxe é a seguinte:
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 os dados só podem ser armazenados sob um nome de propriedade (fooVal
) diferente do nome dos métodos getter-setter (foo
) porque uma propriedade que contenha o getter-setter não pode conter os dados também.
Qual a melhor maneira?
Se você optar por criar getters e setters com palavras-chave, você pode usar o operador de atribuição para definir os dados e o operador de pontos para obter os dados, da mesma maneira que você acessaria/definiria o valor de uma propriedade regular.
No entanto, se você escolher a primeira maneira de codificar getters e setters, você tem que chamar os métodos setter e getter usando a sintaxe de chamada de função porque eles são funções típicas (nada especial como aqueles criados usando as get
e set
palavras-chave).
Também, há uma chance de você acabar atribuindo acidentalmente algum outro valor para as propriedades que mantinham esses métodos getter-setter e perdê-los completamente! Algo com que você não precisa se preocupar no método posterior.
Então, você pode ver porque eu disse que a segunda técnica é mais robusta.
Prevenção de sobreescrita
Se por alguma razão você preferir a primeira técnica, faça as propriedades que seguram os métodos getter-setter somente para leitura, criando-as usando Object.defineProperties
. Propriedades criadas através de Object.defineProperties
, Object.defineProperty
e Reflect.defineProperty
configurar automaticamente para writable: false
o que significa somente leitura:
/* 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"
Operações dentro de getters e setters
Após ter introduzido os getters e setters, você pode ir em frente e realizar operações nos dados antes de alterá-los ou devolvê-los.
No código abaixo, na função getter os dados são concatenados com uma string antes de serem retornados, e na função setter uma validação de se o valor é um número ou não é realizada antes da atualização 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"
Proteger dados com getters e setters
Até agora, cobrimos o uso de getters e setters no primeiro contexto de encapsulamento. Vamos passar para o segundo, ou seja, como ocultar dados de código externo com a ajuda de getters e setters.
Dados desprotegidos
A configuração de getters e setters não significa que os dados só podem ser acessados e alterados através desses métodos. No exemplo seguinte, os dados são alterados diretamente sem tocar nos métodos getter e 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"
Não usamos o setter mas alteramos diretamente os dados (fooVal
). Os dados que inicialmente definimos dentro de obj
desapareceram agora! Para evitar que isso aconteça (acidentalmente), você precisa de alguma proteção para os seus dados. Você pode adicionar isso limitando o escopo de onde seus dados estão disponíveis. Você pode fazer isso através do escopo do bloco ou da função escopo.
1. Block scoping
Uma maneira é usar um escopo de bloco dentro do qual os dados serão definidos usando a palavra-chave let
que limita seu escopo a esse bloco.
Um escopo de bloco pode ser criado colocando seu código dentro de um par de chaves. Sempre que você criar um escopo de bloco certifique-se de deixar um comentário acima dele pedindo que os braces sejam deixados em paz, para que ninguém remova os braces por engano pensando que são alguns braces extra redundantes no código ou adicione uma etiqueta ao escopo do bloco.
/* 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"
Mudando/criando fooVal
fora do bloco não afetará os fooVal
referidos dentro dos getters setters.
2. Função scoping
A maneira mais comum de proteger os dados com scoping é manter os dados dentro de uma função e retornar um objeto com os getters e setters a partir dessa função.
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"
O objeto (com o foo()
getter-setter dentro dele) retornado pela função myobj()
é salvo em obj
, e então obj
é usado para chamar o getter e setter.
3. Proteção de dados sem scoping
Há também outra maneira de proteger os dados contra sobrescritos sem limitar seu escopo. A lógica por trás disso é a seguinte: como você pode alterar um dado se você não sabe o que é chamado?
Se os dados têm um nome de variável/propriedade não tão facilmente reproduzível, é provável que ninguém (mesmo nós mesmos) acabe sobrescrevendo-o atribuindo algum valor a essa variável/propriedade.
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"
Veja, essa é uma maneira de resolver as coisas. Embora o nome que escolhi não seja realmente bom, você também pode usar valores ou símbolos aleatórios para criar nomes de propriedades como é proposto por Derick Bailey neste post do blog. O objetivo principal é manter os dados escondidos de outro código e deixar um par getter-setter acessar/atualizar.
Quando você deve usar getters e setters?
Agora vem a grande pergunta: você começa a atribuir getters e setters a todos os seus dados agora?
Se você está escondendo dados, então não há outra escolha.
Mas se os seus dados sendo vistos por outro código está bem, você ainda precisa usar getters setters apenas para empacotá-los com o código que executa algumas operações sobre eles? Eu diria que sim. O código é muito em breve. Criar micro unidades de dados individuais com seu próprio getter-setter proporciona uma certa independência para trabalhar com esses dados sem afetar outras partes do código.
Ler Também: 10 Razões pelas quais você precisa de otimização de código