Búsqueda de sitios web

cómo usar Object.defineProperty de JavaScript


Busqué información sobre cómo usar el método Object.defineProperty, pero no encontré nada decente.

Alguien me dio este fragmento de código:

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

Pero no lo entiendo. Principalmente, el get es lo que no puedo comprender (juego de palabras intencionado). ¿Cómo funciona?

Respuesta #1

Dado que hiciste una pregunta similar, vamos a tomarlo paso a paso. Es un poco más largo, pero puede ahorrarte mucho más tiempo del que he invertido en escribir esto:

Property es una característica de la POO diseñada para la separación limpia del código cliente. Por ejemplo, en alguna tienda en línea podrías 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 el código de tu cliente (la tienda en línea), puedes añadir descuentos a tus 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 en línea podría darse cuenta de que el descuento no puede ser mayor, digamos, del 80%. Ahora necesitas encontrar CADA ocurrencia de la modificación del descuento en el código del cliente y añadir una línea

if(obj.discount>80) obj.discount = 80;

Entonces el propietario de la tienda en línea puede cambiar su estrategia aún más, como "si el cliente es revendedor, el descuento máximo puede ser del 90%". Y necesitas hacer el cambio en varios lugares nuevamente, además de que necesitas recordar modificar estas líneas cada vez que la estrategia cambie. Este es un mal diseño. Por eso 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 puedes 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 que el descuento necesita un cuidado especial aquí. Pero un buen diseño requiere la encapsulación de cada miembro de datos para mantener el código extensible. Así que necesitas agregar mucho código que no hace nada. Esto también es un mal diseño, un antipatrón de código repetitivo. A veces no puedes simplemente refactorizar los campos a métodos más tarde (el código de la tienda en línea puede crecer o algún código de terceros puede depender de la versión anterior), por lo que el código repetitivo es el mal menor aquí. Pero aún así, es un mal. Por eso se introdujeron las propiedades en muchos lenguajes. Podrías mantener el código original, solo transformar el miembro de descuento en una propiedad con bloques get y set:

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 la penúltima línea: la responsabilidad del valor de descuento correcto se movió del código del cliente (definición de la tienda en línea) a la definición del producto. El producto es responsable de mantener consistentes sus miembros de datos. Un buen diseño es (dicho de manera aproximada) si el código funciona de la misma manera que nuestros pensamientos.

Alternativamente, ES6 permite la sintaxis class, que es una "función especial" que permite la misma funcionalidad en el código que:

class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
  #discount = 0; // # = private member in classes
  get discount() { return this.#discount; }
  set discount(v) { this.#discount = v; if(this.#discount>80) this.#discount = 80; }
}

Tanto sobre las propiedades. Pero JavaScript es diferente de los lenguajes puramente orientados a objetos como C# y codifica las características de manera diferente:

En C#, transformar campos en propiedades es un cambio que rompe la compatibilidad, por lo que los campos públicos deberían codificarse como Propiedades Auto-implementadas si tu código podría ser utilizado en un cliente compilado por separado.

En Javascript, las propiedades estándar (miembro de datos con getter y setter descritos arriba) se definen mediante descriptor de acceso (en el enlace que tienes en tu pregunta). De manera exclusiva, puedes usar descriptor de datos (así que no puedes usar, por ejemplo, value y set en la misma propiedad):

  • descriptor de accesor=get + set (ver el ejemplo anterior)

    • get debe ser una función; su valor de retorno se utiliza para leer la propiedad; si no se especifica, el valor predeterminado es undefined, que se comporta como una función que devuelve undefined
    • set 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 undefined, que se comporta como una función vacía
  • descriptor de datos=valor + escribible (ver el ejemplo abajo)

    • value por defecto undefined; si writable, configurable y enumerable (ver más abajo) son verdaderos, la propiedad se comporta como un campo de datos ordinario
    • escribible - por defecto false; si no es true, la propiedad es de solo lectura; el intento de escribir se ignora sin error*!

Ambos descriptores pueden tener estos miembros:

  • configurable - valor predeterminado false; si no es verdadero, la propiedad no se puede eliminar; el intento de eliminar se ignora sin error*!
  • enumerable - por defecto false; si es true, se iterará en for(var i in theObject); si es false, no se iterará, pero aún así es accesible como público

* a menos que sea en modo estricto - en ese caso, JS detiene la ejecución con TypeError a menos que se capture en un bloque try-catch

Para leer estos ajustes, utiliza Object.getOwnPropertyDescriptor().

Aprende con un 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 permitirle al código del cliente tales trampas, puede restringir el objeto mediante tres niveles de confinamiento:

  • Object.preventExtensions(yourObject) evita que se agreguen nuevas propiedades a yourObject. Usa Object.isExtensible(<yourObject>) para verificar si el método se aplicó al objeto. La prevención es superficial (lee abajo).
  • Object.seal(yourObject) igual que lo anterior y las propiedades no se pueden eliminar (efectivamente establece configurable: false en todas las propiedades). Usa Object.isSealed(<yourObject>) para detectar esta característica en el objeto. El sellado es superficial (lee abajo).
  • Object.freeze(yourObject) igual que lo anterior y las propiedades no se pueden modificar (efectivamente establece writable: false en todas las propiedades con descriptor de datos). La propiedad writable del setter no se ve afectada (ya que no tiene una). El freeze es superficial: esto significa que si la propiedad es un Object, sus propiedades NO están congeladas (si deseas hacerlo, deberías realizar algo como un "deep freeze", similar a una copia profunda - clonación). Usa Object.isFrozen(<tuObjeto>) para detectarlo.

No necesitas preocuparte por esto si solo escribes unas pocas líneas por diversión. Pero si quieres programar un juego (como mencionaste en la pregunta enlazada), deberías preocuparte por un buen diseño. Intenta buscar en Google algo sobre antipatrón y olor a código. Te ayudará a evitar situaciones como “¡Oh, necesito reescribir completamente mi código de nuevo!”, puede ahorrarte meses de desesperación si quieres programar mucho. Buena suerte.

Respuesta #2

get es una función que se llama cuando intentas leer el valor player.health, como en:

console.log(player.health);

En efecto, no es muy diferente de:

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());

