Como usted hizo una pregunta similar , vamos a hacerlo paso a paso. Es un poco más largo, pero puede ahorrarle mucho más tiempo del que he dedicado a escribir esto:
La propiedad es una característica de OOP diseñada para una separación limpia del código del cliente. Por ejemplo, en algunas tiendas electrónicas puede tener 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}
Luego, en su código de cliente (la tienda electrónica), puede agregar descuentos a sus productos:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Más tarde, el propietario de la tienda electrónica podría darse cuenta de que el descuento no puede ser superior al 80%. Ahora necesita encontrar CADA ocurrencia de la modificación del descuento en el código del cliente y agregar una línea
if(obj.discount>80) obj.discount = 80;
Luego, el propietario de la tienda electrónica puede cambiar aún más su estrategia, como "si el cliente es revendedor, el descuento máximo puede ser del 90%" . Y debe volver a hacer el cambio en varios lugares, además debe recordar modificar estas líneas cada vez que cambie la estrategia. Este es un mal diseño. Es por eso que la encapsulación es el principio básico de la POO. Si el constructor fuera así:
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; }
}
Entonces puede simplemente alterar los métodos getDiscount
( accesor ) y setDiscount
( mutador ). El problema es que la mayoría de los miembros se comportan como variables comunes, solo el descuento necesita atención especial aquí. Pero un buen diseño requiere la encapsulación de cada miembro de datos para mantener el código extensible. Por lo tanto, debe agregar mucho código que no hace nada. Este también es un mal diseño, un antipatrón repetitivo . A veces no puede simplemente refactorizar los campos a los métodos más tarde (el código de eshop puede crecer o algunos códigos de terceros pueden depender de la versión anterior), por lo que la placa repetitiva es menos malvada aquí. Pero aún así, es malo. Es por eso que las propiedades se introdujeron en muchos idiomas. Puede conservar el código original, simplemente transformar el miembro de descuento en una propiedad conget
y set
bloques:
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
Tenga en cuenta la última pero una línea: la responsabilidad del valor de descuento correcto se trasladó del código del cliente (definición de tienda electrónica) a la definición del producto. El producto es responsable de mantener consistentes sus miembros de datos. Un buen diseño es (más o menos dicho) si el código funciona de la misma manera que nuestros pensamientos.
Mucho sobre propiedades. Pero JavaScript es diferente de los lenguajes orientados a objetos puros como C # y codifica las características de manera diferente:
En C # , la transformación de campos en propiedades es un cambio radical , por lo que los campos públicos deben codificarse como Propiedades implementadas automáticamente si su código puede usarse en un cliente compilado por separado.
En Javascript , las propiedades estándar (miembro de datos con getter y setter descrito anteriormente) se definen por descriptor de acceso (en el enlace que tiene en su pregunta). Exclusivamente, puede usar el descriptor de datos (por lo que no puede usar, es decir, el valor y establecer en la misma propiedad):
- descriptor de acceso = get + set (ver el ejemplo anterior)
- get debe ser una función; su valor de retorno se usa para leer la propiedad; si no se especifica, el valor predeterminado es undefined , que se comporta como una función que devuelve undefined
- conjunto debe ser una función; su parámetro se llena con RHS al asignar un valor a la propiedad; si no se especifica, el valor predeterminado es indefinido , que se comporta como una función vacía
- descriptor de datos = valor + escritura (ver el ejemplo a continuación)
- valor predeterminado indefinido ; Si se puede escribir , configurar y enumerar (ver a continuación), la propiedad se comporta como un campo de datos ordinario
- grabable : valor predeterminado falso ; si no es cierto , la propiedad es de solo lectura; el intento de escribir se ignora sin error *!
Ambos descriptores pueden tener estos miembros:
- configurable - por defecto falso ; si no es cierto, la propiedad no se puede eliminar; intento de borrar se ignora sin error *!
- enumerable : falso predeterminado; si es verdadero, se repetirá en
for(var i in theObject)
; si es falso, no se repetirá, pero aún es accesible como público
* a menos que esté en modo estricto : en ese caso, JS detiene la ejecución con TypeError a menos que se encuentre en el bloque try-catch
Para leer esta configuración, use Object.getOwnPropertyDescriptor()
.
Aprende con el ejemplo:
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
Si no desea permitir que el cliente codifique tales trucos, puede restringir el objeto por tres niveles de confinamiento:
- Object.preventExtensions (yourObject) evita que se agreguen nuevas propiedades a suObject . Se usa
Object.isExtensible(<yourObject>)
para verificar si el método se usó en el objeto. La prevención es poco profunda (lea a continuación).
- Object.seal (yourObject) igual que el anterior y las propiedades no se pueden eliminar (se establece efectivamente
configurable: false
en todas las propiedades). UseObject.isSealed(<yourObject>)
para detectar esta característica en el objeto. El sello es poco profundo (lea a continuación).
- Object.freeze (yourObject) igual que el anterior y las propiedades no se pueden cambiar (se establece efectivamente
writable: false
en todas las propiedades con descriptor de datos). La propiedad de escritura de Setter no se ve afectada (ya que no tiene una). La congelación es superficial : significa que si la propiedad es Objeto, sus propiedades NO ESTÁN congeladas (si lo desea, debe realizar algo como "congelación profunda", similar a la copia profunda - clonación ). ÚseloObject.isFrozen(<yourObject>)
para detectarlo.
No necesita molestarse con esto si solo escribe unas pocas líneas divertidas. Pero si desea codificar un juego (como mencionó en la pregunta vinculada), realmente debería preocuparse por un buen diseño. Intenta buscar en Google algo sobre antipatrones y código de olor . Le ayudará a evitar situaciones como "¡Oh, necesito reescribir completamente mi código nuevamente!" , puede ahorrarle meses de desesperación si desea codificar mucho. Buena suerte.