una propiedad property polyfill objeto mdn create crear agregar javascript prototype defineproperty

javascript - propiedad - JS defineProperty y prototipo



object assign (6)

Como saben, podemos definir captadores y definidores en JS utilizando defineProperty() . Me he quedado atascado al intentar extender mi clase usando defineProperty() .

Aquí hay un código de ejemplo:

Tengo una matriz de campos que se deben agregar a un objeto

fields = ["id", "name", "last_login"]

También tengo una clase que será modificada.

var User = (function(){ // constructor function User(id, name){ this.id = id this.name = name } return User; })();

Y una función que agregará campos a la clase usando defineProperty()

var define_fields = function (fields){ fields.forEach(function(field_name){ var value = null Object.defineProperty(User.prototype, field_name, { get: function(){ return value } set: function(new_value){ /* some business logic goes here */ value = new_value } }) }) };

Después de ejecutar define_fields() tengo mis campos en la instancia del User

define_fields(fields); user1 = new User(1, "Thomas") user2 = new User(2, "John")

Pero los valores de estas propiedades son idénticos.

console.log(user2.id, user2.name) // 2, John console.log(user1.id, user1.name) // 2, John

¿Hay alguna manera de hacer que defineProperty() funcione correctamente en este caso? Si entiendo que el problema tiene un value que se vuelve idéntico para cada instancia de la clase, pero no me doy cuenta de cómo solucionarlo. Gracias de antemano por sus respuestas.

UPD: De esta manera se lanza "RangeError: Se excedió el tamaño máximo de pila de llamadas"

var define_fields = function (fields){ fields.forEach(function(field_name){ Object.defineProperty(User.prototype, field_name, { get: function(){ return this[field_name] } set: function(new_value){ /* some business logic goes here */ this[field_name] = new_value } }) }) };


A medida que defina sus propiedades en el objeto prototipo de todas las instancias de usuario, todos esos objetos compartirán la misma variable de value . Si eso no es lo que desea, deberá llamar a defineFields en cada instancia de usuario por separado, en el constructor:

function User(id, name){ this.define_fields(["name", "id"]); this.id = id this.name = name } User.prototype.define_fields = function(fields) { var user = this; fields.forEach(function(field_name) { var value; Object.defineProperty(user, field_name, { get: function(){ return value; }, set: function(new_value){ /* some business logic goes here */ value = new_value; } }); }); };


De la respuesta aceptada, me doy cuenta de que lo que intentamos hacer aquí es definir variables de instancia privadas . Estas variables deben estar en la instancia (esto), en lugar de en el objeto prototipo. Normalmente nombramos variables privadas prefijando un guión bajo al nombre de la propiedad.

var Vehicle = {}; Object.defineProperty(Vehicle, "make", { get: function() { return this._make; } set: function(value) { this._make = value; } }); function Car(m) { this.make = m; } //this will set the private var _make Car.prototype = Vehicle;

La respuesta aceptada básicamente coloca todas las variables privadas en un contenedor, lo que en realidad es mejor.


Esta solución es sin consumo extra de memoria. Su código actualizado está cerca. Solo necesita usar this.props [field_name] en lugar de dirigir este [field_name].

Tenga en cuenta que la llamada defineProperty se reemplazó a Object.create

Js Fiddle http://jsfiddle.net/amuzalevskyi/65hnpad8/

// util function createFieldDeclaration(fields) { var decl = {}; for (var i = 0; i < fields.length; i++) { (function(fieldName) { decl[fieldName] = { get: function () { return this.props[fieldName]; }, set: function (value) { this.props[fieldName] = value; } } })(fields[i]); } return decl; } // class definition function User(id, name) { this.props = {}; this.id = id; this.name = name; } User.prototype = Object.create(Object.prototype, createFieldDeclaration([''id'',''name''])); // tests var Alex = new User(0, ''Alex''), Andrey = new User(1, ''Andrey''); document.write(Alex.name + ''<br/>''); // Alex document.write(Andrey.name + ''<br/>''); // Andrey Alex.name = "Alexander"; document.write(Alex.name + ''<br/>''); // Alexander document.write(Andrey.name + ''<br/>''); //Andrey


Llegué a la misma conclusión que Mikhail Kraynov tres minutos después de que respondiera. Esa solución define nuevas propiedades cada vez que se llama al constructor. Me pregunté si, como preguntaste, había una manera de poner a los captadores y establecedores en el prototipo. Aquí es lo que se me ocurrió:

var User = (function () { function User (id, nam) { Object.defineProperty (this, ''__'', // Define property for field values { value: {} }); this.id = id; this.nam = nam; } (function define_fields (fields){ fields.forEach (function (field_name) { Object.defineProperty (User.prototype, field_name, { get: function () { return this.__ [field_name]; }, set: function (new_value) { // some business logic goes here this.__[field_name] = new_value; } }); }); }) (fields); return User; }) ();

En esta solución, defino los captadores y definidores de campo en el prototipo, pero hago referencia a una propiedad (oculta) en cada instancia que contiene los valores de campo.

Vea el violín aquí: http://jsfiddle.net/Ca7yq

Agregué un poco más de código al violín para mostrar algunos efectos en la enumeración de propiedades: http://jsfiddle.net/Ca7yq/1/


Me parece que cuando se definen propiedades para el prototipo, todas las instancias comparten esas propiedades. Así que la variante correcta podría ser

var User = (function(){ // constructor function User(id, name){ this.id = id this.name = name Object.defineProperty(this, "name", { get: function(){ return name }, set: function(new_value){ //Some business logic, upperCase, for example new_value = new_value.toUpperCase(); name = new_value } }) } return User; })();


Por favor, no implemente ninguna otra versión, ya que consumirá toda su memoria en su aplicación:

var Player = function(){this.__gold = 0}; Player.prototype = { get gold(){ return this.__gold * 2; }, set gold(gold){ this.__gold = gold; }, }; var p = new Player(); p.gold = 2; alert(p.gold); // 4

Si instancia 10000 objetos:

  • Con mi método: solo tendrás 2 funciones en la memoria;
  • Con los otros métodos: 10000 * 2 = 20000 funciones en la memoria;