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

如何区分对象文字和其他Javascript对象?,javascript,types,comparison,object-literal,Javascript,Types,Comparison,Object Literal,更新:我重新表述这个问题,因为对我来说重要的一点是识别对象文字: 如何区分对象文字和任何其他Javascript对象(例如DOM节点、日期对象等)之间的区别?如何编写此函数: function f(x) { if (typeof x === 'object literal') console.log('Object literal!'); else console.log('Something else!'); } 这样它只打印对象文字作为以下第

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

如何区分对象文字和任何其他Javascript对象(例如DOM节点、日期对象等)之间的区别?如何编写此函数:

function f(x) {
    if (typeof x === 'object literal')
        console.log('Object literal!');
    else
        console.log('Something else!');
}
这样它只打印
对象文字作为以下第一次调用的结果:

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/代码设计。即使是这样,我仍然想知道如何做到这一点。

将DOM节点检查移到对象文字上方。检查DOM节点上存在的某些属性以检测节点。我正在使用
节点类型
。这并不是很简单,因为您可以传入一个对象
{nodeType:0}
,这会破坏这一点

if (typeof x == 'string') { /* string */ }
else if ('nodeType' in x) { /* dom node */ }
else if (typeof x == 'object') { /* regular object */ }

所有duck类型检查,比如上面的检查,甚至是
instanceof
检查,都注定会失败。要真正确定给定对象是否实际上是DOM节点,您需要使用传入对象本身以外的其他对象。

由于所有DOM节点都从节点接口继承,您可以尝试以下操作:

if(typeof x === 'string') {
    //string
} else if(x instanceof Node) {
    //DOM Node
} else {
    //everything else
}
但我不确定这是否适用于旧版本的Internet Explorer

如何区分对象文字和任何其他Javascript对象(例如DOM节点、日期对象等)之间的区别

简单的回答是你不能

对象文字类似于:

var objLiteral = {foo: 'foo', bar: 'bar'};
而使用对象构造函数创建的同一对象可能是:

var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
我认为没有任何可靠的方法来区分这两个对象是如何创建的

为什么它很重要

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

您可以使用“duck类型”,但仅限于有限的范围。例如,不能仅仅因为对象具有
getFullYear()
方法就保证它是日期对象。类似地,仅仅因为它有一个nodeType属性并不意味着它是一个DOM对象

例如,jQuery
isPlainObject
函数认为,如果一个对象具有nodeType属性,那么它就是DOM节点,如果它具有
setInterval
属性,那么它就是窗口对象。这种duck类型非常简单,在某些情况下会失败

您可能还注意到,jQuery依赖于以特定顺序返回的属性——这是任何标准都不支持的另一个危险假设(尽管一些支持者试图更改标准以适应他们的假设行为)

编辑日期:2014年4月22日:在版本1.10中,jQuery在测试单个属性(显然这是针对IE9支持)的基础上包含support.ownLast属性,以查看继承属性是先枚举还是后枚举。这仍然忽略了一个事实,即对象的属性可以按任何顺序返回,而不管它们是继承的还是自己的,并且可能是混乱的

对于“普通”对象,最简单的测试可能是:

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现在几乎无处不在,因此需要检查对象的
[[Prototype]]]
。如果是内置的Object.prototype,则该对象是普通对象。但是,一些开发人员希望创建没有继承属性的真正“空”对象。这可以通过以下方式实现:

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

还有一种合理广泛使用的方法:

Object.prototype.toString.call(valueToTest)
指定为基于内部
[[Class]]
属性返回字符串,对象的属性为[object object]。但是,ECMAScript 2015中的情况发生了变化,因此对其他类型的对象执行了测试,默认值为[object object],因此该对象可能不是“普通对象”,只是一个无法识别为其他对象的对象。因此,本规范指出:

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

下面是一个更新的函数,它允许ES5之前的主机、具有null的
[[Prototype]]
对象以及其他没有getPrototypeOf的对象类型(例如null,谢谢)

请注意,无法polyfill getPrototypeOf,因此如果需要支持较旧的浏览器(例如。
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;
};
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
}