web-dev-qa-db-ja.com

コンストラクターのゲッター/セッター

私は最近、JavaScriptでゲッター/セッターを定義する可能性があるという事実について読みました。それは非常に役立つようです-セッターは、実際に設定する前に、最初に設定される値を解析できる一種の「ヘルパー」です。

たとえば、私は現在このコードを持っています:

var obj = function(value) {
    var test = !!value; // 'test' has to be a boolean
    return {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new obj(true);

このコードは常にvalueをブール値に変換します。したがって、instance.test = 0、次にinstance.test === false

ただし、これが機能するためには、実際にはobjectを返す必要があります。つまり、新しいインスタンスはobjタイプではなく、単なるオブジェクトです。つまり、objのプロトタイプを変更してもインスタンスには影響がありません。たとえば、これはnot動作します-instance.funcは未定義です:

obj.prototype.func = function() { console.log(this.value); };

instanceはタイプobjではないためです。プロトタイプ関数を機能させるには、通常のコンストラクターが機能するように、instanceobj型になるように、プレーンオブジェクトを返すのではなく、何も返さないようにする必要があります。

次に問題はゲッター/セッターを実装する方法ですか?これらをオブジェクトに追加する方法を説明している記事はありますが、カスタムタイプのコンストラクタの一部ではありません。

では、ゲッター/セッターを使用してプロトタイプを拡張できるようにするには、コンストラクターにゲッター/セッターをどのように実装しますか?

32
pimvdb

それはできません。

ただし、オブジェクトのプロパティのセッター/ゲッターを設定できます。 ES5を使用することをお勧めします Object.defineProperties ですが。もちろん、これは最新のブラウザでのみ機能します。

var obj = function() {
    ...
    Object.defineProperties(this, {
        "test": {
             "get": function() { ... },
             "set": function() { ... }
        }
    });
}

obj.prototype.func = function() { ... }

var o = new obj;
o.test;
o.func();
47
Raynos

通常はclassメソッドが必要です。 2011年5月7日の@Raynosによる答えは仕事を成し遂げますが、それはクラスメソッドではなくinstanceメソッドを定義します。

以下は、getterとsetterがクラスの一部であるクラス定義を示しています。この定義は@Raynosの回答によく似ていますが、コードに2つの違いがあります。(1)「defineProperties()」アクションがコンストラクターから移動されました。 (2)「defineProperties()」の引数は、インスタンスオブジェクト「this」からコンストラクタのプロトタイプオブジェクトに変更されました。

function TheConstructor(side) {
  this.side = side;
}

Object.defineProperties(TheConstructor.prototype, {
        area: {
             get: function()    { return this.side * this.side; }
            ,set: function(val) { this.side = Math.sqrt(val);   }
        }
});

// Test code:

var anInstance = new TheConstructor(2);
console.log("initial  Area:"+anInstance.area);
anInstance.area = 9;
console.log("modified Area:"+anInstance.area);

これらの結果は次のとおりです。

initial  Area:4
modified Area:9

通常、クラス定義とインスタンス定義の違いは単なるスタイルの問題ですが、優れたスタイルには目的があり、その違いが重要な場合があります:メモ化されたgetter。メモ化されたゲッターの目的は次のとおりです: Smart/self-overwriting/lazy getter

メモ化された値がクラス全体に関係する場合は、クラスレベルでゲッターを定義します。たとえば、構成ファイルは1回だけ読み取る必要があります。結果の値は、プログラムの期間中適用されます。次のサンプルコードは、クラスレベルでメモ化されたゲッターを定義しています。

function configureMe() {
  return 42;
}

Object.defineProperties(TheConstructor.prototype, {
    memoizedConfigParam: {
        get: function() {
            delete TheConstructor.prototype.memoizedConfigParam;
            return TheConstructor.prototype.memoizedConfigParam = configureMe();
        }
        ,configurable:  true
    }
});

// Test code:

console.log("memoizedConfigParam:"+anInstance.memoizedConfigParam);

生成する:

memoizedConfigParam:42

例からわかるように、メモ化されたゲッターは、ゲッター関数が自身を削除し、(おそらく)決して変更されない単純な値に自分自身を置き換えるという特性を持っています。 'configurable'は 'true'に設定する必要があることに注意してください。

メモ化された値がインスタンスの内容に依存する場合は、インスタンスレベルでゲッターを定義します。定義はコンストラクタの内部に移動し、注目の対象は「これ」です。

function TheConstructorI(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            return this.memoizedCalculation = this.expensiveOperation();
        }
        ,configurable:  true
    }
  });
}

