Prototype methods, objects without __proto__
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
Setting or reading the prototype with obj.proto is considered outdated and somewhat deprecated (moved to the so-called “Annex B” of the JavaScript standard, meant for browsers only).
The modern methods to get/set a prototype are:
- Object.getPrototypeOf(obj) – returns the of obj.
- Object.setPrototypeOf(obj, proto) – sets the of obj to proto.
The only usage of proto, that’s not frowned upon, is as a property when creating a new object: { proto: … }.
Although, there’s a special method for this too:
- Object.create(proto[, descriptors]) – creates an empty object with given proto as and optional property descriptors.
For instance:
The Object.create method is a bit more powerful, as it has an optional second argument: property descriptors.
We can provide additional properties to the new object there, like this:
The descriptors are in the same format as described in the chapter <info:property-descriptors>.
We can use Object.create to perform an object cloning more powerful than copying properties in for..in:
This call makes a truly exact copy of obj, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right.
Brief history
There’re so many ways to manage “. How did that happen? Why? That’s for historical reasons. The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time. - The prototype property of a constructor function has worked since very ancient times. It’s the oldest way to create objects with a given prototype. - Later, in the year 2012, Object.create appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. Some browsers implemented the non-standard proto accessor that allowed the user to get/set a prototype at any time, to give more flexibility to developers. - Later, in the year 2015, Object.setPrototypeOf and Object.getPrototypeOf were added to the standard, to perform the same functionality as proto. As proto was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments. - Later, in the year 2022, it was officially allowed to use proto in object literals {…} (moved out of Annex B), but not as a getter/setter obj.proto (still in Annex B). Why was proto replaced by the functions getPrototypeOf/setPrototypeOf? Why was proto partially rehabilitated and its usage allowed in {…}, but not as a getter/setter? That’s an interesting question, requiring us to understand why proto is bad. And soon we’ll get the answer.
“Very plain” objects [#very-plain]
As we know, objects can be used as associative arrays to store key/value pairs.
…But if we try to store user-provided keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except “proto”.
Check out the example:
Here, if the user types in proto, the assignment in line 4 is ignored!
That could surely be surprising for a non-developer, but pretty understandable for us. The proto property is special: it must be either an object or null. A string can not become a prototype. That’s why assigning a string to proto is ignored.
But we didn’t intend to implement such behavior, right? We want to store key/value pairs, and the key named “proto” was not properly saved. So that’s a bug!
Here the consequences are not terrible. But in other cases we may be storing objects instead of strings in obj, and then the prototype will indeed be changed. As a result, the execution will go wrong in totally unexpected ways.
What’s worse – usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side.
Unexpected things also may happen when assigning to obj.toString, as it’s a built-in object method.
How can we avoid this problem?
First, we can just switch to using Map for storage instead of plain objects, then everything’s fine:
…But Object syntax is often more appealing, as it’s more concise.
Fortunately, we can use objects, because language creators gave thought to that problem long ago.
As we know, proto is not a property of an object, but an accessor property of Object.prototype:
So, if obj.proto is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `.
As it was said in the beginning of this tutorial section: __proto__ is a way to access, it is not itself.
Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick:
Object.create(null) creates an empty object without a prototype ( is null`):
So, there is no inherited getter/setter for proto. Now it is processed as a regular data property, so the example above works right.
We can call such objects “very plain” or “pure dictionary” objects, because they are even simpler than the regular plain object {…}.
A downside is that such objects lack any built-in object methods, e.g. toString:
…But that’s usually fine for associative arrays.
Note that most object-related methods are Object.something(…), like Object.keys(obj) – they are not in the prototype, so they will keep working on such objects:
Summary
- To create an object with the given prototype, use:
- literal syntax: { proto: … }, allows to specify multiple properties
- or Object.create(proto[, descriptors]), allows to specify property descriptors. The Object.create provides an easy way to shallow-copy an object with all descriptors:
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
- Modern methods to get/set the prototype are:
- Object.getPrototypeOf(obj) – returns the
of obj (same as __proto__getter). - Object.setPrototypeOf(obj, proto) – sets the
of obj to proto (same as __proto__setter). - Getting/setting the prototype using the built-in proto getter/setter isn’t recommended, it’s now in the Annex B of the specification.
- We also covered prototype-less objects, created with Object.create(null) or {proto: null}. These objects are used as dictionaries, to store any (possibly user-generated) keys. Normally, objects inherit built-in methods and proto getter/setter from Object.prototype, making corresponding keys “occupied” and potentially causing side effects. With null prototype, objects are truly empty.
let animal = {
eats: true
};
// create a new object with animal as a prototype
*!*
let rabbit = Object.create(animal); // same as {__proto__: animal}
*/!*
alert(rabbit.eats); // true
*!*
alert(Object.getPrototypeOf(rabbit) === animal); // true
*/!*
*!*
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
*/!*
Follow the lesson from Microsoft Web-Dev-For-Beginners course