如何区分对象文字其他Javascript对象?

Wil*_*hen 25 javascript comparison types object-literal

更新:我正在改写这个问题,因为对我来说重点是识别对象文字:

如何区分对象文字和任何其他Javascript对象(例如DOM节点,Date对象等)?我该怎么写这个函数:

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

因此它只会Object literal!在下面的第一个调用中打印出来:

f({name: 'Tom'});
f(function() {});
f(new String('howdy'));
f('hello');
f(document);
Run Code Online (Sandbox Code Playgroud)

原始问题

我正在编写一个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);
Run Code Online (Sandbox Code Playgroud)

此代码将为后两个调用记录相同的消息.我无法弄清楚该else if条款中包含哪些内容.我尝试过其他类似的变化x instanceof Object也有同样的效果.

据我所知,这可能是我的API /代码设计不好.即使是这样,我仍然想知道如何做到这一点.

Rob*_*obG 44

如何区分对象文字和任何其他Javascript对象(例如DOM节点,Date对象等)?

简短的回答是你不能.

一个文本对象是一样的东西:

var objLiteral = {foo: 'foo', bar: 'bar'};
Run Code Online (Sandbox Code Playgroud)

而使用Object构造函数创建的同一对象可能是:

var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
Run Code Online (Sandbox Code Playgroud)

我认为没有任何可靠的方法来区分两个对象是如何创建的.

它为什么如此重要?

一般功能测试策略是测试传递给函数的对象的属性,以确定它们是否支持要调用的方法.这样你就不会真正关心如何创建一个对象.

你可以使用"鸭子打字",但只能在有限的范围内使用.您不能保证仅仅因为一个对象具有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;
}
Run Code Online (Sandbox Code Playgroud)

对于使用对象文字或对象构造函数创建的对象总是如此,但可能会为其他方式创建的对象提供虚假结果,并且可能(可能会)跨帧失败.你也可以添加一个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
Run Code Online (Sandbox Code Playgroud)

搞乱构造函数属性会导致问题.还有其他陷阱,例如由Object之外的构造函数创建的对象.

由于ES5现在几乎无处不在,因此有Object.getPrototypeOf来检查[[Prototype]]对象.如果它是buit -in Object.prototype,那么该对象是一个普通对象.但是,一些开发人员希望创建没有继承属性的真正"空"对象.这可以使用:

var emptyObj = Object.create(null);
Run Code Online (Sandbox Code Playgroud)

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

还有相当广泛使用的:

Object.prototype.toString.call(valueToTest)
Run Code Online (Sandbox Code Playgroud)

指定为基于内部[[Class]]属性返回字符串,对象为[object Object].但是,在ECMAScript 2015中已经发生了变化,因此对其他类型的对象执行测试,默认为[object Object],因此该对象可能不是"普通对象",只是一个未被识别为其他对象的对象.因此,该规范指出:

"[使用toString进行测试]不能为其他类型的内置或程序定义对象提供可靠的类型测试机制."

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

因此,更新的函数允许预先ES5主机,具有[[Prototype]]null的对象和其他没有getPrototypeOf的对象类型(例如null,感谢Chris Nielsen)如下所示.

请注意,也没有办法来填充工具getPrototypeOf,因此,如果需要(例如,IE 8和下,根据用于旧的浏览器支持可能不适有用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>');
});
Run Code Online (Sandbox Code Playgroud)


Vla*_*ada 6

类似于@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
}
Run Code Online (Sandbox Code Playgroud)

测试:

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
}
Run Code Online (Sandbox Code Playgroud)