更新:我正在改写这个问题,因为对我来说重点是识别对象字面值:
如何区分对象文字和任何其他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 /代码设计不好。即使是这样,我仍然想知道如何做到这一点。
答案 0 :(得分:43)
如何区分对象文字和任何其他Javascript对象(例如DOM节点,Date对象等)?
简短的回答是你不能。
对象文字类似于:
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;
}
对于使用对象文字或对象构造函数创建的对象总是如此,但可能会为以其他方式创建的对象提供虚假结果,并且可能(可能会)跨帧失败。你也可以添加一个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-in Object.prototype ,那么该对象是一个普通对象。但是,一些开发人员希望创建没有继承属性的真正“空”对象。这可以使用:
var emptyObj = Object.create(null);
在这种情况下,[[Prototype]]
属性为 null 。因此,仅仅检查内部原型是否为 Object.prototype 是不够的。
还有相当广泛使用的:
Object.prototype.toString.call(valueToTest)
指定为基于内部[[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 < / em>,谢谢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>');
});
答案 1 :(得分:2)
由于所有DOM节点都继承自Node接口,因此您可以尝试以下方法:
if(typeof x === 'string') {
//string
} else if(x instanceof Node) {
//DOM Node
} else {
//everything else
}
但我不确定这是否适用于旧版本的Internet Explorer
答案 2 :(得分:2)
与@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
}
<强> TEST:强>
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>');
});
答案 3 :(得分: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节点,您需要使用传入对象本身以外的其他东西。
答案 4 :(得分: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;
};
答案 5 :(得分:1)
如果您不介意使用软件包,我建议为此使用lodash: