如何区分对象字面量和其他Javascript对象(例如DOM节点、Date对象等)?
简短的回答是你无法区分。
一个对象字面量是这样的:
var objLiteral = {foo: 'foo', bar: 'bar'};
而使用Object构造函数创建的相同对象可能是:
var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
我认为没有可靠的方法来区分这两个对象是如何创建的。
为什么这很重要?
一般的特性测试策略是测试传递给函数的对象的属性,以确定它们是否支持要调用的方法。这样,您就不需要真正关心对象是如何创建的。
你可以采用"鸭子类型",但只能在有限的范围内使用。你不能保证仅仅因为一个对象有一个getFullYear()方法就是一个日期对象。同样,仅仅因为它有一个nodeType属性并不意味着它是一个DOM对象。
例如,jQuery的isPlainObject函数认为如果一个对象有一个nodeType属性,它就是一个DOM节点,如果它有一个setInterval属性,它就是一个Window对象。这种鸭子类型非常简单化,会在某些情况下失败。
你还可以注意到,jQuery依赖于以特定顺序返回属性 - 这是另一个危险的假设,没有任何标准支持它(尽管一些支持者正在试图改变标准以适应他们假定的行为)。
编辑22-Apr-2014:在版本1.10中,jQuery包括一个基于测试单个属性(显然是为了支持IE9)的support.ownLast属性,以查看继承属性是首先枚举还是最后枚举。这仍然忽略了一个对象的属性可以以任何顺序返回,无论它们是继承还是自有,并且可能会混乱。
"纯"对象的最简单测试可能是:
function isPlainObj(o) {
return typeof o == 'object' && o.constructor == Object;
}
对于使用对象字面量或Object构造函数创建的对象,这将始终成立,但是对于其他方式创建的对象可能会给出虚假结果并且很可能在跨帧时失败。您也可以添加instanceof测试,但我看不到它有任何比构造函数测试更好的作用。
如果您正在传递ActiveX对象,最好将其包装在try..catch中,因为它们可能返回各种奇怪的结果,甚至会抛出错误。
当然还有一些陷阱:
isPlainObject( {constructor: 'foo'} );
var constructor = Object;
isPlainObject( this );
Messing with the constructor property will cause issues. There are other traps too, such as objects created by constructors other than Object.
由于ES5现在已经普及,可以使用
Object.getPrototypeOf来检查对象的
[[Prototype]]
。如果它是内置的
Object.prototype,那么该对象就是一个纯对象。然而,一些开发人员希望创建真正的“空”对象,这些对象没有继承属性。可以使用以下方式实现:
var emptyObj = Object.create(null);
在这种情况下,
[[Prototype]]
属性是
null。因此,仅仅检查内部原型是否为
Object.prototype是不够的。
还有一个相当广泛使用的:
Object.prototype.toString.call(valueToTest)
在ECMAScript 2015中,指定返回基于内部[[Class]]
属性的字符串,对于对象来说是[object Object]。然而,现在会针对其他类型的对象进行测试,默认为[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,因此如果需要支持旧版浏览器(例如IE 8及以下版本,根据
MDN),则可能无用。
function isPlainObject(obj) {
if (typeof obj == 'object' && obj !== null) {
if (typeof Object.getPrototypeOf == 'function') {
var proto = Object.getPrototypeOf(obj);
return proto === Object.prototype || proto === null;
}
return Object.prototype.toString.call(obj) == '[object Object]';
}
return false;
}
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>');
});