El opuesto de get es set, que se usaría cuando se asigna un valor. Dado que no hay un setter, parece que asignar a la salud del jugador no está previsto:

player.health = 5; // Doesn't do anything, since there is no set function defined

Un ejemplo muy simple:

var player = {
  level: 5
};

Object.defineProperty(player, "health", {
  get: function() {
    return 10 + (player.level * 15);
  }
});

console.log(player.health); // 85
player.level++;
console.log(player.health); // 100

player.health = 5; // Does nothing
console.log(player.health); // 100

Respuesta #3

defineProperty es un método de Object que te permite configurar las propiedades para cumplir con ciertos criterios. Aquí hay un ejemplo simple con un objeto empleado con dos propiedades firstName y lastName y concatena las dos propiedades sobrescribiendo el método toString en el objeto.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
employee.toString=function () {
    return this.firstName + " " + this.lastName;
};
console.log(employee.toString());

Obtendrás el resultado como: Jameel Moideen

Voy a cambiar el mismo código usando defineProperty en el objeto

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(employee.toString());

El primer parámetro es el nombre del objeto y luego el segundo parámetro es el nombre de la propiedad que estamos agregando, en nuestro caso es toString y luego el último parámetro es un objeto json que tiene un valor que será una función y tres parámetros: writable, enumerable y configurable. Por ahora, simplemente declaré todo como verdadero.

Si ejecutas el ejemplo obtendrás el resultado como: Jameel Moideen

Entendamos por qué necesitamos las tres propiedades como escribible, enumerable y configurable.

escribible

Una de las partes muy molestas de JavaScript es que, si cambias la propiedad toString a otra cosa, por ejemplo

si ejecutas esto de nuevo, todo se rompe. Cambiemos writable a false. Si lo ejecutas de nuevo obtendrás la salida correcta como ‘Jameel Moideen’. Esta propiedad evitará sobrescribir esta propiedad más tarde.

enumerable

si imprimes todas las claves dentro del objeto, puedes ver todas las propiedades incluyendo toString.

console.log(Object.keys(employee));

si estableces enumerable en false, puedes ocultar la propiedad toString de todos los demás. Si ejecutas esto nuevamente, obtendrás firstName,lastName

configurable

si alguien más tarde redefiniera el objeto más tarde, por ejemplo enumerable a true y lo ejecutara. Puedes ver que la propiedad toString apareció de nuevo.

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: false,
    enumerable: false,
    configurable: true
});

//change enumerable to false
Object.defineProperty(employee, 'toString', {

    enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));

puedes restringir este comportamiento configurando configurable en falso.

La referencia original de esta información es de mi blog personal

Artículos relacionados