web-dev-qa-db-ja.com

オブジェクトリテラルと他のJavascriptオブジェクトをどのように区別できますか?

更新:私にとって重要なポイントはオブジェクトリテラルを識別することなので、この質問を言い換えています:

オブジェクトリテラルと他のJavascriptオブジェクト(DOMノード、Dateオブジェクトなど)の違いをどのように見分けることができますか?この関数を作成するにはどうすればよいですか?

function f(x) {
    if (typeof x === 'object literal')
        console.log('Object literal!');
    else
        console.log('Something else!');
}

以下の最初の呼び出しの結果としてObject literal!のみを出力するように:

f({name: 'Tom'});
f(function() {});
f(new String('howdy'));
f('hello');
f(document);

元の質問

オブジェクトリテラル、文字列、またはDOMノードを引数として受け入れるように設計されたJavascript関数を書いています。各引数を少し異なる方法で処理する必要がありますが、現時点では、DOMノードと単純な古いオブジェクトリテラルを区別する方法がわかりません。

これは、私の関数の大幅に簡略化されたバージョンと、処理する必要のある各種類の引数のテストです。

function f(x) {
    if (typeof x == 'string')
        console.log('Got a string!');
    else if (typeof x == 'object')
        console.log('Got an object literal!');
    else
        console.log('Got a DOM node!');
}

f('hello');
f({name: 'Tom'});
f(document);

このコードは、次の2つの呼び出しで同じメッセージをログに記録します。 else if句に何を含めるべきかわかりません。同じ効果を持つx instanceof Objectのような他のバリエーションを試しました。

これは私の側の悪いAPI /コード設計かもしれないことを理解しています。たとえそうだとしても、私はまだこれを行う方法を知りたいです。

27
Will McCutchen

オブジェクトリテラルと他のJavascriptオブジェクト(DOMノード、Dateオブジェクトなど)の違いをどのように見分けることができますか?

簡単な答えはあなたができないということです。

オブジェクトリテラルは次のようなものです。

_var objLiteral = {foo: 'foo', bar: 'bar'};
_

一方、オブジェクトコンストラクタを使用して作成された同じオブジェクトは次のようになります。

_var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
_

2つのオブジェクトがどのように作成されたかを区別する信頼できる方法はないと思います。

どうしてそれが重要ですか?

一般的な機能テスト戦略は、関数に渡されたオブジェクトのプロパティをテストして、呼び出されるメソッドをサポートしているかどうかを判断することです。そうすれば、オブジェクトがどのように作成されるかを気にする必要はありません。

「ダックタイピング」を採用することはできますが、その範囲は限られています。たとえば、オブジェクトにgetFullYear()メソッドがあるからといって、それがDateオブジェクトであることを保証することはできません。同様に、nodeTypeプロパティがあるからといって、それがDOMオブジェクトであるとは限りません。

たとえば、jQuery isPlainObject関数は、オブジェクトにnodeTypeプロパティがある場合はDOMノードであり、setIntervalプロパティがある場合はWindowオブジェクトであると見なします。この種のダックタイピングは非常に単純で、場合によっては失敗します。

また、jQueryは、特定の順序で返されるプロパティに依存していることに注意してください。これは、どの標準でもサポートされていない別の危険な仮定です(ただし、一部のサポーターは、想定される動作に合わせて標準を変更しようとしています)。

2014年4月22日編集:バージョン1.10では、jQueryには、継承されたプロパティが最初または最後に列挙されているかどうかを確認するための単一のプロパティのテストに基づくsupport.ownLastプロパティが含まれています(明らかにこれはIE9サポート用です)。これは、オブジェクトのプロパティが継承されているか所有されているかに関係なく、anyの順序で返される可能性があり、混乱する可能性があるという事実を無視し続けます。

おそらく、「プレーン」オブジェクトの最も簡単なテストは次のとおりです。

_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は今やほとんどユビキタスになっているので、オブジェクトの_[[Prototype]]_をチェックするための Object.getPrototypeOf があります。それがbuit–in Object.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の_[[Prototype]]_を持つオブジェクト、およびgetPrototypeOfを持たないその他のオブジェクトタイプ(nullなど)を許可する更新された関数、ありがとう Chris Nielsen )は以下のとおりです。

ポリフィルする方法がないことに注意してくださいgetPrototypeOfしたがって、古いブラウザのサポートが必要な場合は役に立たない可能性があります( によるとIE8以下など)。 [〜#〜] mdn [〜#〜] )。

_/*  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>');
});_
44
RobG

すべてのDOMノードはNodeインターフェースから継承するため、次のことを試すことができます。

if(typeof x === 'string') {
    //string
} else if(x instanceof Node) {
    //DOM Node
} else {
    //everything else
}

しかし、これが古いバージョンのInternetExplorerで機能するかどうかはわかりません

2
standardModel

@RobGの例と同様:

function isPlainObject(obj) {
    return  typeof obj === 'object' // separate from primitives
        && obj !== null         // is obvious
        && obj.constructor === Object // separate instances (Array, DOM, ...)
        && Object.prototype.toString.call(obj) === '[object Object]'; // separate build-in like Math
}

テスト:

function isPlainObject(obj) {
        return  typeof obj === 'object'
                && obj !== null
                && obj.constructor === Object
                && Object.prototype.toString.call(obj) === '[object Object]';
}

var data = {
  '{}': {},
  'DOM element': document.createElement('div'),
  'null'       : null,
  'Object.create(null)' : Object.create(null),
  'Instance of other object' : new (function 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 + ':<strong>' + isPlainObject(data[item]) + '</strong><br>');
});
2
Vlada

多分このようなもの?

var isPlainObject = function(value){
    if(value && value.toString && value.toString() === '[object Object]')
        return true;

    return false;
};

またはこの他のアプローチ:

var isObject = function(value){
    var json;

    try {
        json = JSON.stringify(value);
    } catch(e){

    }

    if(!json || json.charAt(0) !== '{' || json.charAt(json.length - 1) !== '}')
        return false;

    return true;
};
1

DOMノードのチェックをオブジェクトリテラルの上に移動します。 DOMノードに存在するいくつかのプロパティをチェックして、ノードを検出します。 nodeTypeを使用しています。オブジェクトを渡すことができるので、それはあまり絶対確実ではありません{nodeType: 0 }そしてそれはこれを壊すでしょう。

if (typeof x == 'string') { /* string */ }
else if ('nodeType' in x) { /* dom node */ }
else if (typeof x == 'object') { /* regular object */ }

上記のようなすべてのダックタイピングチェック、さらにはinstanceofチェックも失敗するはずです。指定されたオブジェクトが実際にDOMノードであるかどうかを正確に判断するには、渡されたオブジェクト自体以外のものを使用する必要があります。

1
Anurag