Object.defineProperty
メソッドの使用方法を調べましたが、適切なものが見つかりませんでした。
誰かが私に与えた コードのこのスニペット :
Object.defineProperty(player, "health", {
get: function () {
return 10 + ( player.level * 15 );
}
})
しかし、私はそれを理解していません。主に、get
は取得できません(意図されたしゃれ)。どのように機能しますか?
類似した質問 を尋ねたので、段階的に見ていきましょう。少し長くなりますが、この記事を書くのに費やした時間よりもはるかに多くの時間を節約できます。
Propertyは、クライアントコードを完全に分離するために設計された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%になります」。そして、あなたは再び複数の場所で変更を行う必要があり、さらに戦略が変更されたときはいつでもこれらの行を変更することを忘れないでください。これは悪い設計です。だからencapsulationは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#では、フィールドのプロパティへの変換は breaking change であるため、パブリックフィールドは Auto-実装されたプロパティ コードが個別にコンパイルされたクライアントで使用される可能性がある場合。
Javascriptでは、標準のプロパティ(上記のゲッターとセッターを持つデータメンバー)はアクセサー記述子によって定義されます(質問にあるリンク内)。排他的に、データ記述子を使用できます(つまり、valueおよびset同じプロパティで):
両方の記述子に次のメンバーを含めることができます。
for(var i in theObject)
で繰り返されます。 falseの場合、反復されませんが、パブリックとしてアクセス可能です。* strict mode の場合を除いて-その場合、JSは try-catch block で捕捉されない限り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.isExtensible(<yourObject>)
を使用して、オブジェクトでメソッドが使用されたかどうかを確認します。防止はshallowです(下記参照)。configurable: false
を効果的に設定します)。 Object.isSealed(<yourObject>)
を使用して、オブジェクトのこの機能を検出します。シールはshallowです(下記参照)。writable: false
を効果的に設定します)。 Setterの書き込み可能なプロパティは影響を受けません(プロパティがないため)。フリーズはshallow:プロパティがObjectの場合、そのプロパティはフリーズされないことを意味します(必要に応じて、「ディープフリーズ」のような何かを実行する必要があります- ディープコピー-クローニング )。 Object.isFrozen(<yourObject>)
を使用して検出します。ほんの数行を楽しく書くだけで、これを気にする必要はありません。しかし、リンクされた質問で述べたように、ゲームをコーディングしたい場合は、本当に良いデザインに気を配るべきです。 アンチパターンとコードの匂いについてグーグルを試してみてください。 「ああ、コードを完全に書き直す必要があります!」のような状況を回避するのに役立ちます。多くのコードを作成したい場合、絶望の月を節約できます。がんばろう。
get
は、次のように、値player.health
を読み取ろうとするときに呼び出される関数です。
console.log(player.health);
事実上、以下とそれほど変わりません:
player.getHealth = function(){
return 10 + this.level*15;
}
console.log(player.getHealth());
値の割り当て時に使用されるgetの反対が設定されます。セッターがないため、プレーヤーのヘルスへの割り当ては意図されていないようです:
player.health = 5; // Doesn't do anything, since there is no set function defined
非常に簡単な例:
var player = {
level: 5
};
Object.defineProperty(player, "health", {
get: function() {
return 10 + (player.level * 15);
}
});
console.log(player.health); // 85
player.level++;
console.log(player.health); // 100
player.health = 5; // Does nothing
console.log(player.health); // 100
definePropertyはObjectのメソッドであり、いくつかの基準を満たすようにプロパティを設定できます。 firstNameとlastNameの2つのプロパティを持つ従業員オブジェクトの簡単な例を次に示し、オブジェクトのtoStringメソッドをオーバーライドして2つのプロパティを追加します。
var employee = {
firstName: "Jameel",
lastName: "Moideen"
};
employee.toString=function () {
return this.firstName + " " + this.lastName;
};
console.log(employee.toString());
次のように出力されます:Jameel Moideen
オブジェクトでdefinePropertyを使用して同じコードを変更します
var employee = {
firstName: "Jameel",
lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
value: function () {
return this.firstName + " " + this.lastName;
},
writable: true,
enumerable: true,
configurable: true
});
console.log(employee.toString());
最初のパラメーターはオブジェクトの名前で、2番目のパラメーターは追加するプロパティの名前です。この場合はtoStringで、最後のパラメーターはjsonオブジェクトで、値は関数になり、3つのパラメーターは書き込み可能、列挙可能ですそして今、私はすべてを真と宣言しました。
この例を実行すると、次のように出力されます:Jameel Moideen
書き込み可能、列挙可能、構成可能の3つのプロパティが必要な理由を理解しましょう。
書き込み可能
Javascriptの非常に迷惑な部分の1つは、toStringプロパティを他の何かに変更した場合です
これを再度実行すると、すべてが壊れます。書き込み可能をfalseに変更しましょう。同じものを再度実行すると、「Jameel Moideen」として正しい出力が得られます。このプロパティは、後でこのプロパティが上書きされるのを防ぎます。
列挙可能
オブジェクト内のすべてのキーを印刷すると、toStringを含むすべてのプロパティを確認できます。
console.log(Object.keys(employee));
enumerableをfalseに設定すると、他のすべてのユーザーに対してtoStringプロパティを非表示にできます。これを再度実行すると、firstName、lastNameが取得されます
設定可能
誰かが後でオブジェクトを再定義して、たとえば、enumerableをtrueに設定して実行した場合。 toStringプロパティが再び表示されます。
var employee = {
firstName: "Jameel",
lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
value: function () {
return this.firstName + " " + this.lastName;
},
writable: false,
enumerable: false,
configurable: true
});
//change enumerable to false
Object.defineProperty(employee, 'toString', {
enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));
構成可能をfalseに設定することにより、この動作を制限できます。
基本的に、defineProperty
は、オブジェクト、プロパティ、記述子の3つのパラメーターを受け取るメソッドです。この特定の呼び出しで行われているのは、player
オブジェクトの"health"
プロパティが、プレーヤーオブジェクトのレベルの10プラス15倍に割り当てられていることです。
Object.defineProperty(player, "health", {
get: function () {
return 10 + ( player.level * 15 );
}
});
Object.defineProperty
は、プレーヤーオブジェクトに新しいプロパティを作成するために使用されます。 Object.defineProperty
は、JSランタイム環境にネイティブに存在する関数であり、次の引数を取ります。
Object.defineProperty(obj, prop, descriptor)
記述子オブジェクトは興味深い部分です。ここでは、次のものを定義できます。
<boolean>
:true
の場合、プロパティ記述子が変更され、プロパティがオブジェクトから削除される場合があります。 configurableがfalse
の場合、Object.defineProperty
で渡される記述子プロパティーは変更できません。<boolean>
:true
の場合、割り当て演算子を使用してプロパティを上書きできます。<boolean>
:true
の場合、for...in
ループでプロパティを反復処理できます。また、Object.keys
関数を使用すると、キーが存在します。プロパティがfalse
である場合、for..in
ループを使用して反復されることはなく、Object.keys
を使用する場合は表示されません。<function>
:プロパティが必要なときに呼び出される関数。直接値を与える代わりに、この関数が呼び出され、戻り値がプロパティの値として与えられます<function>
:プロパティが割り当てられるたびに呼び出される関数。直接値を設定する代わりに、この関数が呼び出され、返された値を使用してプロパティの値が設定されます。const player = {
level: 10
};
Object.defineProperty(player, "health", {
configurable: true,
enumerable: false,
get: function() {
console.log('Inside the get function');
return 10 + (player.level * 15);
}
});
console.log(player.health);
// the get function is called and the return value is returned as a value
for (let prop in player) {
console.log(prop);
// only prop is logged here, health is not logged because is not an iterable property.
// This is because we set the enumerable to false when defining the property
}
import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'
export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400
type Font = {
color: string,
size: string,
accent: Font,
default: Font,
light: Font,
neutral: Font,
xsmall: Font,
small: Font,
medium: Font,
large: Font,
xlarge: Font,
xxlarge: Font
} & (() => CSSProperties)
function font (this: Font): CSSProperties {
const css = {
color: this.color,
fontFamily: FAMILY,
fontSize: this.size,
fontWeight: WEIGHT
}
delete this.color
delete this.size
return css
}
const dp = (type: 'color' | 'size', name: string, value: string) => {
Object.defineProperty(font, name, { get () {
this[type] = value
return this
}})
}
dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)
export default font as Font
はい、セットアップセッターとゲッターの拡張機能はこれが私の例ですObject.defineProperty(obj、name、func)
var obj = {};
['data', 'name'].forEach(function(name) {
Object.defineProperty(obj, name, {
get : function() {
return 'setter & getter';
}
});
});
console.log(obj.data);
console.log(obj.name);
Object.defineProperty()はグローバル関数です。それ以外の場合はオブジェクトを宣言する関数内では使用できません。静的に使用する必要があります...