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/article.md
+15-7Lines changed: 15 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -268,12 +268,12 @@ let rabbit = {
268
268
};
269
269
270
270
*!*
271
-
// only own keys
271
+
//Object.keys only return own keys
272
272
alert(Object.keys(rabbit)); // jumps
273
273
*/!*
274
274
275
275
*!*
276
-
// inherited keys too
276
+
//for..in loops over both own and inherited keys
277
277
for(let prop in rabbit) alert(prop); // jumps, then eats
278
278
*/!*
279
279
```
@@ -294,17 +294,24 @@ let rabbit = {
294
294
295
295
for(let prop in rabbit) {
296
296
let isOwn =rabbit.hasOwnProperty(prop);
297
-
alert(`${prop}: ${isOwn}`); // jumps: true, then eats: false
297
+
298
+
if (isOwn) {
299
+
alert(`Our: ${prop}`); // Our: jumps
300
+
} else {
301
+
alert(`Inherited: ${prop}`); // Inherited: eats
302
+
}
298
303
}
299
304
```
300
305
301
-
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:
306
+
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:
302
307
303
308

304
309
305
-
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.
310
+
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.
311
+
312
+
...But why `hasOwnProperty` does not appear in `for..in` loop, like `eats` and `jumps`, if it lists all inherited properties.
306
313
307
-
...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.
314
+
The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. That's why they are not listed.
308
315
309
316
```smart header="All other iteration methods ignore inherited properties"
310
317
All other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties.
@@ -317,6 +324,7 @@ They only operate on the object itself. Properties from the prototype are taken
317
324
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
318
325
- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon).
319
326
- The object referenced by `[[Prototype]]` is called a "prototype".
320
-
- 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).
327
+
- 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.
328
+
- Write/delete operations for act directly on the object, they don't use the prototype (assuming it's a data property, not is a setter).
321
329
- 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.
322
330
- The `for..in` loop iterates over both own and inherited properties. All other key/value-getting methods only operate on the object itself.
Copy file name to clipboardExpand all lines: 1-js/09-classes/02-class-inheritance/article.md
+81-12Lines changed: 81 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -311,9 +311,9 @@ Let's get a little deeper under the hood of `super`. We'll see some interesting
311
311
312
312
First to say, from all that we've learned till now, it's impossible for `super` to work at all!
313
313
314
-
Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object. How JavaScript engine should get the prototype of `this`?
314
+
Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object.
315
315
316
-
The task may seem simple, but it isn't. The engine could try to get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`. Unfortunately, that doesn't work.
316
+
The task may seem simple, but it isn't. The engine knows the current object `this`, so it could get the parent `method` as `this.__proto__.method`. Unfortunately, such a "naive" solution won't work.
317
317
318
318
Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.
319
319
@@ -414,46 +414,100 @@ The problem can't be solved by using `this` alone.
414
414
415
415
To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`.
416
416
417
-
**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.**
417
+
When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.
418
418
419
-
This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. So that's a very important change in the language.
419
+
Then `super` uses it to resolve the parent prototype and its methods.
420
420
421
-
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't breakcompatibility. Regular method calls know nothing about `[[HomeObject]]`, it only matters for`super`.
422
-
423
-
Let's see how it works for `super` -- again, using plain objects:
421
+
Let's see how it works, first with plain objects:
424
422
425
423
```js run
426
424
let animal = {
427
425
name: "Animal",
428
-
eat() { // [[HomeObject]] == animal
426
+
eat() { // animal.eat.[[HomeObject]] == animal
429
427
alert(`${this.name} eats.`);
430
428
}
431
429
};
432
430
433
431
let rabbit = {
434
432
__proto__: animal,
435
433
name: "Rabbit",
436
-
eat() { // [[HomeObject]] == rabbit
434
+
eat() { // rabbit.eat.[[HomeObject]] == rabbit
437
435
super.eat();
438
436
}
439
437
};
440
438
441
439
let longEar = {
442
440
__proto__: rabbit,
443
441
name: "Long Ear",
444
-
eat() { // [[HomeObject]] == longEar
442
+
eat() { // longEar.eat.[[HomeObject]] == longEar
445
443
super.eat();
446
444
}
447
445
};
448
446
449
447
*!*
448
+
// works correctly
450
449
longEar.eat(); // Long Ear eats.
451
450
*/!*
452
451
```
453
452
454
-
Every method remembers its object in the internal `[[HomeObject]]` property. Then `super` uses it to resolve the parent prototype.
453
+
It works as intended, due to `[[HomeObject]]`mechanics. A method, such as `longEar.eat`, knows its `[[HomeObject]]` and takes the parent method from its prototype. Without any use of`this`.
454
+
455
+
### Methods are not "free"
456
+
457
+
As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`.
458
+
459
+
The very existance of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever.
460
+
461
+
The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. Butwith`super` things may go wrong.
462
+
463
+
Here's the demo of a wrong `super` call:
464
+
465
+
```js run
466
+
let animal = {
467
+
sayHi() {
468
+
console.log(`I'm an animal`);
469
+
}
470
+
};
471
+
472
+
let rabbit = {
473
+
__proto__: animal,
474
+
sayHi() {
475
+
super.sayHi();
476
+
}
477
+
};
455
478
456
-
`[[HomeObject]]` is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`.
479
+
let plant = {
480
+
sayHi() {
481
+
console.log("I'm a plant");
482
+
}
483
+
};
484
+
485
+
let tree = {
486
+
__proto__: plant,
487
+
*!*
488
+
sayHi: rabbit.sayHi // (*)
489
+
*/!*
490
+
};
491
+
492
+
*!*
493
+
tree.sayHi(); // I'm an animal (?!?)
494
+
*/!*
495
+
```
496
+
497
+
A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong.
498
+
499
+
The reason is simple:
500
+
- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication?
501
+
- So its `[[HomeObject]]` is `rabbit`, as it was created in`rabbit`. There's no way to change `[[HomeObject]]`.
502
+
- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`.
503
+
504
+

505
+
506
+
### Methods, not function properties
507
+
508
+
`[[HomeObject]]` is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as `method()`, not as `"method: function()"`.
509
+
510
+
The difference may be non-essential for us, but it's important for JavaScript.
457
511
458
512
In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work:
459
513
@@ -475,3 +529,18 @@ let rabbit = {
475
529
rabbit.eat(); // Error calling super (because there's no [[HomeObject]])
476
530
*/!*
477
531
```
532
+
533
+
## Summary
534
+
535
+
1. To extend a class: `classChildextendsParent`:
536
+
- That means `Child.prototype.__proto__` will be `Parent.prototype`, so methods are inherited.
537
+
2. When overriding a constructor:
538
+
- We must call parent constructor as `super()` in `Child` constructor before using `this`.
539
+
3. When overriding another method:
540
+
- We can use `super.method()` in a `Child` method to call `Parent` method.
541
+
4. Internals:
542
+
- Methods remember their class/object in the internal `[[HomeObject]]` property. That's how `super` resolves parent methods.
543
+
- So it's not safe to copy a method with `super` from one object to another.
544
+
545
+
Also:
546
+
- Arrow functions don't have own `this` or `super`, so they transparently fit into the surrounding context.
Copy file name to clipboardExpand all lines: 2-ui/5-loading/01-onload-ondomcontentloaded/article.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@
3
3
The lifecycle of an HTML page has three important events:
4
4
5
5
-`DOMContentLoaded` -- the browser fully loaded HTML, and the DOM tree is built, but external resources like pictures `<img>` and stylesheets may be not yet loaded.
6
-
-`load` -- not onyl HTML is loaded, but also all the external resources: images, styles etc.
6
+
-`load` -- not only HTML is loaded, but also all the external resources: images, styles etc.
7
7
-`beforeunload/unload` -- the user is leaving the page.
0 commit comments