Skip to content

Commit d2378df

Browse files
committed
minor
1 parent 79f3775 commit d2378df

File tree

14 files changed

+128
-53
lines changed

14 files changed

+128
-53
lines changed

1-js/07-object-oriented-programming/01-property-flags-descriptors/article.md renamed to 1-js/07-object-oriented-programming/01-property-descriptors/article.md

File renamed without changes.

1-js/07-object-oriented-programming/04-function-prototype/article.md

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ alert(rabbit.constructor == Rabbit); // true (from prototype)
8989

9090
![](rabbit-prototype-constructor.png)
9191

92-
We can use `constructor` to create a new object using the same constructor as the existing one.
92+
We can use `constructor` property to create a new object using the same constructor as the existing one.
9393

9494
Like here:
9595

@@ -101,20 +101,36 @@ function Rabbit(name) {
101101

102102
let rabbit = new Rabbit("White Rabbit");
103103

104+
*!*
104105
let rabbit2 = new rabbit.constructor("Black Rabbit");
106+
*/!*
105107
```
106108

107-
That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create the same.
109+
That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind.
108110

109-
...But probably the most important thing about `"constructor"` is that...
111+
But probably the most important thing about `"constructor"` is that...
110112

111-
**JavaScript itself does not ensure the right `"constructor"` at all.**
113+
**...JavaScript itself does not ensure the right `"constructor"` value.**
112114

113-
Yes, it exists in the default `"prototype"` for functions, but that's all. It is created automatically, but what happens with it later -- is totally on us.
115+
Yes, it exists in the default `"prototype"` for functions, but that's all. What happens with it later -- is totally on us.
114116

115-
In particular, if we replace the default prototype by assigning our own `Rabbit.prototype = { jumps: true }`, then there will be no `"constructor"` in it.
117+
In particular, if we replace the default prototype as a whole, then there will be no `"constructor"` in it.
116118

117-
But we may want to keep `"constructor"` for convenience by adding properties to the default `"prototype"` instead of overwriting it as a whole:
119+
For instance:
120+
121+
```js run
122+
function Rabbit() {}
123+
Rabbit.prototype = {
124+
jumps: true
125+
};
126+
127+
let rabbit = new Rabbit();
128+
*!*
129+
alert(rabbit.constructor === Rabbit); // false
130+
*/!*
131+
```
132+
133+
So, to keep the right `"constructor"` we can choose to add/remove properties to the default `"prototype"` instead of overwriting it as a whole:
118134

119135
```js
120136
function Rabbit() {}
@@ -125,7 +141,7 @@ Rabbit.prototype.jumps = true
125141
// the default Rabbit.prototype.constructor is preserved
126142
```
127143

128-
Or, alternatively, recreate it manually:
144+
Or, alternatively, recreate the `constructor` property it manually:
129145

130146
```js
131147
Rabbit.prototype = {
@@ -134,6 +150,8 @@ Rabbit.prototype = {
134150
constructor: Rabbit
135151
*/!*
136152
};
153+
154+
// now constructor is also correct, because we added it
137155
```
138156

139157

@@ -143,17 +161,16 @@ In this chapter we briefly described the way of setting a `[[Prototype]]` for ob
143161

144162
Everything is quite simple, just few notes to make things clear:
145163

146-
- The `F.prototype` property is not the same as `[[Prototype]]`.
147-
- The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called.
164+
- The `F.prototype` property is not the same as `[[Prototype]]`. The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called.
148165
- The value of `F.prototype` should be either an object or null: other values won't work.
149-
- The `"prototype"` property only has such a special effect when is set to a constructor function, and it is invoked with `new`.
166+
- The `"prototype"` property only has such a special effect when is set to a constructor function, and invoked with `new`.
150167

151-
On regular objects this property does nothing. That's an ordinary property:
168+
On regular objects the `prototype` is nothing special:
152169
```js
153170
let user = {
154171
name: "John",
155172
prototype: "Bla-bla" // no magic at all
156173
};
157174
```
158175
159-
By default all functions have `F.prototype = { constructor: F }`. So by default we can get the constructor of an object by accessing its `"constructor"` property.
176+
By default all functions have `F.prototype = { constructor: F }`, so we can get the constructor of an object by accessing its `"constructor"` property.
239 Bytes
Loading
628 Bytes
Loading
239 Bytes
Loading
628 Bytes
Loading

1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/solution.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ To make `toString` non-enumerable, let's define it using a property descriptor.
77
*!*
88
let dictionary = Object.create(null, {
99
toString: { // define toString property
10-
value() { // with function value
10+
value() { // the value is a function
1111
return Object.keys(this).join();
1212
}
1313
}
@@ -26,3 +26,4 @@ for(let key in dictionary) {
2626
alert(dictionary); // "apple,__proto__"
2727
```
2828

29+
When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable.

1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/task.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ importance: 5
44

55
# Add toString to the dictionary
66

7-
There's an object `dictionary`, suited to store any `key/value` pairs.
7+
There's an object `dictionary`, created as `Object.create(null)`, to store any `key/value` pairs.
88

99
Add method `dictionary.toString()` into it, that should return a comma-delimited list of keys. Your `toString` should not show up in `for..in` over the object.
1010

@@ -14,7 +14,7 @@ Here's how it should work:
1414
let dictionary = Object.create(null);
1515

1616
*!*
17-
// your code to add toString
17+
// your code to add dictionary.toString method
1818
*/!*
1919

2020
// add some data

1-js/07-object-oriented-programming/06-prototype-methods/3-compare-calls/task.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function Rabbit(name) {
1212
}
1313
Rabbit.prototype.sayHi = function() {
1414
alert(this.name);
15-
}
15+
};
1616

1717
let rabbit = new Rabbit("Rabbit");
1818
```

1-js/07-object-oriented-programming/06-prototype-methods/article.md

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
# Methods for prototypes
33

4+
In this chapter we cover additional methods to work with the prototype.
5+
46
There are also other ways to get/set a prototype, besides those that we already know:
57

68
- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
@@ -16,28 +18,49 @@ let animal = {
1618
eats: true
1719
};
1820

21+
// create a new object with animal as a prototype
1922
*!*
2023
let rabbit = Object.create(animal);
2124
*/!*
2225

2326
alert(rabbit.eats); // true
24-
alert(Object.getPrototypeOf(rabbit) === animal); // true
27+
*!*
28+
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
29+
*/!*
30+
31+
*!*
32+
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
33+
*/!*
34+
```
35+
36+
`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
37+
38+
```js run
39+
let animal = {
40+
eats: true
41+
};
42+
43+
let rabbit = Object.create(animal, {
44+
jumps: {
45+
value: true
46+
}
47+
});
2548

26-
Object.setPrototypeOf(rabbit, {}); // reset the prototype of rabbit to {}
49+
alert(rabbit.jumps); // true
2750
```
2851

52+
The descriptors are in the same format as described in the chapter <info:property-descriptors>.
2953

30-
````smart header="`Object.create` to make a clone"
31-
`Object.create` can be used to perform a full object cloning:
54+
We can use `Object.create` to perform a full object cloning, like this:
3255

3356
```js
3457
// fully identical shallow clone of obj
3558
let clone = Object.create(obj, Object.getOwnPropertyDescriptors(obj));
3659
```
3760

3861
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]]`. But not an in-depth copy of course.
39-
````
4062

63+
## Brief history
4164

4265
If we count all the ways to manage `[[Prototype]]`, there's a lot! Many ways to do the same!
4366

@@ -46,18 +69,18 @@ Why so?
4669
That's for historical reasons.
4770

4871
- The `"prototype"` property of a constructor function works since very ancient times.
49-
- Later in the year 2012: `Object.create` appeared in the standard. It allowed to create objects with the given prototype, but that was all. So browsers implemented a more powerful, but non-standard `__proto__` accessor that allowed to get/set a prototype at any time.
50-
- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, and also `__proto__` became a part of the Annex B of the standard (optional for non-browser environments), because almost all browsers implemented it.
72+
- 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.
73+
- Later in the year 2015: `Object.setPrototypeOf` and `Object.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.
5174

52-
And now we have all these ways at our disposal.
75+
As of now we have all these ways at our disposal.
5376

54-
But please note: for most practical tasks, prototype chains are fixed: `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.
77+
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.
5578

5679
## "Very plain" objects
5780

5881
As we know, objects can be used as associative arrays to store key/value pairs.
5982

60-
...But if we try to use it for *any* keys (user-provided for instance), we can see an interesting glitch.
83+
...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__"`.
6184

6285
Check out the example:
6386

@@ -70,15 +93,17 @@ obj[key] = "some value";
7093
alert(obj[key]); // [object Object], not "some value"!
7194
```
7295

73-
Here if the user types in `__proto__`, the assignment is ignored! That's because `__proto__` must be either an object or `null`, a string can not become a prototype.
96+
Here if the user types in `__proto__`, the assignment is ignored!
7497

75-
We did not intend to implement such behavior, right? So that's a bug. Here the consequences are not terrible. But in more complex cases the prototype may indeed be changed, so the execution may go wrong in totally unexpected ways.
98+
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.
99+
100+
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.
76101

77102
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.
78103

79104
Such thing happens only with `__proto__`. All other properties are "assignable" normally.
80105

81-
So, what's going and and how to evade the problem?
106+
How to evade the problem?
82107

83108
First, we can just switch to using `Map`, then everything's fine.
84109

@@ -92,7 +117,7 @@ So, if `obj.__proto__` is read or assigned, the corresponding getter/setter is c
92117

93118
As it was said in the beginning: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself.
94119

95-
Now, if we want to use an object as an assotiative array, we can do it with a little trick:
120+
Now, if we want to use an object as an associative array, we can do it with a little trick:
96121

97122
```js run
98123
*!*
@@ -123,7 +148,7 @@ let obj = Object.create(null);
123148
alert(obj); // Error (no toString)
124149
```
125150

126-
...But that's usually fine for associative arrays. If needed, we can add a `toString` of our own.
151+
...But that's usually fine for associative arrays.
127152

128153
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:
129154

@@ -142,9 +167,7 @@ There are many ways to get keys/values from an object.
142167

143168
We already know these ones:
144169

145-
- [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.
146-
147-
These methods only list *enumerable* properties, and those that have *strings as keys*.
170+
- [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. These methods only list *enumerable* properties, and those that have *strings as keys*.
148171

149172
If we want symbolic properties:
150173

@@ -154,13 +177,13 @@ If we want non-enumerable properties:
154177

155178
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
156179

157-
If we want *everything*:
180+
If we want *all* properties:
158181

159182
- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names.
160183

161-
All of them operate on the object itself. Properties from the prototype are not listed.
184+
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.
162185

163-
But `for..in` loop is different: it also gives inherited properties.
186+
The `for..in` loop is different: it loops over inherited properties too.
164187

165188
For instance:
166189

@@ -208,9 +231,9 @@ Here we have the following inheritance chain: `rabbit`, then `animal`, then `Obj
208231

209232
![](rabbit-animal-object.png)
210233

211-
So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? The answer is simple: it's not enumerable just like all other properties of `Object.prototype`.
234+
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.
212235

213-
As a take-away, let's remember that if we want inherited properties -- we should use `for..in`, and otherwise we can use other iteration methods or add the `hasOwnProperty` check.
236+
...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.
214237

215238
## Summary
216239

@@ -227,4 +250,6 @@ Here's a brief list of methods we discussed in this chapter -- as a recap:
227250

228251
We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just as other methods.
229252

230-
`Object.create(null)` doesn't have any object properties and methods, and `__proto__` also doesn't exist for it.
253+
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.
254+
255+
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`.

0 commit comments

Comments
 (0)