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/01-prototype-inheritance/4-hamster-proto/solution.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ Let's look carefully at what's going on in the call `speedy.eat("apple")`.
10
10
11
11
So all hamsters share a single stomach!
12
12
13
-
Every time the `stomach` is taken from the prototype, then `stomach.push` modifies it "at place".
13
+
Both for `lazy.stomach.push(...)` and `speedy.stomach.push()`, the property `stomach` is found in the prototype (as it's not in the object itself), then the new data is pushed into it.
14
14
15
15
Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`:
Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object.
46
46
47
-
Also we can totally evade the problem by making sure that each hamster has their own stomach:
47
+
Also we can totally avoid the problem by making sure that each hamster has their own stomach:
48
48
49
49
```js run
50
50
let hamster ={
@@ -77,4 +77,4 @@ alert( speedy.stomach ); // apple
77
77
alert( lazy.stomach ); // <nothing>
78
78
```
79
79
80
-
As a common solution, all properties that describe the state of a particular object, like `stomach` above, are usually written into that object. That prevents such problems.
80
+
As a common solution, all properties that describe the state of a particular object, like `stomach` above, should be written into that object. That prevents such problems.
Copy file name to clipboardExpand all lines: 1-js/08-prototypes/01-prototype-inheritance/article.md
+82-13Lines changed: 82 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,11 +12,11 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named
12
12
13
13

14
14
15
-
That `[[Prototype]]` has a "magical" meaning. When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it.
15
+
The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it.
16
16
17
17
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
18
18
19
-
One of them is to use `__proto__`, like this:
19
+
One of them is to use the special name `__proto__`, like this:
20
20
21
21
```js run
22
22
let animal ={
@@ -32,9 +32,9 @@ rabbit.__proto__ = animal;
32
32
```
33
33
34
34
```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
35
-
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it.
35
+
Please note that `__proto__` is *not the same* as `[[Prototype]]`. It's a getter/setter for it.
36
36
37
-
It exists for historical reasons, in modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later.
37
+
It exists for historical reasons. In modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later.
38
38
39
39
By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples.
40
40
```
@@ -43,7 +43,7 @@ If we look for a property in `rabbit`, and it's missing, JavaScript automaticall
43
43
44
44
For instance:
45
45
46
-
```js run
46
+
```js
47
47
let animal ={
48
48
eats: true
49
49
};
@@ -101,7 +101,6 @@ The method is automatically taken from the prototype, like this:
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
137
-
2. The value of `__proto__` can be either an object or `null`, other types (like primitives) are ignored.
136
+
2. The value of `__proto__` can be either an object or `null`. Other types are ignored.
138
137
139
138
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
140
139
@@ -171,7 +170,7 @@ From now on, `rabbit.walk()` call finds the method immediately in the object and
171
170
172
171

173
172
174
-
That's for data properties only, not for accessors. If a property is a getter/setter, then it behaves like a function: getters/setters are looked up in the prototype.
173
+
Accessor properties are an exception, as assignment is handled by a setter function. So writing to such a property is actually the same as calling a function.
175
174
176
175
For that reason `admin.fullName` works correctly in the code below:
177
176
@@ -204,15 +203,15 @@ Here in the line `(*)` the property `admin.fullName` has a getter in the prototy
204
203
205
204
## The value of "this"
206
205
207
-
An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: into `user` or `admin`?
206
+
An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where are the properties `this.name` and `this.surname` written: into `user` or `admin`?
208
207
209
208
The answer is simple: `this` is not affected by prototypes at all.
210
209
211
210
**No matter where the method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.**
212
211
213
212
So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`.
214
213
215
-
That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then inherited objects can run its methods, and they will modify the state of these objects, not the big one.
214
+
That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object.
216
215
217
216
For instance, here `animal` represents a "method storage", and `rabbit` makes use of it.
218
217
@@ -247,14 +246,84 @@ The resulting picture:
247
246
248
247

249
248
250
-
If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects.
249
+
If we had other objects, like `bird`, `snake`, etc., inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects.
251
250
252
251
As a result, methods are shared, but the object state is not.
253
252
253
+
## for..in loop
254
+
255
+
The `for..in` loop iterates over inherited properties too.
256
+
257
+
For instance:
258
+
259
+
```js run
260
+
let animal ={
261
+
eats:true
262
+
};
263
+
264
+
let rabbit ={
265
+
jumps:true,
266
+
__proto__: animal
267
+
};
268
+
269
+
*!*
270
+
// Object.keys only returns own keys
271
+
alert(Object.keys(rabbit)); // jumps
272
+
*/!*
273
+
274
+
*!*
275
+
// for..in loops over both own and inherited keys
276
+
for(let prop in rabbit) alert(prop); // jumps, then eats
277
+
*/!*
278
+
```
279
+
280
+
If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
281
+
282
+
So we can filter out inherited properties (or do something else with them):
283
+
284
+
```js run
285
+
let animal ={
286
+
eats:true
287
+
};
288
+
289
+
let rabbit ={
290
+
jumps:true,
291
+
__proto__: animal
292
+
};
293
+
294
+
for(let prop in rabbit){
295
+
let isOwn =rabbit.hasOwnProperty(prop);
296
+
297
+
if (isOwn){
298
+
alert(`Our: ${prop}`); // Our: jumps
299
+
} else{
300
+
alert(`Inherited: ${prop}`); // Inherited: eats
301
+
}
302
+
}
303
+
```
304
+
305
+
Here we have the following inheritance chain: `rabbit` inherits from `animal`, that inherits from `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it:
306
+
307
+

308
+
309
+
Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? We did not define it. Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited.
310
+
311
+
...But why does `hasOwnProperty` not appear in the `for..in` loop like `eats` and `jumps` do, if `for..in` lists inherited properties?
312
+
313
+
The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. And `for..in` only lists enumerable properties. That's why it and the rest of the `Object.prototype` properties are not listed.
314
+
315
+
```smart header="Almost all other key/value-getting methods ignore inherited properties"
316
+
Almost all other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties.
317
+
318
+
They only operate on the object itself. Properties from the prototype are *not* taken into account.
319
+
```
320
+
254
321
## Summary
255
322
256
323
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
257
324
- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon).
258
325
- The object referenced by `[[Prototype]]` is called a "prototype".
259
-
- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. Write/delete operations work directly on the object, they don't use the prototype (unless the property is actually a setter).
326
+
- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype.
327
+
- Write/delete operations act directly on the object, they don't use the prototype (assuming it's a data property, not a setter).
260
328
- If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited.
329
+
- The `for..in` loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself.
0 commit comments