diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index 14b442c2..5abe494e 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -224,6 +224,6 @@ MyClass.property = ... MyClass.method = ... ``` -Static properties are inherited. +Static properties and methods are inherited. For `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 8946d2f7..ef0d497a 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -53,7 +53,7 @@ In JavaScript, there are two types of object fields (properties and methods): - Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods. - Private: accessible only from inside the class. These are for the internal interface. -In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it. They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. +In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated. @@ -297,7 +297,7 @@ Supportable **If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users.** - If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them. + If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them. For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same. diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md index dfe2ddf3..2dc4902b 100644 --- a/1-js/09-classes/05-extend-natives/article.md +++ b/1-js/09-classes/05-extend-natives/article.md @@ -21,14 +21,14 @@ alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false ``` -Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so. +Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses object `constructor` property for that. In the example above, ```js arr.constructor === PowerArray ``` -So when `arr.filter()` is called, it internally creates the new array of results using exactly `new PowerArray`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. +When `arr.filter()` is called, it internally creates the new array of results using exactly `arr.constructor`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. Even more, we can customize that behavior. @@ -64,6 +64,10 @@ alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further. +```smart header="Other collections work similarly" +Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`. +``` + ## No static inheritance in built-ins Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc. @@ -81,3 +85,5 @@ Here's the picture structure for `Date` and `Object`: ![](object-date-inheritance.svg) As you can see, there's no link between `Date` and `Object`. They are independent, only `Date.prototype` inherits from `Object.prototype`. + +That's an important difference of inheritance between built-in objects compared to what we get with `extends`. diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index 684901c6..0b02c99b 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -11,7 +11,7 @@ The syntax is: obj instanceof Class ``` -It returns `true` if `obj` belongs to the `Class` (or a class inheriting from it). +It returns `true` if `obj` belongs to the `Class` or a class inheriting from it. For instance: @@ -46,15 +46,17 @@ alert( arr instanceof Object ); // true Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypally inherits from `Object`. -The `instanceof` operator examines the prototype chain for the check, but we can set a custom logic in the static method `Symbol.hasInstance`. +Normally, `instanceof` operator examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`. The algorithm of `obj instanceof Class` works roughly as follows: -1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`. We're done. - For example: +1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`, and we're done. That's how we can customize the behavior of `instanceof`. + + For example: ```js run - // setup instanceOf check that assumes that anything that canEat is an animal + // setup instanceOf check that assumes that + // anything with canEat property is an animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -68,17 +70,19 @@ The algorithm of `obj instanceof Class` works roughly as follows: 2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain. - In other words, compare: + In other words, compare one after another: ```js - obj.__proto__ === Class.prototype - obj.__proto__.__proto__ === Class.prototype - obj.__proto__.__proto__.__proto__ === Class.prototype + obj.__proto__ === Class.prototype? + obj.__proto__.__proto__ === Class.prototype? + obj.__proto__.__proto__.__proto__ === Class.prototype? ... + // if any answer is true, return true + // otherwise, if we reached the end of the chain, return false ``` - In the example above `Rabbit.prototype === rabbit.__proto__`, so that gives the answer immediately. + In the example above `rabbit.__proto__ === Rabbit.prototype`, so that gives the answer immediately. - In the case of an inheritance, `rabbit` is an instance of the parent class as well: + In the case of an inheritance, the match will be at the second step: ```js run class Animal {} @@ -88,8 +92,11 @@ The algorithm of `obj instanceof Class` works roughly as follows: *!* alert(rabbit instanceof Animal); // true */!* + // rabbit.__proto__ === Rabbit.prototype + *!* // rabbit.__proto__.__proto__ === Animal.prototype (match!) + */!* ``` Here's the illustration of what `rabbit instanceof Animal` compares with `Animal.prototype`: @@ -100,7 +107,7 @@ By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPro That's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. -That can lead to interesting consequences when `prototype` is changed. +That can lead to interesting consequences when `prototype` property is changed after the object is created. Like here: @@ -117,8 +124,6 @@ alert( rabbit instanceof Rabbit ); // false */!* ``` -That's one of the reasons to avoid changing `prototype`. Just to keep safe. - ## Bonus: Object.prototype.toString for the type We already know that plain objects are converted to string as `[object Object]`: @@ -152,7 +157,7 @@ let objectToString = Object.prototype.toString; // what type is this? let arr = []; -alert( objectToString.call(arr) ); // [object Array] +alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`. @@ -196,11 +201,11 @@ As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized. -It can be used instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check. +We can use `{}.toString.call` instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check. ## Summary -Let's recap the type-checking methods that we know: +Let's summarize the type-checking methods that we know: | | works for | returns | |---------------|-------------|---------------| diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 3e06800a..a870e1aa 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -2,9 +2,9 @@ In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class. -But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make a `StreetSweepingBicycle`. +But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make their mix: a `StreetSweepingBicycle`. -Or, talking about programming, we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events. +Or we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events. There's a concept that can help here, called "mixins". @@ -14,7 +14,7 @@ In other words, a *mixin* provides methods that implement a certain behavior, bu ## A mixin example -The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. +The simplest way to implement a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`: @@ -75,10 +75,10 @@ let sayHiMixin = { *!* // call parent method */!* - super.say(`Hello ${this.name}`); + super.say(`Hello ${this.name}`); // (*) }, sayBye() { - super.say(`Bye ${this.name}`); + super.say(`Bye ${this.name}`); // (*) } }; @@ -95,11 +95,13 @@ Object.assign(User.prototype, sayHiMixin); new User("Dude").sayHi(); // Hello Dude! ``` -Please note that the call to the parent method `super.say()` from `sayHiMixin` looks for the method in the prototype of that mixin, not the class. +Please note that the call to the parent method `super.say()` from `sayHiMixin` (at lines labelled with `(*)`) looks for the method in the prototype of that mixin, not the class. + +Here's the diagram (see the right part): ![](mixin-inheritance.svg) -That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above. +That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above. As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. @@ -199,7 +201,7 @@ And `eventMixin` mixin makes it easy to add such behavior to as many classes as *Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes. -Some other languages like e.g. Python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. +Some other languages like allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above. diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index ece54610..ae646ba5 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -11,13 +11,15 @@ In this chapter we first cover theoretical details about how things work, and th The concept of *event loop* is very simple. There's an endless loop, when JavaScript engine waits for tasks, executes them and then sleeps waiting for more tasks. +The general algorithm of the engine: + 1. While there are tasks: - - execute the oldest task. + - execute them, starting with the oldest task. 2. Sleep until a task appears, then go to 1. -That's a formalized algorithm for what we see when browsing a page. JavaScript engine does nothing most of the time, only runs if a script/handler/event activates. +That's a formalization for what we see when browsing a page. JavaScript engine does nothing most of the time, only runs if a script/handler/event activates. -A task can be JS-code triggered by events, but can also be something else, e.g.: +Examples of tasks: - When an external script ` ``` -So, microtasks are asynchronous from the point of code execution, but they don't allow any browser processes or events to stick in-between them. - ## Summary -The richer event loop picture may look like this: - -![](eventLoop-full.svg) - The more detailed algorithm of the event loop (though still simplified compare to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)): 1. Dequeue and run the oldest task from the *macrotask* queue (e.g. "script"). @@ -318,23 +310,23 @@ The more detailed algorithm of the event loop (though still simplified compare t - While the microtask queue is not empty: - Dequeue and run the oldest microtask. 3. Render changes if any. -4. Wait until the macrotask queue is not empty (if needed). +4. If the macrotask queue is empty, wait till a macrotask appears. 5. Go to step 1. -To schedule a new macrotask: +To schedule a new *macrotask*: - Use zero delayed `setTimeout(f)`. That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react on user events and show progress between them. Also, used in event handlers to schedule an action after the event is fully handled (bubbling done). -To schedule a new microtask: +To schedule a new *microtask* - Use `queueMicrotask(f)`. - Also promise handlers go through the microtask queue. There's no UI or network event handling between microtasks: they run immediately one after another. -So one may want to `queueMicrotask` to execute a function asynchronously, but also with the same application state. +So one may want to `queueMicrotask` to execute a function asynchronously, but within the environment state. ```smart header="Web Workers" For long heavy calculations that shouldn't block the event loop, we can use [Web Workers](https://html.spec.whatwg.org/multipage/workers.html). diff --git a/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg b/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg index d6f68f15..31bbee68 100644 --- a/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg +++ b/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg @@ -24,7 +24,7 @@ event loop - + @@ -41,16 +41,16 @@ render - + - + - - - - + + + + diff --git a/figures.sketch b/figures.sketch index 5f194036..3d452349 100644 Binary files a/figures.sketch and b/figures.sketch differ