提起される最後の2つの問題-
Proxy
と
Symbol
はECMAScript 6仕様に関連しており、一部の近代的なブラウザでのみ部分的に実装および実装されています。
ゲッターとセッター
ゲッターとセッターはしばらくの間JavaScriptで使用できましたが、頻繁に使用する必要があることに気づきませんでした。 多くの場合、次のようなプロパティを取得するための通常の関数を作成します。
/** * @param {string} prefix * @constructor */ function Product(prefix) { /** * @private * @type {string} */ this.prefix_ = prefix; /** * @private * @type {string} */ this.type_ = ""; } /** * @param {string} newType */ Product.prototype.setType = function (newType) { this.type_ = newType; }; /** * @return {string} */ Product.prototype.type = function () { return this.prefix_ + ": " + this.type_; } var product = new Product("fruit"); product.setType("apple"); console.log(product.type()); //logs fruit: apple
jsfiddle
ゲッターを使用すると、このコードを簡素化できます。
/** * @param {string} prefix * @constructor */ function Product(prefix) { /** * @private * @type {number} */ this.prefix_ = prefix; /** * @private * @type {string} */ this.type_ = ""; } /** * @param {string} newType */ Product.prototype = { /** * @return {string} */ get type () { return this.prefix_ + ": " + this.type_; }, /** * @param {string} */ set type (newType) { this.type_ = newType; } }; var product = new Product("fruit"); product.type = "apple"; console.log(product.type); //logs "fruit: apple" console.log(product.type = "orange"); //logs "orange" console.log(product.type); //logs "fruit: orange"
jsfiddle
コードは少し冗長であり、構文は少し変わっていますが、
get
と
set
を使用する利点は、直接使用するとより明らかになります。 私は自分自身にそれを見つけました:
product.type = "apple"; console.log(product.type);
よりはるかに読みやすい:
product.setType("apple"); console.log(product.type());
ただし、組み込みの不正なJavaScriptアラームは、オブジェクトインスタンスへの直接アクセスとプロパティの設定を確認するとトリガーされます。 長い間、クラスインスタンスへのプロパティのby意的な割り当てを避けるために、バグと技術的要件によって教えられました。これは、情報がそれらの間で確実に分散されるという事実につながるためです。 また、設定値が返される順序には微妙な違いがあります。以下の例を参照してください。
console.log(product.type = "orange"); //logs "orange" console.log(product.type); //logs "fruit: orange"
最初に
“orange”
がコンソールに表示され、次に
“fruit: orange”
が表示されることに注意してください。 設定値が返される間、ゲッターは実行されません。そのため、この形式の省略された書き込みでは、問題が発生する場合があります。
set
返される値は無視されます。
return this.type;
追加
return this.type;
set
すると、この問題は解決しません。 これは通常、セットポイントを再利用することで解決されますが、ゲッターを持つプロパティの問題が発生する可能性があります。
defineProperty
構文
get propertyname ()
はオブジェクトリテラルで機能し、前の例では
Product.prototype
オブジェクト
Product.prototype
を割り当てました。 これには何の問題もありませんが、このようなリテラルを使用すると、プロトタイプ呼び出しの連鎖が複雑になり、継承が実装されます。 リテラルを使用せずにプロトタイプでゲッターとセッターを定義することは可能
defineProperty
を使用して
/** * @param {string} prefix * @constructor */ function Product(prefix) { /** * @private * @type {number} */ this.prefix_ = prefix; /** * @private * @type {string} */ this.type_ = ""; } /** * @param {string} newType */ Object.defineProperty(Product.prototype, "type", { /** * @return {string} */ get: function () { return this.prefix_ + ": " + this.type_; }, /** * @param {string} */ set: function (newType) { this.type_ = newType; } });
jsfiddle
このコードの動作は、前の例と同じです。 ゲッターとセッターを追加する代わりに、
defineProperty
が
defineProperty
ます。
defineProperty
の3番目の引数はハンドルであり、
set
および
get
に加えて
get
アクセシビリティを構成し、値を設定する機能を提供します。
defineProperty
を使用すると、定数のようなものを作成できます-削除またはオーバーライドされないプロパティ。
var obj = { foo: "bar", }; //A normal object property console.log(obj.foo); //logs "bar" obj.foo = "foobar"; console.log(obj.foo); //logs "foobar" delete obj.foo; console.log(obj.foo); //logs undefined Object.defineProperty(obj, "foo", { value: "bar", }); console.log(obj.foo); //logs "bar", we were able to modify foo obj.foo = "foobar"; console.log(obj.foo); //logs "bar", write failed silently delete obj.foo; console.log(obj.foo); //logs bar, delete failed silently
jsfiddle
結果:
bar foobar undefined bar bar bar
foo.bar
このデフォルトの動作は変更を禁止することであるため、例の
foo.bar
をオーバーライドする最後の2つの試行は失敗しました(エラーメッセージによって中断されなかった場合でも)。 この動作を変更するには、
configurable
writable
および
writable
使用でき
writable
。 strictモードを使用すると、一般的なJavaScriptエラーであるため、エラーがスローされます。
var obj = {}; Object.defineProperty(obj, "foo", { value: "bar", configurable: true, writable: true, }); console.log(obj.foo); //logs "bar" obj.foo = "foobar"; console.log(obj.foo); //logs "foobar" delete obj.foo; console.log(obj.foo); //logs undefined
jsfiddle
configurable
なキーは、プロパティがオブジェクトから削除されるのを防ぎます。 さらに、
defineProperty
別の呼び出しによるプロパティへの後続の変更を防ぐことができます。
writable
キーを使用すると、プロパティへの書き込みまたは値の変更ができます。
configurable
false
設定されている場合(デフォルト)、
defineProperty
を2回呼び出すと、エラーがスローされます。
var obj = {}; Object.defineProperty(obj, "foo", { value: "bar", }); Object.defineProperty(obj, "foo", { value: "foobar", }); // Uncaught TypeError: Cannot redefine property: foo
jsfiddle
configurable
true
に設定されている
true
、将来プロパティーを変更できます。 これは、書き込み不可のプロパティの値を変更するために使用できます。
var obj = {}; Object.defineProperty(obj, "foo", { value: "bar", configurable: true, }); obj.foo = "foobar"; console.log(obj.foo); // logs "bar", write failed Object.defineProperty(obj, "foo", { value: "foobar", configurable: true, }); console.log(obj.foo); // logs "foobar"
jsfiddle
また、
defineProperty
定義された値が
for in
ループで
defineProperty
ないことに注意する必要があります。
var i, inventory; inventory = { "apples": 10, "oranges": 13, }; Object.defineProperty(inventory, "strawberries", { value: 3, }); for (i in inventory) { console.log(i, inventory[i]); }
jsfiddle
apples 10 oranges 13
これを有効にするには、
enumerable
プロパティを使用する必要があります
var i, inventory; inventory = { "apples": 10, "oranges": 13, }; Object.defineProperty(inventory, "strawberries", { value: 3, enumerable: true, }); for (i in inventory) { console.log(i, inventory[i]); }
jsfiddle
apples 10 oranges 13 strawberries 3
for in
ループにプロパティが表示されるかどうかを確認するには、
isPropertyEnumerable
を使用できます
var i, inventory; inventory = { "apples": 10, "oranges": 13, }; Object.defineProperty(inventory, "strawberries", { value: 3, }); console.log(inventory.propertyIsEnumerable("apples")); //console logs true console.log(inventory.propertyIsEnumerable("strawberries")); //console logs false
jsfiddle
propertyIsEnumerable
呼び出しは、プロトタイプチェーンで上記で定義されたプロパティ、またはこのオブジェクトに対して他の方法で定義されていないプロパティに対しても
false
を返しますが、これは明らかです。
最後に、
defineProperty
の使用に関するいくつかの言葉:
set
メソッドと
get
アクセスメソッドを
writable: true
に結合したり、
value
と結合したりするのは間違い
value
。 番号を使用してプロパティを定義すると、他の状況の場合と同様に、その番号が文字列に反映されます。
defineProperty
を使用して、
value
を関数として定義することもできます。
defineProperties
defineProperties
もあります。 このメソッドを使用すると、一度に複数のプロパティを定義できます。 definePropertiesの使用と
defineProperties
を比較する
defineProperties
に
defineProperty
ました。少なくともChromeでは、どのメソッドを使用するかに大きな違いはありませんでした。
var foo = {} Object.defineProperties(foo, { bar: { value: "foo", writable: true, }, foo: { value: function() { console.log(this.bar); } }, }); foo.bar = "foobar"; foo.foo(); //logs "foobar"
jsfiddle
Object.create
Object.create
は
new
の代替であり、特定のプロトタイプを使用してオブジェクトを作成できます。 この関数は2つの引数を取ります。1つ目はオブジェクトを作成するプロトタイプで、2つ目は
Object.defineProperties
呼び出すときに使用されるハンドルと同じ
Object.defineProperties
var prototypeDef = { protoBar: "protoBar", protoLog: function () { console.log(this.protoBar); } }; var propertiesDef = { instanceBar: { value: "instanceBar" }, instanceLog: { value: function () { console.log(this.instanceBar); } } } var foo = Object.create(prototypeDef, propertiesDef); foo.protoLog(); //logs "protoBar" foo.instanceLog(); //logs "instanceBar"
jsfiddle
物性 記述子を使用して説明すると、プロトタイプの対応するプロパティが上書きされます。
var prototypeDef = { bar: "protoBar", }; var propertiesDef = { bar: { value: "instanceBar", }, log: { value: function () { console.log(this.bar); } } } var foo = Object.create(prototypeDef, propertiesDef); foo.log(); //logs "instanceBar"
jsfiddle
定義されたプロパティの値として
Array
や
Object
などの非プリミティブ型を使用すると、これらのプロパティが作成されたすべてのインスタンスと共有されるため、間違いになる可能性があります。
var prototypeDef = { protoArray: [], }; var propertiesDef = { propertyArray: { value: [], } } var foo = Object.create(prototypeDef, propertiesDef); var bar = Object.create(prototypeDef, propertiesDef); foo.protoArray.push("foobar"); console.log(bar.protoArray); //logs ["foobar"] foo.propertyArray.push("foobar"); console.log(bar.propertyArray); //also logs ["foobar"]
jsfiddle
これは、
null
値で
propertyArray
を初期化することで回避でき
null
。その後、必要な配列を追加するか、たとえばゲッターを使用して何か流行に敏感なことを行います。
var prototypeDef = { protoArray: [], }; var propertiesDef = { propertyArrayValue_: { value: null, writable: true }, propertyArray: { get: function () { if (!this.propertyArrayValue_) { this.propertyArrayValue_ = []; } return this.propertyArrayValue_; } } } var foo = Object.create(prototypeDef, propertiesDef); var bar = Object.create(prototypeDef, propertiesDef); foo.protoArray.push("foobar"); console.log(bar.protoArray); //logs ["foobar"] foo.propertyArray.push("foobar"); console.log(bar.propertyArray); //logs []
jsfiddle
これは、変数の初期化とその定義を組み合わせるエレガントな方法です。 変数の初期化とともに変数の定義を実行することを好むと思いますが、これはコンストラクタで同じことを行うよりもはるかに良いでしょう。 過去に、初期化を実行する多くのコードがある巨大なコンストラクターを作成しました。
前の例は、
Object.create
記述子の任意の値に渡される式は、記述子が定義されるときに実行されることを覚えておく必要があることを示しています。 これが、配列がクラスのすべてのインスタンスに共通になった理由です。 また、複数のプロパティが一緒に定義されている場合、固定された順序に頼らないことをお勧めします。 本当に必要な場合
Object.defineProperty
つのプロパティを他のプロパティよりも先に定義する場合-この場合は、
Object.defineProperty
を使用することをお
Object.defineProperty
します。
Object.create
はコンストラクター関数を呼び出さないため、
instanceof
を使用してオブジェクトのIDを確認することはできなくなりました。 代わりに、オブジェクトの
prototype
プロパティに対してチェックする
isPrototypeOf
を使用できます。 これは、コンストラクターの場合はMyFunction.prototype、または
Object.create
最初の引数として渡されるオブジェクトになります
function Foo() { } var prototypeDef = { protoArray: [], }; var propertiesDef = { propertyArrayValue_: { value: null, writable: true }, propertyArray: { get: function () { if (!this.propertyArrayValue_) { this.propertyArrayValue_ = []; } return this.propertyArrayValue_; } } } var foo1 = new Foo(); //old way using instanceof works with constructors console.log(foo1 instanceof Foo); //logs true //You check against the prototype object, not the constructor function console.log(Foo.prototype.isPrototypeOf(foo1)); //true var foo2 = Object.create(prototypeDef, propertiesDef); //can't use instanceof with Object.create, test against prototype object... //...given as first agument to Object.create console.log(prototypeDef.isPrototypeOf(foo2)); //true
jsfiddle
isPrototypeOf
はプロトタイプチェーンを下降し、比較対象のオブジェクトと一致するものがあれば
true
を返し
true
。
var foo1Proto = { foo: "foo", }; var foo2Proto = Object.create(foo1Proto); foo2Proto.bar = "bar"; var foo = Object.create(foo2Proto); console.log(foo.foo, foo.bar); //logs "foo bar" console.log(foo1Proto.isPrototypeOf(foo)); // logs true console.log(foo2Proto.isPrototypeOf(foo)); // logs true
jsfiddle
オブジェクトの「シール」、「フリーズ」および拡張の防止
そのような可能性があるという理由だけで、ランダムなオブジェクトとクラスのインスタンスに任意のプロパティを追加すると、少なくともコードは良くなりません。 node.jsおよび最新のブラウザーでは、
defineProperty
を使用して個々のプロパティへの変更を制限する機能に加えて、オブジェクト全体への変更を制限する機能があります。
Object.preventExtensions
、
Object.seal
および
Object.freeze
これらの各メソッドは、オブジェクトの変更に対してより厳しい制限を課します。 厳格モードでは、これらのメソッドによって課せられた制限に違反すると、エラーがスローされます。そうでない場合、エラーは「静かに」発生します。
Object.preventExtensions
メソッドは、オブジェクトに新しいプロパティを追加できないようにします。 書き込み用に開いているプロパティを変更したり、カスタマイズ可能なプロパティを削除したりすることを妨げません。 さらに、
Object.preventExtensions
は、既存のプロパティを変更するために
Object.defineProperty
の呼び出しを使用することを不可能にしません。
var obj = { foo: "foo", }; obj.bar = "bar"; console.log(obj); // logs Object {foo: "foo", bar: "bar"} Object.preventExtensions(obj); delete obj.bar; console.log(obj); // logs Object {foo: "foo"} obj.bar = "bar"; console.log(obj); // still logs Object {foo: "foo"} obj.foo = "foobar" console.log(obj); // logs {foo: "foobar"} can still change values
jsfiddle
(オブジェクトの最終値のみがコンソールに表示されるため、開発者コンソールを開いた状態で以前のjsfiddleを再起動する必要があることに注意してください)
Object.seal
はさらに
Object.seal
進みます。
Object.preventExtensions
よりも。 このメソッドは、オブジェクトへの新しいプロパティの追加を禁止することに加えて、既存のプロパティをさらに構成および削除する機能も制限します。 オブジェクトが封印されると、
defineProperty
既存のプロパティを変更できなくなります。 上記のように、strictモードでこれらの禁止事項に違反すると、エラーがスローされます。
"use strict"; var obj = {}; Object.defineProperty(obj, "foo", { value: "foo" }); Object.seal(obj); //Uncaught TypeError: Cannot redefine property: foo Object.defineProperty(obj, "foo", { value: "bar" });
jsfiddle
また、プロパティが元々カスタマイズ可能であったとしても、プロパティを削除することはできません。 あとは、プロパティ値を変更するだけです。
"use strict"; var obj = {}; Object.defineProperty(obj, "foo", { value: "foo", writable: true, configurable: true, }); Object.seal(obj); console.log(obj.foo); //logs "foo" obj.foo = "bar"; console.log(obj.foo); //logs "bar" delete obj.foo; //TypeError, cannot delete
jsfiddle
最終的に、
Object.freeze
はオブジェクトを変更から完全に保護します。 凍結された「オブジェクト」のプロパティ値を追加、削除、または変更することはできません。
Object.defineProperty
を使用してオブジェクトの既存のプロパティの値を変更する方法もありません。
"use strict"; var obj = { foo: "foo1" }; Object.freeze(obj); //All of the following will fail, and result in errors in strict mode obj.foo = "foo2"; //cannot change values obj.bar = "bar"; //cannot add a property delete obj.bar; //cannot delete a property //cannot call defineProperty on a frozen object Object.defineProperty(obj, "foo", { value: "foo2" });
jsfiddle
オブジェクトが「フリーズ」、「シール」、または展開から保護されているかどうかを確認できるメソッドは次のとおりです。
Object.isFrozen
、
Object.isSealed
および
Object.isExtensible
valueOfおよびtoString
JavaScriptがプリミティブな値を取得することを期待している場合、
valueOf
と
toString
を使用して、コンテキスト内のオブジェクトの動作をカスタマイズできます。
toString
の使用例を次に示します。
function Foo (stuff) { this.stuff = stuff; } Foo.prototype.toString = function () { return this.stuff; } var f = new Foo("foo"); console.log(f + "bar"); //logs "foobar"
jsfiddle
そして
valueOf
:
function Foo (stuff) { this.stuff = stuff; } Foo.prototype.valueOf = function () { return this.stuff.length; } var f = new Foo("foo"); console.log(1 + f); //logs 4 (length of "foo" + 1);
jsfiddle
これらの2つの方法の使用を組み合わせると、予期しない結果を得ることができます。
function Foo (stuff) { this.stuff = stuff; } Foo.prototype.valueOf = function () { return this.stuff.length; } Foo.prototype.toString = function () { return this.stuff; } var f = new Foo("foo"); console.log(f + "bar"); //logs "3bar" instead of "foobar" console.log(1 + f); //logs 4 (length of "foo" + 1);
jsfiddle
toString
を使用する適切な方法は、オブジェクトをハッシュ可能にすることです。
function Foo (stuff) { this.stuff = stuff; } Foo.prototype.toString = function () { return this.stuff; } var f = new Foo("foo"); var obj = {}; obj[f] = true; console.log(obj); //logs {foo: true}
jsfiddle
getOwnPropertyNamesおよびキー
オブジェクトのすべてのプロパティを取得するには、
Object.getOwnPropertyNames
を使用できます。 Pythonに精通している場合、
Object.keys
メソッドも存在しますが、一般的には辞書の
keys
メソッドに似てい
keys
。
Object.keys
と
Object.getOwnPropertyNames
の主な違いは、後者が「列挙不可能な」プロパティを返すことです。これらのプロパティは
for in
ループの
for in
時に考慮されません。
var obj = { foo: "foo", }; Object.defineProperty(obj, "bar", { value: "bar" }); console.log(Object.getOwnPropertyNames(obj)); //logs ["foo", "bar"] console.log(Object.keys(obj)); //logs ["foo"]
jsfiddle
記号
Symbol
は、ECMAScrpt 6ハーモニーで定義されている特別な新しいプリミティブであり、JavaScriptの次の反復で使用可能になります。 今すぐChrome CanaryとFirefox Nightlyで試してみてください。次のjsfiddleの例は、少なくとも2014年8月のこの記事を書いている時点では、これらのブラウザーでのみ機能します。
Symbol
は、オブジェクトのプロパティを作成および参照する方法として使用できます。
var obj = {}; var foo = Symbol("foo"); obj[foo] = "foobar"; console.log(obj[foo]); //logs "foobar"
jsfiddle
Symbol
はユニークで不変です
//console logs false, symbols are unique: console.log(Symbol("foo") === Symbol("foo"));
jsfiddle
Symbol
は
Object.defineProperty
で使用できます:
var obj = {}; var foo = Symbol("foo"); Object.defineProperty(obj, foo, { value: "foobar", }); console.log(obj[foo]); //logs "foobar"
jsfiddle
Symbol
定義されたプロパティは
for in
ループで繰り返されませんが、
hasOwnProperty
呼び出しは正常に機能します。
var obj = {}; var foo = Symbol("foo"); Object.defineProperty(obj, foo, { value: "foobar", }); console.log(obj.hasOwnProperty(foo)); //logs true
jsfiddle
Symbol
は
Object.getOwnPropertyNames
関数によって返される配列に入りませんが、
Object. getOwnPropertySymbols
メソッドがあり
Object. getOwnPropertySymbols
Object. getOwnPropertySymbols
var obj = {}; var foo = Symbol("foo"); Object.defineProperty(obj, foo, { value: "foobar", }); //console logs [] console.log(Object.getOwnPropertyNames(obj)); //console logs [Symbol(foo)] console.log(Object.getOwnPropertySymbols(obj));
jsfiddle
Symbol
を使用すると、偶発的な変更からプロパティを保護するだけでなく、通常の操作中に表示したくない場合にも便利です。 可能性のあるすべての機会について真剣に考えたことはありませんが、まだまだあると思います。
プロキシ
ECMAScript 6のもう1つの革新は
Proxy
です。 2014年8月現在、プロキシはFirefoxでのみ機能します。 次のjsfiddleの例はFirefoxでのみ機能し、実際、インストールしたFirefoxベータ版でテストしました。
プロキシはすべてのプロパティを取得する機会を提供し、例に注意を払うので、プロキシは楽しいと感じます:
var obj = { foo: "foo", }; var handler = { get: function (target, name) { if (target.hasOwnProperty(name)) { return target[name]; } return "foobar"; }, }; var p = new Proxy(obj, handler); console.log(p.foo); //logs "foo" console.log(p.bar); //logs "foobar" console.log(p.asdf); //logs "foobar"
jsfiddle
この例では、
obj
オブジェクトをプロキシしています。 作成されたオブジェクトとの相互作用を処理する
handler
オブジェクトを作成します。
get
handlerメソッドは非常に簡単です。 オブジェクトと、アクセスされるプロパティの名前を受け取ります。 この情報はいつでも返すことができますが、この例では、キーがある場合は実際の値が返され、キーがない場合はfoobarが返されます。 私は、
Scala
ような
switch
ようなものの1つである、プロキシを使用する可能性と興味深い方法の巨大なフィールドを見ています。
プロキシの別のアプリケーションはテストです。
get
加え
has
、
set
、
has
、および他のハンドラー
has
あります。 プロキシがより良いサポートを得るとき、私は彼らに私のブログに全体の投稿をすることをheしません。 MDNプロキシのドキュメントを見て、記載されている例に注意することをお勧めします。
とりわけ、jsconfからの優れたプロキシレポートもあります。 スライド
JavaScriptでオブジェクトを使用する方法は、ランダムデータを単に保存するよりも多くあります。 プロパティを定義するための強力な方法は既に利用可能であり、将来、JavaScriptコードの記述方法をプロキシがどのように変更できるかを考えるとわかるように、さらに興味深いものがあります。 明確化やコメントがありましたら、それについて私に知らせてください。ここに私のツイッターがあります:@bjorntipling。