Just show me the code:
function isPlainObjectMy(obj) { if (obj === null) return false; const prototype = Object.getPrototypeOf(obj); return prototype === null || prototype === Object.prototype; }
In JavaScript, a ton of stuff is an object!
const arr = []; typeof arr; // 'object' const date = new Date(); typeof date; // 'object' class MyClass {} const myClass = new MyClass(); typeof myClass; // 'object' const nullValue = null; typeof nullValue; // 'object' const regex = /a/; typeof regex; // 'object'
So when you are writing a utility that expects an object that is more like a dictionary/record/map-like/has-like, you want to check that the object is a "plain" object.
Ok, so it took me a bit to figure out (1) how to break my code and (2) why Lodash isPlainObject
code is written the way it is.
Let's see the example that breaks my code:
const iframe = document.createElement('iframe'); document.body.appendChild(iframe); const iframeObject = iframe.contentWindow.Object; const obj = new iframeObject(); isPlainObject(obj); // Output: false (expected: true)
What the heck? The Object
in the iframe is not the same as the Object
in the main window. So the prototype of obj
is not Object.prototype
.
function _isPlainObject(value) { if (!isObjectLike(value) || getTag(value) !== '[object Object]') { return false; } if (Object.getPrototypeOf(value) === null) { return true; } let proto = value; while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto); } return Object.getPrototypeOf(value) === proto; } // where function isObjectLike(value) { return typeof value === 'object' && value !== null; } // and function getTag(value) { if (value == null) { return value === undefined ? '[object Undefined]' : '[object Null]'; } return Object.prototype.toString.call(value); }
Look at that weird while loop. Lodash keeps checking up the prototype chain until it finds null
or Object.prototype
. This is a more robust check than the one I provided.
const iframe = document.createElement('iframe'); document.body.appendChild(iframe); const iframeObject = iframe.contentWindow.Object; const obj = new iframeObject(); _.isPlainObject(obj); // Output: true // let's walk up the prototype chain step by step Object.getPrototypeOf(obj); // {__defineGetter__: f, __defineSetter__: f, ...} Object.getPrototypeOf(Object.getPrototypeOf(obj)); // null (yay!)
JavaScript does equality checks on objects by reference. So when you create an object in the iframe, while its Object.prototype implementation might be exactly the same as the one in the main window, the reference (in memory) is different.
Remembering that checking equality is by reference and not value is important when working with objects in JavaScript. And something even Google's AI doesnt always get right:
WARNING: a == b
will return false