Skip to content

Commit c20debc

Browse files
committed
closes #3014
1 parent 30a5d5e commit c20debc

File tree

1 file changed

+59
-42
lines changed

1 file changed

+59
-42
lines changed

1-js/08-prototypes/04-prototype-methods/article.md

+59-42
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33

44
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
55

6-
The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).
6+
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).
77

8-
The modern methods are:
8+
The modern methods to get/set a prototype are:
99

10-
- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
1110
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`.
1211
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`.
1312

14-
These should be used instead of `__proto__`.
13+
The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`.
14+
15+
Although, there's a special method for this too:
16+
17+
- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
1518

1619
For instance:
1720

@@ -22,7 +25,7 @@ let animal = {
2225

2326
// create a new object with animal as a prototype
2427
*!*
25-
let rabbit = Object.create(animal);
28+
let rabbit = Object.create(animal); // same as {__proto__: animal}
2629
*/!*
2730

2831
alert(rabbit.eats); // true
@@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
3639
*/!*
3740
```
3841

39-
`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
42+
The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors.
43+
44+
We can provide additional properties to the new object there, like this:
4045

4146
```js run
4247
let animal = {
@@ -57,26 +62,34 @@ The descriptors are in the same format as described in the chapter <info:propert
5762
We can use `Object.create` to perform an object cloning more powerful than copying properties in `for..in`:
5863

5964
```js
60-
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
65+
let clone = Object.create(
66+
Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)
67+
);
6168
```
6269

6370
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]]`.
6471

65-
## Brief history
6672

67-
If we count all the ways to manage `[[Prototype]]`, there are a lot! Many ways to do the same thing!
73+
## Brief history
6874

69-
Why?
75+
There're so many ways to manage `[[Prototype]]`. How did that happen? Why?
7076

7177
That's for historical reasons.
7278

73-
- The `"prototype"` property of a constructor function has worked since very ancient times.
74-
- 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. So browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time.
79+
The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time.
80+
81+
- 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.
82+
- 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.
7583
- 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.
84+
- 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).
85+
86+
Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`?
7687

77-
As of now we have all these ways at our disposal.
88+
Why was `__proto__` partially rehabilitated and its usage allowed in `{...}`, but not as a getter/setter?
7889

79-
Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer.
90+
That's an interesting question, requiring us to understand why `__proto__` is bad.
91+
92+
And soon we'll get the answer.
8093

8194
```warn header="Don't change `[[Prototype]]` on existing objects if speed matters"
8295
Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time and don't modify it anymore: `rabbit` inherits from `animal`, and that is not going to change.
@@ -101,25 +114,36 @@ obj[key] = "some value";
101114
alert(obj[key]); // [object Object], not "some value"!
102115
```
103116

104-
Here, if the user types in `__proto__`, the assignment is ignored!
117+
Here, if the user types in `__proto__`, the assignment in line 4 is ignored!
105118

106-
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.
119+
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 an assignment a string to `__proto__` is ignored.
107120

108121
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!
109122

110-
Here the consequences are not terrible. But in other cases we may be assigning object values, and then the prototype may indeed be changed. As a result, the execution will go wrong in totally unexpected ways.
123+
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.
111124

112125
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.
113126

114-
Unexpected things also may happen when assigning to `toString`, which is a function by default, and to other built-in methods.
127+
Unexpected things also may happen when assigning to `obj.toString`, as it's a built-in object method.
115128

116129
How can we avoid this problem?
117130

118-
First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine.
131+
First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine:
119132

120-
But `Object` can also serve us well here, because language creators gave thought to that problem long ago.
133+
```js run
134+
let map = new Map();
121135

122-
`__proto__` is not a property of an object, but an accessor property of `Object.prototype`:
136+
let key = prompt("What's the key?", "__proto__");
137+
map.set(key, "some value");
138+
139+
alert(map.get(key)); // "some value" (as intended)
140+
```
141+
142+
...But `Object` syntax is often more appealing, as it's more concise.
143+
144+
Fortunately, we *can* use objects, because language creators gave thought to that problem long ago.
145+
146+
As we know, `__proto__` is not a property of an object, but an accessor property of `Object.prototype`:
123147

124148
![](object-prototype-2.svg)
125149

@@ -132,6 +156,7 @@ Now, if we intend to use an object as an associative array and be free of such p
132156
```js run
133157
*!*
134158
let obj = Object.create(null);
159+
// or: obj = { __proto__: null }
135160
*/!*
136161

137162
let key = prompt("What's the key?", "__proto__");
@@ -173,32 +198,24 @@ alert(Object.keys(chineseDictionary)); // hello,bye
173198

174199
## Summary
175200

176-
Modern methods to set up and directly access the prototype are:
177-
178-
- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with a given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors.
179-
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter).
180-
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter).
181-
182-
The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys into an object. Just because a user may enter `"__proto__"` as the key, and there'll be an error, with hopefully light, but generally unpredictable consequences.
201+
- To create an object with the given prototype, use:
183202

184-
So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that.
203+
- literal syntax: `{ __proto__: ... }`, allows to specify multiple properties
204+
- or [Object.create(proto, [descriptors])](mdn:js/Object/create), allows to specify property descriptors.
185205

186-
Also, `Object.create` provides an easy way to shallow-copy an object with all descriptors:
206+
The `Object.create` provides an easy way to shallow-copy an object with all descriptors:
187207

188-
```js
189-
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
190-
```
208+
```js
209+
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
210+
```
191211

192-
We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just like other methods.
212+
- Modern methods to get/set the prototype are:
193213

194-
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.
214+
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter).
215+
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter).
195216

196-
Other methods:
217+
- Getting/setting the prototype using the built-in `__proto__` getter/setter isn't recommended, it's now in the Annex B of the specification.
197218

198-
- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs.
199-
- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic keys.
200-
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string keys.
201-
- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own keys.
202-
- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): returns `true` if `obj` has its own (not inherited) key named `key`.
219+
- We covered prototype-less objects, created with `Object.create(null)` or `{__proto__: null}.
203220
204-
All methods that return object properties (like `Object.keys` and others) -- return "own" properties. If we want inherited ones, we can use `for..in`.
221+
These objects are used as dictionaries, to store any (possibly user-generated) keys. Without explicitly `null` prototype, 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.

0 commit comments

Comments
 (0)