Cómo usar JavaScript Object.defineProperty

183

Miré a mi alrededor para Object.definePropertysaber cómo usar el método, pero no pude encontrar 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, esto getes lo que no puedo obtener (juego de palabras). ¿Como funciona?

Enfriador de matemáticas
fuente
1
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… este es un excelente tutorial aquí.
Martian2049

Respuestas:

499

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 congety setbloques:

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á enfor(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 usaObject.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 efectivamenteconfigurable: falseen 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 efectivamentewritable: falseen 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.

Jan Turoň
fuente
Esta porción es clara. function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount>80) _discount = 80; } }); } 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
abu abu
27

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

console.log(player.health);

Efectivamente, no es muy diferente a:

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

Se establece lo contrario de get, que se usaría cuando asigne el valor. Como no hay setter, parece que la asignación a la salud del jugador no tiene la intención:

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

Pablo
fuente
es como una función que no necesitas usar ()para llamar ... No entiendo cuál fue la idea cuando inventaron esto. Las funciones son totalmente iguales: jsbin.com/bugipi/edit?js,console,output
vsync
15

defineProperty es un método en Object que le permite configurar las propiedades para cumplir con algunos criterios. Aquí hay un ejemplo simple con un objeto de empleado con dos propiedades firstName y lastName y agregue las dos propiedades anulando 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á salida 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 va a ser una función y tres parámetros escribibles, enumerables y configurable. En este momento acabo de declarar todo como verdadero.

Si ejecuta el ejemplo, obtendrá Output como: Jameel Moideen

Comprendamos por qué necesitamos las tres propiedades, como escritura, enumerable y configurable.

grabable

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

ingrese la descripción de la imagen aquí

Si ejecuta esto de nuevo, todo se rompe. Cambiemos de escritura a falso. Si vuelve a ejecutar lo mismo, obtendrá la salida correcta como 'Jameel Moideen'. Esta propiedad evitará sobrescribir esta propiedad más adelante.

enumerable

Si imprime todas las claves dentro del objeto, puede ver todas las propiedades, incluyendo toString.

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

ingrese la descripción de la imagen aquí

si establece enumerable en falso, puede ocultar la propiedad toString de todos los demás. Si ejecuta esto nuevamente, obtendrá firstName, lastName

configurable

si alguien más tarde redefinió el objeto más tarde, por ejemplo, enumerable a verdadero y lo ejecutó. Puede ver que la propiedad toString volvió a aparecer.

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));

ingrese la descripción de la imagen aquí

puede restringir este comportamiento configurando configurable en falso.

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

Code-EZ
fuente
1
Entiendo que tenías esto en tu blog y lo acabas de pegar aquí, pero al menos sé esto para el futuro: las capturas de pantalla no son populares en SO. No puede copiar el código de la pasta para probarlo y los motores de búsqueda o la tecnología de asistencia no verán el código.
Domino
@JacqueGoupil Tienes razón. Actualizaré agregando código en lugar de captura de pantalla
Code-EZ
3

Básicamente, definePropertyes un método que toma 3 parámetros: un objeto, una propiedad y un descriptor. Lo que sucede en esta llamada en particular es que la "health"propiedad del playerobjeto se asigna a 10 más 15 veces el nivel de ese objeto jugador.

Cole Pilegard
fuente
0

sí, no más funciones que se extienden para el configurador y getter de configuración. Este es mi ejemplo Object.defineProperty (obj, name, func)

var obj = {};
['data', 'name'].forEach(function(name) {
    Object.defineProperty(obj, name, {
        get : function() {
            return 'setter & getter';
        }
    });
});


console.log(obj.data);
console.log(obj.name);
Faizal Pribadi
fuente
0

Object.defineProperty () es una función global ... No está disponible dentro de la función que declara el objeto de otra manera. Tendrá que usarlo estáticamente ...

ISONecroMAn
fuente
0

Resumen:

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

Object.definePropertyse utiliza para hacer una nueva propiedad en el objeto jugador. Object.definePropertyes una función que está presente de forma nativa en el entorno de tiempo de ejecución JS y toma los siguientes argumentos:

Object.defineProperty(obj, prop, descriptor)

  1. El objeto sobre el que queremos definir una nueva propiedad
  2. El nombre de la nueva propiedad que queremos definir.
  3. objeto descriptor

El objeto descriptor es la parte interesante. Aquí podemos definir las siguientes cosas:

  1. configurable <boolean> : si true el descriptor de propiedad puede modificarse y la propiedad puede eliminarse del objeto. Si es configurable, falselas propiedades del descriptor que se pasan Object.definePropertyno se pueden cambiar.
  2. Escribible <boolean> : si truela propiedad puede sobrescribirse utilizando el operador de asignación.
  3. Enumerable <boolean> : si true la propiedad se puede iterar en un for...inbucle. Además, al usar la Object.keysfunción, la tecla estará presente. Si la propiedad es false, no se repetirán usando un for..inbucle y no se mostrarán cuando se use Object.keys.
  4. get <function> : una función que se llama siempre que se requiera la propiedad. En lugar de dar el valor directo, se llama a esta función y el valor devuelto se da como el valor de la propiedad
  5. set <function> : una función que se llama siempre que se asigna la propiedad. En lugar de establecer el valor directo, se llama a esta función y el valor devuelto se utiliza para establecer el valor de la propiedad.

Ejemplo:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}

Willem van der Veen
fuente
0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font

Alvin Smith
fuente
0

Define una nueva propiedad directamente en un objeto, o modifica una propiedad existente en un objeto, y devuelve el objeto.

Nota: Llame a este método directamente en el constructor de objetos en lugar de en una instancia de tipo Object.

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

ingrese la descripción de la imagen aquí

Explicación simple sobre definir propiedad.

Código de ejemplo: https://jsfiddle.net/manoj_antony32/pu5n61fs/

Mano
fuente
0

Object.defineProperty(Array.prototype, "last", {
  get: function() {
    if (this[this.length -1] == undefined) { return [] }
    else { return this[this.length -1] }
  }
});

console.log([1,2,3,4].last) //returns 4

Jaeyson Anthony Y.
fuente