1. ホーム
  2. javascript

[解決済み] javascriptのObject.definePropertyの使い方

2022-03-01 23:34:50

質問

を使用する方法を調べました。 Object.defineProperty というメソッドがあるのですが、まともなものが見つかりませんでした。

ある人から このコードのスニペット :

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

でも、よくわからないんです。主に get が、理解できないのです(ダジャレ)。どのような仕組みになっているのでしょうか?

どのように解決するのですか?

という質問がありましたので 類似の質問 では、順を追って説明しましょう。少し長いですが、私がこれを書くのに費やした時間よりはるかに長い時間を節約できるかもしれません。

プロパティ は、クライアントコードをきれいに分離するために設計された OOP 機能です。例えば、あるe-shopでは、このようなオブジェクトがあるかもしれません。

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-shop)で、商品に割引を追加することができます。

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 ( アクセサー ) と setDiscount ( ミューテーター ) メソッドを使用します。問題は、ほとんどのメンバーが一般的な変数と同じように振る舞うことです。ただ、ここでは割引に特別な注意が必要です。しかし、良い設計では、コードの拡張性を保つために、すべてのデータメンバをカプセル化する必要があります。そのため、何もしないコードをたくさん追加する必要があります。これもまた悪いデザインで ボイラープレート・アンチパターン . フィールドを後でメソッドにリファクタリングできないこともあります(eshopのコードが大きくなったり、サードパーティのコードが古いバージョンに依存することもあります)ので、ここではボイラープレートはあまり悪ではありません。しかし、それでも、それは悪です。だから、多くの言語でプロパティが導入されたのだ。元のコードのまま、discount メンバをプロパティに変換して getset のブロックを作成します。

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-shopの定義)から商品定義に移されました。商品には、そのデータメンバーの一貫性を保つ責任があります。良いデザインとは、(おおざっぱに言うと)コードが私たちの考えと同じように動くことです。

プロパティの話はこれくらいにして。しかし、javascriptはC#のような純粋なオブジェクト指向言語とは異なり、機能のコーディングが異なります。

C#の場合 フィールドからプロパティへの変換は 変更 そのため、パブリックフィールドは、以下のようにコーディングする必要があります。 自動実装されるプロパティ もし、あなたのコードが別途コンパイルされたクライアントで使用される可能性がある場合。

ジャバスクリプトの場合 の場合、標準的なプロパティ(上記のゲッターとセッターを持つデータメンバ)は アクセサー記述子 (質問にあるリンクの中)。専ら、あなたは データ記述子 (を使用することはできません。 セット を同じプロパティの上に置く)。

  • アクセサー記述子 = get + set (上の例参照)
    • 得る は関数でなければなりません。その返り値はプロパティを読み込む際に使用されます。 未定義 これは、undefined を返す関数と同じように動作します。
    • セット は関数でなければなりません; そのパラメータはプロパティに値を割り当てる際にRHSで埋められます; 指定されない場合、デフォルトは 未定義 これは空の関数と同じように動作します。
  • データ記述子 = 値 + 書き込み可能(下記例参照)
    • デフォルト 未定義 もし 書き込み可能 , 設定可能 列挙可能 (が真であれば、このプロパティは通常のデータフィールドと同様に動作します。
    • 書き込み可能 - デフォルト false でない場合は 書き込もうとしてもエラーにならず無視されます*!

どちらの記述子もこれらのメンバを持つことができる。

  • コンフィギュラブル - デフォルト 虚偽 もしtrueでなければ、そのプロパティは削除できません。削除しようとしてもエラーにならず無視されます*!
  • 列挙可能 - デフォルト false で反復処理されます。 for(var i in theObject) false の場合、反復処理されませんが、public としてアクセス可能です。

* でない限り ストリクトモード - で捕捉されない限り、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段階の制限でオブジェクトを制限することができます。

  • オブジェクト.preventExtensions(yourObject) に新しいプロパティが追加されるのを防ぎます。 yourObject . 使用方法 Object.isExtensible(<yourObject>) を使用して、そのメソッドがオブジェクト上で使用されたかどうかをチェックします。防止策は 浅い (を読んでください(以下略)。
  • Object.seal(yourObject)です。 上記と同じで、プロパティは削除できません。 configurable: false をすべてのプロパティに適用します)。使用方法 Object.isSealed(<yourObject>) を使用すると、オブジェクト上でこの機能を検出することができます。シールは 浅い (をお読みください)。
  • Object.freeze(yourObject)です。 上記と同じで、プロパティは変更できません。 writable: false をデータディスクリプタを持つすべてのプロパティに適用します)。セッターの書き込み可能なプロパティは影響を受けません(プロパティがないため)。フリーズは 浅い これは、プロパティがオブジェクトの場合、そのプロパティは凍結されないことを意味します。 ディープコピー - 複製 ). 使用方法 Object.isFrozen(<yourObject>) で検出します。

ほんの数行楽しく書くだけなら、わざわざこんなことをする必要はないでしょう。しかし、もしあなたが(リンク先の質問にあるように)ゲームをコーディングしたいのであれば、良いデザインに気を配るべきです。について何かググってみてください。 アンチパターン コード臭 . のような状況を回避するのに役立ちます。 ああ、またコードを完全に書き直さなければならない!" もし、あなたがたくさんのコードを書きたいのであれば、何ヶ月も絶望する必要はありません。幸運を祈ります。