JS defineProperty и прототип
Как вы знаете, мы можем определить геттеры и сеттеры в JS, используя defineProperty()
. Я пытаюсь расширить класс, используя defineProperty()
.
Вот пример кода:
У меня есть массив полей, которые нужно добавить к объекту
fields = ["id", "name", "last_login"]
Также у меня есть класс, который будет изменен
var User = (function(){
// constructor
function User(id, name){
this.id = id
this.name = name
}
return User;
})();
И функция, которая добавит поля в класс с помощью 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
}
})
})
};
После запуска define_fields()
у меня есть мои поля в экземпляре User
define_fields(fields);
user1 = new User(1, "Thomas")
user2 = new User(2, "John")
Но значения этих свойств идентичны
console.log(user2.id, user2.name) // 2, John
console.log(user1.id, user1.name) // 2, John
Есть ли способ сделать defineProperty()
корректно работать в этом случае?
Если я понимаю, что проблема связана с value
, которая становится идентичной для
каждый экземпляр класса, но я не могу понять, как его исправить. заранее спасибо
для ваших ответов.
UPD:
Таким образом, "RangeError: превышен максимальный размер стека вызовов"
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
}
})
})
};
Ответы
Ответ 1
Пожалуйста, не устанавливайте никакую другую версию, потому что она съест всю вашу память в вашем приложении:
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
Если создано 10000 объектов:
- С моим методом: у вас будет только 2 функции в памяти;
- С другими методами: 10000 * 2 = 20000 функций в памяти;
Ответ 2
Я пришел к тому же выводу, что http://jsfiddle.net/Ca7yq
Я добавил еще немного кода в скрипт, чтобы показать некоторые эффекты для перечисления свойств: http://jsfiddle.net/Ca7yq/1/
Ответ 3
Мне кажется, что когда вы определяетеProperties для прототипа, все экземпляры разделяют эти свойства. Таким образом, правильный вариант может быть
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;
})();
Ответ 4
Когда вы определяете свои свойства на объекте прототипа всех пользовательских экземпляров, все эти объекты будут иметь одну и ту же переменную value
. Если это не то, что вы хотите, вам нужно вызвать defineFields
для каждого экземпляра пользователя отдельно - в конструкторе:
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;
}
});
});
};
Ответ 5
Это решение не требует дополнительного объема памяти. Ваш обновленный код близок. Вам просто нужно использовать this.props [field_name] вместо прямого этого [field_name].
Обратите внимание, что вызов defineProperty заменен на 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
Ответ 6
Из принятого ответа я понимаю, что мы здесь пытаемся определить частные переменные экземпляра. Эти переменные должны быть в экземпляре (это), а не на объекте прототипа. Обычно мы называем частные переменные префиксом подчеркивания имени свойства.
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;
Принятый ответ в основном помещает все частные переменные в контейнер, что на самом деле лучше.