同様の質問をしたので、少しずつ見ていきましょう。少し長くなりますが、これを書くのに費やした時間よりもはるかに多くの時間を節約できます。
プロパティは、クライアントコードを完全に分離するために設計されたOOP機能です。たとえば、一部のeショップでは、次のようなオブジェクトがあるとします。
function Product(name,price) {
this.name = name;
this.price = price;
this.discount = 0;
}
var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10); // {name:"T-shirt",price:10,discount:0}
次に、クライアントコード(eショップ)で、製品に割引を追加できます。
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
その後、eショップの所有者は、割引が80%を超えることはできないことに気付くでしょう。ここで、クライアントコードで割引の変更が発生するたびに検出し、行を追加する必要があります。
if(obj.discount>80) obj.discount = 80;
次に、eショップの所有者は、「顧客がリセラーの場合、最大の割引は90%になる可能性があります」のように、さらに戦略を変更できます。そして、あなたは再び複数の場所で変更を行う必要があり、さらに戦略が変更されたときはいつでもこれらの行を変更することを覚えておく必要があります。これは悪いデザインです。それが、カプセル化がOOPの基本原理である理由です。コンストラクタが次のようである場合:
function Product(name,price) {
var _name=name, _price=price, _discount=0;
this.getName = function() { return _name; }
this.setName = function(value) { _name = value; }
this.getPrice = function() { return _price; }
this.setPrice = function(value) { _price = value; }
this.getDiscount = function() { return _discount; }
this.setDiscount = function(value) { _discount = value; }
}
次に、getDiscount
(accessor)およびsetDiscount
(mutator)メソッドを変更するだけです。問題は、ほとんどのメンバーが共通変数のように動作することです。ここでは割引だけに特別な注意が必要です。ただし、優れた設計では、コードを拡張可能に保つために、すべてのデータメンバーをカプセル化する必要があります。したがって、何もしないコードをたくさん追加する必要があります。これも悪いデザイン、定型アンチパターンです。後でフィールドをメソッドにリファクタリングできない場合があります(eshopコードが大きくなるか、一部のサードパーティコードが古いバージョンに依存する可能性があるため)。しかし、それでも、それは悪です。これが、プロパティが多くの言語に導入された理由です。元のコードを保持して、割引メンバーをプロパティに変換するだけですget
とset
ブロック:
function Product(name,price) {
this.name = name;
this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
var _discount; // private member
Object.defineProperty(this,"discount",{
get: function() { return _discount; },
set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
});
}
// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called
最後の1行に注意してください。正しい割引値の責任は、クライアントコード(eショップ定義)から製品定義に移動しました。製品は、データメンバーの一貫性を保つ責任があります。コードが私たちの考えと同じように機能する場合、良いデザインは(大雑把に言えば)です。
プロパティについてはこれくらいです。ただし、JavaScriptはC#のような純粋なオブジェクト指向言語とは異なり、機能のコーディング方法が異なります。
C#では、フィールドをプロパティに変換することは重大な変更です。そのため、コードが個別にコンパイルされたクライアントで使用される可能性がある場合は、パブリックフィールドを自動実装プロパティとしてコーディングする必要があります。
Javascriptでは、標準プロパティ(上記のゲッターとセッターを持つデータメンバー)は、アクセサーディスクリプター(質問のリンク内)によって定義されます。排他的に、データ記述子を使用できます(つまり、値を使用して同じプロパティに設定することはできません)。
- アクセサー記述子 = get + set(上記の例を参照)
- getは関数でなければなりません。その戻り値は、プロパティの読み取りに使用されます。指定しない場合、デフォルトは undefinedで、undefinedを返す関数のように動作します
- セットは関数でなければなりません。プロパティに値を割り当てる際、そのパラメーターにはRHSが入力されます。指定しない場合、デフォルトは undefinedで、空の関数のように動作します
- データ記述子 =値+書き込み可能(以下の例を参照)
- 値デフォルト未定義 ; 場合は、書き込み可能な、設定可能と列挙(下記参照)が真である、通常のデータフィールドのようなプロパティに振る舞います
- 書き込み可能 -デフォルトは falseです。trueでない場合、プロパティは読み取り専用です。書き込みを試みてもエラーなしで無視されます*!
どちらの記述子にも次のメンバーを含めることができます。
- 構成可能 -デフォルトは falseです。trueでない場合、プロパティは削除できません。エラーなしで削除しようとしても無視されます*!
- 列挙可能 -デフォルトは falseです。trueの場合、で反復され
for(var i in theObject)
ます。falseの場合は反復されませんが、パブリックとしてアクセスできます
* strictモードでない限り-その場合、try-catchブロックでキャッチされない限り、JSはTypeErrorで実行を停止します
これらの設定を読み取るには、を使用しますObject.getOwnPropertyDescriptor()
。
例で学ぶ:
var o = {};
Object.defineProperty(o,"test",{
value: "a",
configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings
for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable
クライアントコードにこのようなチートを許可したくない場合は、オブジェクトを3つのレベルの制限で制限できます。
- Object.preventExtensions(yourObject)は、新しいプロパティが yourObjectに追加されないようにします。使用
Object.isExtensible(<yourObject>)
方法は、オブジェクトに使用されたかどうかを確認します。予防は浅いです(以下をお読みください)。
- 上記と同じ Object.seal(yourObject)とプロパティは削除できません(
configurable: false
すべてのプロパティに効果的に設定されます)。Object.isSealed(<yourObject>)
オブジェクトのこの機能を検出するために使用します。シールは浅いです(以下を参照)。
- 上記と同じ Object.freeze(yourObject)とプロパティは変更できません(
writable: false
データ記述子を使用してすべてのプロパティに効果的に設定されます)Setterの書き込み可能なプロパティは影響を受けません(プロパティがないため)。フリーズは浅いです。つまり、プロパティがObjectの場合、そのプロパティはフリーズされないことを意味します(必要に応じて、ディープコピー-クローニングと同様に、「ディープフリーズ」などを実行する必要があります)。Object.isFrozen(<yourObject>)
それを検出するために使用します。
ほんの数行を書くだけなら、これを気にする必要はありません。しかし、(リンクされた質問で述べたように)ゲームをコーディングしたい場合は、優れたデザインを本当に気にする必要があります。アンチパターンとコードの匂いについてグーグルで試してみてください。「ああ、コードをもう一度完全に書き直す必要がある」などの状況を回避するのに役立ちます。、多くのコードを作成したい場合は、何ヶ月にもわたる絶望を救うことができます。幸運を。