35

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

オブジェクトリテラルと他の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番目の2つの呼び出しについて同じメッセージをログに記録します。else if条項に何を含めるべきかわかりません。私はx instanceof Object同じ効果を持つそのような他のバリエーションを試しました。

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

4

6 に答える 6

58

オブジェクトリテラルと他の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>');
});

于 2011-05-04T03:17:00.813 に答える
7

@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>');
});

于 2016-07-24T19:23:38.177 に答える
2

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

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

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

于 2011-05-03T22:47:50.930 に答える
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ノードであるかどうかを正確に判断するには、渡されたオブジェクト自体以外のものを使用する必要があります。

于 2011-05-03T22:21:00.057 に答える
1

多分このようなもの?

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;
};
于 2014-02-24T14:13:33.820 に答える
1

パッケージを使用してもかまわない場合は、lodashを使用することをお勧めします。

https://lodash.com/docs/4.17.15#isPlainObject

于 2020-07-07T09:02:53.950 に答える