オブジェクトリテラルと他のJavascriptオブジェクト(DOMノード、Dateオブジェクトなど)の違いをどのように見分けることができますか?
簡単な答えはあなたができないということです。
オブジェクトリテラルは次のようなものです。
var objLiteral = {foo: 'foo', bar: 'bar'};
一方、 Objectコンストラクターを使用して作成された同じオブジェクトは次のようになります。
var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
2つのオブジェクトがどのように作成されたかを区別する信頼できる方法はないと思います。
どうしてそれが重要ですか?
一般的な機能テスト戦略は、関数に渡されたオブジェクトのプロパティをテストして、呼び出されるメソッドをサポートしているかどうかを判断することです。そうすれば、オブジェクトがどのように作成されるかを気にする必要はありません。
「ダックタイピング」を採用することはできますが、その範囲は限られています。たとえば、getFullYear()
オブジェクトにDateオブジェクトであるというメソッドがあるからといって、それを保証することはできません。同様に、 nodeTypeプロパティがあるからといって、それがDOMオブジェクトであるとは限りません。
たとえば、jQueryisPlainObject
関数は、オブジェクトにnodeTypeプロパティがある場合、それはDOMノードであり、setInterval
プロパティがある場合、それはWindowオブジェクトであると見なします。この種のダックタイピングは非常に単純で、場合によっては失敗します。
また、jQueryは、特定の順序で返されるプロパティに依存していることに注意してください。これは、どの標準でもサポートされていない別の危険な仮定です(ただし、一部のサポーターは、想定される動作に合わせて標準を変更しようとしています)。
2014年4月22日編集:バージョン1.10では、jQueryには、継承されたプロパティが最初または最後に列挙されているかどうかを確認するための単一のプロパティのテストに基づくsupport.ownLastプロパティが含まれています(明らかにこれはIE9サポート用です)。これは、オブジェクトのプロパティが継承されているか所有されているかに関係なく、オブジェクトのプロパティが任意の順序で返される可能性があり、混乱する可能性があるという事実を無視し続けます。
おそらく、「プレーン」オブジェクトの最も簡単なテストは次のとおりです。
function isPlainObj(o) {
return typeof o == 'object' && o.constructor == Object;
}
これは、オブジェクトリテラルまたはオブジェクトコンストラクタを使用して作成されたオブジェクトには常に当てはまりますが、他の方法で作成されたオブジェクトには誤った結果をもたらす可能性があり、フレーム間で失敗する可能性があります(おそらく失敗します)。テストを追加することもできますがinstanceof
、コンストラクターテストが実行しないことを実行していることはわかりません。
ActiveXオブジェクトを渡す場合は、try..catchでラップするのが最適です。これらのオブジェクトは、エラーをスローする場合でも、あらゆる種類の奇妙な結果を返す可能性があるためです。
2015年10月13日編集
もちろん、いくつかの罠があります:
isPlainObject( {constructor: 'foo'} ); // false, should be true
// In global scope
var constructor = Object;
isPlainObject( this ); // true, should be false
コンストラクタープロパティをいじると、問題が発生します。Object以外のコンストラクターによって作成されたオブジェクトなど、他のトラップもあります。
ES5は今ではほとんど普及しているので、オブジェクトのをチェックするためのObject.getPrototypeOf[[Prototype]]
があります。それがbuit- inObject.prototypeの場合、オブジェクトはプレーンオブジェクトです。ただし、一部の開発者は、継承されたプロパティを持たない真に「空の」オブジェクトを作成したいと考えています。これは、以下を使用して実行できます。
var emptyObj = Object.create(null);
この場合、[[Prototype]]
プロパティはnullです。したがって、内部プロトタイプがObject.prototypeであるかどうかを確認するだけでは不十分です。
かなり広く使用されているものもあります。
Object.prototype.toString.call(valueToTest)
[[Class]]
これは、Objectsの場合は[objectObject]である内部プロパティに基づいて文字列を返すように指定されました。ただし、これはECMAScript 2015で変更され、他のタイプのオブジェクトに対してテストが実行され、デフォルトは[object Object]であるため、オブジェクトは「プレーンオブジェクト」ではなく、他のものとして認識されないオブジェクトである可能性があります。したがって、仕様には次のように記載されています。
「[toStringを使用したテスト]は、他の種類の組み込みオブジェクトまたはプログラム定義オブジェクトに対して信頼できる型テストメカニズムを提供しません。」
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.tostring
したがって、ES5より前のホスト、 nullのオブジェクト、およびgetPrototypeOf[[Prototype]]
を持たないその他のオブジェクトタイプ(nullなど、 Chris Nielsenに感謝)を許可する更新された関数を以下に示します。
getPrototypeOfをポリフィルする方法がないため、古いブラウザーのサポートが必要な場合(MDNによるとIE 8以下など)は役に立たない可能性があることに注意してください。
/* Function to test if an object is a plain object, i.e. is constructed
** by the built-in Object constructor and inherits directly from Object.prototype
** or null. Some built-in objects pass the test, e.g. Math which is a plain object
** and some host or exotic objects may pass also.
**
** @param {} obj - value to test
** @returns {Boolean} true if passes tests, false otherwise
*/
function isPlainObject(obj) {
// Basic check for Type object that's not null
if (typeof obj == 'object' && obj !== null) {
// If Object.getPrototypeOf supported, use it
if (typeof Object.getPrototypeOf == 'function') {
var proto = Object.getPrototypeOf(obj);
return proto === Object.prototype || proto === null;
}
// Otherwise, use internal class
// This should be reliable as if getPrototypeOf not supported, is pre-ES5
return Object.prototype.toString.call(obj) == '[object Object]';
}
// Not an object
return false;
}
// Tests
var data = {
'Host object': document.createElement('div'),
'null' : null,
'new Object' : {},
'Object.create(null)' : Object.create(null),
'Instance of other object' : (function() {function Foo(){};return new Foo()}()),
'Number primitive ' : 5,
'String primitive ' : 'P',
'Number Object' : new Number(6),
'Built-in Math' : Math
};
Object.keys(data).forEach(function(item) {
document.write(item + ': ' + isPlainObject(data[item]) + '<br>');
});