如何区分对象文字和其他 Javascript 对象?
Posted
技术标签:
【中文标题】如何区分对象文字和其他 Javascript 对象?【英文标题】:How can I differentiate between an object literal other Javascript objects? 【发布时间】:2011-08-18 02:15:01 【问题描述】:更新:我重新表述这个问题,因为对我来说重要的是识别对象文字:
如何区分对象字面量和任何其他 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/代码设计。即使是这样,我仍然想知道如何做到这一点。
【问题讨论】:
***.com/questions/384286/… @Ben,现在我已经更新了这个问题,我不认为它是那个问题的副本。但如果它与另一个问题重复,我会很乐意关闭它! 这几乎是重复的,但是在给定用户定义的对象时,有没有一种不会导致误报的解决方案? ***.com/questions/4320767/… 您之前评论中的link 包含的答案不会对您的所有示例给出任何误报。如果该答案仍然对您不起作用,您能否用反例更新您的问题? 【参考方案1】:如何区分对象字面量和任何其他 Javascript 对象(例如 DOM 节点、日期对象等)?
简短的回答是你不能。
对象字面量类似于:
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,那么该对象就是一个普通对象。但是,一些开发人员希望创建没有继承属性的真正“空”对象。这可以使用:
var emptyObj = Object.create(null);
在这种情况下,[[Prototype]]
属性为 null。所以仅仅检查内部原型是否是 Object.prototype 是不够的。
还有合理广泛使用的:
Object.prototype.toString.call(valueToTest)
被指定为返回一个基于内部[[Class]]
属性的字符串,对于Objects 是[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>');
);
【讨论】:
感谢您的详尽回答(这主要证实了我的假设)。我认为您的isPlainObj
函数对于我的目的来说足够准确。非常感谢!
请注意typeof null === "object"
和Object.getPrototypeOf(null)
会抛出错误。如果您希望 null
输入返回 false 而不是抛出错误,您的 isPlainObject
函数应该包括对 null
的检查。
@ChrisNielsen — 感谢您让我回到这个话题。鉴于从 ES5 开始,可以使用 Object.create(null)
创建具有 null 作为其[[Prototype]]
的对象,以上内容现在已经非常过时了,我一有时间就会更新。
“鸭子打字”,太完美了。有用的答案。【参考方案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
测试:
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】:由于所有 DOM 节点都继承自 Node 接口,您可以尝试以下操作:
if(typeof x === 'string')
//string
else if(x instanceof Node)
//DOM Node
else
//everything else
但我不确定这是否适用于旧版本的 Internet Explorer
【讨论】:
你不知道。虽然使用模仿 W3C DOM 规范建议的方案的原型继承来实现 DOM 对象可能是有意义的,但没有理由期望 所有 浏览器都这样做。事实上,浏览器不必为宿主对象实现任何类型的继承。 好吧,我已经在 Chrome(因此它可能也可以在 Safari 中运行)、Firefox 和 IE9 中对其进行了测试,因此似乎至少所有现代浏览器都以这种方式运行。 @standardModel @RobG 我刚刚遇到an in-depth article by kangax,它警告instanceof Node
。不过,我不知道有任何可靠的替代方案。
@tomekwi — 那篇文章来自 2010 年,所表达的原则以前是众所周知的,但在此之前没有被充分记录(参见 Richard Cornford 的回复 here)。它们今天仍然有效。重点是,虽然 DOM 规范推断继承,但它们没有指定原型继承(尽管在曾经更加语言中立的新规范中强烈倾向于 ECMAScript)。并且 instanceof 可能会跨帧失败,对于原生对象也是如此。
@standardModel — 这是 Richard Cornford 链接的原件。它在 comp.lang.javascript 上,已被 Google Groups 纳入:Client-side DOM: are 'window' and 'document' real objects?。注意 Richard 关于扩展宿主对象原型的警告比 kangax 的文章早了大约 7 年。 Kangax 参与了prototype.js,他学到了很多东西。 ;-)【参考方案4】:
将检查 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 节点,您需要使用传入对象本身以外的其他内容。
【讨论】:
很抱歉,在您回答问题后我更新了我的问题,因为我意识到我需要强调这样一个事实,即尽可能准确地识别对象文字是我的实际目标。【参考方案5】:也许是这样的?
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;
;
【讨论】:
isPlainObject(toString: () => 123) //false【参考方案6】:如果您不介意使用软件包,我建议您为此使用 lodash:
https://lodash.com/docs/4.17.15#isPlainObject
【讨论】:
以上是关于如何区分对象文字和其他 Javascript 对象?的主要内容,如果未能解决你的问题,请参考以下文章