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;
}
};
}())
});
All three of the above pass the tests when executed on FireFox v 19.0 an Opera v12.14
No, at least in Opera the tests in the fiddle fail for window.screen
, Math
, JSON
, DOMError
, LSParserFilter
, DOMImplementationLS
, window.opera
, SVGException
, and document.implementation
.
Is this a bug in Chrome/Chromium?
What? That different functions return different results? No.
And for each of the four objects, what should the correct result be (so I can determine which function is the most accurate)?
How do you defined "correct"? How do you defined "plain object"?
I believe that all 3 routines are using the following criteria:
Check to see if an object is a plain object (created using "{}" or "new 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 truthy nodeName
property, that has not a window
property pointing to itself, and that has no constructor
property or whose prototype
property of the constructor
has an own isPrototypeOf
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 is Object.prototype
(or rather, whose prototype has a isPrototypeOf
method that yields true
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
via getPrototypeOf(getPrototypeOf(…))
from a valueOf
method if it exists. If it found one, either the object or its prototype must be that Object.prototype
object and it must not be an Arguments
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 a getPrototypeOf
method.
"Alternative" implementation: This tests the prototype of the object to be either null
(but explicitly excluding Object.prototype
) or to be Object.prototype
and the [[Class]] value to be Object
.
should host objects be counted as plain objects?
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). The Math
and JSON
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 include Object.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 like JSON
, Math
, Arguments
and all those host objects with implementation-specific classes. Would you really expect those to be passed in the functions that test isPlainObject
, and would they cause havoc if they passed the other tests?
See also Say what? at niftysnippets.org (blog by T.J.Crowder)