Methods for prototypes
In this chapter we cover additional methods to work with a prototype.
There are also other ways to get/set a prototype, besides those that we already know:
-
Object.create(proto[, descriptors]) -- creates an empty object with given
proto
asPrototype
and optional property descriptors. -
Object.getPrototypeOf(obj) -- returns the
Prototype
ofobj
. -
Object.setPrototypeOf(obj, proto) -- sets the
Prototype
ofobj
toproto
.
For instance:
let animal = {
eats: true
};
// create a new object with animal as a prototype
*!*
let rabbit = Object.create(animal);
*/!*
alert(rabbit.eats); // true
*!*
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
*/!*
*!*
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
*/!*
Object.create
has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
let animal = {
eats: true
};
let rabbit = Object.create(animal, {
jumps: {
value: true
}
});
alert(rabbit.jumps); // true
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
:
// fully identical shallow clone of obj
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
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 Prototype
.
Brief history
If we count all the ways to manage Prototype
, there's a lot! Many ways to do the same!
Why so?
That's for historical reasons.
-
The
"prototype"
property of a constructor function works since very ancient times. -
Later in the year 2012:
Object.create
appeared in the standard. It allowed to create objects with the given prototype, but did not allow to get/set it. So browsers implemented non-standard__proto__
accessor that allowed to get/set a prototype at any time. -
Later in the year 2015:
Object.setPrototypeOf
andObject.getPrototypeOf
were added to the standard. The__proto__
was de-facto implemented everywhere, so it made its way to the Annex B of the standard, that is optional for non-browser environments.
As of now we have all these ways at our disposal.
Technically, we can get/set Prototype
at any time. But usually we only set it once at the object creation time, and then do not modify: rabbit
inherits from animal
, and that is not going to change. And JavaScript engines are highly optimized to that. Changing a prototype "on-the-fly" with Object.setPrototypeOf
or obj.__proto__=
is a very slow operation. But it is possible.
"Very plain" objects
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:
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // [object Object], not "some value"!
Here if the user types in __proto__
, the assignment is ignored!
That shouldn't surprise us. The __proto__
property is special: it must be either an object or null
, a string can not become a prototype.
But we did not 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 the prototype may indeed be changed, so the execution may go wrong in totally unexpected ways.
What's worst -- 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.
Such thing happens only with __proto__
. All other properties are "assignable" normally.
How to evade the problem?
First, we can just switch to using Map
, then everything's fine.
But Object
also can serve us well here, because language creators gave a thought to that problem long ago.
The __proto__
is not a property of an object, but an accessor property of Object.prototype
:
So, if obj.__proto__
is read or assigned, the corresponding getter/setter is called from its prototype, and it gets/sets Prototype
.
As it was said in the beginning: __proto__
is a way to access Prototype
, it is not Prototype
itself.
Now, if we want to use an object as an associative array, we can do it with a little trick:
*!*
let obj = Object.create(null);
*/!*
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // "some value"
Object.create(null)
creates an empty object without a prototype (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 object "very plain" or "pure dictionary objects", because they are even simpler than regular plain object {...}
.
A downside is that such objects lack any built-in object methods, e.g. toString
:
*!*
let obj = Object.create(null);
*/!*
alert(obj); // Error (no toString)
...But that's usually fine for associative arrays.
Please 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:
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";
alert(Object.keys(chineseDictionary)); // hello,bye
Getting all properties
There are many ways to get keys/values from an object.
We already know these ones:
- Object.keys(obj) / Object.values(obj) / Object.entries(obj) -- returns an array of enumerable own string property names/values/key-value pairs. These methods only list enumerable properties, and those that have strings as keys.
If we want symbolic properties:
If we want non-enumerable properties:
If we want all properties:
These methods are a bit different about which properties they return, but all of them operate on the object itself. Properties from the prototype are not listed.
The for..in
loop is different: it loops over inherited properties too.
For instance:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
*!*
// only own keys
alert(Object.keys(rabbit)); // jumps
*/!*
*!*
// inherited keys too
for(let prop in rabbit) alert(prop); // jumps, then eats
*/!*
If we want to distinguish inherited properties, there's a built-in method obj.hasOwnProperty(key): it returns true
if obj
has its own (not inherited) property named key
.
So we can filter out inherited properties (or do something else with them):
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false
}
Here we have the following inheritance chain: rabbit
, then animal
, then Object.prototype
(because animal
is a literal object {...}
, so it's by default), and then null
above it:
Note, there's one funny thing. Where is the method rabbit.hasOwnProperty
coming from? Looking at the chain we can see that the method is provided by Object.prototype.hasOwnProperty
. In other words, it's inherited.
...But why hasOwnProperty
does not appear in for..in
loop, if it lists all inherited properties? The answer is simple: it's not enumerable. Just like all other properties of Object.prototype
. That's why they are not listed.
Summary
Here's a brief list of methods we discussed in this chapter -- as a recap:
-
Object.create(proto[, descriptors]) -- creates an empty object with given
proto
asPrototype
(can benull
) and optional property descriptors. -
Object.getPrototypeOf(obj) -- returns the
Prototype
ofobj
(same as__proto__
getter). -
Object.setPrototypeOf(obj, proto) -- sets the
Prototype
ofobj
toproto
(same as__proto__
setter). - Object.keys(obj) / Object.values(obj) / Object.entries(obj) -- returns an array of enumerable own string property names/values/key-value pairs.
- Object.getOwnPropertySymbols(obj) -- returns an array of all own symbolic property names.
- Object.getOwnPropertyNames(obj) -- returns an array of all own string property names.
- Reflect.ownKeys(obj) -- returns an array of all own property names.
-
obj.hasOwnProperty(key): it returns
true
ifobj
has its own (not inherited) property namedkey
.
We also made it clear that __proto__
is a getter/setter for Prototype
and resides in Object.prototype
, just as other methods.
We can create an object without a prototype by Object.create(null)
. Such objects are used as "pure dictionaries", they have no issues with "__proto__"
as the key.
All methods that return object properties (like Object.keys
and others) -- return "own" properties. If we want inherited ones, then we can use for..in
.