Подтвердить что ты не робот

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
            }
        })
    })
};
4b9b3361

Ответ 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;

Принятый ответ в основном помещает все частные переменные в контейнер, что на самом деле лучше.