Clean object without a prototype
A clean object (also called an object dictionary) is an object with no prototype, that is, without Object.prototype and all of its built-in methods such as toString or hasOwnProperty. Such an object is a perfect fit for pure data stores, dictionaries and maps, where you want to be sure that no built-in property collides with your keys.
Theory
TL;DR
- A clean object is created with
Object.create(null). - For it
Object.getPrototypeOf(obj)isnull, there is no prototype chain at all. - It has no
toString, nohasOwnPropertyand no special__proto__behaviour. - So keys like
toStringor__proto__become ordinary keys and break nothing. - Check for a property with
Object.prototype.hasOwnProperty.call(obj, key)orObject.hasOwn(obj, key). - The modern alternative for dictionaries is
Map.
Quick example
The simplest and correct way:
const obj = Object.create(null);This creates an object without a prototype: obj.__proto__ is undefined and Object.getPrototypeOf(obj) is null.
Let us check:
const obj = Object.create(null);
console.log(Object.getPrototypeOf(obj)); // null
console.log('__proto__' in obj); // false
console.log(typeof obj.toString); // "undefined"Such an object has no inheritance chain: it does not even inherit from Object.prototype.
Why this is useful
Ordinary objects inherit a pile of built-in methods:
const normal = {};
console.log(Object.getPrototypeOf(normal) === Object.prototype); // true
// These methods are available
console.log(normal.hasOwnProperty); // [Function]
console.log(normal.toString); // [Function]That can be dangerous when your keys overlap with those names:
const map = {};
map.toString = 'value'; // may break code that later calls map.toString()A clean object removes the problem:
const map = Object.create(null);
map.toString = 'value'; // just a key
console.log(map.toString); // "value"Example use: dictionaries and maps
const dict = Object.create(null);
dict.apple = 1;
dict.banana = 2;
dict['__proto__'] = 3; // safe, it does not affect the prototype
console.log(dict.apple); // 1
console.log(dict['__proto__']); // 3On ordinary objects __proto__ has special behaviour (it is an accessor on Object.prototype), while here it is a plain property. That is why clean objects are often used for caches and for parsing data that came from outside: they are naturally protected against prototype pollution.
The drawback: no built-in methods
const obj = Object.create(null);
obj.name = 'Alice';
console.log(obj.hasOwnProperty('name')); // TypeError: obj.hasOwnProperty is not a functionIf you need to check whether a property exists, use the safe form:
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'name')); // true, the modern formThe same applies to toString(): such an object cannot simply be concatenated with a string, and in some environments console.log prints it as [Object: null prototype].
The alternative: use Map
The modern way to store dictionaries:
const map = new Map();
map.set('apple', 1);
map.set('__proto__', 2);
console.log(map.get('__proto__')); // 2Map solves the same problem, but it accepts keys of any type, objects included, preserves insertion order and exposes a size property.
Summary
| Approach | Prototype | Access to built-in methods | Suitable for dictionaries |
|---|---|---|---|
{} | Object.prototype | yes (toString, hasOwnProperty) | risky |
Object.create(null) | null | no | yes |
new Map() | not a plain object | the set, get, has methods | yes, the modern option |
To create a clean object without
Object.prototype, useconst obj = Object.create(null): it inherits nothing and is a perfect fit for safe dictionaries.
Common mistakes
- Calling
obj.hasOwnProperty(key)on a clean object. You get aTypeError, because the method simply is not there. UseObject.hasOwn(obj, key). - Confusing
Object.create(null)with{}. The latter inherits fromObject.prototypewith everything that implies. - Passing a clean object into code that expects a normal one. A library that internally calls
value.toString()will throw. - Assuming
JSON.stringifydoes not work. It works fine, the trouble is with implicit string coercion. - Using a clean object where
Mapfits better. If the keys are not strings, or you need size and ordering, reach forMap. - Forgetting that an object from
JSON.parseis an ordinary one. Parsing external JSON yields an object with a prototype, so validate the__proto__key before merging.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.