更新:我正在重述此问题,因为对我来说重要的一点是识别对象文字:

如何分辨对象文字和任何其他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);

原始问题

我正在编写一个Javascript函数,该函数旨在接受对象文字,字符串或DOM节点作为其参数。它需要稍微不同地处理每个参数,但是目前我还不知道如何区分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);

此代码将在后两个调用中记录相同的消息。我不知道要在else if子句中包括什么。我尝试了其他类似x instanceof Object的变体,它们具有相同的效果。

我了解这对我而言可能是不良的API/代码设计。即使是,我仍然想知道如何执行此操作。

最佳答案



简短的答案是你不能。

对象文字是这样的:

var objLiteral = {foo: 'foo', bar: 'bar'};

而使用Object构造函数创建的同一对象可能是:
var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';

我认为没有任何可靠的方法可以区分这两个对象的创建方式。

它为什么如此重要?

一般的功能测试策略是测试传递给函数的对象的属性,以确定它们是否支持要调用的方法。这样,您就不必真正在意对象的创建方式。

您可以使用“鸭式打字”,但只能在有限的范围内进行。您不能仅仅因为对象具有getFullYear()方法即它是Date对象而保证。同样,仅因为它具有nodeType属性并不意味着它是DOM对象。

例如,jQuery isPlainObject函数认为,如果对象具有nodeType属性,则它是DOM节点;如果具有setInterval属性,则它是Window对象。这种鸭子输入非常简单,在某些情况下会失败。

您可能还注意到,jQuery依赖于按特定顺序返回的属性-另一个危险的假设,不受任何标准的支持(尽管某些支持者正在尝试更改标准以适应其假定的行为)。

编辑2014年4月22日:在1.10版中,jQuery包含了一个support.ownLast属性,该属性基于测试单个属性(显然是针对IE9支持)来查看继承的属性是第一个还是最后一个枚举。这继续忽略了这样一个事实,即对象的属性可以按顺序以的形式返回,而不管它们是继承的还是拥有的,并且可能混杂在一起。

对于“普通”对象,最简单的测试可能是:
function isPlainObj(o) {
  return typeof o == 'object' && o.constructor == Object;
}

对于使用对象文字或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]]。如果是Object.prototype中的buit,则该对象为纯对象。但是,一些开发人员希望创建没有继承属性的真正“空”对象。可以使用以下方法完成:
var emptyObj = Object.create(null);

在这种情况下,[[Prototype]]属性为null。因此,仅检查内部原型(prototype)是否为Object.prototype是不够的。

还有合理广泛使用的:
Object.prototype.toString.call(valueToTest)

根据内部[[Class]]属性被指定为返回字符串,该属性对于Objects为[object Object]。但是,这已在ECMAScript 2015中进行了更改,因此可以对其他类型的对象执行测试,默认设置为[object Object],因此该对象可能不是“普通对象”,只是一个未被识别为其他对象的对象。因此,规范指出:



http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.tostring

因此,下面提供了一个更新的功能,该功能允许使用ES5之前的主机,[[Prototype]]为null的对象以及其他没有getPrototypeOf的对象类型(例如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>');
});

10-05 20:50
查看更多