You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/08-prototypes/04-prototype-methods/article.md
+59-42
Original file line number
Diff line number
Diff line change
@@ -3,15 +3,18 @@
3
3
4
4
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
5
5
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).
7
7
8
-
The modern methods are:
8
+
The modern methods to get/set a prototype are:
9
9
10
-
-[Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
11
10
-[Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`.
12
11
-[Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`.
13
12
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.
15
18
16
19
For instance:
17
20
@@ -22,7 +25,7 @@ let animal = {
22
25
23
26
// create a new object with animal as a prototype
24
27
*!*
25
-
let rabbit =Object.create(animal);
28
+
let rabbit =Object.create(animal);// same as {__proto__: animal}
26
29
*/!*
27
30
28
31
alert(rabbit.eats); // true
@@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
36
39
*/!*
37
40
```
38
41
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:
40
45
41
46
```js run
42
47
let animal = {
@@ -57,26 +62,34 @@ The descriptors are in the same format as described in the chapter <info:propert
57
62
We can use `Object.create` to perform an object cloning more powerful than copying properties in `for..in`:
58
63
59
64
```js
60
-
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]]`.
64
71
65
-
## Brief history
66
72
67
-
If we count all the ways to manage `[[Prototype]]`, there are a lot! Many ways to do the same thing!
73
+
## Brief history
68
74
69
-
Why?
75
+
There're so many ways to manage `[[Prototype]]`. How did that happen? Why?
70
76
71
77
That's for historical reasons.
72
78
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.
75
83
- 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`?
76
87
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?
78
89
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.
80
93
81
94
```warn header="Don't change `[[Prototype]]` on existing objects if speed matters"
82
95
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";
101
114
alert(obj[key]); // [object Object], not "some value"!
102
115
```
103
116
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!
105
118
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.
107
120
108
121
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!
109
122
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.
111
124
112
125
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.
113
126
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.
115
128
116
129
How can we avoid this problem?
117
130
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:
119
132
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 =newMap();
121
135
122
-
`__proto__` is not a property of an object, but an accessor property of `Object.prototype`:
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:
183
202
184
-
So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that.
- or [Object.create(proto, [descriptors])](mdn:js/Object/create), allows to specify property descriptors.
185
205
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:
187
207
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
+
```
191
211
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:
193
213
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).
195
216
196
-
Other methods:
217
+
- Getting/setting the prototype using the built-in`__proto__` getter/setter isn't recommended, it's now in the Annex Bof the specification.
197
218
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}.
203
220
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