What is a Plain Old JavaScript Object (POJO)?
There's a lot of debate as to what a POJO is in JavaScript:
StackOverflow thinks it is any class that contains user data, but the
top npm module on Google defines a POJO as
any object whose prototype is Object.prototype
.
The intuition behind POJOs is that a POJO is an object that only contains data,
as opposed to methods or internal state. Most JavaScript codebases consider objects
created using curly braces {}
to be POJOs. However, more strict codebases
sometimes create POJOs
by calling Object.create(null)
to avoid inheriting from the built-in Object
class.
// If you create an object `obj` with `{}`, `obj` is an instance of
// the `Object` class, and so it has some built-in properties.
let obj = {};
obj.hasOwnProperty; // [Function]
obj.constructor === Object; // true
// On the other hand, `Object.create(null)` creates an object that
// doesn't inherit from **any** class.
obj = Object.create(null);
typeof obj; // 'object'
obj.hasOwnProperty; // undefined
obj.constructor; // undefined
obj.prop = 42;
obj.prop; // 42
POJOs vs Maps
JavaScript Maps are an alternative to POJOs for storing data because they do not have any inherited keys from the Object
class. However,
objects are generally easier to work with than maps, because not all JavaScript
functions, frameworks, and libraries support maps.
For example, the JSON.stringify()
function doesn't serialize maps by default.
const map = new Map([['answer', 42]]);
JSON.stringify(map); // '{}'
Checking if an Object is a POJO
Checking if an object is a POJO can be somewhat tricky and depends on whether
you consider objects created using Object.create(null)
to be POJOs. The safest
way is using the Object.getPrototypeOf()
function and
comparing the object's prototype.
function isPOJO(arg) {
if (arg == null || typeof arg !== 'object') {
return false;
}
const proto = Object.getPrototypeOf(arg);
if (proto == null) {
return true; // `Object.create(null)`
}
return proto === Object.prototype;
}
isPOJO({}); // true
isPOJO(Object.create(null)); // true
isPOJO(null); // false
isPOJO(new Number(42)); // false
For example, below is Mongoose's internal isPOJO()
function
exports.isPOJO = function isPOJO(arg) {
if (arg == null || typeof arg !== 'object') {
return false;
}
const proto = Object.getPrototypeOf(arg);
// Prototype may be null if you used `Object.create(null)`
// Checking `proto`'s constructor is safe because `getPrototypeOf()`
// explicitly crosses the boundary from object data to object metadata
return !proto || proto.constructor.name === 'Object';
};
Mongoose checks for the constructor.name
property instead of checking if proto.constructor === Object
to support objects generated using Node.js vm
module.
const obj = require('vm').runInNewContext('({})');
// `obj` inherits from a different JavaScript context's `Object` class.
obj.constructor === Object; // false
obj.constructor.name; // 'Object'