JavaScript Prototype Methods Without __proto__
In the realm of web development, JavaScript stands as a cornerstone technology, enabling the creation of dynamic, interactive web applications. Among its many
Every JavaScript object is linked to another object called its prototype, and property lookups walk up that chain. The old way to read or change that link was the special __proto__ property, but it has real problems: it's an inherited accessor (not a normal data slot), it behaves inconsistently as an object key, and it can be exploited to corrupt objects. Modern JavaScript replaces it with explicit, predictable methods on the Object constructor.
This chapter shows the standard prototype methods — Object.create(), Object.getPrototypeOf(), and Object.setPrototypeOf() — and the inspection helpers Object.keys(), Object.values(), Object.entries(), and Object.hasOwn(). It then covers the most useful case for an object with no prototype: a safe "dictionary" for arbitrary keys.
For the bigger picture of how the chain works, see Prototypal Inheritance and Beyond.
Reading and Setting a Prototype
Use Object.getPrototypeOf() to read an object's prototype and Object.setPrototypeOf() to change it. These are the standardized replacements for reading and writing __proto__.
Object.create(proto) builds a brand-new object whose prototype is exactly proto. It is the cleanest way to create an object with a chosen prototype without using __proto__ at all.
Why Avoid __proto__
__proto__ is a getter/setter defined on Object.prototype, not a property that lives on your object. That difference causes two practical issues:
- It can fail or behave oddly as a data key. Because
obj.__proto__ = valuetriggers the setter, you cannot reliably store a key literally named"__proto__"on an ordinary object — assigning a non-object is silently ignored, and assigning an object changes the prototype instead of adding a key. - It is a known attack surface ("prototype pollution"). Code that copies untrusted keys onto an object can be tricked into writing to
__proto__, pollutingObject.prototypefor the whole program.
The standardized methods are explicit about intent: getPrototypeOf/setPrototypeOf change the prototype, while normal property writes never touch it.
Objects Without a Prototype
Object.create(null) creates an object whose prototype is null. It inherits nothing — not even toString, hasOwnProperty, or the __proto__ accessor.
The Dictionary Problem It Solves
A common pattern is using a plain object as a map from string keys to values. The trouble is that a plain {} already inherits keys from Object.prototype, so user-supplied keys like "constructor", "toString", or "__proto__" collide with the inherited names and break lookups.
A null-prototype object has no inherited keys, so every key behaves exactly as written — including "__proto__":
This is why null-prototype objects make safe dictionaries for untrusted keys. (The built-in Map is another good choice and allows non-string keys.)
Adding Methods to a Null-Prototype Object
Because there is no prototype to inherit from, you assign methods directly as own properties.
Iterating Keys, Values, and Entries
Object.keys(), Object.values(), and Object.entries() return arrays of an object's own enumerable properties. Crucially, they ignore the prototype chain, so they work identically on normal and null-prototype objects. See Object.keys, values, entries for more.
One caveat for null-prototype objects: they have no inherited toString, so passing one straight to template literals or String() throws. Iterate with the Object.* helpers (above) instead of relying on automatic string conversion.
Checking Own Properties with Object.hasOwn()
To test whether a key is the object's own property (not inherited), prefer Object.hasOwn(). It is the modern replacement for obj.hasOwnProperty() and works even on null-prototype objects, which don't have a hasOwnProperty method at all.
Composition with Object.assign()
When you want one object to gain the behavior of several others, composition is often clearer than a single inheritance chain: an object can only have one prototype, but Object.assign(target, ...sources) can fold in methods from many sources by copying their own enumerable properties onto the target.
Object.assign() also copies onto a null-prototype object, giving you a composed dictionary with no inherited surface:
Shallow-copy caveat: Object.assign() copies property values, not deep clones. Object and array values are copied by reference, so the source and target share the same nested objects.
For a deep, independent copy use structuredClone(source) instead.
Best Practices
- Use the standardized methods, not
__proto__. Reach forObject.create(),Object.getPrototypeOf(), andObject.setPrototypeOf(). Treat__proto__as a legacy accessor to avoid in your own code. - Avoid changing prototypes after creation. Setting a chosen prototype at creation time with
Object.create(proto)is cleaner and easier to reason about than mutating it later withObject.setPrototypeOf(). - Use
Object.create(null)(orMap) for dictionaries of untrusted keys. It removes inherited keys and prevents prototype-pollution surprises. - Prefer
Object.hasOwn()overobj.hasOwnProperty(). It is shorter, safer to call, and works on null-prototype objects. - Compose with
Object.assign()when you need behavior from multiple sources — just remember it is a shallow copy.
For related material, see Object methods and "this" and Property flags and descriptors.
Conclusion
The modern Object.* prototype methods give you explicit, predictable control over the prototype chain, replacing the quirky __proto__ accessor. Object.create(null) produces a clean, prototype-free object that is ideal for dictionaries, while Object.keys/values/entries, Object.hasOwn(), and Object.assign() let you inspect and compose objects safely regardless of their prototype.