TheConstructorI.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instance2 = new TheConstructorI(2);
var instance3 = new TheConstructorI(3);

console.log("memoizedCalculation 2:"+instance2.memoizedCalculation);
console.log("memoizedCalculation 3:"+instance3.memoizedCalculation);

生成する:

memoizedCalculation 2:8
memoizedCalculation 3:27

メモ化された値が変更されないことを(推定ではなく)保証したい場合は、「書き込み可能」属性を変更する必要があります。これにより、コードが少し複雑になります。

function TheConstructorJ(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            Object.defineProperty( this, 'memoizedCalculation'
              ,{  value    : this.expensiveOperation()
                 ,writable : false
              });
            return this.memoizedCalculation;
        }
        ,configurable:  true
    }
  });
}

TheConstructorJ.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instanceJ = new TheConstructorJ(2);

console.log("memoizedCalculation:"+instanceJ.memoizedCalculation);
instanceJ.memoizedCalculation = 42;  // results in error

生成する:

memoizedCalculation:8
>Uncaught TypeError: Cannot assign to read only property 'memoizedCalculation' of object '#<TheConstructorJ>'

2011年3月7日のOPの最初の質問は、基本的なゲッターとセッターの構文を示し、オブジェクトでは機能するが 'this'では機能しないと述べ、コンストラクター内でゲッターとセッターを定義する方法を尋ねました。上記のすべての例に加えて、それを実行する「簡単な」方法もあります。OPのように、コンストラクター内で新しいオブジェクトを作成し、そのオブジェクトを 'this'内のメンバーになるように割り当てます。したがって、元のコードは次のようになります。

var MyClass = function(value) {
    var test = !!value; // 'test' has to be a boolean
    this.data = {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new MyClass(true);

// But now 'data' is part of the access path
instance.data.test = 0;
console.log(instance.data.test);

生成する:

false

信じられないかもしれませんが、私は実際にこの "格安ショット"が最良の解決策である状況に遭遇しました。具体的には、単一のクラス内にカプセル化された複数のテーブルのレコードがあり、それらが「データ」と呼ばれる単一のレコードであるかのように統一されたビューを表示したいときに、この手法を使用しました。

楽しんで。

IAM_AL_X

10
IAM_AL_X

ES6の更新-Alex Rauschmayerの本のセクション19.3.1をご覧くださいExploring ES6http://exploringjs.com/es6 /ch_maps-sets.html#sec_weakmaps-private-data は、ゲッターとセッターでWeakMapを使用してプライベートデータを保持する方法を示しています。セクション16.2.2.3と組み合わせる http://exploringjs.com/es6/ch_classes.html#leanpub-auto-getters-and-setters は次のような結果になります

# module test_WeakMap_getter.js
var _MyClassProp = new WeakMap();
class MyClass {
    get prop() {
        return _MyClassProp.get( this ); 
    }
    set prop(value) {
        _MyClassProp.set( this, value );
    }
}
var mc = new MyClass();
mc.prop = 5 ;
console.log( 'My value is', mc.prop );

$ node --use_strict test_WeakMap_getter.js 
My value is 5
7
Jeff
function Obj(value){
    this.value = !!value;
}

Obj.prototype = {
    get test () {
        return this.value;``
    },
    set test (value) {
        this.value = !!this.value;
    }
};
var obj = new Obj(true);
4

@Alex私はそれをより多くのオプションとより多くの力として見ています。プログラミングは芸術であり、@ Natは彼の発見を私たちと共有し、そのために彼に感謝します。たぶん誰かがそのようにしたいのでしょう。

セッターのバージョンは同じですが、gをsに変更するだけです。

例:

function Constructor(input){
     this.input = input;
}

Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

Object.__defineSetter__.call(Constructor.prototype, "bar", function(foo){
    return this.input *= foo;
});

var test = new Constructor(5);
console.log(test.value); // 10
test.bar = 5;
console.log(test.input); //25

そうは言っても、この機能は非推奨であり、本番用のコーディングでは使用しないことをお勧めします。

1
user3552042

私はこれが非常に遅いかもしれないことを知っていますが、私があなたが望むものを達成するための別の方法を考え出しました。

function Constructor(input){
     this.input = input;
}
Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

var test = new Constructor(5);
alert(test.value) // 10

私はこれをchrome、safari、mobile safari、firefoxでテストしましたが、すべて動作します(もちろん最新バージョン)

1
Akinos