JavaScriptにおけるオブジェクト指向プログラミング(OOP)には、いくつかの階層があります。
新しい階層は前の階層にのみ依存しているため、JavaScript OOPを段階的に学習できます。階層1と2はシンプルなコアを形成しており、階層3と4が複雑になって混乱したときは、いつでもこのコアを参照できます。
大まかに言えば、JavaScriptのすべてのオブジェクトは、文字列から値へのマップ(辞書)です。オブジェクト内の(キー、値)のエントリーは、プロパティと呼ばれます。プロパティのキーは常にテキスト文字列です。プロパティの値は、関数を含む任意のJavaScriptの値にすることができます。メソッドは、値が関数であるプロパティです。
プロパティには3つの種類があります。
[[Prototype]]はオブジェクトのプロトタイプを保持し、Object.getPrototypeOf()を介して読み取ることができます。JavaScriptのオブジェクトリテラルを使用すると、プレーンオブジェクト(Objectの直接インスタンス)を直接作成できます。次のコードでは、オブジェクトリテラルを使用して、オブジェクトを変数janeに割り当てています。オブジェクトには、nameとdescribeの2つのプロパティがあります。describeはメソッドです。
varjane={name:'Jane',describe:function(){return'Person named '+this.name;// (1)},// (2)};
thisを使用して、現在のオブジェクトを参照します。(メソッド呼び出しのレシーバーとも呼ばれます)。オブジェクトは、文字列から値への単なるマップであるという印象を受けるかもしれません。しかし、オブジェクトはそれ以上のものです。オブジェクトは真の汎用オブジェクトです。たとえば、オブジェクト間で継承を使用したり(「第2層:オブジェクト間のプロトタイプ関係」を参照)、オブジェクトが変更されないように保護することができます。オブジェクトを直接作成する機能は、JavaScriptの傑出した機能の1つです。具体的なオブジェクト(クラスは不要!)から始めて、後で抽象概念を導入することができます。たとえば、オブジェクトのファクトリーであるコンストラクター(「第3層:コンストラクター—インスタンスのファクトリー」で説明)は、他の言語のクラスとほぼ同様です。
ドット演算子は、プロパティにアクセスするためのコンパクトな構文を提供します。プロパティのキーは識別子である必要があります(「有効な識別子」を参照)。任意の名前を持つプロパティを読み書きする場合は、角括弧演算子を使用する必要があります(「角括弧演算子([]):計算されたキーによるプロパティへのアクセス」を参照)。
このセクションの例では、次のオブジェクトを使用します。
varjane={name:'Jane',describe:function(){return'Person named '+this.name;}};
ドット演算子を使用すると、プロパティを「取得」できます(その値を読み取ります)。次に例をいくつか示します。
> jane.name // get property `name` 'Jane' > jane.describe // get property `describe` [Function]
存在しないプロパティを取得すると、undefinedが返されます。
> jane.unknownProperty undefined
ドット演算子は、メソッドを呼び出すためにも使用されます。
> jane.describe() // call method `describe` 'Person named Jane'
代入演算子(=)を使用して、ドット表記で参照されるプロパティの値を設定できます。例:
> jane.name = 'John'; // set property `name` > jane.describe() 'Person named John'
プロパティがまだ存在しない場合は、設定すると自動的に作成されます。プロパティが既に存在する場合は、設定すると値が変更されます。
delete演算子を使用すると、オブジェクトからプロパティ(キーと値のペア全体)を完全に削除できます。例:
> var obj = { hello: 'world' };
> delete obj.hello
true
> obj.hello
undefinedプロパティをundefinedに設定するだけでは、プロパティはまだ存在し、オブジェクトにはまだキーが含まれています。
> var obj = { foo: 'a', bar: 'b' };
> obj.foo = undefined;
> Object.keys(obj)
[ 'foo', 'bar' ]プロパティを削除すると、そのキーもなくなります。
> delete obj.foo true > Object.keys(obj) [ 'bar' ]
deleteは、オブジェクトの直接の(「own」、継承されていない)プロパティのみに影響します。そのプロトタイプは変更されません(「継承されたプロパティを削除する」を参照)。
delete演算子は控えめに使用してください。ほとんどの最新のJavaScriptエンジンは、コンストラクターによって作成されたインスタンスの「形状」が変更されない場合(大まかに言えば、プロパティが削除または追加されない場合)に、そのパフォーマンスを最適化します。プロパティを削除すると、その最適化が妨げられます。
プロパティが自身のプロパティであるが削除できない場合、deleteはfalseを返します。それ以外の場合はすべてtrueを返します。次にいくつかの例を示します。
準備として、削除できるプロパティと削除できない別のプロパティを作成します(「記述子によるプロパティの取得と定義」では、Object.defineProperty()について説明しています)。
varobj={};Object.defineProperty(obj,'canBeDeleted',{value:123,configurable:true});Object.defineProperty(obj,'cannotBeDeleted',{value:456,configurable:false});
deleteは、削除できない自身のプロパティに対してfalseを返します。
> delete obj.cannotBeDeleted false
deleteは、それ以外のすべての場合にtrueを返します。
> delete obj.doesNotExist true > delete obj.canBeDeleted true
deleteは、何も変更しない場合でもtrueを返します(継承されたプロパティは削除されません)。
> delete obj.toString true > obj.toString // still there [Function: toString]
予約語(varやfunctionなど)を変数名として使用することはできませんが、プロパティキーとして使用することはできます。
> var obj = { var: 'a', function: 'b' };
> obj.var
'a'
> obj.function
'b'数値はオブジェクトリテラルでプロパティキーとして使用できますが、文字列として解釈されます。ドット演算子は、キーが識別子であるプロパティにのみアクセスできます。したがって、キーが数値であるプロパティにアクセスするには、角括弧演算子(次の例に示します)が必要です。
> var obj = { 0.7: 'abc' };
> Object.keys(obj)
[ '0.7' ]
> obj['0.7']
'abc'オブジェクトリテラルでは、任意の文字列(識別子でも数値でもない)をプロパティキーとして使用することもできますが、引用符で囲む必要があります。この場合も、プロパティ値にアクセスするには角括弧演算子が必要です。
> var obj = { 'not an identifier': 123 };
> Object.keys(obj)
[ 'not an identifier' ]
> obj['not an identifier']
123ドット演算子は固定プロパティキーを使用しますが、角括弧演算子を使用すると、式を介してプロパティを参照できます。
角括弧演算子を使用すると、式を介してプロパティのキーを計算できます。
> var obj = { someProperty: 'abc' };
> obj['some' + 'Property']
'abc'
> var propKey = 'someProperty';
> obj[propKey]
'abc'これにより、キーが識別子でないプロパティにもアクセスできます。
> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123角括弧演算子は内部を文字列に強制変換することに注意してください。例:
> var obj = { '6': 'bar' };
> obj[3+3] // key: the string '6'
'bar'メソッドの呼び出しは、期待どおりに機能します。
> var obj = { myMethod: function () { return true } };
> obj['myMethod']()
trueプロパティの設定は、ドット演算子と同様に機能します。
> var obj = {};
> obj['anotherProperty'] = 'def';
> obj.anotherProperty
'def'プロパティの削除も、ドット演算子と同様に機能します。
> var obj = { 'not an identifier': 1, prop: 2 };
> Object.keys(obj)
[ 'not an identifier', 'prop' ]
> delete obj['not an identifier']
true
> Object.keys(obj)
[ 'prop' ]頻繁に使用するケースではありませんが、場合によっては、任意の値をオブジェクトに変換する必要があります。Object()は、関数として(コンストラクターとしてではなく)使用すると、その機能を提供します。これにより、次の結果が生成されます。
| 値 | 結果 |
(パラメーターなしで呼び出された場合) |
|
|
|
|
|
ブール値 |
|
数値 |
|
文字列 |
|
オブジェクト |
|
次に例をいくつか示します。
> Object(null) instanceof Object
true
> Object(false) instanceof Boolean
true
> var obj = {};
> Object(obj) === obj
true次の関数は、valueがオブジェクトであるかどうかを確認します。
functionisObject(value){returnvalue===Object(value);}
前の関数は、valueがオブジェクトでない場合、オブジェクトを作成することに注意してください。typeofを使用して、それを行わずに同じ関数を実装できます(「落とし穴:typeof null」を参照)。
Objectをコンストラクターとして呼び出すこともできます。これは、関数として呼び出す場合と同じ結果を生成します。
> var obj = {};
> new Object(obj) === obj
true
> new Object(123) instanceof Number
trueコンストラクターは避けてください。空のオブジェクトリテラルの方がほとんどの場合、より適切な選択肢です。
varobj=newObject();// avoidvarobj={};// prefer
関数を呼び出すと、thisは常に(暗黙的な)パラメーターになります。
通常の関数はthisを使用する必要がなくても、その値が常にグローバルオブジェクトである(ブラウザではwindow、「グローバルオブジェクト」を参照)特別な変数として存在します。
> function returnThisSloppy() { return this }
> returnThisSloppy() === window
true
this は常に undefined
> function returnThisStrict() { 'use strict'; return this }
> returnThisStrict() === undefined
true
this はメソッドが呼び出されたオブジェクトを参照します
> var obj = { method: returnThisStrict };
> obj.method() === obj
trueメソッドの場合、this の値は、メソッド呼び出しのレシーバーと呼ばれます。
関数もオブジェクトであることを忘れないでください。したがって、各関数は独自のメソッドを持っています。このセクションでは、関数呼び出しを支援する3つのメソッドを紹介します。これらの3つのメソッドは、関数呼び出しのいくつかの落とし穴を回避するために、以下のセクションで使用されます。以下の例はすべて、次のオブジェクト jane を参照しています。
varjane={name:'Jane',sayHelloTo:function(otherName){'use strict';console.log(this.name+' says hello to '+otherName);}};
最初のパラメータは、呼び出される関数内で this が持つ値です。残りのパラメータは、呼び出される関数への引数として渡されます。次の3つの呼び出しは同等です。
jane.sayHelloTo('Tarzan');jane.sayHelloTo.call(jane,'Tarzan');varfunc=jane.sayHelloTo;func.call(jane,'Tarzan');
2回目の呼び出しでは、call() は呼び出された関数をどのように取得したかを知らないため、jane を繰り返す必要があります。
最初のパラメータは、呼び出される関数内で this が持つ値です。2番目のパラメータは、呼び出しの引数を提供する配列です。次の3つの呼び出しは同等です。
jane.sayHelloTo('Tarzan');jane.sayHelloTo.apply(jane,['Tarzan']);varfunc=jane.sayHelloTo;func.apply(jane,['Tarzan']);
2回目の呼び出しでは、apply() は呼び出された関数をどのように取得したかを知らないため、jane を繰り返す必要があります。
コンストラクターのためのapply()では、コンストラクターで apply() を使用する方法を説明します。
このメソッドは、部分関数適用を実行します。つまり、次の方法で bind() のレシーバーを呼び出す新しい関数を作成します。this の値は thisValue であり、引数は arg1 から argN まで続き、その後に新しい関数の引数が続きます。言い換えれば、新しい関数は、元の関数を呼び出すときに、その引数を arg1, ..., argN に追加します。例を見てみましょう。
functionfunc(){console.log('this: '+this);console.log('arguments: '+Array.prototype.slice.call(arguments));}varbound=func.bind('abc',1,2);
配列メソッド slice は、arguments を配列に変換するために使用されます。これは、ログ記録に必要です(この操作は配列のようなオブジェクトとジェネリックメソッドで説明されています)。 bound は新しい関数です。次にインタラクションを示します。
> bound(3) this: abc arguments: 1,2,3
次の sayHelloTo の3つの呼び出しはすべて同等です。
jane.sayHelloTo('Tarzan');varfunc1=jane.sayHelloTo.bind(jane);func1('Tarzan');varfunc2=jane.sayHelloTo.bind(jane,'Tarzan');func2();
JavaScript に配列を実際のパラメーターに変換する三点リーダー演算子 (...) があると仮定しましょう。そのような演算子を使用すると、配列で Math.max() (その他の関数を参照)を使用できるようになります。 その場合、次の2つの式は同等になります。
Math.max(...[13,7,30])Math.max(13,7,30)
関数については、apply() を介して三点リーダー演算子の効果を実現できます。
> Math.max.apply(null, [13, 7, 30]) 30
三点リーダー演算子は、コンストラクターにも理にかなっています。
newDate(...[2011,11,24])// Christmas Eve 2011
残念ながら、ここでは apply() は機能しません。関数またはメソッドの呼び出しには役立ちますが、コンストラクターの呼び出しには役立たないためです。
2つのステップで apply() をシミュレートできます。
(まだ配列ではない)メソッド呼び出しを介して引数を Date に渡します。
new(Date.bind(null,2011,11,24))
上記のコードでは、bind() を使用して、パラメーターなしでコンストラクターを作成し、new 経由で呼び出します。
apply() を使用して、配列を bind() に渡します。bind() はメソッド呼び出しであるため、apply() を使用できます。
new(Function.prototype.bind.apply(Date,[null,2011,11,24]))
上記の配列には、arr の要素の後に null が含まれています。 concat() を使用して、null を arr の先頭に追加して作成できます。
vararr=[2011,11,24];new(Function.prototype.bind.apply(Date,[null].concat(arr)))
上記の回避策は、Mozillaが公開したライブラリメソッドに触発されています。次に、少し編集したバージョンを示します。
if(!Function.prototype.construct){Function.prototype.construct=function(argArray){if(!Array.isArray(argArray)){thrownewTypeError("Argument must be an array");}varconstr=this;varnullaryFunc=Function.prototype.bind.apply(constr,[null].concat(argArray));returnnewnullaryFunc();};}
次に、使用中のメソッドを示します。
> Date.construct([2011, 11, 24]) Sat Dec 24 2011 00:00:00 GMT+0100 (CET)
以前のアプローチの代替は、Object.create() を介して初期化されていないインスタンスを作成し、次にコンストラクターを(関数として)apply() を介して呼び出すことです。これは、事実上、new 演算子を再実装することを意味します(一部のチェックは省略されています)。
Function.prototype.construct=function(argArray){varconstr=this;varinst=Object.create(constr.prototype);varresult=constr.apply(inst,argArray);// (1)// Check: did the constructor return an object// and prevent `this` from being the result?returnresult?result:inst;};
上記のコードは、関数として呼び出されると常に新しいインスタンスを生成するほとんどの組み込みコンストラクターでは機能しません。言い換えれば、(1)行のステップでは、意図したとおりに inst を設定しません。
オブジェクトからメソッドを抽出すると、再び真の関数になります。オブジェクトとの接続が切断され、通常は正常に機能しなくなります。たとえば、次のオブジェクト counter を見てみましょう。
varcounter={count:0,inc:function(){this.count++;}}
inc を抽出して、(関数として!)呼び出すと失敗します。
> var func = counter.inc; > func() > counter.count // didn’t work 0
説明は次のとおりです。counter.inc の値を関数として呼び出しました。したがって、this はグローバルオブジェクトであり、window.count++ を実行しました。window.count は存在せず、undefined です。 ++ 演算子を適用すると、NaN に設定されます。
> count // global variable NaN
メソッド inc() が厳格モードの場合、警告が表示されます。
> counter.inc = function () { 'use strict'; this.count++ };
> var func2 = counter.inc;
> func2()
TypeError: Cannot read property 'count' of undefined理由は、厳格モード関数 func2 を呼び出すと、this が undefined になり、エラーが発生するためです。
bind() のおかげで、inc が counter との接続を失わないようにすることができます。
> var func3 = counter.inc.bind(counter); > func3() > counter.count // it worked! 1
JavaScriptには、コールバックを受け入れる多くの関数とメソッドがあります。ブラウザーの例としては、setTimeout() やイベント処理などがあります。counter.inc をコールバックとして渡すと、それも関数として呼び出され、先ほど説明したのと同じ問題が発生します。この現象を示すために、シンプルなコールバック呼び出し関数を使用してみましょう。
functioncallIt(callback){callback();}
callIt を介して counter.count を実行すると、(厳格モードのために)警告がトリガーされます。
> callIt(counter.inc) TypeError: Cannot read property 'count' of undefined
以前と同様に、bind() を使用して問題を修正します。
> callIt(counter.inc.bind(counter)) > counter.count // one more than before 2
bind() を呼び出すたびに、新しい関数が作成されます。これは、コールバックを登録および登録解除する場合(たとえば、イベント処理の場合)に影響します。登録した値をどこかに保存し、登録解除にも使用する必要があります。
関数はパラメーター(例:コールバック)になり、関数式を介してインプレースで作成できるため、JavaScriptで関数定義をネストすることがよくあります。メソッドに通常の関数が含まれており、後者の中で前者の this にアクセスしたい場合に問題が発生します。メソッドの this は、通常の関数の this によってシャドウされるためです(通常の関数の this には独自の this を使用する理由はありません)。次の例では、(1)の関数が (2) でメソッドの this にアクセスしようとしています。
varobj={name:'Jane',friends:['Tarzan','Cheeta'],loop:function(){'use strict';this.friends.forEach(function(friend){// (1)console.log(this.name+' knows '+friend);// (2)});}};
これは失敗します。(1) の関数には独自の this があり、ここでは undefined であるためです。
> obj.loop(); TypeError: Cannot read property 'name' of undefined
この問題を回避する方法は3つあります。
ネストされた関数内でシャドウされない変数に this を割り当てます。
loop:function(){'use strict';varthat=this;this.friends.forEach(function(friend){console.log(that.name+' knows '+friend);});}
次にインタラクションを示します。
> obj.loop(); Jane knows Tarzan Jane knows Cheeta
bind() を使用して、コールバックに this の固定値、つまり、メソッドの this (行 (1)) を指定できます。
loop:function(){'use strict';this.friends.forEach(function(friend){console.log(this.name+' knows '+friend);}.bind(this));// (1)}
forEach() (検査メソッドを参照)に固有の回避策は、コールバックの後にコールバックの this になる2番目のパラメーターを指定することです。
loop:function(){'use strict';this.friends.forEach(function(friend){console.log(this.name+' knows '+friend);},this);}
2つのオブジェクト間のプロトタイプ関係は継承に関するものです。すべてのオブジェクトは、別のオブジェクトをプロトタイプとして持つことができます。次に、前者のオブジェクトは、プロトタイプのすべてのプロパティを継承します。オブジェクトは、内部プロパティ [[Prototype]] を介してプロトタイプを指定します。すべてのオブジェクトにはこのプロパティがありますが、null になる可能性があります。[[Prototype]] プロパティによって接続されたオブジェクトのチェーンは、プロトタイプチェーン(図17-1)と呼ばれます。
プロトタイプベース(またはプロトタイプ型)の継承がどのように機能するかを確認するために、例を見てみましょう([[Prototype]] プロパティを指定するための発明された構文を使用)。
varproto={describe:function(){return'name: '+this.name;}};varobj={[[Prototype]]:proto,name:'obj'};
オブジェクト obj は、proto からプロパティ describe を継承します。また、いわゆる独自の(非継承の、直接の)プロパティである name を持っています。
obj はプロパティ describe を継承します。オブジェクト自体がそのプロパティを持っているかのようにアクセスできます。
> obj.describe [Function]
obj を介してプロパティにアクセスするたびに、JavaScriptはそのオブジェクトで検索を開始し、そのプロトタイプ、プロトタイプのプロトタイプなどで続行します。これが、obj.describe を介して proto.describe にアクセスできる理由です。プロトタイプチェーンは、単一のオブジェクトであるかのように動作します。メソッドを呼び出すときに、そのイリュージョンは維持されます。this の値は常に、メソッドの検索が開始されたオブジェクトであり、メソッドが見つかった場所ではありません。これにより、メソッドはプロトタイプチェーンのすべてのプロパティにアクセスできます。たとえば、
> obj.describe() 'name: obj'
describe() の内部では、this は obj になり、メソッドが obj.name にアクセスできるようになります。
プロトタイプチェーンでは、オブジェクト内のプロパティは、後続のオブジェクトにある同じキーを持つプロパティを オーバーライド します。前者のプロパティが最初に見つかります。後者のプロパティは隠され、アクセスできなくなります。例として、obj 内でメソッド proto.describe() をオーバーライドしてみましょう。
> obj.describe = function () { return 'overridden' };
> obj.describe()
'overridden'これは、クラスベースの言語でのメソッドのオーバーライドの動作と似ています。
プロトタイプは、オブジェクト間でデータを共有するのに最適です。複数のオブジェクトが同じプロトタイプを取得し、そのプロトタイプにすべての共有プロパティが保持されます。例を見てみましょう。オブジェクト jane と tarzan は両方とも同じメソッド describe() を含んでいます。これは、共有を使用することで避けたいものです。
varjane={name:'Jane',describe:function(){return'Person named '+this.name;}};vartarzan={name:'Tarzan',describe:function(){return'Person named '+this.name;}};
どちらのオブジェクトも人物です。name プロパティは異なりますが、メソッド describe を共有させることができます。これを行うには、PersonProto という共通のプロトタイプを作成し、そこに describe を配置します(図17-2)。
次のコードは、プロトタイプ PersonProto を共有するオブジェクト jane と tarzan を作成します。
varPersonProto={describe:function(){return'Person named '+this.name;}};varjane={[[Prototype]]:PersonProto,name:'Jane'};vartarzan={[[Prototype]]:PersonProto,name:'Tarzan'};
そして、これがそのやり取りです。
> jane.describe() Person named Jane > tarzan.describe() Person named Tarzan
これは一般的なパターンです。データはプロトタイプチェーンの最初のオブジェクトに存在し、メソッドは後続のオブジェクトに存在します。JavaScript のプロトタイプ継承の仕組みは、このパターンをサポートするように設計されています。プロパティの設定はプロトタイプチェーンの最初のオブジェクトのみに影響しますが、プロパティの取得は完全なチェーンを考慮します(設定と削除は自身のプロパティにのみ影響するを参照)。
これまで、JavaScript から内部プロパティ [[Prototype]] にアクセスできると想定してきました。しかし、言語ではそれが許可されていません。代わりに、プロトタイプを読み取るための関数と、指定されたプロトタイプを持つ新しいオブジェクトを作成するための関数があります。
次の呼び出し:
Object.create(proto,propDescObj?)
プロトタイプが proto であるオブジェクトを作成します。オプションで、記述子を介してプロパティを追加できます(記述子についてはプロパティ記述子で説明します)。次の例では、オブジェクト jane はプロトタイプ PersonProto と、値が 'Jane' である可変プロパティ name を(プロパティ記述子を介して指定されたとおりに)取得します。
varPersonProto={describe:function(){return'Person named '+this.name;}};varjane=Object.create(PersonProto,{name:{value:'Jane',writable:true}});
これがそのやり取りです。
> jane.describe() 'Person named Jane'
ただし、記述子は冗長であるため、空のオブジェクトを作成してからプロパティを手動で追加することがよくあります。
varjane=Object.create(PersonProto);jane.name='Jane';
このメソッド呼び出し:
Object.getPrototypeOf(obj)
obj のプロトタイプを返します。前の例を続けます。
> Object.getPrototypeOf(jane) === PersonProto true
次の構文:
Object.prototype.isPrototypeOf(obj)
メソッドのレシーバーが obj の(直接または間接の)プロトタイプであるかどうかを確認します。言い換えれば、レシーバーと obj は同じプロトタイプチェーン内にあり、obj はレシーバーより前にありますか?例えば
> var A = {};
> var B = Object.create(A);
> var C = Object.create(B);
> A.isPrototypeOf(C)
true
> C.isPrototypeOf(A)
false次の関数は、オブジェクト obj のプロパティチェーンを反復処理します。キーが propKey の自身のプロパティを持つ最初のオブジェクト、またはそのようなオブジェクトがない場合は null を返します。
functiongetDefiningObject(obj,propKey){obj=Object(obj);// make sure it’s an objectwhile(obj&&!{}.hasOwnProperty.call(obj,propKey)){obj=Object.getPrototypeOf(obj);// obj is null if we have reached the end}returnobj;}
上記のコードでは、メソッド Object.prototype.hasOwnProperty を汎用的に呼び出しました(汎用メソッド:プロトタイプからメソッドを借りるを参照)。
一部の JavaScript エンジンには、オブジェクトのプロトタイプを取得および設定するための特別なプロパティ __proto__ があります。これにより、[[Prototype]] への直接アクセスが言語に提供されます。
> var obj = {};
> obj.__proto__ === Object.prototype
true
> obj.__proto__ = Array.prototype
> Object.getPrototypeOf(obj) === Array.prototype
true__proto__ について知っておくべきことがいくつかあります。
__proto__ は、「ダブルアンダースコアプロト」の略である「ダンダープロト」と発音されます。この発音は、Python プログラミング言語から(2006 年に Ned Batchelder によって提案されたように)借用されています。ダブルアンダースコアを持つ特殊な変数は、Python では非常に頻繁に使用されます。__proto__ は ECMAScript 5 標準の一部ではありません。したがって、コードをその標準に準拠させ、現在の JavaScript エンジンで確実に実行する場合は、それを使用しないでください。__proto__ のサポートを追加しており、ECMAScript 6 の一部になる予定です。次の式は、エンジンが特殊なプロパティとして __proto__ をサポートしているかどうかを確認します。
Object.getPrototypeOf({__proto__:null})===null
プロパティの取得のみが、オブジェクトの完全なプロトタイプチェーンを考慮します。設定と削除は継承を無視し、自身のプロパティにのみ影響します。
プロパティを設定すると、そのキーを持つ継承されたプロパティがある場合でも、自身のプロパティが作成されます。たとえば、次のソースコードが与えられたとします。
varproto={foo:'a'};varobj=Object.create(proto);
obj は proto から foo を継承します。
> obj.foo
'a'
> obj.hasOwnProperty('foo')
falsefoo を設定すると、目的の結果が得られます。
> obj.foo = 'b'; > obj.foo 'b'
ただし、自身のプロパティを作成し、proto.foo は変更していません。
> obj.hasOwnProperty('foo')
true
> proto.foo
'a'その理由は、プロトタイププロパティが複数のオブジェクトで共有されることを意図しているためです。このアプローチにより、破壊的でない方法でそれらを「変更」できます。影響を受けるのは現在のオブジェクトのみです。
自身のプロパティのみを削除できます。もう一度、プロトタイプ proto を持つオブジェクト obj を設定しましょう。
varproto={foo:'a'};varobj=Object.create(proto);
継承されたプロパティ foo を削除しても効果はありません。
> delete obj.foo true > obj.foo 'a'
delete 演算子の詳細については、プロパティの削除を参照してください。
継承されたプロパティを変更する場合は、まずそれを所有するオブジェクトを見つけ(プロパティが定義されているオブジェクトの検索を参照)、そのオブジェクトに対して変更を実行する必要があります。たとえば、前の例からプロパティ foo を削除してみましょう。
> delete getDefiningObject(obj, 'foo').foo; true > obj.foo undefined
プロパティの反復処理および検出のための操作は、次のものによって影響を受けます。
true または false にできるフラグです。列挙可能性が問題になることはめったになく、通常は無視できます(列挙可能性:ベストプラクティスを参照)。自身のプロパティキーを一覧表示したり、すべての列挙可能なプロパティキーを一覧表示したり、プロパティが存在するかどうかを確認したりできます。次のサブセクションでは、その方法を示します。
すべての自身のプロパティキー、または列挙可能なキーのみを一覧表示できます。
Object.getOwnPropertyNames(obj) は、obj のすべての自身のプロパティのキーを返します。Object.keys(obj) は、obj のすべての列挙可能な自身のプロパティのキーを返します。プロパティは通常、列挙可能であることに注意してください(列挙可能性:ベストプラクティスを参照)。したがって、特に作成したオブジェクトには、Object.keys() を使用できます。
オブジェクトのすべてのプロパティ(自身のプロパティと継承されたプロパティの両方)を一覧表示する場合は、2つのオプションがあります。
オプション 1 はループを使用することです。
for(«variable»in«object»)«statement»
object のすべての列挙可能なプロパティのキーを反復処理します。for-in を参照して、より詳細な説明を確認してください。
オプション 2 は、すべてのプロパティ(列挙可能なプロパティだけでなく)を反復処理する関数を自分で実装することです。例えば
functiongetAllPropertyNames(obj){varresult=[];while(obj){// Add the own property names of `obj` to `result`result=result.concat(Object.getOwnPropertyNames(obj));obj=Object.getPrototypeOf(obj);}returnresult;}
オブジェクトにプロパティがあるかどうか、またはプロパティがオブジェクト内に直接存在するかどうかを確認できます。
propKey in obj
obj にキーが propKey のプロパティがある場合は true を返します。継承されたプロパティはこのテストに含まれます。Object.prototype.hasOwnProperty(propKey)
this)にキーが propKey である自身の(非継承)プロパティがある場合は true を返します。オブジェクトで hasOwnProperty() を直接呼び出すことは避けてください。オーバーライドされる可能性があるためです(例:キーが hasOwnProperty である自身のプロパティによって)。
> var obj = { hasOwnProperty: 1, foo: 2 };
> obj.hasOwnProperty('foo') // unsafe
TypeError: Property 'hasOwnProperty' is not a function代わりに、汎用的に呼び出すことをお勧めします(汎用メソッド:プロトタイプからメソッドを借りるを参照)。
> Object.prototype.hasOwnProperty.call(obj, 'foo') // safe
true
> {}.hasOwnProperty.call(obj, 'foo') // shorter
truevarproto=Object.defineProperties({},{protoEnumTrue:{value:1,enumerable:true},protoEnumFalse:{value:2,enumerable:false}});varobj=Object.create(proto,{objEnumTrue:{value:1,enumerable:true},objEnumFalse:{value:2,enumerable:false}});
Object.defineProperties() は、記述子を介したプロパティの取得と定義で説明されていますが、どのように機能するかはかなり明白なはずです。proto には、自身のプロパティ protoEnumTrue と protoEnumFalse があり、obj には、自身のプロパティ objEnumTrue と objEnumFalse があります(また、proto のすべてのプロパティを継承します)。
オブジェクト(前の例の proto など)は通常、少なくともプロトタイプ Object.prototype を持っていることに注意してください(ここでは、toString() や hasOwnProperty() などの標準メソッドが定義されています)。
> Object.getPrototypeOf({}) === Object.prototype
trueプロパティ関連の操作の中で、列挙可能性が影響を与えるのは for-in ループと Object.keys() だけです(JSON.stringify() にも影響を与えます。JSON.stringify(value, replacer?, space?)を参照)。
for-in ループは、継承されたものも含め、すべての列挙可能なプロパティのキーを反復処理します(Object.prototype の列挙不可能なプロパティは表示されないことに注意してください)。
> for (var x in obj) console.log(x); objEnumTrue protoEnumTrue
Object.keys() は、すべての自身(継承されていない)の列挙可能なプロパティのキーを返します。
> Object.keys(obj) [ 'objEnumTrue' ]
すべての自身のプロパティのキーが必要な場合は、Object.getOwnPropertyNames() を使用する必要があります。
> Object.getOwnPropertyNames(obj) [ 'objEnumTrue', 'objEnumFalse' ]
継承を考慮するのは、for-in ループ(前の例を参照)と in 演算子だけです。
> 'toString' in obj
true
> obj.hasOwnProperty('toString')
false
> obj.hasOwnProperty('objEnumFalse')
truefor-in で説明されているように、for-in と hasOwnProperty() を組み合わせます。これは、古い JavaScript エンジンでも機能します。例:
for(varkeyinobj){if(Object.prototype.hasOwnProperty.call(obj,key)){console.log(key);}}
Object.keys() または Object.getOwnPropertyNames() を forEach() 配列反復処理と組み合わせます。
varobj={first:'John',last:'Doe'};// Visit non-inherited enumerable keysObject.keys(obj).forEach(function(key){console.log(key);});
プロパティ値または(キー、値)ペアを反復処理するには
ECMAScript 5 では、プロパティを取得または設定しているように見えるメソッドを記述できます。つまり、プロパティは仮想であり、ストレージスペースではありません。たとえば、プロパティの設定を禁止し、読み取り時に返される値を常に計算することができます。
次の例では、オブジェクトリテラルを使用してプロパティ foo のセッターとゲッターを定義します。
varobj={getfoo(){return'getter';},setfoo(value){console.log('setter: '+value);}};
次にインタラクションを示します。
> obj.foo = 'bla'; setter: bla > obj.foo 'getter'
ゲッターとセッターを指定するもう 1 つの方法は、プロパティ記述子を使用することです(プロパティ記述子を参照)。次のコードは、前のリテラルと同じオブジェクトを定義します。
varobj=Object.create(Object.prototype,{// object with property descriptorsfoo:{// property descriptorget:function(){return'getter';},set:function(value){console.log('setter: '+value);}}});
ゲッターとセッターはプロトタイプから継承されます。
> var proto = { get foo() { return 'hello' } };
> var obj = Object.create(proto);
> obj.foo
'hello'プロパティ属性とプロパティ記述子は、高度なトピックです。通常、それらがどのように機能するかを知る必要はありません。
このセクションでは、プロパティの内部構造を見ていきます。
プロパティのデータとそのメタデータの両方を含む、プロパティのすべての状態は、属性に格納されます。それらは、オブジェクトがプロパティを持っているように、プロパティが持っているフィールドです。属性キーは、二重角かっこで囲んで記述されることがよくあります。属性は、通常のプロパティとアクセサ(ゲッターとセッター)にとって重要です。
次の属性は、通常のプロパティに固有です。
[[Value]] は、プロパティの値、つまりデータを保持します。[[Writable]] は、プロパティの値を変更できるかどうかを示すブール値を保持します。次の属性は、アクセサに固有です。
[[Get]] は、プロパティが読み取られたときに呼び出される関数であるゲッターを保持します。この関数は、読み取りアクセスの結果を計算します。[[Set]] は、プロパティが値に設定されたときに呼び出される関数であるセッターを保持します。この関数は、その値をパラメーターとして受け取ります。すべてのプロパティには、次の属性があります。
[[Enumerable]] は、ブール値を保持します。プロパティを列挙不可能にすると、一部の操作から隠されます(プロパティの反復と検出を参照)。[[Configurable]] は、ブール値を保持します。これが false の場合、プロパティを削除したり、その属性([[Value]] を除く)を変更したり、データプロパティからアクセサプロパティに、またはその逆に変換したりすることはできません。言い換えれば、[[Configurable]] は、プロパティのメタデータの書き込み可能性を制御します。このルールには例外が 1 つあります。JavaScript では、設定不可能なプロパティを書き込み可能から読み取り専用に変更できます。これは歴史的な理由によるものです。配列のプロパティ length は常に書き込み可能で設定不可能です。この例外がないと、配列をフリーズ(フリーズを参照)することができなくなります。属性を指定しない場合、次のデフォルトが使用されます。
| 属性キー | デフォルト値 |
|
|
|
|
|
|
|
|
|
|
|
|
これらのデフォルトは、プロパティ記述子を使用してプロパティを作成している場合に重要です(次のセクションを参照)。
{value:123,writable:false,enumerable:true,configurable:false}
アクセサを使用して同じ目標、つまり不変性を達成できます。この場合、記述子は次のようになります。
{get:function(){return123},enumerable:true,configurable:false}
プロパティを定義することは、プロパティが既に存在するかどうかによって意味が異なります。
プロパティが存在しない場合は、記述子で指定された属性を持つ新しいプロパティを作成します。属性に記述子に対応するプロパティがない場合は、デフォルト値を使用します。デフォルトは、属性名の意味によって決まります。これらは、代入によってプロパティを作成するときに使用される値の反対です(この場合、プロパティは書き込み可能、列挙可能、設定可能です)。 例:
> var obj = {};
> Object.defineProperty(obj, 'foo', { configurable: true });
> Object.getOwnPropertyDescriptor(obj, 'foo')
{ value: undefined,
writable: false,
enumerable: false,
configurable: true }私は通常、デフォルトに頼らず、すべてを明確にするために、すべての属性を明示的に記述します。
プロパティが既に存在する場合は、記述子で指定されたプロパティの属性を更新します。属性に記述子に対応するプロパティがない場合は、変更しないでください。次に例を示します(前の例から続きます)。
> Object.defineProperty(obj, 'foo', { writable: true });
> Object.getOwnPropertyDescriptor(obj, 'foo')
{ value: undefined,
writable: true,
enumerable: false,
configurable: true }次の操作では、プロパティ記述子を介してプロパティの属性を取得および設定できます。
Object.getOwnPropertyDescriptor(obj, propKey)
キーが propKey である obj の自身(継承されていない)のプロパティの記述子を返します。そのようなプロパティがない場合は、undefined が返されます。
> Object.getOwnPropertyDescriptor(Object.prototype, 'toString')
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true }
> Object.getOwnPropertyDescriptor({}, 'toString')
undefinedObject.defineProperty(obj, propKey, propDesc)
キーが propKey であり、属性が propDesc を介して指定されている obj のプロパティを作成または変更します。変更されたオブジェクトを返します。例:
varobj=Object.defineProperty({},'foo',{value:123,enumerable:true// writable: false (default value)// configurable: false (default value)});
Object.defineProperties(obj, propDescObj)
Object.defineProperty() のバッチバージョンです。propDescObj の各プロパティは、プロパティ記述子を保持します。プロパティのキーと値は、Object.defineProperties に、obj で作成または変更するプロパティを伝えます。例:
varobj=Object.defineProperties({},{foo:{value:123,enumerable:true},bar:{value:'abc',enumerable:true}});
Object.create(proto, propDescObj?)
まず、プロトタイプが proto であるオブジェクトを作成します。次に、オプションのパラメーター propDescObj が指定されている場合は、Object.defineProperties と同じ方法で、プロパティをそれに追加します。最後に、結果を返します。たとえば、次のコードスニペットは、前のスニペットと同じ結果を生成します。
varobj=Object.create(Object.prototype,{foo:{value:123,enumerable:true},bar:{value:'abc',enumerable:true}});
オブジェクトの同一コピーを作成するには、次の 2 つのことを正しく行う必要があります。
次の関数は、そのようなコピーを実行します。
functioncopyObject(orig){// 1. copy has same prototype as origvarcopy=Object.create(Object.getPrototypeOf(orig));// 2. copy has all of orig’s propertiescopyOwnPropertiesFrom(copy,orig);returncopy;}
プロパティは、この関数を介して orig から copy にコピーされます。
functioncopyOwnPropertiesFrom(target,source){Object.getOwnPropertyNames(source)// (1).forEach(function(propKey){// (2)vardesc=Object.getOwnPropertyDescriptor(source,propKey);// (3)Object.defineProperty(target,propKey,desc);// (4)});returntarget;};
これらは、関連する手順です。
source のすべての自身のプロパティのキーを持つ配列を取得します。target に自身のプロパティを作成します。この関数は、Underscore.js ライブラリの関数 _.extend() に非常に似ていることに注意してください。
次の 2 つの操作は非常に似ています。
defineProperty() と defineProperties() によるプロパティの定義(記述子を介したプロパティの取得と定義を参照)。= によるプロパティへの代入。ただし、いくつかの微妙な違いがあります。
プロパティへの代入prop とは、既存のプロパティを変更することを意味します。プロセスは次のとおりです。
prop がセッター(自身または継承)である場合は、そのセッターを呼び出します。prop が読み取り専用(自身または継承)である場合は、例外をスローするか(厳格モード)、何も実行しません(非厳格モード)。次のセクションでは、この(やや予想外の)現象について詳しく説明します。prop が自身のものであり(かつ書き込み可能)、そのプロパティの値を変更します。prop が存在しないか、継承されていて書き込み可能です。どちらの場合も、書き込み可能、構成可能、列挙可能な自身のプロパティ prop を定義します。後者の場合、継承されたプロパティを上書き(非破壊的に変更)したことになります。前者の場合、存在しなかったプロパティが自動的に定義されました。この種の自動定義は、代入の際のタイプミスを検出するのが難しい可能性があるため、問題があります。オブジェクト obj が、プロトタイプからプロパティ foo を継承しており、foo が書き込み可能でない場合、obj.foo に代入することはできません。
varproto=Object.defineProperty({},'foo',{value:'a',writable:false});varobj=Object.create(proto);
obj は、proto から読み取り専用プロパティ foo を継承します。非厳格モードでは、プロパティを設定しても効果はありません。
> obj.foo = 'b'; > obj.foo 'a'
厳格モードでは、例外が発生します。
> (function () { 'use strict'; obj.foo = 'b' }());
TypeError: Cannot assign to read-only property 'foo'これは、代入が継承されたプロパティを非破壊的に変更するという考え方に一致します。継承されたプロパティが読み取り専用の場合、非破壊的な変更を含め、すべての変更を禁止する必要があります。
自身のプロパティを定義することによって、この保護を回避できることに注意してください(定義と代入の違いについては、前のサブセクションを参照)。
> Object.defineProperty(obj, 'foo', { value: 'b' });
> obj.foo
'b'一般的なルールは、システムによって作成されたプロパティは列挙不可であり、ユーザーによって作成されたプロパティは列挙可能であるということです。
> Object.keys([]) [] > Object.getOwnPropertyNames([]) [ 'length' ] > Object.keys(['a']) [ '0' ]
これは、組み込みインスタンスプロトタイプのメソッドに特に当てはまります。
> Object.keys(Object.prototype) [] > Object.getOwnPropertyNames(Object.prototype) [ hasOwnProperty', 'valueOf', 'constructor', 'toLocaleString', 'isPrototypeOf', 'propertyIsEnumerable', 'toString' ]
列挙可能性の主な目的は、for-in ループにどのプロパティを無視すべきかを伝えることです。先ほど組み込みコンストラクターのインスタンスを見たときに、ユーザーが作成したものではないものはすべて for-in から隠されていることがわかりました。
列挙可能性の影響を受ける操作は次のとおりです。
for-in ループObject.keys()(自身のプロパティキーのリスト)JSON.stringify()(JSON.stringify(value, replacer?, space?))以下に、覚えておくべきベストプラクティスをいくつか示します。
for-in ループは避ける必要があります(ベストプラクティス:配列の反復処理)。オブジェクトを保護するには、次の3つのレベルがあり、弱いものから強いものの順にリストされています。
Object.preventExtensions(obj)
obj にプロパティを追加することを不可能にします。例えば
varobj={foo:'a'};Object.preventExtensions(obj);
これで、プロパティの追加は非厳格モードでは何も起こらずに失敗します。
> obj.bar = 'b'; > obj.bar undefined
厳格モードではエラーをスローします。
> (function () { 'use strict'; obj.bar = 'b' }());
TypeError: Can't add property bar, object is not extensibleただし、プロパティを削除することはできます。
> delete obj.foo true > obj.foo undefined
オブジェクトが拡張可能かどうかは、次の方法で確認できます。
Object.isExtensible(obj)
次の方法によるシーリング
Object.seal(obj)
拡張を防止し、すべてのプロパティを「構成不可」にします。後者は、プロパティの属性(プロパティ属性とプロパティ記述子を参照)をもう変更できないことを意味します。たとえば、読み取り専用プロパティは、永久に読み取り専用のままです。
次の例は、シーリングによってすべてのプロパティが構成不可になることを示しています。
> var obj = { foo: 'a' };
> Object.getOwnPropertyDescriptor(obj, 'foo') // before sealing
{ value: 'a',
writable: true,
enumerable: true,
configurable: true }
> Object.seal(obj)
> Object.getOwnPropertyDescriptor(obj, 'foo') // after sealing
{ value: 'a',
writable: true,
enumerable: true,
configurable: false }プロパティ foo を変更することはできます。
> obj.foo = 'b'; 'b' > obj.foo 'b'
ただし、その属性を変更することはできません。
> Object.defineProperty(obj, 'foo', { enumerable: false });
TypeError: Cannot redefine property: fooオブジェクトがシールされているかどうかは、次の方法で確認できます。
Object.isSealed(obj)
Object.freeze(obj)
varpoint={x:17,y:-5};Object.freeze(point);
ここでも、非厳格モードでは何も起こらずに失敗します。
> point.x = 2; // no effect, point.x is read-only
> point.x
17
> point.z = 123; // no effect, point is not extensible
> point
{ x: 17, y: -5 }そして、厳格モードではエラーが発生します。
> (function () { 'use strict'; point.x = 2 }());
TypeError: Cannot assign to read-only property 'x'
> (function () { 'use strict'; point.z = 123 }());
TypeError: Can't add property z, object is not extensibleオブジェクトが凍結されているかどうかは、次の方法で確認できます。
Object.isFrozen(obj)
オブジェクトの保護は浅いです。つまり、自身のプロパティに影響しますが、それらのプロパティの値には影響しません。たとえば、次のオブジェクトについて考えてみましょう。
varobj={foo:1,bar:['a','b']};Object.freeze(obj);
obj を凍結しても、完全に不変というわけではありません。プロパティ bar の(可変の)値を変更できます。
> obj.foo = 2; // no effect
> obj.bar.push('c'); // changes obj.bar
> obj
{ foo: 1, bar: [ 'a', 'b', 'c' ] }さらに、obj はプロトタイプ Object.prototype を持っており、これも可変です。
コンストラクター関数(略して コンストラクター)は、ある意味で類似したオブジェクトを生成するのに役立ちます。これは通常の関数ですが、名前が付けられ、セットアップされ、異なる方法で呼び出されます。このセクションでは、コンストラクターの仕組みについて説明します。これらは他の言語のクラスに対応します。
すでに、(プロトタイプを介したオブジェクト間でのデータの共有で)類似した2つのオブジェクトの例を見てきました。
varPersonProto={describe:function(){return'Person named '+this.name;}};varjane={[[Prototype]]:PersonProto,name:'Jane'};vartarzan={[[Prototype]]:PersonProto,name:'Tarzan'};
オブジェクト jane と tarzan はどちらも「person」と見なされ、プロトタイプオブジェクト PersonProto を共有します。このプロトタイプを、jane や tarzan のようなオブジェクトを作成するコンストラクター Person に変換してみましょう。コンストラクターが作成するオブジェクトは、そのインスタンスと呼ばれます。このようなインスタンスは、jane や tarzan と同じ構造を持ち、次の2つの部分で構成されます。
jane と tarzan)。PersonProto)を持ちます。コンストラクターは、new 演算子を介して呼び出される関数です。慣例により、コンストラクターの名前は大文字で始まり、通常の関数とメソッドの名前は小文字で始まります。関数自体がパート1をセットアップします。
functionPerson(name){this.name=name;}
Person.prototype 内のオブジェクトは、Person のすべてのインスタンスのプロトタイプになります。これにより、パート2が提供されます。
Person.prototype.describe=function(){return'Person named '+this.name;};
Person のインスタンスを作成して使用してみましょう。
> var jane = new Person('Jane');
> jane.describe()
'Person named Jane'Person は通常の関数であることがわかります。new を介して呼び出された場合にのみ、コンストラクターになります。new 演算子は、次の手順を実行します。
Person. prototype である新しいオブジェクトが作成されます。Person はそのオブジェクトを暗黙のパラメーター this として受け取り、インスタンスプロパティを追加します。図 17-3 は、インスタンス jane がどのように見えるかを示しています。Person.prototype のプロパティ constructor はコンストラクターを指し、インスタンスの constructor プロパティで説明されています。
instanceof 演算子を使用すると、オブジェクトが特定のコンストラクターのインスタンスであるかどうかを確認できます。
> jane instanceof Person true > jane instanceof Date false
手動で new 演算子を実装すると、おおよそ次のようになります。
functionnewOperator(Constr,args){varthisValue=Object.create(Constr.prototype);// (1)varresult=Constr.apply(thisValue,args);if(typeofresult==='object'&&result!==null){returnresult;// (2)}returnthisValue;}
(1)行では、コンストラクター Constr によって作成されたインスタンスのプロトタイプが Constr.prototype であることがわかります。
(2)行は、new 演算子のもう1つの機能を示しています。コンストラクターから任意のオブジェクトを返し、それが new 演算子の結果にすることができます。これは、コンストラクターにサブコンストラクターのインスタンスを返させたい場合に便利です(コンストラクターから任意のオブジェクトを返すで例を示します)。
残念ながら、プロトタイプという用語は、JavaScript であいまいな方法で使用されています。
オブジェクトは、別のオブジェクトのプロトタイプになることができます。
> var proto = {};
> var obj = Object.create(proto);
> Object.getPrototypeOf(obj) === proto
true前の例では、proto が obj のプロトタイプです。
prototype の値各コンストラクター C には、オブジェクトを参照する prototype プロパティがあります。そのオブジェクトは、C のすべてのインスタンスのプロトタイプになります。
> function C() {}
> Object.getPrototypeOf(new C()) === C.prototype
true通常、文脈からどちらのプロトタイプが意図されているかが明らかになります。あいまいさを解消する必要がある場合は、オブジェクト間の関係を記述するために、プロトタイプを使用する必要があります。これは、その名前が getPrototypeOf と isPrototypeOf を介して標準ライブラリに入っているためです。したがって、prototype プロパティによって参照されるオブジェクトには別の名前を見つける必要があります。1つの可能性は コンストラクタープロトタイプ ですが、コンストラクターにもプロトタイプがあるため、問題があります。
> function Foo() {}
> Object.getPrototypeOf(Foo) === Function.prototype
trueしたがって、インスタンスプロトタイプが最適なオプションです。
デフォルトでは、各関数 C には、プロパティ constructor が C を指すインスタンスプロトタイプオブジェクト C.prototype が含まれています。
> function C() {}
> C.prototype.constructor === C
trueconstructor プロパティはプロトタイプから各インスタンスに継承されるため、それを使用してインスタンスのコンストラクターを取得できます。
> var o = new C(); > o.constructor [Function: C]
次の catch 句では、キャッチされた例外のコンストラクターに応じて異なるアクションを実行します。
try{...}catch(e){switch(e.constructor){caseSyntaxError:...break;caseCustomError:...break;...}}
このアプローチでは、特定のコンストラクターの直接インスタンスのみが検出されます。対照的に、instanceof は、直接インスタンスとすべてのサブコンストラクターのインスタンスの両方を検出します。
例えば
> function Foo() {}
> var f = new Foo();
> f.constructor.name
'Foo'すべてのJavaScriptエンジンが関数のプロパティ name をサポートしているわけではありません。
これは、既存のオブジェクト x と同じコンストラクターを持つ新しいオブジェクト y を作成する方法です。
functionConstr(){}varx=newConstr();vary=newx.constructor();console.log(yinstanceofConstr);// true
このトリックは、サブコンストラクターのインスタンスに対して機能する必要があり、this に似た新しいインスタンスを作成したいメソッドに便利です。その場合、固定のコンストラクターを使用することはできません。
SuperConstr.prototype.createCopy=function(){returnnewthis.constructor(...);};
一部の継承ライブラリは、サブコンストラクタのプロパティにスーパプロトタイプを割り当てます。例えば、YUIフレームワークは、Y.extendを介してサブクラス化を提供します。
functionSuper(){}functionSub(){Sub.superclass.constructor.call(this);// (1)}Y.extend(Sub,Super);
(1)行の呼び出しが機能するのは、extendがSub.superclassをSuper.prototypeに設定するためです。constructorプロパティのおかげで、スーパーコンストラクタをメソッドとして呼び出すことができます。
instanceof演算子(「instanceof演算子」を参照)は、プロパティconstructorには依存しません。
すべてのコンストラクタCに対して、次のアサーションが保持されることを確認してください:
C.prototype.constructor===C
デフォルトでは、すべての関数fは、正しく設定されたプロパティprototypeを既に持っています
> function f() {}
> f.prototype.constructor === f
trueしたがって、このオブジェクトを置き換えることは避け、プロパティを追加するだけにする必要があります
// Avoid:C.prototype={method1:function(...){...},...};// Prefer:C.prototype.method1=function(...){...};...
置き換える場合は、constructorに正しい値を手動で割り当てる必要があります
C.prototype={constructor:C,method1:function(...){...},...};
JavaScriptの重要な部分はconstructorプロパティに依存していないことに注意してください。しかし、このセクションで述べた手法を可能にするため、設定するのは良いスタイルです。
instanceof演算子は
valueinstanceofConstr
valueがコンストラクタConstrまたはサブコンストラクタによって作成されたかどうかを判断します。これは、Constr.prototypeがvalueのプロトタイプチェーンにあるかどうかをチェックすることで行います。したがって、次の2つの式は同等です。
valueinstanceofConstrConstr.prototype.isPrototypeOf(value)
次に例をいくつか示します。
> {} instanceof Object
true
> [] instanceof Array // constructor of []
true
> [] instanceof Object // super-constructor of []
true
> new Date() instanceof Date
true
> new Date() instanceof Object
true予想どおり、instanceofはプリミティブ値に対して常にfalseになります
> 'abc' instanceof Object false > 123 instanceof Number false
最後に、右辺が関数でない場合、instanceofは例外をスローします
> [] instanceof 123 TypeError: Expecting a function in instanceof check
ほとんどすべてのオブジェクトはObjectのインスタンスです。これは、Object.prototypeがそれらのプロトタイプチェーンにあるためです。しかし、そうでないオブジェクトも存在します。以下に2つの例を示します。
> Object.create(null) instanceof Object false > Object.prototype instanceof Object false
前者のオブジェクトについては、「dictパターン:プロトタイプを持たないオブジェクトはより良いマップ」で詳しく説明します。後者のオブジェクトは、ほとんどのプロトタイプチェーンが終わる場所です(そしてどこかで終わる必要があります)。どちらのオブジェクトもプロトタイプを持っていません
> Object.getPrototypeOf(Object.create(null)) null > Object.getPrototypeOf(Object.prototype) null
ただし、typeofはそれらをオブジェクトとして正しく分類します
> typeof Object.create(null) 'object' > typeof Object.prototype 'object'
この落とし穴は、instanceofのほとんどのユースケースにとって致命的なものではありませんが、注意する必要があります。
Webブラウザでは、各フレームとウィンドウには、レルムが個別に存在し、グローバル変数が別々になっています。そのため、レルムをまたぐオブジェクトではinstanceofが機能しません。その理由を理解するために、次のコードを見てください。
if(myvarinstanceofArray)...// Doesn’t always work
myvarが別のレルムからの配列である場合、そのプロトタイプは、そのレルムのArray.prototypeになります。したがって、instanceofはmyvarのプロトタイプチェーンに現在のレルムのArray.prototypeを見つけることができず、falseを返します。ECMAScript 5には、常に機能する関数Array.isArray()があります。:
<head><script>functiontest(arr){variframe=frames[0];console.log(arrinstanceofArray);// falseconsole.log(arrinstanceofiframe.Array);// trueconsole.log(Array.isArray(arr));// true}</script></head><body><iframesrcdoc="<script>window.parent.test([])</script>"></iframe></body>
明らかに、これは組み込み以外のコンストラクタでも問題になります。
Array.isArray()を使用すること以外に、この問題を回避するためにできることがいくつかあります
postMessage()メソッドがあります。インスタンスのコンストラクタの名前を確認します(関数のnameプロパティをサポートするエンジンでのみ機能します)
someValue.constructor.name==='NameOfExpectedConstructor'
プロトタイププロパティを使用して、インスタンスがタイプTに属していることをマークします。これを行うにはいくつかの方法があります。valueがTのインスタンスかどうかをチェックするには、次のようになります
value.isT():Tインスタンスのプロトタイプは、このメソッドからtrueを返す必要があります。共通のスーパーコンストラクタは、デフォルト値のfalseを返す必要があります。'T' in value:Tインスタンスのプロトタイプに、キーが'T'(またはより固有のもの)であるプロパティでタグ付けする必要があります。value.TYPE_NAME === 'T':関連するすべてのプロトタイプには、適切な値を持つTYPE_NAMEプロパティが必要です。このセクションでは、コンストラクタを実装するためのいくつかのヒントを示します。
コンストラクタを使用するときにnewを忘れると、コンストラクタとしてではなく、関数として呼び出していることになります。非厳格モードでは、インスタンスを取得せず、グローバル変数が作成されます。残念ながら、これはすべて警告なしに行われます。
functionSloppyColor(name){this.name=name;}varc=SloppyColor('green');// no warning!// No instance is created:console.log(c);// undefined// A global variable is created:console.log(name);// green
厳格モードでは、例外が発生します。
functionStrictColor(name){'use strict';this.name=name;}varc=StrictColor('green');// TypeError: Cannot set property 'name' of undefined
classExpression{// Static factory method:publicstaticExpressionparse(Stringstr){if(...){returnnewAddition(...);}elseif(...){returnnewMultiplication(...);}else{thrownewExpressionException(...);}}}...Expressionexpr=Expression.parse(someStr);
JavaScriptでは、コンストラクタから必要なオブジェクトを返すだけです。したがって、上記のコードのJavaScriptバージョンは次のようになります
functionExpression(str){if(...){returnnewAddition(..);}elseif(...){returnnewMultiplication(...);}else{thrownewExpressionException(...);}}...varexpr=newExpression(someStr);
これは良い知らせです:JavaScriptコンストラクタはあなたを束縛しないため、コンストラクタが直接インスタンスを返すか、それ以外のものを返すかをいつでも変更できます。
このセクションでは、ほとんどの場合、プロトタイププロパティにデータを配置すべきではないことを説明します。ただし、そのルールにはいくつかの例外があります。
コンストラクタは通常、インスタンスプロパティを初期値に設定します。そのような値の1つがデフォルトである場合、インスタンスプロパティを作成する必要はありません。同じキーを持つプロトタイププロパティで、値がデフォルトである必要があります。例えば
/*** Anti-pattern: don’t do this** @param data an array with names*/functionNames(data){if(data){// There is a parameter// => create instance propertythis.data=data;}}Names.prototype.data=[];
パラメータdataはオプションです。それが欠落している場合、インスタンスはプロパティdataを取得しませんが、代わりにNames.prototype.dataを継承します。
このアプローチはほとんどの場合機能します。インスタンスnのNamesを作成できます。n.dataを取得すると、Names.prototype.dataが読み取られます。n.dataを設定すると、nに新しい独自のプロパティが作成され、プロトタイプ内の共有デフォルト値が保持されます。デフォルト値を(新しい値で置き換えるのではなく)変更した場合にのみ問題が発生します。
> var n1 = new Names();
> var n2 = new Names();
> n1.data.push('jane'); // changes default value
> n1.data
[ 'jane' ]
> n2.data
[ 'jane' ]前の例では、push()がNames.prototype.dataの配列を変更しました。その配列は、独自のプロパティdataを持たないすべてのインスタンスで共有されるため、n2.dataの初期値も変更されました。
これまで説明したことを踏まえると、デフォルト値を共有せず、常に新しい値を作成する方が良いでしょう
functionNames(data){this.data=data||[];}
明らかに、その値が不変である場合(すべてのプリミティブがそうであるように、「プリミティブ値」を参照)、共有のデフォルト値を変更するという問題は発生しません。しかし、一貫性のために、プロパティを設定する単一の方法に固執するのが最善です。また、懸念事項の通常の分離を維持することも好みます(「レイヤー3:コンストラクタ—インスタンスのファクトリ」を参照):コンストラクタはインスタンスプロパティを設定し、プロトタイプにはメソッドが含まれます。
ECMAScript 6は、コンストラクタパラメータにデフォルト値を設定でき、クラスを介してプロトタイプメソッドを定義できますが、データを持つプロトタイププロパティを定義できないため、これをさらにベストプラクティスにするでしょう。
場合によっては、プロパティ値の作成が(計算上またはストレージの点で)コストのかかる操作になることがあります。その場合は、オンデマンドでインスタンスプロパティを作成できます:
functionNames(data){if(data)this.data=data;}Names.prototype={constructor:Names,// (1)getdata(){// Define, don’t assign// => avoid calling the (nonexistent) setterObject.defineProperty(this,'data',{value:[],enumerable:true,configurable:false,writable:false});returnthis.data;}};
JavaScriptは(getterのみを見つけた場合に)欠落しているセッターについて不平を言うため、代入によってインスタンスにプロパティdataを追加することはできません。したがって、Object.defineProperty()を介して追加します。定義と代入の違いを確認するには、「プロパティ:定義と代入」を参照してください。(1)行では、プロパティconstructorが適切に設定されていることを確認しています(「インスタンスのconstructorプロパティ」を参照)。
明らかに、これはかなりの作業量になるため、それだけの価値があることを確認する必要があります。
もし同じプロパティ(同じキー、同じセマンティクス、通常は異なる値)が複数のプロトタイプに存在する場合、それはポリモーフィックと呼ばれます。 この場合、インスタンス経由でプロパティを読み取る結果は、そのインスタンスのプロトタイプによって動的に決定されます。ポリモーフィックに使用されないプロトタイププロパティは、(その非ポリモーフィックな性質をより良く反映する)変数に置き換えることができます。
例えば、プロトタイププロパティに定数を格納し、this経由でアクセスできます。
functionFoo(){}Foo.prototype.FACTOR=42;Foo.prototype.compute=function(x){returnx*this.FACTOR;};
この定数はポリモーフィックではありません。したがって、変数経由でアクセスしても同じです。
// This code should be inside an IIFE or a modulefunctionFoo(){}varFACTOR=42;Foo.prototype.compute=function(x){returnx*FACTOR;};
以下は、ポリモーフィックなプロトタイププロパティの例です。不変データを使用しています。プロトタイププロパティを介してコンストラクタのインスタンスにタグ付けすることで、異なるコンストラクタのインスタンスと区別できます。
functionConstrA(){}ConstrA.prototype.TYPE_NAME='ConstrA';functionConstrB(){}ConstrB.prototype.TYPE_NAME='ConstrB';
ポリモーフィックな「タグ」であるTYPE_NAMEのおかげで、ConstrAとConstrBのインスタンスを、それらがレルムをまたいでいる場合でも区別できます(この場合、instanceofは機能しません。 「落とし穴:レルム(フレームまたはウィンドウ)をまたぐ」を参照してください)。
JavaScriptには、オブジェクトのプライベートデータを管理するための専用の手段はありません。このセクションでは、その制限を回避するための3つの手法について説明します。
さらに、IIFEを介してグローバルデータをプライベートに保つ方法について説明します。
コンストラクタが呼び出されると、2つのものが作成されます。コンストラクタのインスタンスと環境です(「環境:変数の管理」を参照してください)。インスタンスはコンストラクタによって初期化されます。環境は、コンストラクタのパラメータとローカル変数を保持します。コンストラクタ内で作成されたすべての関数(メソッドを含む)は、その環境(作成された環境)への参照を保持します。その参照のおかげで、コンストラクタが終了した後でも、常に環境にアクセスできます。この関数と環境の組み合わせはクロージャと呼ばれます(「クロージャ:関数は誕生時のスコープとの接続を維持する」)。したがって、コンストラクタの環境は、インスタンスとは独立したデータストレージであり、両者が同時に作成されるという理由だけで関連付けられています。それらを適切に接続するには、両方の世界に存在する関数が必要です。ダグラス・クロックフォードの用語を使用すると、インスタンスには3種類の関連付けられた値があります(「図17-4」を参照してください)。
次のセクションでは、それぞれの種類の値をより詳細に説明します。
コンストラクタConstrが与えられた場合、誰でもアクセスできるパブリックなプロパティが2種類あることを覚えておいてください。まず、プロトタイププロパティはConstr.prototypeに格納され、すべてのインスタンスで共有されます。プロトタイププロパティは通常メソッドです。
Constr.prototype.publicMethod=...;
次に、インスタンスプロパティは、各インスタンスに固有です。それらはコンストラクタで追加され、通常は(メソッドではなく)データを保持します。
functionConstr(...){this.publicData=...;...}
コンストラクタの環境は、パラメータとローカル変数で構成されます。それらはコンストラクタ内部からのみアクセスでき、したがってインスタンスに対してプライベートです。
functionConstr(...){...varthat=this;// make accessible to private functionsvarprivateData=...;functionprivateFunction(...){// Access everythingprivateData=...;that.publicData=...;that.publicMethod(...);}...}
プライベートデータは、外部からのアクセスから非常に安全であり、プロトタイプメソッドもアクセスできません。それでは、コンストラクタを離れた後にどのように使用すればよいのでしょうか?答えは特権メソッドです。コンストラクタで作成された関数は、インスタンスメソッドとして追加されます。これは、一方ではプライベートデータにアクセスできることを意味し、他方では、パブリックであるため、プロトタイプメソッドによって認識されることを意味します。言い換えれば、それらはプライベートデータとパブリック(プロトタイプメソッドを含む)の間の仲介者として機能します。
functionConstr(...){...this.privilegedMethod=function(...){// Access everythingprivateData=...;privateFunction(...);this.publicData=...;this.publicMethod(...);};}
以下は、Crockfordプライバシーパターンを使用して、StringBuilderを実装したものです。
functionStringBuilder(){varbuffer=[];this.add=function(str){buffer.push(str);};this.toString=function(){returnbuffer.join('');};}// Can’t put methods in the prototype!
これがそのやり取りです。
> var sb = new StringBuilder();
> sb.add('Hello');
> sb.add(' world!');
> sb.toString()
’Hello world!’Crockfordプライバシーパターンを使用する場合に検討すべきいくつかの点を示します。
ほとんどのセキュリティクリティカルでないアプリケーションの場合、プライバシーはAPIのクライアントへのヒントのようなものです。「これを見る必要はありません。」 それがカプセル化の重要な利点であり、複雑さを隠すことです。内部ではさらに多くのことが行われていますが、APIのパブリック部分のみを理解する必要があります。命名規則の考え方は、プロパティのキーをマークすることで、クライアントにプライバシーについて知らせることです。この目的のために、プレフィックス付きのアンダースコアがよく使用されます。
前のStringBuilderの例を書き換えて、バッファがプロパティ_bufferに保持されるようにします。これはプライベートですが、あくまで慣習上のものです。
functionStringBuilder(){this._buffer=[];}StringBuilder.prototype={constructor:StringBuilder,add:function(str){this._buffer.push(str);},toString:function(){returnthis._buffer.join('');}};
以下に、マークされたプロパティキーによるプライバシーの長所と短所をいくつか示します。
プライベートプロパティの命名規則の1つの問題は、キーが衝突する可能性があることです(たとえば、コンストラクタのキーとサブコンストラクタのキー、またはmixinのキーとコンストラクタのキー)。コンストラクタの名前を含む、より長いキーを使用することで、このような衝突を減らすことができます。たとえば、前のケースでは、プライベートプロパティ_bufferは_StringBuilder_bufferと呼ばれます。このようなキーが長すぎる場合は、具象化して、変数に格納するという選択肢があります。
varKEY_BUFFER='_StringBuilder_buffer';
プライベートデータにはthis[KEY_BUFFER]を介してアクセスします。
varStringBuilder=function(){varKEY_BUFFER='_StringBuilder_buffer';functionStringBuilder(){this[KEY_BUFFER]=[];}StringBuilder.prototype={constructor:StringBuilder,add:function(str){this[KEY_BUFFER].push(str);},toString:function(){returnthis[KEY_BUFFER].join('');}};returnStringBuilder;}();
定数KEY_BUFFERがローカルに留まり、グローバル名前空間を汚染しないように、StringBuilderをIIFEでラップしました。
具象化されたプロパティキーを使用すると、キーにUUID(Universally Unique Identifier)を使用できます。たとえば、Robert Kiefferのnode-uuidを介して。
varKEY_BUFFER='_StringBuilder_buffer_'+uuid.v4();
KEY_BUFFERには、コードが実行されるたびに異なる値が設定されます。たとえば、次のようになる可能性があります。
_StringBuilder_buffer_110ec58a-a0f2-4ac4-8393-c866d813b8d1
UUIDを使用した長いキーを使用すると、キーの衝突をほぼ不可能にできます。
このサブセクションでは、IIFEを介して、シングルトンオブジェクト、コンストラクタ、およびメソッドに対してグローバルデータをプライベートに保つ方法について説明します(「IIFEを介して新しいスコープを導入する」を参照してください)。これらのIIFEは新しい環境を作成し(「環境:変数の管理」を参照してください)、そこにプライベートデータを配置します。
環境内のプライベートデータをオブジェクトに関連付けるために、コンストラクタは必要ありません。次の例は、同じ目的でIIFEをシングルトンオブジェクトの周りにラップする方法を示しています。
varobj=function(){// open IIFE// publicvarself={publicMethod:function(...){privateData=...;privateFunction(...);},publicData:...};// privatevarprivateData=...;functionprivateFunction(...){privateData=...;self.publicData=...;self.publicMethod(...);}returnself;}();// close IIFE
グローバルデータの中には、コンストラクタとプロトタイプメソッドにのみ関連するものがあります。IIFEで両方をラップすることで、外部から隠蔽することができます。具象化されたキーを持つプロパティにおけるプライベートデータでは、例として、コンストラクタStringBuilderとそのプロトタイプメソッドが、プロパティキーを含む定数KEY_BUFFERを使用していることを示しました。この定数は、IIFEの環境に格納されます。
varStringBuilder=function(){// open IIFEvarKEY_BUFFER='_StringBuilder_buffer_'+uuid.v4();functionStringBuilder(){this[KEY_BUFFER]=[];}StringBuilder.prototype={// Omitted: methods accessing this[KEY_BUFFER]};returnStringBuilder;}();// close IIFE
モジュールシステムを使用している場合(第31章を参照)、コンストラクタとメソッドをモジュールに入れることで、よりクリーンなコードで同じ効果を得ることができます。
単一のメソッドに対してのみグローバルデータが必要な場合があります。 メソッドをラップするIIFEの環境に入れることで、プライベートに保つことができます。例:
varobj={method:function(){// open IIFE// method-private datavarinvocCount=0;returnfunction(){invocCount++;console.log('Invocation #'+invocCount);return'result';};}()// close IIFE};
これがそのやり取りです。
> obj.method() Invocation #1 'result' > obj.method() Invocation #2 'result'
このセクションでは、コンストラクタがどのように継承されるかを調べます。コンストラクタSuperがある場合、Superのすべての機能に加えて独自の機能も持つ新しいコンストラクタSubをどのように記述できるでしょうか? 残念ながら、JavaScriptにはこのタスクを実行するための組み込みメカニズムがありません。したがって、手動で作業を行う必要があります。
図17-5は、その考えを示しています。サブコンストラクタSubは、独自のプロパティに加えて、Superのすべてのプロパティ(プロトタイププロパティとインスタンスプロパティの両方)を持つ必要があります。したがって、Subがどのようなものになるかの大まかなアイデアはありますが、そこに到達する方法がわかりません。次に説明するいくつかのことを理解する必要があります。
instanceofが機能することを確認する:subがSubのインスタンスである場合、sub instanceof Superもtrueになるようにします。Superのメソッドの1つをSubで適応させる。Superのメソッドの1つをオーバーライドした場合、Subから元のメソッドを呼び出す必要がある場合があります。インスタンスプロパティは、コンストラクタ自体で設定されるため、スーパークラスのインスタンスプロパティを継承するには、そのコンストラクタを呼び出す必要があります。
functionSub(prop1,prop2,prop3,prop4){Super.call(this,prop1,prop2);// (1)this.prop3=prop3;// (2)this.prop4=prop4;// (3)}
Subがnewを介して呼び出されると、その暗黙的なパラメータthisは新しいインスタンスを参照します。最初に、そのインスタンスをSuper(1)に渡し、インスタンスプロパティを追加します。その後、Subは独自のインスタンスプロパティを設定します(2,3)。コツは、Superをnewを介して呼び出さないことです。これは、新しいスーパーインスタンスを作成するためです。代わりに、Superを関数として呼び出し、現在の(サブ)インスタンスをthisの値として渡します。
メソッドなどの共有プロパティは、インスタンスプロトタイプに保持されます。 したがって、Sub.prototypeがSuper.prototypeのすべてのプロパティを継承する方法を見つける必要があります。解決策は、Sub.prototypeにプロトタイプSuper.prototypeを与えることです。
はい、ここでのJavaScriptの用語は混乱を招きます。迷った場合は、用語:2つのプロトタイプを参照してください。それらの違いについて説明しています。
これがそれを実現するコードです
Sub.prototype=Object.create(Super.prototype);Sub.prototype.constructor=Sub;Sub.prototype.methodB=...;Sub.prototype.methodC=...;
Object.create()は、プロトタイプがSuper.prototypeである新しいオブジェクトを作成します。その後、Subのメソッドを追加します。インスタンスのコンストラクタプロパティで説明したように、プロパティconstructorも設定する必要があります。これは、正しい値を持っていた元のインスタンスプロトタイプを置き換えたためです。
図17-6は、SubとSuperがどのように関連付けられているかを示しています。Subの構造は、図17-5でスケッチしたものに似ています。図にはインスタンスプロパティは表示されていません。これらは図で説明されている関数呼び出しによって設定されます。
instanceofが機能することを確認する「instanceofが機能することを確認する」とは、SubのすべてのインスタンスがSuperのインスタンスでもある必要があることを意味します。図17-7は、SubのインスタンスであるsubInstanceのプロトタイプチェーンがどのように見えるかを示しています。その最初のプロトタイプはSub.prototypeであり、その2番目のプロトタイプはSuper.prototypeです。
まず、より簡単な質問から始めましょう。subInstanceはSubのインスタンスですか?はい、そうです。これは、次の2つのアサーションが同等であるためです(後者は前者の定義と見なすことができます)。
subInstanceinstanceofSubSub.prototype.isPrototypeOf(subInstance)
前述のように、Sub.prototypeはsubInstanceのプロトタイプの1つであるため、どちらのアサーションもtrueです。同様に、subInstanceはSuperのインスタンスでもあります。これは、次の2つのアサーションが成り立つためです。
subInstanceinstanceofSuperSuper.prototype.isPrototypeOf(subInstance)
私たちは、Super.prototypeのメソッドを、同じ名前のメソッドをSub.prototypeに追加することでオーバーライドします。methodBはその例であり、図17-7で、その理由を確認できます。methodBの検索はsubInstanceで始まり、Super.prototype.methodBの前にSub.prototype.methodBを見つけます。
スーパーコールを理解するには、ホームオブジェクトという用語を知る必要があります。 メソッドのホームオブジェクトとは、その値がメソッドであるプロパティを所有するオブジェクトです。たとえば、Sub.prototype.methodBのホームオブジェクトはSub.prototypeです。メソッドfooのスーパーコールには、次の3つのステップが含まれます。
fooのメソッドを探します。thisでそのメソッドを呼び出します。根拠は、スーパーメソッドは現在のメソッドと同じインスタンスで動作する必要があり、同じインスタンスプロパティにアクセスできる必要があるということです。したがって、サブメソッドのコードは次のようになります。それ自体をスーパーコールし、オーバーライドしたメソッドを呼び出します
Sub.prototype.methodB=function(x,y){varsuperResult=Super.prototype.methodB.call(this,x,y);// (1)returnthis.prop3+' '+superResult;}
(1)でのスーパーコールの読み方の1つは、スーパーメソッドを直接参照し、現在のthisで呼び出すことです。ただし、3つの部分に分割すると、前述の手順が見つかります
Super.prototype:Sub.prototypeのプロトタイプ(現在のメソッドSub.prototype.methodBのホームオブジェクト)であるSuper.prototypeで検索を開始します。methodB:名前がmethodBのメソッドを探します。call(this, ...):前のステップで見つけたメソッドを呼び出し、現在のthisを維持します。これまで、スーパークラスの名前を記述することで、常にスーパーメソッドとスーパーコンストラクタを参照してきました。 このようなハードコーディングは、コードの柔軟性を低下させます。スーパークラスプロトタイプをSubのプロパティに割り当てることで、これを回避できます。
Sub._super=Super.prototype;
その後、スーパークラスコンストラクタとスーパーメソッドの呼び出しは次のようになります
functionSub(prop1,prop2,prop3,prop4){Sub._super.constructor.call(this,prop1,prop2);this.prop3=prop3;this.prop4=prop4;}Sub.prototype.methodB=function(x,y){varsuperResult=Sub._super.methodB.call(this,x,y);returnthis.prop3+' '+superResult;}
Sub._superの設定は通常、サブプロトタイプをスーパープロトタイプにも接続するユーティリティ関数によって処理されます。例:
functionsubclasses(SubC,SuperC){varsubProto=Object.create(SuperC.prototype);// Save `constructor` and, possibly, other methodscopyOwnPropertiesFrom(subProto,SubC.prototype);SubC.prototype=subProto;SubC._super=SuperC.prototype;};
このコードでは、オブジェクトのコピーで示され、説明されているヘルパー関数copyOwnPropertiesFrom()を使用しています。
「サブクラス」を動詞として読んでください:SubCはサブクラスSuperCです。このようなユーティリティ関数は、サブコンストラクタの作成の苦痛を軽減できます。手動で行うことが少なくなり、スーパークラスの名前が冗長に記述されることがなくなります。次の例は、コードをどのように簡略化するかを示しています。
具体的な例として、コンストラクタPersonが既に存在すると仮定しましょう。
functionPerson(name){this.name=name;}Person.prototype.describe=function(){return'Person called '+this.name;};
次に、PersonのサブコンストラクタとしてコンストラクタEmployeeを作成します。これを手動で行うと、次のようになります
functionEmployee(name,title){Person.call(this,name);this.title=title;}Employee.prototype=Object.create(Person.prototype);Employee.prototype.constructor=Employee;Employee.prototype.describe=function(){returnPerson.prototype.describe.call(this)+' ('+this.title+')';};
これがそのやり取りです。
> var jane = new Employee('Jane', 'CTO');
> jane.describe()
Person called Jane (CTO)
> jane instanceof Employee
true
> jane instanceof Person
true前のセクションのユーティリティ関数subclasses()を使用すると、Employeeのコードが少し簡単になり、スーパークラスPersonのハードコーディングが回避されます
functionEmployee(name,title){Employee._super.constructor.call(this,name);this.title=title;}Employee.prototype.describe=function(){returnEmployee._super.describe.call(this)+' ('+this.title+')';};subclasses(Employee,Person);
組み込みコンストラクタは、このセクションで説明したのと同じサブクラス化アプローチを使用します。 たとえば、ArrayはObjectのサブコンストラクタです。したがって、Arrayのインスタンスのプロトタイプチェーンは次のようになります。
> var p = Object.getPrototypeOf > p([]) === Array.prototype true > p(p([])) === Object.prototype true > p(p(p([]))) === null true
ECMAScript 5とObject.create()が登場する前は、サブプロトタイプを作成する際に、スーパコンストラクタを呼び出すという方法がよく使われていました。
Sub.prototype=newSuper();// Don’t do this
これはECMAScript 5では推奨されていません。プロトタイプは、Superのインスタンスプロパティをすべて持つことになりますが、これはプロトタイプにとって不要です。したがって、前述のパターン(Object.create()を使用する)を使用する方が良いです。
ほとんどすべてのオブジェクトは、Object.prototypeをプロトタイプチェーンに持っています。
> Object.prototype.isPrototypeOf({})
true
> Object.prototype.isPrototypeOf([])
true
> Object.prototype.isPrototypeOf(/xyz/)
true以下のサブセクションでは、Object.prototypeがそのプロトタイプに対して提供するメソッドについて説明します。
次の2つのメソッドは、オブジェクトをプリミティブ値に変換するために使用されます。
Object.prototype.toString()
> ({ first: 'John', last: 'Doe' }.toString())
'[object Object]'
> [ 'a', 'b', 'c' ].toString()
'a,b,c'Object.prototype.valueOf()
これは、オブジェクトを数値に変換する際の推奨される方法です。デフォルトの実装ではthisを返します。
> var obj = {};
> obj.valueOf() === obj
truevalueOfは、ラッパーコンストラクタによってオーバーライドされ、ラップされたプリミティブを返します。
> new Number(7).valueOf() 7
数値および文字列への変換(暗黙的または明示的)は、プリミティブへの変換に基づいています(詳細は、アルゴリズム: ToPrimitive() — 値をプリミティブに変換するを参照)。そのため、前述の2つのメソッドを使用して、これらの変換を設定できます。valueOf()は、数値への変換で優先されます。
> 3 * { valueOf: function () { return 5 } }
15toString()は、文字列への変換で優先されます。
> String({ toString: function () { return 'ME' } })
'Result: ME'ブール値への変換は設定できません。オブジェクトは常にtrueとみなされます(ブール値への変換を参照)。
このメソッドは、オブジェクトのロケール固有の文字列表現を返します。デフォルトの実装では、toString()を呼び出します。ほとんどのエンジンでは、このメソッドのサポートはこれ以上進んでいません。ただし、ECMAScript国際化API(ECMAScript国際化APIを参照)は、多くの最新エンジンでサポートされており、いくつかの組み込みコンストラクタでオーバーライドされています。
次のメソッドは、プロトタイプ継承とプロパティに役立ちます。
Object.prototype.isPrototypeOf(obj)
レシーバーがobjのプロトタイプチェーンの一部である場合は、trueを返します。
> var proto = { };
> var obj = Object.create(proto);
> proto.isPrototypeOf(obj)
true
> obj.isPrototypeOf(obj)
falseObject.prototype.hasOwnProperty(key)
thisがキーがkeyであるプロパティを所有している場合、trueを返します。「所有」とは、プロパティがオブジェクト自体に存在し、そのプロトタイプのいずれにも存在しないことを意味します。
通常、特にプロパティを静的に知らないオブジェクトに対しては、このメソッドをジェネリックに(直接ではなく)呼び出す必要があります。その理由と方法については、プロパティの反復処理と検出で説明します。
> var proto = { foo: 'abc' };
> var obj = Object.create(proto);
> obj.bar = 'def';
> Object.prototype.hasOwnProperty.call(obj, 'foo')
false
> Object.prototype.hasOwnProperty.call(obj, 'bar')
trueObject.prototype.propertyIsEnumerable(propKey)
レシーバーが、propKeyというキーを持つ、列挙可能なプロパティを持っている場合はtrueを返し、それ以外の場合はfalseを返します。
> var obj = { foo: 'abc' };
> obj.propertyIsEnumerable('foo')
true
> obj.propertyIsEnumerable('toString')
false
> obj.propertyIsEnumerable('unknown')
falseインスタンスのプロトタイプには、そこから継承するオブジェクトよりも多くのオブジェクトに対して便利なメソッドがある場合があります。このセクションでは、プロトタイプから継承せずに、そのプロトタイプのメソッドを使用する方法について説明します。たとえば、インスタンスプロトタイプWine.prototypeには、メソッドincAge()があります。
functionWine(age){this.age=age;}Wine.prototype.incAge=function(years){this.age+=years;}
インタラクションは次のとおりです。
> var chablis = new Wine(3); > chablis.incAge(1); > chablis.age 4
メソッドincAge()は、プロパティageを持つ任意のオブジェクトで機能します。 Wineのインスタンスではないオブジェクトでこれを呼び出すにはどうすればよいでしょうか?前のメソッド呼び出しを見てみましょう。
chablis.incAge(1)
実際には2つの引数があります。
chablisはメソッド呼び出しのレシーバーであり、this経由でincAgeに渡されます。1は引数であり、years経由でincAgeに渡されます。前者を任意のオブジェクトに置き換えることはできません。レシーバーはWineのインスタンスである必要があります。そうでない場合、メソッドincAgeは見つかりません。ただし、前のメソッド呼び出しは、以下と同等です(「thisを設定しながら関数を呼び出す: call()、apply()、およびbind()」を参照)。
Wine.prototype.incAge.call(chablis,1)
前のパターンを使用すると、レシーバー(callの最初の引数)をWineのインスタンスではないオブジェクトにすることができます。これは、レシーバーがメソッドWine.prototype.incAgeを見つけるために使用されないためです。次の例では、メソッドincAge()をオブジェクトjohnに適用します。
> var john = { age: 51 };
> Wine.prototype.incAge.call(john, 3)
> john.age
54このように使用できる関数は、ジェネリックメソッドと呼ばれます。これは、thisが「その」コンストラクタのインスタンスではない場合に備えて準備する必要があります。したがって、すべてのメソッドがジェネリックであるわけではありません。ECMAScript言語仕様では、どれがジェネリックであるかが明示的に述べられています(すべてのジェネリックメソッドのリストを参照)。
Object.prototype.hasOwnProperty.call(obj,'propKey')
空のオブジェクトリテラル{}によって作成されたObjectのインスタンスを介してhasOwnPropertyにアクセスすることで、これを短縮できます。
{}.hasOwnProperty.call(obj,'propKey')
同様に、次の2つの式は同等です。
Array.prototype.join.call(str,'-')[].join.call(str,'-')
このパターンの利点は、冗長性が少ないことです。ただし、自己説明的ではありません。エンジンはリテラルがオブジェクトを作成すべきではないことを静的に判断できるため、パフォーマンスは問題にならないはずです(少なくとも長期的には)。
以下は、使用中のジェネリックメソッドの例です。
配列を(個々の要素ではなく)push()するために、apply()を使用します(Function.prototype.apply(thisValue, argArray)および要素の追加と削除(破壊的)を参照)。
> var arr1 = [ 'a', 'b' ]; > var arr2 = [ 'c', 'd' ]; > [].push.apply(arr1, arr2) 4 > arr1 [ 'a', 'b', 'c', 'd' ]
この例は、配列を引数に変換することに関するものであり、別のコンストラクタからメソッドを借用することに関するものではありません。
配列メソッドjoin()を文字列(配列ではない)に適用します。
> Array.prototype.join.call('abc', '-')
'a-b-c'配列メソッドmap()を文字列に適用します。[17]
> [].map.call('abc', function (x) { return x.toUpperCase() })
[ 'A', 'B', 'C' ]map()をジェネリックに使用する方が、中間配列を作成するsplit('')を使用するよりも効率的です。
> 'abc'.split('').map(function (x) { return x.toUpperCase() })
[ 'A', 'B', 'C' ]文字列メソッドを非文字列に適用します。toUpperCase()はレシーバーを文字列に変換し、その結果を大文字にします。
> String.prototype.toUpperCase.call(true) 'TRUE' > String.prototype.toUpperCase.call(['a','b','c']) 'A,B,C'
プレーンオブジェクトでジェネリック配列メソッドを使用すると、その動作を理解できます。
偽の配列で配列メソッドを呼び出します。
> var fakeArray = { 0: 'a', 1: 'b', length: 2 };
> Array.prototype.join.call(fakeArray, '-')
'a-b'配列メソッドが配列のように扱うオブジェクトをどのように変換するかを見てください。
> var obj = {};
> Array.prototype.push.call(obj, 'hello');
1
> obj
{ '0': 'hello', length: 1 }JavaScriptには、配列のように感じられるが、実際には配列ではないオブジェクトがいくつかあります。つまり、インデックス付きアクセスとlengthプロパティは持っているものの、配列メソッド(forEach()、push、concat()など)は持っていません。これは残念なことですが、後で説明するように、ジェネリック配列メソッドを使用すると回避策が可能になります。配列のようなオブジェクトの例には、次のようなものがあります。
特別な変数arguments(「インデックスによるすべてのパラメータ: 特殊な変数arguments」を参照)は、JavaScriptの基本的な部分であるため、重要な配列のようなオブジェクトです。argumentsは配列のように見えます。
> function args() { return arguments }
> var arrayLike = args('a', 'b');
> arrayLike[0]
'a'
> arrayLike.length
2ただし、配列メソッドはどれも使用できません。
> arrayLike.join('-')
TypeError: object has no method 'join'これは、arrayLikeがArrayのインスタンスではないためです(Array.prototypeはプロトタイプチェーンにありません)。
> arrayLike instanceof Array false
ブラウザのDOMノードリスト。これは、document.getElementsBy*()(たとえば、getElementsByTagName())、document.formsなどによって返されます。
> var elts = document.getElementsByTagName('h3');
> elts.length
3
> elts instanceof Array
false文字列も配列のようなものです。
> 'abc'[1] 'b' > 'abc'.length 3
「配列のような」という用語は、ジェネリック配列メソッドとオブジェクトの間の契約とみなすこともできます。オブジェクトは特定の要件を満たす必要があります。そうしないと、メソッドはオブジェクトで機能しません。要件は次のとおりです。
配列のようなオブジェクトの要素は、角かっこ([])と0から始まる整数インデックスを使用してアクセスできる必要があります。すべてのメソッドには読み取りアクセスが必要であり、一部のメソッドには追加で書き込みアクセスが必要です。すべてのオブジェクトがこの種のインデックス作成をサポートしていることに注意してください。角かっこ内のインデックスは文字列に変換され、プロパティ値を検索するためのキーとして使用されます。
> var obj = { '0': 'abc' };
> obj[0]
'abc'lengthプロパティを持っている必要があり、その値は要素の数になります。一部のメソッドでは、lengthが可変である必要があります(たとえば、reverse())。長さが不変の値(たとえば、文字列)は、これらのメソッドでは使用できません。次のパターンは、配列のようなオブジェクトを扱うのに役立ちます。
配列のようなオブジェクトを配列に変換します。
vararr=Array.prototype.slice.call(arguments);
引数なしのメソッドslice()(連結、スライス、結合(非破壊的)を参照)は、配列のようなレシーバーのコピーを作成します。
varcopy=['a','b'].slice();
配列のようなオブジェクトのすべての要素を反復処理するには、単純なforループを使用できます。
functionlogArgs(){for(vari=0;i<arguments.length;i++){console.log(i+'. '+arguments[i]);}}
ただし、Array.prototype.forEach()を借用することもできます。
functionlogArgs(){Array.prototype.forEach.call(arguments,function(elem,i){console.log(i+'. '+elem);});}
どちらの場合も、インタラクションは次のようになります。
> logArgs('hello', 'world');
0. hello
1. world以下のリストには、ECMAScript言語仕様で言及されているすべてのジェネリックメソッドが含まれています。
Array.prototype(配列プロトタイプメソッドを参照)
concat
every
filter
forEach
indexOf
join
lastIndexOf
map
pop
push
reduce
reduceRight
reverse
shift
slice
some
sort
splice
toLocaleString
toString
unshift
Date.prototype(Dateプロトタイプメソッドを参照)
toJSON
Object.prototype(すべてのオブジェクトのメソッドを参照)
Objectメソッドは自動的にジェネリックです。これらはすべてのオブジェクトに対して機能する必要があります。)
String.prototype(Stringプロトタイプメソッドを参照)
charAt
charCodeAt
concat
indexOf
lastIndexOf
localeCompare
match
replace
search
slice
split
substring
toLocaleLowerCase
toLocaleUpperCase
toLowerCase
toUpperCase
trim
JavaScript にはマップのための組み込みデータ構造がないため、オブジェクトが文字列から値へのマップとしてよく使用されます。残念ながら、それは見た目よりもエラーが発生しやすいものです。このセクションでは、このタスクに関わる3つの落とし穴について説明します。
プロパティを読み取る操作は、2種類に分けられます。
オブジェクトをマップとして解釈してエントリを読み取る際には、これらの操作の種類を慎重に選択する必要があります。理由を見るために、次の例を考えてみましょう。
varproto={protoProp:'a'};varobj=Object.create(proto);obj.ownProp='b';
obj は1つの自身のプロパティを持つオブジェクトで、そのプロトタイプは proto です。 proto も1つの自身のプロパティを持っています。proto は、オブジェクトリテラルで作成されたすべてのオブジェクトと同様に、プロトタイプ Object.prototype を持ちます。したがって、obj は proto と Object.prototype の両方からプロパティを継承します。
私たちは obj を、単一のエントリを持つマップとして解釈したいと考えています。
ownProp: 'b'
つまり、継承されたプロパティを無視し、自身のプロパティのみを考慮したいのです。どの読み取り操作がこのように obj を解釈し、どれがそうでないかを見てみましょう。オブジェクトをマップとして使用する場合、通常、変数に格納された任意のプロパティキーを使用したいことに注意してください。これはドット記法を排除します。
in 演算子は、オブジェクトが指定されたキーを持つプロパティを持っているかどうかを確認しますが、継承されたプロパティも考慮します。
> 'ownProp' in obj // ok true > 'unknown' in obj // ok false > 'toString' in obj // wrong, inherited from Object.prototype true > 'protoProp' in obj // wrong, inherited from proto true
継承されたプロパティを無視するチェックが必要です。hasOwnProperty() は私たちが望むことを実行します。
> obj.hasOwnProperty('ownProp') // ok
true
> obj.hasOwnProperty('unknown') // ok
false
> obj.hasOwnProperty('toString') // ok
false
> obj.hasOwnProperty('protoProp') // ok
falseマップとしての obj の解釈に従いながら、obj のすべてのキーを見つけるために使用できる操作は何でしょうか? for-in が機能するかもしれません。しかし、残念ながらそうではありません。
> for (propKey in obj) console.log(propKey) ownProp protoProp
継承された列挙可能なプロパティを考慮します。ここに Object.prototype のプロパティが表示されない理由は、それらがすべて非列挙可能であるためです。
対照的に、Object.keys() は自身のプロパティのみをリストします。
> Object.keys(obj) [ 'ownProp' ]
このメソッドは、列挙可能な自身のプロパティのみを返します。ownProp は代入によって追加されたため、デフォルトで列挙可能です。すべての自身のプロパティをリストしたい場合は、Object.getOwnPropertyNames() を使用する必要があります。
プロパティの値を読み取るために、ドット演算子とブラケット演算子のどちらかしか選択できません。前者は、変数に格納された任意のキーがあるため使用できません。そのため、継承されたプロパティを考慮するブラケット演算子を使用することになります。
> obj['toString'] [Function: toString]
これは私たちが望むものではありません。自身のプロパティのみを読み取るための組み込み操作はありませんが、自分で簡単に実装できます。
functiongetOwnProperty(obj,propKey){// Using hasOwnProperty() in this manner is problematic// (explained and fixed later)return(obj.hasOwnProperty(propKey)?obj[propKey]:undefined);}
この関数を使用すると、継承されたプロパティ toString は無視されます。
> getOwnProperty(obj, 'toString') undefined
関数 getOwnProperty() は、obj でメソッド hasOwnProperty() を呼び出しました。通常、それは問題ありません。
> getOwnProperty({ foo: 123 }, 'foo')
123ただし、キーが hasOwnProperty であるプロパティを obj に追加すると、そのプロパティはメソッド Object.prototype.hasOwnProperty() をオーバーライドし、getOwnProperty() は動作しなくなります。
> getOwnProperty({ hasOwnProperty: 123 }, 'foo')
TypeError: Property 'hasOwnProperty' is not a functionこの問題を解決するには、hasOwnProperty() を直接参照します。これにより、obj を介してそれを検索することを回避できます。
functiongetOwnProperty(obj,propKey){return(Object.prototype.hasOwnProperty.call(obj,propKey)?obj[propKey]:undefined);}
hasOwnProperty() をジェネリックに呼び出しました (「ジェネリックメソッド: プロトタイプからメソッドを借りる」を参照)。
多くの JavaScript エンジンでは、プロパティ __proto__ (「特殊なプロパティ __proto__」を参照) は特別です。取得するとオブジェクトのプロトタイプを取得し、設定するとオブジェクトのプロトタイプを変更します。 これが、オブジェクトがキーが '__proto__' であるプロパティにマップデータを格納できない理由です。マップキー '__proto__' を許可する場合は、プロパティキーとして使用する前にエスケープする必要があります。
functionget(obj,key){returnobj[escapeKey(key)];}functionset(obj,key,value){obj[escapeKey(key)]=value;}// Similar: checking if key exists, deleting an entryfunctionescapeKey(key){if(key.indexOf('__proto__')===0){// (1)returnkey+'%';}else{returnkey;}}
競合を避けるために、'__proto__' のエスケープされたバージョン (など) もエスケープする必要があります。つまり、キー '__proto__' を '__proto__%' としてエスケープする場合、'__proto__' エントリを置き換えないように、キー '__proto__%' もエスケープする必要があります。それが (1) 行で起こることです。
Mark S. Miller は、メールで、この落とし穴が現実世界に及ぼす影響について述べています。
この演習は学術的であり、現実のシステムでは発生しないと思いますか?サポートスレッドで観察されたように、最近まで、すべての非IEブラウザで、新しい Google ドキュメントの先頭に「__proto__」と入力すると、Google ドキュメントがハングしていました。これは、オブジェクトを文字列マップとして使用するこのようなバグのある使用法に起因していました。
次のようにして、プロトタイプのないオブジェクトを作成します。
vardict=Object.create(null);
このようなオブジェクトは、通常のオブジェクトよりも優れたマップ (辞書) であり、そのため、このパターンはdict パターン (辞書のためのdict) と呼ばれることもあります。まず、通常のオブジェクトを調べてから、プロトタイプのないオブジェクトがより優れたマップである理由を見つけてみましょう。
通常、JavaScript で作成する各オブジェクトは、少なくともプロトタイプチェーンに Object.prototype を持っています。Object.prototype のプロトタイプは null であるため、それがほとんどのプロトタイプチェーンが終わる場所です。
> Object.getPrototypeOf({}) === Object.prototype
true
> Object.getPrototypeOf(Object.prototype)
nullプロトタイプのないオブジェクトには、マップとして2つの利点があります。
in 演算子を自由に使い、プロパティを読み取るためにブラケットを使用できるようになりました。__proto__ は無効になります。ECMAScript 6 では、オブジェクトのプロトタイプチェーンに Object.prototype がない場合、特殊なプロパティ __proto__ は無効になります。JavaScript エンジンは徐々にこの動作に移行すると予想されますが、まだ一般的ではありません。唯一の欠点は、Object.prototype によって提供されるサービスが失われることです。たとえば、dict オブジェクトは自動的に文字列に変換できなくなります。
> console.log('Result: '+obj)
TypeError: Cannot convert object to primitive valueしかし、それは実際には欠点ではありません。dict オブジェクトでメソッドを直接呼び出すのはいずれにしても安全ではないからです。
クイックハックやライブラリの基礎として、dict パターンを使用します。(非ライブラリ) 本番コードでは、すべての落とし穴を回避できるため、ライブラリが推奨されます。次のセクションでは、そのようなライブラリをいくつか紹介します。
オブジェクトをマップとして使用するアプリケーションは多数あります。すべてのプロパティキーが静的に (開発時に) わかっている場合は、継承を無視し、自身のプロパティのみを調べるようにする必要があります。任意のキーを使用できる場合は、このセクションで説明した落とし穴を避けるためにライブラリを使用する必要があります。次に2つの例を示します。
このセクションは、より詳しい説明へのポインターを含む、クイックリファレンスです。
オブジェクトリテラル (「オブジェクトリテラル」を参照)
varjane={name:'Jane','not an identifier':123,describe:function(){// methodreturn'Person named '+this.name;},};// Call a method:console.log(jane.describe());// Person named Jane
ドット演算子 (.) (「ドット演算子 (.): 固定キーによるプロパティへのアクセス」を参照)
obj.propKeyobj.propKey=valuedeleteobj.propKey
ブラケット演算子 ([]) (「ブラケット演算子 ([]): 計算されたキーによるプロパティへのアクセス」を参照)
obj['propKey']obj['propKey']=valuedeleteobj['propKey']
プロトタイプの取得と設定 (「プロトタイプの取得と設定」を参照)
Object.create(proto,propDescObj?)Object.getPrototypeOf(obj)
プロパティの反復と検出 (「プロパティの反復と検出」を参照)
Object.keys(obj)Object.getOwnPropertyNames(obj)Object.prototype.hasOwnProperty.call(obj,propKey)propKeyinobj
記述子によるプロパティの取得と定義 (「記述子によるプロパティの取得と定義」を参照)
Object.defineProperty(obj,propKey,propDesc)Object.defineProperties(obj,propDescObj)Object.getOwnPropertyDescriptor(obj,propKey)Object.create(proto,propDescObj?)
Object.preventExtensions(obj)Object.isExtensible(obj)Object.seal(obj)Object.isSealed(obj)Object.freeze(obj)Object.isFrozen(obj)
すべてのオブジェクトのメソッド (「すべてのオブジェクトのメソッド」を参照)
Object.prototype.toString()Object.prototype.valueOf()Object.prototype.toLocaleString()Object.prototype.isPrototypeOf(obj)Object.prototype.hasOwnProperty(key)Object.prototype.propertyIsEnumerable(propKey)