JavaScriptでのオブジェクトの高度な使用

この投稿は、JavaScriptでのオブジェクトの日常的な使用を超えています。 オブジェクトの操作の基本は、JSON表記を使用するのとほとんど同じくらい簡単です。 それでも、JavaScriptを使用すると、いくつかの興味深い便利な方法でオブジェクトを作成でき、最新のブラウザーの最新バージョンで使用できるようになった微妙なツールを使用できます。



提起される最後の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。



All Articles