35

ご存じのように、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
            }
        })
    })
};
4

6 に答える 6

18

私はミハイル・クレイノフが答えてから3分後に同じ結論に達しました。このソリューションは、コンストラクターが呼び出されるたびに新しいプロパティを定義します。おっしゃるように、ゲッターとセッターを試作品に入れる方法はあるのでしょうか。これが私が思いついたものです:

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

このソリューションでは、プロトタイプでフィールドゲッターとセッターを定義しますが、フィールド値を保持する各インスタンスの(非表示の)プロパティを参照します。

ここでフィドルを参照してください:http://jsfiddle.net/Ca7yq

プロパティの列挙への影響を示すために、フィドルにコードを追加しました:http: //jsfiddle.net/Ca7yq/1/

于 2012-12-27T11:18:04.597 に答える
8

プロトタイプのプロパティを定義すると、すべてのインスタンスがそのプロパティを共有しているように見えます。したがって、適切なバリアントは

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;
})();
于 2012-12-27T09:40:21.830 に答える
5

すべてのユーザーインスタンスのプロトタイプオブジェクトでプロパティを定義すると、それらのオブジェクトはすべて同じ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;
            }
        });
    });
};
于 2012-12-27T11:16:49.417 に答える
1

このソリューションは、余分なメモリを消費しません。更新されたコードは近いです。this[field_name] を直接使用する代わりに、this.props[field_name] を使用するだけです。

defineProperty 呼び出しが Object.create に置き換えられていることに注意してください。

JS フィドル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
于 2014-11-29T19:53:29.727 に答える
0

受け入れられた答えから、ここでやろうとしていることはプライベートインスタンス変数を定義していることに気づきました。これらの変数は、プロトタイプ オブジェクトではなく、インスタンス (this) にある必要があります。通常、プロパティ名の前にアンダースコアを付けて、プライベート変数に名前を付けます。

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;

受け入れられた答えは、基本的にすべてのプライベート変数を代わりにコンテナーに入れます。これは実際にはより優れています。

于 2014-10-21T17:05:21.800 に答える