Question

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;
            }
        };
    }())
});
Was it helpful?

Solution

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)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top