I've been doing some testing of isPlainObject functions from different libraries on different browsers.
There are 4 different (code wise) isPlainObject functions being tested on a wide range of objects:
- jquery
- lodash
- utility (a library that I'm working on)
- alternative, suggested in comments below
All four of the above show differences on Chrome v23.0.1271.95 through to Chrome v25.0.1364.160, FireFox v 19.0 an Opera v12.14, but utility at least gives the same response of false for these object on all the browsers
The tests on jsfiddle when run on Chrome
Failed to agree: JSON - jquery: true - utility: false - lodash: true - alt: false
Failed to agree: Math - jquery: true - utility: false - lodash: true - alt: false
Failed to agree: top - jquery: false - utility: false - lodash: true - alt: true
Failed to agree: parent - jquery: false - utility: false - lodash: true - alt: true
- true being that the routine thinks the object is plain, and false is not plain
EDIT: I believe that all routines are using the following similar criteria:
jquery states
Check to see if an object is a plain object (created using "{}" or "new Object").
lodash states
Checks if a given value is an object created by the Object constructor.
I understand that host objects are not the same as objects constructed by using "{}" or "new Object", so I guess my question is: should host objects be counted as plain objects?
Presently, utility is consistant and says they are not, the other routines give different results for host objects on different browsers.
EDIT: Accuracy of the result is the most important factor for me, performance is of secondary consideration.
Performance results of the 3 libraries and the suggested alternative are available on jsperf
EDIT: this is the utility library function, so you don't need to search through the code.
defineProperty(utility, "isPlainObject", {
value: (function () {
var o = {};
return function (obj) {
try {
return utility.isObject(obj) && getPrototypeOf(obj).isPrototypeOf(o);
} catch (e) {
return false;
}
};
}())
});
No, at least in Opera the tests in the fiddle fail for
window.screen
,Math
,JSON
,DOMError
,LSParserFilter
,DOMImplementationLS
,window.opera
,SVGException
, anddocument.implementation
.What? That different functions return different results? No.
How do you defined "correct"? How do you defined "plain object"?
That's hardly a useful criterion, since those objects where you experience discrepancies are not "created" - they are host objects (or even native objects) that just happen to exist.
Yet, we could compare which criteria those functions use:
jQuery is very odd; you can read it's source code at github. In short: An object or function, whose [[Class]] is not one of
Boolean Number String Function Array Date RegExp Error
, that has not a truthynodeName
property, that has not awindow
property pointing to itself, and that has noconstructor
property or whoseprototype
property of theconstructor
has an ownisPrototypeOf
property.They seem to do this for cross-browser support, but as you can see it fails in some cases where you would have it expected not to be a plain object.
Utility is a bit obfuscated, but the check itself is simple: An object whose [[Class]] is
Object
and whose prototype isObject.prototype
(or rather, whose prototype has aisPrototypeOf
method that yieldstrue
for{}
).Lodash has a few oddities like jQuery, but not that hard - you can read the source at github. It first checks for object type and not null, then gets
Object.prototype
viagetPrototypeOf(getPrototypeOf(…))
from avalueOf
method if it exists. If it found one, either the object or its prototype must be thatObject.prototype
object and it must not be anArguments
object. If it did not found one, it falls back to the shim which I'm not going to explain here.All these things are done to support detecting plain objects from different environments (e.g. iframes) with a different
Object.prototype
object and in browsers that do not provide agetPrototypeOf
method."Alternative" implementation: This tests the prototype of the object to be either
null
(but explicitly excludingObject.prototype
) or to beObject.prototype
and the [[Class]] value to beObject
.Maybe. It always depends on your use case…
act like it was created by
new Object
Then just
getPrototypeOf(obj) == Object.prototype
should be fine (if you don't need to support cross-frame objects). TheMath
andJSON
objects would fulfill this requirement.Have no interfering enumerable properties on the prototypes
Then you might also allow
getPrototypeOf(obj) == null
or even manually check for enumerable properties like lodash does. This would now includeObject.prototype
as a "plain object" for example.creatable by
new Object
Then also add the check for [[Class]] to be
Object
to exclude native objects likeJSON
,Math
,Arguments
and all those host objects with implementation-specific classes. Would you really expect those to be passed in the functions that testisPlainObject
, and would they cause havoc if they passed the other tests?See also Say what? at niftysnippets.org (blog by T.J.Crowder)