Como você fez uma pergunta semelhante , vamos passo a passo. É um pouco mais longo, mas você pode economizar muito mais tempo do que gastei escrevendo isso:
A propriedade é um recurso de POO projetado para uma separação limpa do código do cliente. Por exemplo, em algumas lojas eletrônicas, você pode ter objetos como este:
function Product(name,price) {
this.name = name;
this.price = price;
this.discount = 0;
}
var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10); // {name:"T-shirt",price:10,discount:0}
Em seguida, no código do seu cliente (a e-shop), você pode adicionar descontos aos seus produtos:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Mais tarde, o proprietário da loja virtual pode perceber que o desconto não pode ser maior do que 80%. Agora você precisa encontrar TODAS as ocorrências da modificação do desconto no código do cliente e adicionar uma linha
if(obj.discount>80) obj.discount = 80;
Em seguida, o proprietário da loja virtual poderá mudar ainda mais sua estratégia, como "se o cliente for revendedor, o desconto máximo pode ser de 90%" . E você precisa fazer a alteração em vários locais novamente, além de lembrar de alterar essas linhas sempre que a estratégia for alterada. Este é um design ruim. É por isso que o encapsulamento é o princípio básico da OOP. Se o construtor fosse assim:
function Product(name,price) {
var _name=name, _price=price, _discount=0;
this.getName = function() { return _name; }
this.setName = function(value) { _name = value; }
this.getPrice = function() { return _price; }
this.setPrice = function(value) { _price = value; }
this.getDiscount = function() { return _discount; }
this.setDiscount = function(value) { _discount = value; }
}
Então você pode apenas alterar os métodos getDiscount
( acessador ) e setDiscount
( mutador ). O problema é que a maioria dos membros se comporta como variáveis comuns, apenas o desconto precisa de cuidados especiais aqui. Mas um bom design exige o encapsulamento de todos os membros de dados para manter o código extensível. Então você precisa adicionar muito código que não faz nada. Esse também é um design ruim, um antipadrão padrão . Às vezes, você não pode refatorar os campos para os métodos posteriormente (o código da eshop pode crescer grande ou algum código de terceiros pode depender da versão antiga); portanto, o clichê é menos ruim aqui. Mas ainda assim, é mau. É por isso que as propriedades foram introduzidas em vários idiomas. Você pode manter o código original, basta transformar o membro de desconto em uma propriedade comget
e set
blocos:
function Product(name,price) {
this.name = name;
this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
var _discount; // private member
Object.defineProperty(this,"discount",{
get: function() { return _discount; },
set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
});
}
// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called
Observe a última, mas uma linha: a responsabilidade pelo valor correto do desconto foi movida do código do cliente (definição da loja virtual) para a definição do produto. O produto é responsável por manter seus membros de dados consistentes. Um bom design é (grosso modo) se o código funcionar da mesma maneira que nossos pensamentos.
Muito sobre propriedades. Mas o javascript é diferente das linguagens orientadas a objetos puras como C # e codifica os recursos de maneira diferente:
Em C # , transformar campos em propriedades é uma mudança radical ; portanto, os campos públicos devem ser codificados como Propriedades Auto-Implementadas se o seu código puder ser usado no cliente compilado separadamente.
Em Javascript , as propriedades padrão (membro de dados com getter e setter descritas acima) são definidas pelo descritor do acessador (no link que você tem na sua pergunta). Exclusivamente, você pode usar o descritor de dados (para que você não possa usar, por exemplo, valor e definir na mesma propriedade):
- descritor do acessador = get + set (veja o exemplo acima)
- get deve ser uma função; seu valor de retorno é usado na leitura da propriedade; se não especificado, o padrão é indefinido , que se comporta como uma função que retorna indefinida
- conjunto deve ser uma função; seu parâmetro é preenchido com RHS ao atribuir um valor à propriedade; se não especificado, o padrão é indefinido , que se comporta como uma função vazia
- descritor de dados = valor + gravável (veja o exemplo abaixo)
- valor padrão indefinido ; se gravável , configurável e enumerável (veja abaixo) forem verdadeiros, a propriedade se comportará como um campo de dados comum
- gravável - padrão falso ; se não for verdade , a propriedade é somente leitura; tentativa de gravação é ignorada sem erro *!
Ambos os descritores podem ter estes membros:
- configurável - padrão false ; se não for verdade, a propriedade não pode ser excluída; tentativa de excluir é ignorada sem erro *!
- enumerável - padrão falso ; se for verdade, será iterado
for(var i in theObject)
; se falso, não será iterado, mas ainda estará acessível como público
* a menos que no modo estrito - nesse caso, o JS interrompe a execução com TypeError, a menos que seja capturado no bloco try-catch
Para ler essas configurações, use Object.getOwnPropertyDescriptor()
.
Aprenda pelo exemplo:
var o = {};
Object.defineProperty(o,"test",{
value: "a",
configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings
for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable
Se você não deseja permitir que o cliente codifique tais truques, pode restringir o objeto em três níveis de confinamento:
- Object.preventExtensions (yourObject) impede que novas propriedades sejam adicionadas ao yourObject . Use
Object.isExtensible(<yourObject>)
para verificar se o método foi usado no objeto. A prevenção é superficial (leia abaixo).
- Object.seal (yourObject) é o mesmo que acima e as propriedades não podem ser removidas (configuradas efetivamente
configurable: false
para todas as propriedades). UseObject.isSealed(<yourObject>)
para detectar esse recurso no objeto. O selo é raso (leia abaixo).
- O Object.freeze (yourObject) é o mesmo que acima e as propriedades não podem ser alteradas (efetivamente configuradas
writable: false
para todas as propriedades com o descritor de dados). A propriedade gravável do setter não é afetada (pois não possui uma). O congelamento é raso : significa que, se a propriedade for Objeto, suas propriedades NÃO serão congeladas (se desejar, você deverá executar algo como "congelamento profundo", semelhante à clonagem profunda ). UseObject.isFrozen(<yourObject>)
para detectá-lo.
Você não precisa se preocupar com isso se escrever apenas algumas linhas divertidas. Mas se você deseja codificar um jogo (como você mencionou na pergunta vinculada), realmente deve se preocupar com o bom design. Tente pesquisar algo no Google sobre antipadrões e cheiro de código . Isso ajudará você a evitar situações como "Ah, preciso reescrever completamente meu código novamente!" , você pode economizar meses de desespero se quiser codificar muito. Boa sorte.