` shows increasing values of `i`.
-
## Summary
- Methods `setInterval(func, delay, ...args)` and `setTimeout(func, delay, ...args)` allow to run the `func` regularly/once after `delay` milliseconds.
- To cancel the execution, we should call `clearInterval/clearTimeout` with the value returned by `setInterval/setTimeout`.
-- Nested `setTimeout` calls is a more flexible alternative to `setInterval`. Also they can guarantee the minimal time *between* the executions.
-- Zero-timeout scheduling `setTimeout(...,0)` is used to schedule the call "as soon as possible, but after the current code is complete".
+- Nested `setTimeout` calls is a more flexible alternative to `setInterval`, allowing to set the time *between* executions more precisely.
+- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current code is complete".
+- The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons.
-Some use cases of `setTimeout(...,0)`:
-- To split CPU-hungry tasks into pieces, so that the script doesn't "hang"
-- To let the browser do something else while the process is going on (paint the progress bar).
-
-Please note that all scheduling methods do not *guarantee* the exact delay. We should not rely on that in the scheduled code.
+Please note that all scheduling methods do not *guarantee* the exact delay.
For example, the in-browser timer may slow down for a lot of reasons:
- The CPU is overloaded.
- The browser tab is in the background mode.
- The laptop is on battery.
-All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and settings.
+All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings.
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.png b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.png
deleted file mode 100644
index ccafd7f1..00000000
Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.png and /dev/null differ
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg
new file mode 100644
index 00000000..9a214c54
--- /dev/null
+++ b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval@2x.png b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval@2x.png
deleted file mode 100644
index 9fcd057e..00000000
Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval@2x.png and /dev/null differ
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.png b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.png
deleted file mode 100644
index 094cf915..00000000
Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.png and /dev/null differ
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg
new file mode 100644
index 00000000..13b22a89
--- /dev/null
+++ b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval@2x.png b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval@2x.png
deleted file mode 100644
index bee58575..00000000
Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval@2x.png and /dev/null differ
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
index 716b4e1d..567c9ce7 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
@@ -12,20 +12,20 @@ Let's check the real-life application to better understand that requirement and
**For instance, we want to track mouse movements.**
-In browser we can setup a function to run at every mouse micro-movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
+In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
-**The tracking function should update some information on the web-page.**
+**We'd like to update some information on the web-page when the pointer moves.**
-Updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in making it more often than once per 100ms.
+...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms.
-So we'll assign `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but `update()` will be called at maximum once per 100ms.
+So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms.
Visually, it will look like this:
-1. For the first mouse movement the decorated variant passes the call to `update`. That's important, the user sees our reaction to their move immediately.
+1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately.
2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls.
-3. At the end of `100ms` -- one more `update` happens with the last coordinates.
-4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, perhaps the most important, the final mouse coordinates are processed.
+3. At the end of `100ms` -- one more `update` happens with the last coordinates.
+4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed.
A code example:
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
index 75c510d1..ceaf2b6a 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
@@ -6,9 +6,9 @@ JavaScript gives exceptional flexibility when dealing with functions. They can b
Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result.
-If the function is called often, we may want to cache (remember) the results for different `x` to avoid spending extra-time on recalculations.
+If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations.
-But instead of adding that functionality into `slow()` we'll create a wrapper. As we'll see, there are many benefits of doing so.
+But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so.
Here's the code, and explanations follow:
@@ -23,13 +23,13 @@ function cachingDecorator(func) {
let cache = new Map();
return function(x) {
- if (cache.has(x)) { // if the result is in the map
- return cache.get(x); // return it
+ if (cache.has(x)) { // if there's such key in cache
+ return cache.get(x); // read the result from it
}
- let result = func(x); // otherwise call func
+ let result = func(x); // otherwise call func
- cache.set(x, result); // and cache (remember) the result
+ cache.set(x, result); // and cache (remember) the result
return result;
};
}
@@ -49,18 +49,16 @@ The idea is that we can call `cachingDecorator` for any function, and it will re
By separating caching from the main function code we also keep the main code simpler.
-Now let's get into details of how it works.
-
The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic:
-
+
-As we can see, the wrapper returns the result of `func(x)` "as is". From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior.
+From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior.
To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself:
- The `cachingDecorator` is reusable. We can apply it to another function.
-- The caching logic is separate, it did not increase the complexity of `slow` itself (if there were any).
+- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any).
- We can combine multiple decorators if needed (other decorators will follow).
@@ -231,9 +229,7 @@ let worker = {
worker.slow = cachingDecorator(worker.slow);
```
-We have two tasks to solve here.
-
-First is how to use both arguments `min` and `max` for the key in `cache` map. Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key.
+Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key.
There are many solutions possible:
@@ -241,85 +237,11 @@ There are many solutions possible:
2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`.
3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many.
-
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
-The second task to solve is how to pass many arguments to `func`. Currently, the wrapper `function(x)` assumes a single argument, and `func.call(this, x)` passes it.
+Also we need to replace `func.call(this, x)` with `func.call(this, ...arguments)`, to pass all arguments to the wrapped function call, not just the first one.
-Here we can use another built-in method [func.apply](mdn:js/Function/apply).
-
-The syntax is:
-
-```js
-func.apply(context, args)
-```
-
-It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments.
-
-
-For instance, these two calls are almost the same:
-
-```js
-func(1, 2, 3);
-func.apply(context, [1, 2, 3])
-```
-
-Both run `func` giving it arguments `1,2,3`. But `apply` also sets `this=context`.
-
-For instance, here `say` is called with `this=user` and `messageData` as a list of arguments:
-
-```js run
-function say(time, phrase) {
- alert(`[${time}] ${this.name}: ${phrase}`);
-}
-
-let user = { name: "John" };
-
-let messageData = ['10:00', 'Hello']; // become time and phrase
-
-*!*
-// user becomes this, messageData is passed as a list of arguments (time, phrase)
-say.apply(user, messageData); // [10:00] John: Hello (this=user)
-*/!*
-```
-
-The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them.
-
-We already know the spread operator `...` from the chapter that can pass an array (or any iterable) as a list of arguments. So if we use it with `call`, we can achieve almost the same as `apply`.
-
-These two calls are almost equivalent:
-
-```js
-let args = [1, 2, 3];
-
-*!*
-func.call(context, ...args); // pass an array as list with spread operator
-func.apply(context, args); // is same as using apply
-*/!*
-```
-
-If we look more closely, there's a minor difference between such uses of `call` and `apply`.
-
-- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
-- The `apply` accepts only *array-like* `args`.
-
-So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
-
-And if `args` is both iterable and array-like, like a real array, then we technically could use any of them, but `apply` will probably be faster, because it's a single operation. Most JavaScript engines internally optimize it better than a pair `call + spread`.
-
-One of the most important uses of `apply` is passing the call to another function, like this:
-
-```js
-let wrapper = function() {
- return anotherFunction.apply(this, arguments);
-};
-```
-
-That's called *call forwarding*. The `wrapper` passes everything it gets: the context `this` and arguments to `anotherFunction` and returns back its result.
-
-When an external code calls such `wrapper`, it is indistinguishable from the call of the original function.
-
-Now let's bake it all into the more powerful `cachingDecorator`:
+Here's a more powerful `cachingDecorator`:
```js run
let worker = {
@@ -340,7 +262,7 @@ function cachingDecorator(func, hash) {
}
*!*
- let result = func.apply(this, arguments); // (**)
+ let result = func.call(this, ...arguments); // (**)
*/!*
cache.set(key, result);
@@ -358,13 +280,52 @@ alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)
```
-Now the wrapper operates with any number of arguments.
+Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below).
There are two changes:
- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions.
-- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function.
+- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function.
+Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`.
+
+The syntax of built-in method [func.apply](mdn:js/Function/apply) is:
+
+```js
+func.apply(context, args)
+```
+
+It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments.
+
+The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them.
+
+So these two calls are almost equivalent:
+
+```js
+func.call(context, ...args); // pass an array as list with spread operator
+func.apply(context, args); // is same as using apply
+```
+
+There's only a minor difference:
+
+- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
+- The `apply` accepts only *array-like* `args`.
+
+So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
+
+And for objects that are both iterable and array-like, like a real array, we technically could use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better.
+
+Passing all arguments along with the context to another function is called *call forwarding*.
+
+That's the simplest form of it:
+
+```js
+let wrapper = function() {
+ return func.apply(this, arguments);
+};
+```
+
+When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`.
## Borrowing a method [#method-borrowing]
@@ -450,10 +411,9 @@ The generic *call forwarding* is usually done with `apply`:
```js
let wrapper = function() {
return original.apply(this, arguments);
-}
+};
```
-We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to arguments. The alternative is to use rest parameters object that is a real array.
-
+We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array.
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.png b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.png
deleted file mode 100644
index e45e4867..00000000
Binary files a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.png and /dev/null differ
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg
new file mode 100644
index 00000000..5fc7743f
--- /dev/null
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper@2x.png b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper@2x.png
deleted file mode 100644
index eec94c5b..00000000
Binary files a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper@2x.png and /dev/null differ
diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
index 0cb673b1..403107ca 100644
--- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
+++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
@@ -38,6 +38,6 @@ An alternative solution could be:
askPassword(() => user.loginOk(), () => user.loginFail());
```
-Usually that also works, but may fail in more complex situations where `user` has a chance of being overwritten between the moments of asking and running `() => user.loginOk()`.
-
+Usually that also works and looks good.
+It's a bit less reliable though in more complex situations where `user` variable might change *after* `askPassword` is called, but *before* the visitor answers and calls `() => user.loginOk()`.
diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md
index eb19e664..fe6a9b4e 100644
--- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md
+++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md
@@ -2,7 +2,7 @@ importance: 5
---
-# Ask losing this
+# Fix a function that loses "this"
The call to `askPassword()` in the code below should check the password and then call `user.loginOk/loginFail` depending on the answer.
@@ -34,5 +34,3 @@ let user = {
askPassword(user.loginOk, user.loginFail);
*/!*
```
-
-
diff --git a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md
similarity index 100%
rename from 1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md
rename to 1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md
diff --git a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
similarity index 100%
rename from 1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md
rename to 1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md
index fdb07c60..ce0c94a5 100644
--- a/1-js/06-advanced-functions/10-bind/article.md
+++ b/1-js/06-advanced-functions/10-bind/article.md
@@ -5,13 +5,13 @@ libs:
# Function binding
-When using `setTimeout` with object methods or passing object methods along, there's a known problem: "losing `this`".
+When passing object methods as callbacks, for instance to `setTimeout`, there's a known problem: "losing `this`".
-Suddenly, `this` just stops working right. The situation is typical for novice developers, but happens with experienced ones as well.
+In this chapter we'll see the ways to fix it.
## Losing "this"
-We already know that in JavaScript it's easy to lose `this`. Once a method is passed somewhere separately from the object -- `this` is lost.
+We've already seen examples of losing `this`. Once a method is passed somewhere separately from the object -- `this` is lost.
Here's how it may happen with `setTimeout`:
@@ -37,7 +37,7 @@ let f = user.sayHi;
setTimeout(f, 1000); // lost user context
```
-The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.JS, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases as we'll see, usually `this` just becomes `undefined`.
+The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases, usually `this` just becomes `undefined`.
The task is quite typical -- we want to pass an object method somewhere else (here -- to the scheduler) where it will be called. How to make sure that it will be called in the right context?
@@ -100,7 +100,7 @@ The basic syntax is:
```js
// more complex syntax will be little later
let boundFunc = func.bind(context);
-````
+```
The result of `func.bind(context)` is a special function-like "exotic object", that is callable as function and transparently passes the call to `func` setting `this=context`.
@@ -196,8 +196,124 @@ for (let key in user) {
JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash.
````
+## Partial functions
+
+Until now we have only been talking about binding `this`. Let's take it a step further.
+
+We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy.
+
+The full syntax of `bind`:
+
+```js
+let bound = func.bind(context, [arg1], [arg2], ...);
+```
+
+It allows to bind context as `this` and starting arguments of the function.
+
+For instance, we have a multiplication function `mul(a, b)`:
+
+```js
+function mul(a, b) {
+ return a * b;
+}
+```
+
+Let's use `bind` to create a function `double` on its base:
+
+```js run
+function mul(a, b) {
+ return a * b;
+}
+
+*!*
+let double = mul.bind(null, 2);
+*/!*
+
+alert( double(3) ); // = mul(2, 3) = 6
+alert( double(4) ); // = mul(2, 4) = 8
+alert( double(5) ); // = mul(2, 5) = 10
+```
+
+The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is".
+
+That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one.
+
+Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`.
+
+The function `triple` in the code below triples the value:
+
+```js run
+function mul(a, b) {
+ return a * b;
+}
+
+*!*
+let triple = mul.bind(null, 3);
+*/!*
+
+alert( triple(3) ); // = mul(3, 3) = 9
+alert( triple(4) ); // = mul(3, 4) = 12
+alert( triple(5) ); // = mul(3, 5) = 15
+```
+
+Why do we usually make a partial function?
+
+The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide first argument of every time as it's fixed with `bind`.
+
+In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience.
+
+For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user.
+
+## Going partial without context
+
+What if we'd like to fix some arguments, but not the context `this`? For example, for an object method.
+
+The native `bind` does not allow that. We can't just omit the context and jump to arguments.
+
+Fortunately, a helper function `partial` for binding only arguments can be easily implemented.
+
+Like this:
+
+```js run
+*!*
+function partial(func, ...argsBound) {
+ return function(...args) { // (*)
+ return func.call(this, ...argsBound, ...args);
+ }
+}
+*/!*
+
+// Usage:
+let user = {
+ firstName: "John",
+ say(time, phrase) {
+ alert(`[${time}] ${this.firstName}: ${phrase}!`);
+ }
+};
+
+// add a partial method with fixed time
+user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
+
+user.sayNow("Hello");
+// Something like:
+// [10:00] John: Hello!
+```
+
+The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with:
+- Same `this` as it gets (for `user.sayNow` call it's `user`)
+- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`)
+- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`)
+
+So easy to do it with the spread operator, right?
+
+Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library.
+
## Summary
Method `func.bind(context, ...args)` returns a "bound variant" of function `func` that fixes the context `this` and first arguments if given.
-Usually we apply `bind` to fix `this` in an object method, so that we can pass it somewhere. For example, to `setTimeout`. There are more reasons to `bind` in the modern development, we'll meet them later.
+Usually we apply `bind` to fix `this` for an object method, so that we can pass it somewhere. For example, to `setTimeout`.
+
+When we fix some arguments of an existing function, the resulting (less universal) function is called *partially applied* or *partial*.
+
+Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it.
diff --git a/1-js/06-advanced-functions/11-currying-partials/article.md b/1-js/06-advanced-functions/11-currying-partials/article.md
deleted file mode 100644
index 310e4830..00000000
--- a/1-js/06-advanced-functions/11-currying-partials/article.md
+++ /dev/null
@@ -1,308 +0,0 @@
-libs:
- - lodash
-
----
-
-# Currying and partials
-
-Until now we have only been talking about binding `this`. Let's take it a step further.
-
-We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy.
-
-The full syntax of `bind`:
-
-```js
-let bound = func.bind(context, arg1, arg2, ...);
-```
-
-It allows to bind context as `this` and starting arguments of the function.
-
-For instance, we have a multiplication function `mul(a, b)`:
-
-```js
-function mul(a, b) {
- return a * b;
-}
-```
-
-Let's use `bind` to create a function `double` on its base:
-
-```js run
-function mul(a, b) {
- return a * b;
-}
-
-*!*
-let double = mul.bind(null, 2);
-*/!*
-
-alert( double(3) ); // = mul(2, 3) = 6
-alert( double(4) ); // = mul(2, 4) = 8
-alert( double(5) ); // = mul(2, 5) = 10
-```
-
-The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is".
-
-That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one.
-
-Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`.
-
-The function `triple` in the code below triples the value:
-
-```js run
-function mul(a, b) {
- return a * b;
-}
-
-*!*
-let triple = mul.bind(null, 3);
-*/!*
-
-alert( triple(3) ); // = mul(3, 3) = 9
-alert( triple(4) ); // = mul(3, 4) = 12
-alert( triple(5) ); // = mul(3, 5) = 15
-```
-
-Why do we usually make a partial function?
-
-Here our benefit is that we created an independent function with a readable name (`double`, `triple`). We can use it and don't write the first argument of every time, cause it's fixed with `bind`.
-
-In other cases, partial application is useful when we have a very generic function, and want a less universal variant of it for convenience.
-
-For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user.
-
-## Going partial without context
-
-What if we'd like to fix some arguments, but not bind `this`?
-
-The native `bind` does not allow that. We can't just omit the context and jump to arguments.
-
-Fortunately, a `partial` function for binding only arguments can be easily implemented.
-
-Like this:
-
-```js run
-*!*
-function partial(func, ...argsBound) {
- return function(...args) { // (*)
- return func.call(this, ...argsBound, ...args);
- }
-}
-*/!*
-
-// Usage:
-let user = {
- firstName: "John",
- say(time, phrase) {
- alert(`[${time}] ${this.firstName}: ${phrase}!`);
- }
-};
-
-// add a partial method that says something now by fixing the first argument
-user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
-
-user.sayNow("Hello");
-// Something like:
-// [10:00] John: Hello!
-```
-
-The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with:
-- Same `this` as it gets (for `user.sayNow` call it's `user`)
-- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`)
-- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`)
-
-So easy to do it with the spread operator, right?
-
-Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library.
-
-## Currying
-
-Sometimes people mix up partial function application mentioned above with another thing named "currying". That's another interesting technique of working with functions that we just have to mention here.
-
-[Currying](https://en.wikipedia.org/wiki/Currying) is translating a function from callable as `f(a, b, c)` into callable as `f(a)(b)(c)`.
-
-Literally, currying is a transformation of functions: from one way of calling into another. In JavaScript, we usually make a wrapper to keep the original function.
-
-Currying doesn't call a function. It just transforms it. We'll see use cases soon.
-
-Let's make `curry` function that performs currying for two-argument functions. In other words, `curry(f)` for two-argument `f(a, b)` translates it into `f(a)(b)`
-
-```js run
-*!*
-function curry(f) { // curry(f) does the currying transform
- return function(a) {
- return function(b) {
- return f(a, b);
- };
- };
-}
-*/!*
-
-// usage
-function sum(a, b) {
- return a + b;
-}
-
-let carriedSum = curry(sum);
-
-alert( carriedSum(1)(2) ); // 3
-```
-
-As you can see, the implementation is a series of wrappers.
-
-- The result of `curry(func)` is a wrapper `function(a)`.
-- When it is called like `sum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`.
-- Then `sum(1)(2)` finally calls `function(b)` providing `2`, and it passes the call to the original multi-argument `sum`.
-
-More advanced implementations of currying like [_.curry](https://lodash.com/docs#curry) from lodash library do something more sophisticated. They return a wrapper that allows a function to be called normally when all arguments are supplied *or* returns a partial otherwise.
-
-```js
-function curry(f) {
- return function(...args) {
- // if args.length == f.length (as many arguments as f has),
- // then pass the call to f
- // otherwise return a partial function that fixes args as first arguments
- };
-}
-```
-
-## Currying? What for?
-
-To understand the benefits we definitely need a worthy real-life example. Advanced currying allows the function to be both callable normally and get partials.
-
-For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions also have many other useful features like: sending it over the network or filtering:
-
-```js
-function log(date, importance, message) {
- alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
-}
-```
-
-Let's curry it!
-
-```js
-log = _.curry(log);
-```
-
-After that `log` still works the normal way:
-
-```js
-log(new Date(), "DEBUG", "some debug");
-```
-
-...But also can be called in the curried form:
-
-```js
-log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
-```
-
-Let's get a convenience function for today's logs:
-
-```js
-// todayLog will be the partial of log with fixed first argument
-let todayLog = log(new Date());
-
-// use it
-todayLog("INFO", "message"); // [HH:mm] INFO message
-```
-
-And now a convenience function for today's debug messages:
-
-```js
-let todayDebug = todayLog("DEBUG");
-
-todayDebug("message"); // [HH:mm] DEBUG message
-```
-
-So:
-1. We didn't lose anything after currying: `log` is still callable normally.
-2. We were able to generate partial functions that are convenient in many cases.
-
-## Advanced curry implementation
-
-In case you're interested, here's the "advanced" curry implementation that we could use above, it's pretty short:
-
-```js run
-function curry(func) {
-
- return function curried(...args) {
- if (args.length >= func.length) {
- return func.apply(this, args);
- } else {
- return function(...args2) {
- return curried.apply(this, args.concat(args2));
- }
- }
- };
-
-}
-
-function sum(a, b, c) {
- return a + b + c;
-}
-
-let curriedSum = curry(sum);
-
-// still callable normally
-alert( curriedSum(1, 2, 3) ); // 6
-
-// get the partial with curried(1) and call it with 2 other arguments
-alert( curriedSum(1)(2,3) ); // 6
-
-// full curried form
-alert( curriedSum(1)(2)(3) ); // 6
-```
-
-The new `curry` may look complicated, but it's actually easy to understand.
-
-The result of `curry(func)` is the wrapper `curried` that looks like this:
-
-```js
-// func is the function to transform
-function curried(...args) {
- if (args.length >= func.length) { // (1)
- return func.apply(this, args);
- } else {
- return function pass(...args2) { // (2)
- return curried.apply(this, args.concat(args2));
- }
- }
-};
-```
-
-When we run it, there are two branches:
-
-1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it.
-2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result.
-
-For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`.
-
-For the call `curried(1)(2)(3)`:
-
-1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`.
-2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together.
-
- As the argument count is still less than 3, `curry` returns `pass`.
-3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function.
-
-If that's still not obvious, just trace the calls sequence in your mind or on the paper.
-
-```smart header="Fixed-length functions only"
-The currying requires the function to have a known fixed number of arguments.
-```
-
-```smart header="A little more than currying"
-By definition, currying should convert `sum(a, b, c)` into `sum(a)(b)(c)`.
-
-But most implementations of currying in JavaScript are advanced, as described: they also keep the function callable in the multi-argument variant.
-```
-
-## Summary
-
-- When we fix some arguments of an existing function, the resulting (less universal) function is called *a partial*. We can use `bind` to get a partial, but there are other ways also.
-
- Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it.
-
-- *Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough.
-
- Currying is great when we want easy partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`.
diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md
index 1ade1a41..abc5dd80 100644
--- a/1-js/06-advanced-functions/12-arrow-functions/article.md
+++ b/1-js/06-advanced-functions/12-arrow-functions/article.md
@@ -2,7 +2,7 @@
Let's revisit arrow functions.
-Arrow functions are not just a "shorthand" for writing small stuff.
+Arrow functions are not just a "shorthand" for writing small stuff. They have some very specific and useful features.
JavaScript is full of situations where we need to write a small function, that's executed somewhere else.
@@ -14,7 +14,7 @@ For instance:
It's in the very spirit of JavaScript to create a function and pass it somewhere.
-And in such functions we usually don't want to leave the current context.
+And in such functions we usually don't want to leave the current context. That's where arrow functions come in handy.
## Arrow functions have no "this"
diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md
index c44e5650..8ac5fd0d 100644
--- a/1-js/07-object-properties/01-property-descriptors/article.md
+++ b/1-js/07-object-properties/01-property-descriptors/article.md
@@ -63,7 +63,7 @@ Object.defineProperty(obj, propertyName, descriptor)
```
`obj`, `propertyName`
-: The object and property to work on.
+: The object and its property to apply the descriptor.
`descriptor`
: Property descriptor to apply.
@@ -116,31 +116,34 @@ Object.defineProperty(user, "name", {
});
*!*
-user.name = "Pete"; // Error: Cannot assign to read only property 'name'...
+user.name = "Pete"; // Error: Cannot assign to read only property 'name'
*/!*
```
Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours.
-Here's the same operation, but for the case when a property doesn't exist:
+```smart header="Errors appear only in strict mode"
+In the non-strict mode, no errors occur when writing to read-only properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
+```
+
+Here's the same example, but the property is created from scratch:
```js run
let user = { };
Object.defineProperty(user, "name", {
*!*
- value: "Pete",
+ value: "John",
// for new properties need to explicitly list what's true
enumerable: true,
configurable: true
*/!*
});
-alert(user.name); // Pete
-user.name = "Alice"; // Error
+alert(user.name); // John
+user.name = "Pete"; // Error
```
-
## Non-enumerable
Now let's add a custom `toString` to `user`.
@@ -239,10 +242,6 @@ Object.defineProperty(user, "name", {writable: true}); // Error
*/!*
```
-```smart header="Errors appear only in use strict"
-In the non-strict mode, no errors occur when writing to read-only properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
-```
-
## Object.defineProperties
There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
@@ -298,14 +297,13 @@ Property descriptors work at the level of individual properties.
There are also methods that limit access to the *whole* object:
[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions)
-: Forbids to add properties to the object.
+: Forbids the addition of new properties to the object.
[Object.seal(obj)](mdn:js/Object/seal)
-: Forbids to add/remove properties, sets for all existing properties `configurable: false`.
+: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties.
[Object.freeze(obj)](mdn:js/Object/freeze)
-: Forbids to add/remove/change properties, sets for all existing properties `configurable: false, writable: false`.
-
+: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties.
And also there are tests for them:
[Object.isExtensible(obj)](mdn:js/Object/isExtensible)
diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md
index 43cd5ae6..78fab9a6 100644
--- a/1-js/07-object-properties/02-property-accessors/article.md
+++ b/1-js/07-object-properties/02-property-accessors/article.md
@@ -3,7 +3,7 @@
There are two kinds of properties.
-The first kind is *data properties*. We already know how to work with them. Actually, all properties that we've been using till now were data properties.
+The first kind is *data properties*. We already know how to work with them. All properties that we've been using till now were data properties.
The second type of properties is something new. It's *accessor properties*. They are essentially functions that work on getting and setting a value, but look like regular properties to an external code.
@@ -34,7 +34,7 @@ let user = {
};
```
-Now we want to add a "fullName" property, that should be "John Smith". Of course, we don't want to copy-paste existing information, so we can implement it as an accessor:
+Now we want to add a `fullName` property, that should be `"John Smith"`. Of course, we don't want to copy-paste existing information, so we can implement it as an accessor:
```js run
let user = {
@@ -55,7 +55,19 @@ alert(user.fullName); // John Smith
From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes.
-As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error.
+As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error:
+
+```js run
+let user = {
+ get fullName() {
+ return `...`;
+ }
+};
+
+*!*
+user.fullName = "Test"; // Error (property has only a getter)
+*/!*
+```
Let's fix it by adding a setter for `user.fullName`:
@@ -82,15 +94,10 @@ alert(user.name); // Alice
alert(user.surname); // Cooper
```
-Now we have a "virtual" property. It is readable and writable, but in fact does not exist.
+As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist.
-```smart header="Accessor properties are only accessible with get/set"
-Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data properety any more.
-
-- If there's a getter -- we can read `object.prop`, othrewise we can't.
-- If there's a setter -- we can set `object.prop=...`, othrewise we can't.
-
-And in either case we can't `delete` an accessor property.
+```smart header="No support for `delete`"
+An attempt to `delete` on accessor property causes an error.
```
@@ -100,7 +107,7 @@ Descriptors for accessor properties are different -- as compared with data prope
For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions.
-So an accessor descriptor may have:
+That is, an accessor descriptor may have:
- **`get`** -- a function without arguments, that works when a property is read,
- **`set`** -- a function with one argument, that is called when the property is set,
@@ -132,7 +139,7 @@ alert(user.fullName); // John Smith
for(let key in user) alert(key); // name, surname
```
-Please note once again that a property can be either an accessor or a data property, not both.
+Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both.
If we try to supply both `get` and `value` in the same descriptor, there will be an error:
@@ -151,9 +158,9 @@ Object.defineProperty({}, 'prop', {
## Smarter getters/setters
-Getters/setters can be used as wrappers over "real" property values to gain more control over them.
+Getters/setters can be used as wrappers over "real" property values to gain more control over operations with them.
-For instance, if we want to forbid too short names for `user`, we can store `name` in a special property `_name`. And filter assignments in the setter:
+For instance, if we want to forbid too short names for `user`, we can have a setter `name` and keep the value in a separate property `_name`:
```js run
let user = {
@@ -176,14 +183,16 @@ alert(user.name); // Pete
user.name = ""; // Name is too short...
```
-Technically, the external code may still access the name directly by using `user._name`. But there is a widely known agreement that properties starting with an underscore `"_"` are internal and should not be touched from outside the object.
+So, the name is stored in `_name` property, and the access is done via getter and setter.
+
+Technically, external code is able to access the name directly by using `user._name`. But there is a widely known convention that properties starting with an underscore `"_"` are internal and should not be touched from outside the object.
## Using for compatibility
-One of the great ideas behind getters and setters -- they allow to take control over a "normal" data property and tweak it at any moment.
+One of the great uses of accessors -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior.
-For instance, we started implementing user objects using data properties `name` and `age`:
+Imagine, we started implementing user objects using data properties `name` and `age`:
```js
function User(name, age) {
@@ -209,9 +218,11 @@ let john = new User("John", new Date(1992, 6, 1));
Now what to do with the old code that still uses `age` property?
-We can try to find all such places and fix them, but that takes time and can be hard to do if that code is written by other people. And besides, `age` is a nice thing to have in `user`, right? In some places it's just what we want.
+We can try to find all such places and fix them, but that takes time and can be hard to do if that code is used by many other people. And besides, `age` is a nice thing to have in `user`, right?
-Adding a getter for `age` mitigates the problem:
+Let's keep it.
+
+Adding a getter for `age` solves the problem:
```js run no-beautify
function User(name, birthday) {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md
index 002b24b8..421b57e0 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md
@@ -6,7 +6,7 @@ importance: 5
The task has two parts.
-We have an object:
+We have objects:
```js
let head = {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md
index c7d147b9..4d6ea265 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md
@@ -3,4 +3,5 @@
That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`.
Property lookup and execution are two different things.
-The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`
+
+The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`.
diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md
index fad4b886..bd412f12 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md
@@ -10,7 +10,7 @@ Let's look carefully at what's going on in the call `speedy.eat("apple")`.
So all hamsters share a single stomach!
-Every time the `stomach` is taken from the prototype, then `stomach.push` modifies it "at place".
+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.
Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`:
@@ -77,4 +77,4 @@ alert( speedy.stomach ); // apple
alert( lazy.stomach ); //
```
-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.
+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.
diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md
index 1641de23..5895a0b3 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/article.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/article.md
@@ -10,9 +10,9 @@ For instance, we have a `user` object with its properties and methods, and want
In JavaScript, objects have a special hidden property `[[Prototype]]` (as named in the specification), that is either `null` or references another object. That object is called "a prototype":
-
+
-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.
+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.
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
@@ -66,7 +66,7 @@ Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
-
+
Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` prototypically inherits from `animal`".
@@ -97,11 +97,10 @@ rabbit.walk(); // Animal walk
The method is automatically taken from the prototype, like this:
-
+
The prototype chain can be longer:
-
```js run
let animal = {
eats: true,
@@ -129,12 +128,12 @@ longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)
```
-
+
-There are actually only two limitations:
+There are only two limitations:
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
-2. The value of `__proto__` can be either an object or `null`, other types (like primitives) are ignored.
+2. The value of `__proto__` can be either an object or `null`. Other types are ignored.
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
@@ -169,9 +168,9 @@ rabbit.walk(); // Rabbit! Bounce-bounce!
From now on, `rabbit.walk()` call finds the method immediately in the object and executes it, without using the prototype:
-
+
-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.
+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.
For that reason `admin.fullName` works correctly in the code below:
@@ -245,16 +244,86 @@ alert(animal.isSleeping); // undefined (no such property in the prototype)
The resulting picture:
-
+
-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.
+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.
As a result, methods are shared, but the object state is not.
+## for..in loop
+
+The `for..in` loops over inherited properties too.
+
+For instance:
+
+```js run
+let animal = {
+ eats: true
+};
+
+let rabbit = {
+ jumps: true,
+ __proto__: animal
+};
+
+*!*
+// Object.keys only return own keys
+alert(Object.keys(rabbit)); // jumps
+*/!*
+
+*!*
+// for..in loops over both own and inherited keys
+for(let prop in rabbit) alert(prop); // jumps, then eats
+*/!*
+```
+
+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`.
+
+So we can filter out inherited properties (or do something else with them):
+
+```js run
+let animal = {
+ eats: true
+};
+
+let rabbit = {
+ jumps: true,
+ __proto__: animal
+};
+
+for(let prop in rabbit) {
+ let isOwn = rabbit.hasOwnProperty(prop);
+
+ if (isOwn) {
+ alert(`Our: ${prop}`); // Our: jumps
+ } else {
+ alert(`Inherited: ${prop}`); // Inherited: eats
+ }
+}
+```
+
+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:
+
+
+
+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.
+
+...But why `hasOwnProperty` does not appear in `for..in` loop, like `eats` and `jumps`, if it lists all inherited properties.
+
+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.
+
+```smart header="Almost all other key/value-getting methods ignore inherited properties"
+Almost all other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties.
+
+They only operate on the object itself. Properties from the prototype are *not* taken into account.
+```
+
## Summary
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon).
- The object referenced by `[[Prototype]]` is called a "prototype".
-- 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).
+- 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 for act directly on the object, they don't use the prototype (assuming it's a data property, not is a setter).
- 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.
+- The `for..in` loop iterates over both own and inherited properties. All other key/value-getting methods only operate on the object itself.
diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png
deleted file mode 100644
index d0a905b3..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg
new file mode 100644
index 00000000..fe4c2cac
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png
deleted file mode 100644
index 91a2d084..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png
deleted file mode 100644
index 2b07f76d..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg
new file mode 100644
index 00000000..3e81f262
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png
deleted file mode 100644
index b3976f96..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png
deleted file mode 100644
index 3c122d5f..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg
new file mode 100644
index 00000000..3cf5c4c7
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png
deleted file mode 100644
index 35db68f1..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png
deleted file mode 100644
index 33e71d8f..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg
new file mode 100644
index 00000000..acd42063
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png
deleted file mode 100644
index 29d6883c..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png
deleted file mode 100644
index 3b26a582..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg
new file mode 100644
index 00000000..ebdef958
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png
deleted file mode 100644
index 66aaa501..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png
deleted file mode 100644
index eae25e0d..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg
new file mode 100644
index 00000000..735e1f2b
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png
deleted file mode 100644
index 39194571..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png
deleted file mode 100644
index dae56a32..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg
new file mode 100644
index 00000000..433bc613
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png
deleted file mode 100644
index 6443eee6..00000000
Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg
new file mode 100644
index 00000000..d32585b4
--- /dev/null
+++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md
index 771e3061..ebbdf3a7 100644
--- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md
+++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md
@@ -7,7 +7,7 @@ Answers:
2. `false`.
- Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object is referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`.
+ Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`.
So when we change its content through one reference, it is visible through the other one.
diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md
index 6de6e03e..29b3773e 100644
--- a/1-js/08-prototypes/02-function-prototype/article.md
+++ b/1-js/08-prototypes/02-function-prototype/article.md
@@ -36,14 +36,14 @@ Setting `Rabbit.prototype = animal` literally states the following: "When a `new
That's the resulting picture:
-
+
On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
```smart header="`F.prototype` only used at `new F` time"
-`F.prototype` is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift".
+`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift".
-After the creation, `F.prototype` may change, new objects created by `new F` will have another `[[Prototype]]`, but already existing objects keep the old one.
+If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one.
```
## Default F.prototype, constructor property
@@ -62,7 +62,7 @@ Rabbit.prototype = { constructor: Rabbit };
*/
```
-
+
We can check it:
@@ -86,7 +86,7 @@ let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}
alert(rabbit.constructor == Rabbit); // true (from prototype)
```
-
+
We can use `constructor` property to create a new object using the same constructor as the existing one.
@@ -160,9 +160,9 @@ In this chapter we briefly described the way of setting a `[[Prototype]]` for ob
Everything is quite simple, just few notes to make things clear:
-- 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.
-- The value of `F.prototype` should be either an object or null: other values won't work.
-- The `"prototype"` property only has such a special effect when is set to a constructor function, and invoked with `new`.
+- The `F.prototype` property (don't mess with `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called.
+- The value of `F.prototype` should be either an object or `null`: other values won't work.
+- The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`.
On regular objects the `prototype` is nothing special:
```js
diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png
deleted file mode 100644
index 92f80cea..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg
new file mode 100644
index 00000000..35cdc61f
--- /dev/null
+++ b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png
deleted file mode 100644
index d8c83f6e..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png
deleted file mode 100644
index 02f0e747..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png
deleted file mode 100644
index 7abe2c93..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png
deleted file mode 100644
index 03737f2d..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png
deleted file mode 100644
index a2e9390e..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1.png b/1-js/08-prototypes/02-function-prototype/object-prototype-1.png
deleted file mode 100644
index b0ff1532..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype-1.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png b/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png
deleted file mode 100644
index 5d4365cf..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype.png b/1-js/08-prototypes/02-function-prototype/object-prototype.png
deleted file mode 100644
index 581105aa..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png b/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png
deleted file mode 100644
index 4f0e5330..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png
deleted file mode 100644
index 2a745e8f..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg
new file mode 100644
index 00000000..3489ecdd
--- /dev/null
+++ b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png
deleted file mode 100644
index 609ac141..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png
deleted file mode 100644
index e1758e9f..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png
deleted file mode 100644
index 2d282a9d..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png
deleted file mode 100644
index fe71481c..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png and /dev/null differ
diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg
new file mode 100644
index 00000000..3e11f275
--- /dev/null
+++ b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png
deleted file mode 100644
index 09f7631f..00000000
Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index a3fdfc6c..66be00ca 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -2,7 +2,7 @@
The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
-We'll see how it is for plain objects first, and then for more complex ones.
+First we'll see at the details, and then how to use it for adding new capabilities to built-in objects.
## Object.prototype
@@ -15,17 +15,17 @@ alert( obj ); // "[object Object]" ?
Where's the code that generates the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty!
-...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is a built-in object constructor function. And that function has `Object.prototype` that references a huge object with `toString` and other functions.
+...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` is a built-in object constructor function, with its own `prototype` referencing a huge object with `toString` and other methods.
-Like this (all that is built-in):
+Here's what's going on:
-
+
-When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` by the rule that we've discussed in the previous chapter:
+When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` according to the rule that we discussed in the previous chapter:
-
+
-Afterwards when `obj.toString()` is called -- the method is taken from `Object.prototype`.
+So then when `obj.toString()` is called the method is taken from `Object.prototype`.
We can check it like this:
@@ -36,7 +36,7 @@ alert(obj.__proto__ === Object.prototype); // true
// obj.toString === obj.__proto__.toString == Object.prototype.toString
```
-Please note that there is no additional `[[Prototype]]` in the chain above `Object.prototype`:
+Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`:
```js run
alert(Object.prototype.__proto__); // null
@@ -46,13 +46,13 @@ alert(Object.prototype.__proto__); // null
Other built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes.
-For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So the array data is written into the new object, and `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient.
+For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient.
-By specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects".
+By specification, all of the built-in prototypes have `Object.prototype` on the top. That's why some people say that "everything inherits from objects".
Here's the overall picture (for 3 built-ins to fit):
-
+
Let's check the prototypes manually:
@@ -79,14 +79,14 @@ alert(arr); // 1,2,3 <-- the result of Array.prototype.toString
As we've seen before, `Object.prototype` has `toString` as well, but `Array.prototype` is closer in the chain, so the array variant is used.
-
+
-In-browser tools like Chrome developer console also show inheritance (may need to use `console.dir` for built-in objects):
+In-browser tools like Chrome developer console also show inheritance (`console.dir` may need to be used for built-in objects):

-Other built-in objects also work the same way. Even functions. They are objects of a built-in `Function` constructor, and their methods: `call/apply` and others are taken from `Function.prototype`. Functions have their own `toString` too.
+Other built-in objects also work the same way. Even functions -- they are objects of a built-in `Function` constructor, and their methods (`call`/`apply` and others) are taken from `Function.prototype`. Functions have their own `toString` too.
```js run
function f() {}
@@ -119,17 +119,17 @@ String.prototype.show = function() {
"BOOM!".show(); // BOOM!
```
-During the process of development we may have ideas which new built-in methods we'd like to have. And there may be a slight temptation to add them to native prototypes. But that is generally a bad idea.
+During the process of development, we may have ideas for new built-in methods we'd like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea.
```warn
-Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them overwrites the other one.
+Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the method of the other.
-So, generally modifying a native prototypeis considered a bad idea.
+So, generally, modifying a native prototype is considered a bad idea.
```
-**In modern programming, there is only one case when modifying native prototypes is approved. That's polyfilling.**
+**In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.**
-Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but not yet supported by current JavaScript engine .
+Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but not yet supported by current JavaScript engine.
Then we may implement it manually and populate the built-in prototype with it.
@@ -144,7 +144,7 @@ if (!String.prototype.repeat) { // if there's no such method
// actually, the code should be a little bit more complex than that
// (the full algorithm is in the specification)
- // but even an imperfect polyfill is often considered good enough
+ // but even an imperfect polyfill is often considered good enough for use
return new Array(n + 1).join(this);
};
}
@@ -161,7 +161,7 @@ That's when we take a method from one object and copy it into another.
Some methods of native prototypes are often borrowed.
-For instance, if we're making an array-like object, we may want to copy some array methods to it.
+For instance, if we're making an array-like object, we may want to copy some `Array` methods to it.
E.g.
@@ -181,7 +181,7 @@ alert( obj.join(',') ); // Hello,world!
It works, because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property, it doesn't check that the object is indeed the array. And many built-in methods are like that.
-Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, then all `Array` methods are automatically available in `obj`.
+Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, so all `Array` methods are automatically available in `obj`.
But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time.
@@ -192,5 +192,5 @@ Borrowing methods is flexible, it allows to mix functionality from different obj
- All built-in objects follow the same pattern:
- The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype` etc).
- The object itself stores only the data (array items, object properties, the date).
-- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. There are no wrapper objects only for `undefined` and `null`.
+- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects.
- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable cause is when we add-in a new standard, but not yet supported by the engine JavaScript method.
diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png
deleted file mode 100644
index 92f80cea..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg
new file mode 100644
index 00000000..35cdc61f
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png
deleted file mode 100644
index d8c83f6e..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png
deleted file mode 100644
index 02f0e747..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg
new file mode 100644
index 00000000..770c908c
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png
deleted file mode 100644
index 7abe2c93..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png
deleted file mode 100644
index 03737f2d..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg
new file mode 100644
index 00000000..4989df56
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png
deleted file mode 100644
index a2e9390e..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png
deleted file mode 100644
index b0ff1532..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg
new file mode 100644
index 00000000..38c33cae
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png
deleted file mode 100644
index 5d4365cf..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png
deleted file mode 100644
index 9115d5f3..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg
new file mode 100644
index 00000000..858f8317
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png
deleted file mode 100644
index 3120a8d5..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.png b/1-js/08-prototypes/03-native-prototypes/object-prototype.png
deleted file mode 100644
index 581105aa..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg
new file mode 100644
index 00000000..8d3d0bee
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png
deleted file mode 100644
index 4f0e5330..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png
deleted file mode 100644
index 2a745e8f..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg
new file mode 100644
index 00000000..3489ecdd
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png
deleted file mode 100644
index 609ac141..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png
deleted file mode 100644
index fe71481c..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png and /dev/null differ
diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg
new file mode 100644
index 00000000..3e11f275
--- /dev/null
+++ b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png
deleted file mode 100644
index 09f7631f..00000000
Binary files a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md
index 3a925ab1..8a71dbf1 100644
--- a/1-js/08-prototypes/04-prototype-methods/article.md
+++ b/1-js/08-prototypes/04-prototype-methods/article.md
@@ -3,7 +3,7 @@
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
-The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the Javascript standard).
+The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).
The modern methods are:
@@ -26,6 +26,7 @@ let rabbit = Object.create(animal);
*/!*
alert(rabbit.eats); // true
+
*!*
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
*/!*
@@ -72,19 +73,19 @@ That's for historical reasons.
- The `"prototype"` property of a constructor function works since very ancient times.
- 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.
-- 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.
+- 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.
As of now we have all these ways at our disposal.
-Why `__proto__` was replaced by the functions? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer.
+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.
-```warn header="Don't reset `[[Prototype]]` unless the speed doesn't matter"
+```warn header="Don't change `[[Prototype]]` on existing objects if speed matters"
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, it breaks internal optimizations for object property access operations. So evade it unless you know what you're doing, or Javascript speed totally doesn't matter for you.
+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, it breaks internal optimizations for object property access operations. So evade it unless you know what you're doing, or JavaScript speed totally doesn't matter for you.
```
-## "Very plain" objects
+## "Very plain" objects [#very-plain]
As we know, objects can be used as associative arrays to store key/value pairs.
@@ -107,11 +108,11 @@ That shouldn't surprise us. The `__proto__` property is special: it must be eith
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!
-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.
+Here the consequences are not terrible. But in other cases, we may be assigning object values, then the prototype may indeed be changed. As the result, the execution will go wrong in totally unexpected ways.
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.
-Unexpected things also may happen when accessing `toString` property -- that's a function by default, and other built-in properties.
+Unexpected things also may happen when assigning to `toString` -- that's a function by default, and other built-in methods.
How to evade the problem?
@@ -121,7 +122,7 @@ But `Object` also can serve us well here, because language creators gave a thoug
The `__proto__` is not a property of an object, but an accessor property of `Object.prototype`:
-
+
So, if `obj.__proto__` is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`.
@@ -142,7 +143,7 @@ alert(obj[key]); // "some value"
`Object.create(null)` creates an empty object without a prototype (`[[Prototype]]` is `null`):
-
+
So, there is no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right.
@@ -160,7 +161,7 @@ alert(obj); // Error (no toString)
...But that's usually fine for associative arrays.
-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:
+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:
```js run
@@ -179,7 +180,7 @@ Modern methods to setup and directly access the prototype are:
- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter).
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter).
-The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys in to an object. Just because a user may enter "__proto__" as the key, and there'll be an error with hopefully easy, but generally unpredictable consequences.
+The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys in to 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.
So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that.
@@ -189,15 +190,16 @@ Also, `Object.create` provides an easy way to shallow-copy an object with all de
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
```
-
-- [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.
-- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
-- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
-- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names.
-- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
-
We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just as other methods.
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.
+Other methods:
+
+- [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.
+- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic keys.
+- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string keys.
+- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own keys.
+- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) key named `key`.
+
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`.
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png
deleted file mode 100644
index 8f26210d..00000000
Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png and /dev/null differ
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg
new file mode 100644
index 00000000..86d09ae0
--- /dev/null
+++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png
deleted file mode 100644
index e42d3947..00000000
Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png
deleted file mode 100644
index 9115d5f3..00000000
Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png and /dev/null differ
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg
new file mode 100644
index 00000000..858f8317
--- /dev/null
+++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png
deleted file mode 100644
index 3120a8d5..00000000
Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png and /dev/null differ
diff --git a/1-js/08-prototypes/05-getting-all-properties/article.md b/1-js/08-prototypes/05-getting-all-properties/article.md
deleted file mode 100644
index 3339066d..00000000
--- a/1-js/08-prototypes/05-getting-all-properties/article.md
+++ /dev/null
@@ -1,83 +0,0 @@
-
-# Getting all properties
-
-There are many ways to get keys/values from an object.
-
-Most of them operate on the object itself, excluding the prototype, let's recall them:
-
-- [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*.
-
-If we want symbolic properties:
-
-- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
-
-If we want non-enumerable properties:
-
-- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
-
-If we want *all* properties:
-
-- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names.
-
-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.
-
-## for..in loop
-
-The `for..in` loop is different: it loops over inherited properties too.
-
-For instance:
-
-```js run
-let animal = {
- eats: true
-};
-
-let rabbit = {
- jumps: true,
- __proto__: animal
-};
-
-*!*
-// only own keys
-alert(Object.keys(rabbit)); // jumps
-*/!*
-
-*!*
-// inherited keys too
-for(let prop in rabbit) alert(prop); // jumps, then eats
-*/!*
-```
-
-If that's no 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`.
-
-So we can filter out inherited properties (or do something else with them):
-
-```js run
-let animal = {
- eats: true
-};
-
-let rabbit = {
- jumps: true,
- __proto__: animal
-};
-
-for(let prop in rabbit) {
- let isOwn = rabbit.hasOwnProperty(prop);
- alert(`${prop}: ${isOwn}`); // jumps: true, then eats: false
-}
-```
-
-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:
-
-
-
-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.
-
-...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.
-
-## Summary
-
-Most methods ignore inherited properties, with a notable exception of `for..in`.
-
-For the latter we can use [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
diff --git a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object.png b/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object.png
deleted file mode 100644
index e1758e9f..00000000
Binary files a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object.png and /dev/null differ
diff --git a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object@2x.png b/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object@2x.png
deleted file mode 100644
index 2d282a9d..00000000
Binary files a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object@2x.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/1-inheritance-error-assign/solution.md b/1-js/09-classes/01-class-patterns/1-inheritance-error-assign/solution.md
deleted file mode 100644
index 55f945ca..00000000
--- a/1-js/09-classes/01-class-patterns/1-inheritance-error-assign/solution.md
+++ /dev/null
@@ -1,46 +0,0 @@
-Here's the line with the error:
-
-```js
-Rabbit.prototype = Animal.prototype;
-```
-
-Here `Rabbit.prototype` and `Animal.prototype` become the same object. So methods of both classes become mixed in that object.
-
-As a result, `Rabbit.prototype.walk` overwrites `Animal.prototype.walk`, so all animals start to bounce:
-
-```js run
-function Animal(name) {
- this.name = name;
-}
-
-Animal.prototype.walk = function() {
- alert(this.name + ' walks');
-};
-
-function Rabbit(name) {
- this.name = name;
-}
-
-*!*
-Rabbit.prototype = Animal.prototype;
-*/!*
-
-Rabbit.prototype.walk = function() {
- alert(this.name + " bounces!");
-};
-
-*!*
-let animal = new Animal("pig");
-animal.walk(); // pig bounces!
-*/!*
-```
-
-The correct variant would be:
-
-```js
-Rabbit.prototype.__proto__ = Animal.prototype;
-// or like this:
-Rabbit.prototype = Object.create(Animal.prototype);
-```
-
-That makes prototypes separate, each of them stores methods of the corresponding class, but `Rabbit.prototype` inherits from `Animal.prototype`.
diff --git a/1-js/09-classes/01-class-patterns/1-inheritance-error-assign/task.md b/1-js/09-classes/01-class-patterns/1-inheritance-error-assign/task.md
deleted file mode 100644
index ee486c3d..00000000
--- a/1-js/09-classes/01-class-patterns/1-inheritance-error-assign/task.md
+++ /dev/null
@@ -1,29 +0,0 @@
-importance: 5
-
----
-
-# An error in the inheritance
-
-Find an error in the prototypal inheritance below.
-
-What's wrong? What are consequences going to be?
-
-```js
-function Animal(name) {
- this.name = name;
-}
-
-Animal.prototype.walk = function() {
- alert(this.name + ' walks');
-};
-
-function Rabbit(name) {
- this.name = name;
-}
-
-Rabbit.prototype = Animal.prototype;
-
-Rabbit.prototype.walk = function() {
- alert(this.name + " bounces!");
-};
-```
diff --git a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.md b/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.md
deleted file mode 100644
index 300b25d9..00000000
--- a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.md
+++ /dev/null
@@ -1 +0,0 @@
-Please note that properties that were internal in functional style (`template`, `timer`) and the internal method `render` are marked private with the underscore `_`.
diff --git a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.view/clock.js b/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.view/clock.js
deleted file mode 100644
index bdf7bb72..00000000
--- a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.view/clock.js
+++ /dev/null
@@ -1,32 +0,0 @@
-function Clock({ template }) {
- this.template = template;
-}
-
-Clock.prototype.render = function() {
- let date = new Date();
-
- let hours = date.getHours();
- if (hours < 10) hours = '0' + hours;
-
- let mins = date.getMinutes();
- if (mins < 10) mins = '0' + mins;
-
- let secs = date.getSeconds();
- if (secs < 10) secs = '0' + secs;
-
- let output = this.template
- .replace('h', hours)
- .replace('m', mins)
- .replace('s', secs);
-
- console.log(output);
-};
-
-Clock.prototype.stop = function() {
- clearInterval(this.timer);
-};
-
-Clock.prototype.start = function() {
- this.render();
- this.timer = setInterval(() => this.render(), 1000);
-};
diff --git a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.view/index.html b/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.view/index.html
deleted file mode 100644
index fdee13d0..00000000
--- a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/solution.view/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- Console clock
-
-
-
-
-
-
-
-
-
-
diff --git a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/source.view/index.html b/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/source.view/index.html
deleted file mode 100644
index fdee13d0..00000000
--- a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/source.view/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- Console clock
-
-
-
-
-
-
-
-
-
-
diff --git a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/task.md b/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/task.md
deleted file mode 100644
index 71131816..00000000
--- a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/task.md
+++ /dev/null
@@ -1,9 +0,0 @@
-importance: 5
-
----
-
-# Rewrite to prototypes
-
-The `Clock` class is written in functional style. Rewrite it using prototypes.
-
-P.S. The clock ticks in the console, open it to see.
diff --git a/1-js/09-classes/01-class-patterns/article.md b/1-js/09-classes/01-class-patterns/article.md
deleted file mode 100644
index 837941cb..00000000
--- a/1-js/09-classes/01-class-patterns/article.md
+++ /dev/null
@@ -1,240 +0,0 @@
-
-# Class patterns
-
-```quote author="Wikipedia"
-In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
-```
-
-There's a special syntax construct and a keyword `class` in JavaScript. But before studying it, we should consider that the term "class" comes from the theory of object-oriented programming. The definition is cited above, and it's language-independent.
-
-In JavaScript there are several well-known programming patterns to make classes even without using the `class` keyword. People talk about "classes" meaning no only those defined with `class`, but also with these patterns.
-
-The `class` construct will be described in the next chapter, but in JavaScript it's a "syntax sugar" and an extension of the prototypal class pattern described here.
-
-
-## Functional class pattern
-
-The constructor function below can be considered a "class" according to the definition:
-
-```js run
-function User(name) {
- this.sayHi = function() {
- alert(name);
- };
-}
-
-let user = new User("John");
-user.sayHi(); // John
-```
-
-It follows all parts of the definition:
-
-1. It is a "program-code-template" for creating objects (callable with `new`).
-2. It provides initial values for the state (`name` from parameters).
-3. It provides methods (`sayHi`).
-
-This is called *functional class pattern*.
-
-In the functional class pattern, local variables and nested functions inside `User`, that are not assigned to `this`, are visible from inside, but not accessible by the outer code.
-
-So we can easily add internal functions and variables, like `calcAge()` here:
-
-```js run
-function User(name, birthday) {
-*!*
- // only visible from other methods inside User
- function calcAge() {
- return new Date().getFullYear() - birthday.getFullYear();
- }
-*/!*
-
- this.sayHi = function() {
- alert(`${name}, age:${calcAge()}`);
- };
-}
-
-let user = new User("John", new Date(2000, 0, 1));
-user.sayHi(); // John, age:17
-```
-
-In this code variables `name`, `birthday` and the function `calcAge()` are internal, *private* to the object. They are only visible from inside of it.
-
-On the other hand, `sayHi` is the external, *public* method. The external code that creates `user` can access it.
-
-This way we can hide internal implementation details and helper methods from the outer code. Only what's assigned to `this` becomes visible outside.
-
-## Factory class pattern
-
-We can create a class without using `new` at all.
-
-Like this:
-
-```js run
-function User(name, birthday) {
- // only visible from other methods inside User
- function calcAge() {
- return new Date().getFullYear() - birthday.getFullYear();
- }
-
- return {
- sayHi() {
- alert(`${name}, age:${calcAge()}`);
- }
- };
-}
-
-*!*
-let user = User("John", new Date(2000, 0, 1));
-*/!*
-user.sayHi(); // John, age:17
-```
-
-As we can see, the function `User` returns an object with public properties and methods. The only benefit of this method is that we can omit `new`: write `let user = User(...)` instead of `let user = new User(...)`. In other aspects it's almost the same as the functional pattern.
-
-## Prototype-based classes
-
-Prototype-based classes are the most important and generally the best. Functional and factory class patterns are rarely used in practice.
-
-Soon you'll see why.
-
-Here's the same class rewritten using prototypes:
-
-```js run
-function User(name, birthday) {
-*!*
- this._name = name;
- this._birthday = birthday;
-*/!*
-}
-
-*!*
-User.prototype._calcAge = function() {
-*/!*
- return new Date().getFullYear() - this._birthday.getFullYear();
-};
-
-User.prototype.sayHi = function() {
- alert(`${this._name}, age:${this._calcAge()}`);
-};
-
-let user = new User("John", new Date(2000, 0, 1));
-user.sayHi(); // John, age:17
-```
-
-The code structure:
-
-- The constructor `User` only initializes the current object state.
-- Methods are added to `User.prototype`.
-
-As we can see, methods are lexically not inside `function User`, they do not share a common lexical environment. If we declare variables inside `function User`, then they won't be visible to methods.
-
-So, there is a widely known agreement that internal properties and methods are prepended with an underscore `"_"`. Like `_name` or `_calcAge()`. Technically, that's just an agreement, the outer code still can access them. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in the external code.
-
-Here are the advantages over the functional pattern:
-
-- In the functional pattern, each object has its own copy of every method. We assign a separate copy of `this.sayHi = function() {...}` and other methods in the constructor.
-- In the prototypal pattern, all methods are in `User.prototype` that is shared between all user objects. An object itself only stores the data.
-
-So the prototypal pattern is more memory-efficient.
-
-...But not only that. Prototypes allow us to setup the inheritance in a really efficient way. Built-in JavaScript objects all use prototypes. Also there's a special syntax construct: "class" that provides nice-looking syntax for them. And there's more, so let's go on with them.
-
-## Prototype-based inheritance for classes
-
-Let's say we have two prototype-based classes.
-
-`Rabbit`:
-
-```js
-function Rabbit(name) {
- this.name = name;
-}
-
-Rabbit.prototype.jump = function() {
- alert(`${this.name} jumps!`);
-};
-
-let rabbit = new Rabbit("My rabbit");
-```
-
-
-
-...And `Animal`:
-
-```js
-function Animal(name) {
- this.name = name;
-}
-
-Animal.prototype.eat = function() {
- alert(`${this.name} eats.`);
-};
-
-let animal = new Animal("My animal");
-```
-
-
-
-Right now they are fully independent.
-
-But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
-
-What does it mean in the language of prototypes?
-
-Right now methods for `rabbit` objects are in `Rabbit.prototype`. We'd like `rabbit` to use `Animal.prototype` as a "fallback", if the method is not found in `Rabbit.prototype`.
-
-So the prototype chain should be `rabbit` -> `Rabbit.prototype` -> `Animal.prototype`.
-
-Like this:
-
-
-
-The code to implement that:
-
-```js run
-// Same Animal as before
-function Animal(name) {
- this.name = name;
-}
-
-// All animals can eat, right?
-Animal.prototype.eat = function() {
- alert(`${this.name} eats.`);
-};
-
-// Same Rabbit as before
-function Rabbit(name) {
- this.name = name;
-}
-
-Rabbit.prototype.jump = function() {
- alert(`${this.name} jumps!`);
-};
-
-*!*
-// setup the inheritance chain
-Rabbit.prototype.__proto__ = Animal.prototype; // (*)
-*/!*
-
-let rabbit = new Rabbit("White Rabbit");
-*!*
-rabbit.eat(); // rabbits can eat too
-*/!*
-rabbit.jump();
-```
-
-The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, let's mention that if the method is not found in `Animal.prototype`, then the search continues in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it.
-
-So here's the full picture:
-
-
-
-## Summary
-
-The term "class" comes from the object-oriented programming. In JavaScript it usually means the functional class pattern or the prototypal pattern. The prototypal pattern is more powerful and memory-efficient, so it's recommended to stick to it.
-
-According to the prototypal pattern:
-1. Methods are stored in `Class.prototype`.
-2. Prototypes inherit from each other.
-
-In the next chapter we'll study `class` keyword and construct. It allows to write prototypal classes shorter and provides some additional benefits.
diff --git a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal-2.png b/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal-2.png
deleted file mode 100644
index 86b1a585..00000000
Binary files a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal-2.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal-2@2x.png b/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal-2@2x.png
deleted file mode 100644
index f44bfb1d..00000000
Binary files a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal-2@2x.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal.png b/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal.png
deleted file mode 100644
index 0da6479d..00000000
Binary files a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal@2x.png b/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal@2x.png
deleted file mode 100644
index ebe8c032..00000000
Binary files a/1-js/09-classes/01-class-patterns/class-inheritance-rabbit-animal@2x.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-1.png b/1-js/09-classes/01-class-patterns/rabbit-animal-independent-1.png
deleted file mode 100644
index f1d31218..00000000
Binary files a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-1.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-1@2x.png b/1-js/09-classes/01-class-patterns/rabbit-animal-independent-1@2x.png
deleted file mode 100644
index 29c878ea..00000000
Binary files a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-1@2x.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-2.png b/1-js/09-classes/01-class-patterns/rabbit-animal-independent-2.png
deleted file mode 100644
index bae44e57..00000000
Binary files a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-2.png and /dev/null differ
diff --git a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-2@2x.png b/1-js/09-classes/01-class-patterns/rabbit-animal-independent-2@2x.png
deleted file mode 100644
index 6197bd29..00000000
Binary files a/1-js/09-classes/01-class-patterns/rabbit-animal-independent-2@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/source.view/clock.js b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js
similarity index 91%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/source.view/clock.js
rename to 1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js
index d701c0ca..0b31cf33 100644
--- a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/source.view/clock.js
+++ b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js
@@ -32,3 +32,7 @@ class Clock {
this.timer = setInterval(() => this.render(), 1000);
}
}
+
+
+let clock = new Clock({template: 'h:m:s'});
+clock.start();
diff --git a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/source.view/clock.js b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js
similarity index 90%
rename from 1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/source.view/clock.js
rename to 1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js
index c4bfaa0f..f1749c8b 100644
--- a/1-js/09-classes/01-class-patterns/2-rewrite-to-prototypes/source.view/clock.js
+++ b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js
@@ -32,3 +32,6 @@ function Clock({ template }) {
};
}
+
+let clock = new Clock({template: 'h:m:s'});
+clock.start();
diff --git a/1-js/09-classes/02-class/1-rewrite-to-class/solution.md b/1-js/09-classes/01-class/1-rewrite-to-class/solution.md
similarity index 100%
rename from 1-js/09-classes/02-class/1-rewrite-to-class/solution.md
rename to 1-js/09-classes/01-class/1-rewrite-to-class/solution.md
diff --git a/1-js/09-classes/02-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md
similarity index 53%
rename from 1-js/09-classes/02-class/1-rewrite-to-class/task.md
rename to 1-js/09-classes/01-class/1-rewrite-to-class/task.md
index a29d347f..05365e41 100644
--- a/1-js/09-classes/02-class/1-rewrite-to-class/task.md
+++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md
@@ -4,6 +4,6 @@ importance: 5
# Rewrite to class
-Rewrite the `Clock` class from prototypes to the modern "class" syntax.
+The `Clock` class is written in functional style. Rewrite it the "class" syntax.
P.S. The clock ticks in the console, open it to see.
diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md
new file mode 100644
index 00000000..3cfcd4cb
--- /dev/null
+++ b/1-js/09-classes/01-class/article.md
@@ -0,0 +1,351 @@
+
+# Class basic syntax
+
+```quote author="Wikipedia"
+In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
+```
+
+In practice, we often need to create many objects of the same kind, like users, or goods or whatever.
+
+As we already know from the chapter , `new function` can help with that.
+
+But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming.
+
+## The "class" syntax
+
+The basic syntax is:
+```js
+class MyClass {
+ // class methods
+ constructor() { ... }
+ method1() { ... }
+ method2() { ... }
+ method3() { ... }
+ ...
+}
+```
+
+Then use `new MyClass()` to create a new object with all the listed methods.
+
+The `constructor()` method is called automatically by `new`, so we can initialize the object there.
+
+For example:
+
+```js run
+class User {
+
+ constructor(name) {
+ this.name = name;
+ }
+
+ sayHi() {
+ alert(this.name);
+ }
+
+}
+
+// Usage:
+let user = new User("John");
+user.sayHi();
+```
+
+When `new User("John")` is called:
+1. A new object is created.
+2. The `constructor` runs with the given argument and assigns `this.name` to it.
+
+...Then we can call object methods, such as `user.sayHi()`.
+
+
+```warn header="No comma between class methods"
+A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error.
+
+The notation here is not to be confused with object literals. Within the class, no commas are required.
+```
+
+## What is a class?
+
+So, what exactly is a `class`? That's not an entirely new language-level entity, as one might think.
+
+Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects.
+
+In JavaScript, a class is a kind of a function.
+
+Here, take a look:
+
+```js run
+class User {
+ constructor(name) { this.name = name; }
+ sayHi() { alert(this.name); }
+}
+
+// proof: User is a function
+*!*
+alert(typeof User); // function
+*/!*
+```
+
+What `class User {...}` construct really does is:
+
+1. Creates a function named `User`, that becomes the result of the class declaration. The function code is taken from the `constructor` method (assumed empty if we don't write such method).
+2. Stores class methods, such as `sayHi`, in `User.prototype`.
+
+Afterwards, for `new User` objects, when we call a method, it's taken from the prototype, just as described in the chapter . So the object has access to class methods.
+
+We can illustrate the result of `class User` declaration as:
+
+
+
+Here's the code to introspect it:
+
+```js run
+class User {
+ constructor(name) { this.name = name; }
+ sayHi() { alert(this.name); }
+}
+
+// class is a function
+alert(typeof User); // function
+
+// ...or, more precisely, the constructor method
+alert(User === User.prototype.constructor); // true
+
+// The methods are in User.prototype, e.g:
+alert(User.prototype.sayHi); // alert(this.name);
+
+// there are exactly two methods in the prototype
+alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
+```
+
+## Not just a syntax sugar
+
+Sometimes people say that `class` is a "syntax sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without `class` keyword at all:
+
+```js run
+// rewriting class User in pure functions
+
+// 1. Create constructor function
+function User(name) {
+ this.name = name;
+}
+// any function prototype has constructor property by default,
+// so we don't need to create it
+
+// 2. Add the method to prototype
+User.prototype.sayHi = function() {
+ alert(this.name);
+};
+
+// Usage:
+let user = new User("John");
+user.sayHi();
+```
+
+The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods.
+
+Although, there are important differences.
+
+1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually.
+
+ Unlike a regular function, a class constructor must be called with `new`:
+
+ ```js run
+ class User {
+ constructor() {}
+ }
+
+ alert(typeof User); // function
+ User(); // Error: Class constructor User cannot be invoked without 'new'
+ ```
+
+ Also, a string representation of a class constructor in most JavaScript engines starts with the "class..."
+
+ ```js run
+ class User {
+ constructor() {}
+ }
+
+ alert(User); // class User { ... }
+ ```
+
+2. Class methods are non-enumerable.
+ A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`.
+
+ That's good, because if we `for..in` over an object, we usually don't want its class methods.
+
+3. Classes always `use strict`.
+ All code inside the class construct is automatically in strict mode.
+
+Besides, `class` syntax brings many other features that we'll explore later.
+
+## Class Expression
+
+Just like functions, classes can be defined inside another expression, passed around, returned, assigned etc.
+
+Here's an example of a class expression:
+
+```js
+let User = class {
+ sayHi() {
+ alert("Hello");
+ }
+};
+```
+
+Similar to Named Function Expressions, class expressions may have a name.
+
+If a class expression has a name, it's visible inside the class only:
+
+```js run
+// "Named Class Expression"
+// (no such term in the spec, but that's similar to Named Function Expression)
+let User = class *!*MyClass*/!* {
+ sayHi() {
+ alert(MyClass); // MyClass name is visible only inside the class
+ }
+};
+
+new User().sayHi(); // works, shows MyClass definition
+
+alert(MyClass); // error, MyClass name isn't visible outside of the class
+```
+
+
+We can even make classes dynamically "on-demand", like this:
+
+```js run
+function makeClass(phrase) {
+ // declare a class and return it
+ return class {
+ sayHi() {
+ alert(phrase);
+ };
+ };
+}
+
+// Create a new class
+let User = makeClass("Hello");
+
+new User().sayHi(); // Hello
+```
+
+
+## Getters/setters, other shorthands
+
+Just like literal objects, classes may include getters/setters, generators, computed properties etc.
+
+Here's an example for `user.name` implemented using `get/set`:
+
+```js run
+class User {
+
+ constructor(name) {
+ // invokes the setter
+ this.name = name;
+ }
+
+*!*
+ get name() {
+*/!*
+ return this._name;
+ }
+
+*!*
+ set name(value) {
+*/!*
+ if (value.length < 4) {
+ alert("Name is too short.");
+ return;
+ }
+ this._name = value;
+ }
+
+}
+
+let user = new User("John");
+alert(user.name); // John
+
+user = new User(""); // Name too short.
+```
+
+The class declaration creates getters and setters in `User.prototype`, like this:
+
+```js
+Object.defineProperties(User.prototype, {
+ name: {
+ get() {
+ return this._name
+ },
+ set(name) {
+ // ...
+ }
+ }
+});
+```
+
+Here's an example with a computed property in brackets `[...]`:
+
+```js run
+class User {
+
+*!*
+ ['say' + 'Hi']() {
+*/!*
+ alert("Hello");
+ }
+
+}
+
+new User().sayHi();
+```
+
+For a generator method, similarly, prepend it with `*`.
+
+## Class properties
+
+```warn header="Old browsers may need a polyfill"
+Class-level properties are a recent addition to the language.
+```
+
+In the example above, `User` only had methods. Let's add a property:
+
+```js run
+class User {
+*!*
+ name = "Anonymous";
+*/!*
+
+ sayHi() {
+ alert(`Hello, ${this.name}!`);
+ }
+}
+
+new User().sayHi();
+```
+
+The property `name` is not placed into `User.prototype`. Instead, it is created by `new` before calling constructor, it's the property of the object itself.
+
+## Summary
+
+The basic class syntax looks like this:
+
+```js
+class MyClass {
+ prop = value; // property
+
+ constructor(...) { // constructor
+ // ...
+ }
+
+ method(...) {} // method
+
+ get something(...) {} // getter method
+ set something(...) {} // setter method
+
+ [Symbol.iterator]() {} // method with computed name (symbol here)
+ // ...
+}
+```
+
+`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and settors are written to `MyClass.prototype`.
+
+In the next chapters we'll learn more about classes, including inheritance and other features.
diff --git a/1-js/09-classes/01-class/class-user.svg b/1-js/09-classes/01-class/class-user.svg
new file mode 100644
index 00000000..5ac0146a
--- /dev/null
+++ b/1-js/09-classes/01-class/class-user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/03-class-inheritance/1-class-constructor-error/solution.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/1-class-constructor-error/solution.md
rename to 1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md
diff --git a/1-js/09-classes/03-class-inheritance/1-class-constructor-error/task.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/1-class-constructor-error/task.md
rename to 1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.md
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md
diff --git a/1-js/09-classes/02-class/1-rewrite-to-class/solution.view/clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/clock.js
similarity index 100%
rename from 1-js/09-classes/02-class/1-rewrite-to-class/solution.view/clock.js
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/clock.js
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.view/index.html b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/index.html
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.view/index.html
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/index.html
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.view/clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/clock.js
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/solution.view/clock.js
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/clock.js
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/source.view/index.html b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/source.view/index.html
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html
diff --git a/1-js/09-classes/03-class-inheritance/2-clock-class-extended/task.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/2-clock-class-extended/task.md
rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md
diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg
new file mode 100644
index 00000000..0a1f4382
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/03-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md
similarity index 98%
rename from 1-js/09-classes/03-class-inheritance/3-class-extend-object/solution.md
rename to 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md
index c1483aa3..fa26ec83 100644
--- a/1-js/09-classes/03-class-inheritance/3-class-extend-object/solution.md
+++ b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md
@@ -71,7 +71,7 @@ By the way, `Function.prototype` has "generic" function methods, like `call`, `b
Here's the picture:
-
+
So, to put it short, there are two differences:
diff --git a/1-js/09-classes/03-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md
similarity index 100%
rename from 1-js/09-classes/03-class-inheritance/3-class-extend-object/task.md
rename to 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md
diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg
new file mode 100644
index 00000000..3412d982
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/03-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md
similarity index 58%
rename from 1-js/09-classes/03-class-inheritance/article.md
rename to 1-js/09-classes/02-class-inheritance/article.md
index 1541a965..108cc11f 100644
--- a/1-js/09-classes/03-class-inheritance/article.md
+++ b/1-js/09-classes/02-class-inheritance/article.md
@@ -1,40 +1,82 @@
# Class inheritance
-Classes can extend one another. There's a nice syntax, technically based on the prototypal inheritance.
+Let's say we have two classes.
-To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`.
+`Animal`:
+
+```js
+class Animal {
+ constructor(name) {
+ this.speed = 0;
+ this.name = name;
+ }
+ run(speed) {
+ this.speed += speed;
+ alert(`${this.name} runs with speed ${this.speed}.`);
+ }
+ stop() {
+ this.speed = 0;
+ alert(`${this.name} stands still.`);
+ }
+}
+
+let animal = new Animal("My animal");
+```
+
+
+
+
+...And `Rabbit`:
+
+```js
+class Rabbit {
+ constructor(name) {
+ this.name = name;
+ }
+ hide() {
+ alert(`${this.name} hides!`);
+ }
+}
+
+let rabbit = new Rabbit("My rabbit");
+```
+
+
+
+
+Right now they are fully independent.
+
+But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
+
+To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`.
Here `Rabbit` inherits from `Animal`:
```js run
class Animal {
-
constructor(name) {
this.speed = 0;
this.name = name;
}
-
run(speed) {
this.speed += speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
-
stop() {
this.speed = 0;
- alert(`${this.name} stopped.`);
+ alert(`${this.name} stands still.`);
}
-
}
+// Inherit from Animal by specifying "extends Animal"
*!*
-// Inherit from Animal
class Rabbit extends Animal {
+*/!*
hide() {
alert(`${this.name} hides!`);
}
}
-*/!*
let rabbit = new Rabbit("White Rabbit");
@@ -42,11 +84,15 @@ rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
```
-The `extends` keyword actually adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before.
+Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do.
-
+Internally, `extends` keyword adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`:
-So now `rabbit` has access both to its own methods and to methods of `Animal`.
+
+
+So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`.
+
+As we can recall from the chapter , JavaScript uses prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods.
````smart header="Any expression is allowed after `extends`"
Class syntax allows to specify not just a class, but any expression after `extends`.
@@ -85,7 +131,6 @@ class Rabbit extends Animal {
}
```
-
...But usually we don't want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.
Classes provide `"super"` keyword for that.
@@ -110,7 +155,7 @@ class Animal {
stop() {
this.speed = 0;
- alert(`${this.name} stopped.`);
+ alert(`${this.name} stands still.`);
}
}
@@ -131,7 +176,7 @@ class Rabbit extends Animal {
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
-rabbit.stop(); // White Rabbit stopped. White rabbit hides!
+rabbit.stop(); // White Rabbit stands still. White rabbit hides!
```
Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process.
@@ -163,7 +208,7 @@ With constructors it gets a little bit tricky.
Till now, `Rabbit` did not have its own `constructor`.
-According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following `constructor` is generated:
+According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated:
```js
class Rabbit extends Animal {
@@ -220,12 +265,12 @@ In JavaScript, there's a distinction between a "constructor function of an inher
The difference is:
-- When a normal constructor runs, it creates an empty object as `this` and continues with it.
-- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job.
+- When a normal constructor runs, it creates an empty object and assigns it to `this`.
+- But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job.
-So if we're making a constructor of our own, then we must call `super`, because otherwise the object with `this` reference to it won't be created. And we'll get an error.
+So if we're making a constructor of our own, then we must call `super`, because otherwise the object for `this` won't be created. And we'll get an error.
-For `Rabbit` to work, we need to call `super()` before using `this`, like here:
+For `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here:
```js run
class Animal {
@@ -261,17 +306,25 @@ alert(rabbit.earLength); // 10
## Super: internals, [[HomeObject]]
+```warn header="Advanced information"
+If you're reading the tutorial for the first time - this section may be skipped.
+
+It's about the internal mechanisms behind inheritance and `super`.
+```
+
Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way.
-First to say, from all that we've learned till now, it's impossible for `super` to work.
+First to say, from all that we've learned till now, it's impossible for `super` to work at all!
-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, how to retrieve the `method`? Naturally, we need to take the `method` from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
+Yeah, indeed, let's ask ourselves, how it should technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, the engine needs to get the `method` from the prototype of the current object. But how?
-Maybe we can get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that doesn't work.
+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.
-Let's try to do it. Without classes, using plain objects for the sake of simplicity.
+Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.
-Here, `rabbit.eat()` should call `animal.eat()` method of the parent object:
+You may skip this part and go below to the `[[HomeObject]]` subsection if you don't want to know the details. That won't harm. Or read on if you're interested in understanding things in-depth.
+
+In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`:
```js run
let animal = {
@@ -338,7 +391,7 @@ So, in both lines `(*)` and `(**)` the value of `this.__proto__` is exactly the
Here's the picture of what happens:
-
+
1. Inside `longEar.eat()`, the line `(**)` calls `rabbit.eat` providing it with `this=longEar`.
```js
@@ -368,18 +421,16 @@ The problem can't be solved by using `this` alone.
To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`.
-**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.**
+When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.
-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.
+Then `super` uses it to resolve the parent prototype and its methods.
-But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility.
-
-Let's see how it works for `super` -- again, using plain objects:
+Let's see how it works, first with plain objects:
```js run
let animal = {
name: "Animal",
- eat() { // [[HomeObject]] == animal
+ eat() { // animal.eat.[[HomeObject]] == animal
alert(`${this.name} eats.`);
}
};
@@ -387,7 +438,7 @@ let animal = {
let rabbit = {
__proto__: animal,
name: "Rabbit",
- eat() { // [[HomeObject]] == rabbit
+ eat() { // rabbit.eat.[[HomeObject]] == rabbit
super.eat();
}
};
@@ -395,19 +446,79 @@ let rabbit = {
let longEar = {
__proto__: rabbit,
name: "Long Ear",
- eat() { // [[HomeObject]] == longEar
+ eat() { // longEar.eat.[[HomeObject]] == longEar
super.eat();
}
};
*!*
+// works correctly
longEar.eat(); // Long Ear eats.
*/!*
```
-Every method remembers its object in the internal `[[HomeObject]]` property. Then `super` uses it to resolve the parent prototype.
+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`.
-`[[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()"`.
+### Methods are not "free"
+
+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`.
+
+The very existance of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever.
+
+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. But with `super` things may go wrong.
+
+Here's the demo of a wrong `super` result after copying:
+
+```js run
+let animal = {
+ sayHi() {
+ console.log(`I'm an animal`);
+ }
+};
+
+// rabbit inherits from animal
+let rabbit = {
+ __proto__: animal,
+ sayHi() {
+ super.sayHi();
+ }
+};
+
+let plant = {
+ sayHi() {
+ console.log("I'm a plant");
+ }
+};
+
+// tree inherits from plant
+let tree = {
+ __proto__: plant,
+*!*
+ sayHi: rabbit.sayHi // (*)
+*/!*
+};
+
+*!*
+tree.sayHi(); // I'm an animal (?!?)
+*/!*
+```
+
+A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong.
+
+The reason is simple:
+- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication?
+- Its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`.
+- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`.
+
+Here's the diagram of what happens:
+
+
+
+### Methods, not function properties
+
+`[[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()"`.
+
+The difference may be non-essential for us, but it's important for JavaScript.
In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work:
@@ -429,3 +540,18 @@ let rabbit = {
rabbit.eat(); // Error calling super (because there's no [[HomeObject]])
*/!*
```
+
+## Summary
+
+1. To extend a class: `class Child extends Parent`:
+ - That means `Child.prototype.__proto__` will be `Parent.prototype`, so methods are inherited.
+2. When overriding a constructor:
+ - We must call parent constructor as `super()` in `Child` constructor before using `this`.
+3. When overriding another method:
+ - We can use `super.method()` in a `Child` method to call `Parent` method.
+4. Internals:
+ - Methods remember their class/object in the internal `[[HomeObject]]` property. That's how `super` resolves parent methods.
+ - So it's not safe to copy a method with `super` from one object to another.
+
+Also:
+- Arrow functions don't have own `this` or `super`, so they transparently fit into the surrounding context.
diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg
new file mode 100644
index 00000000..546aa334
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg
new file mode 100644
index 00000000..3bdda5a0
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg
new file mode 100644
index 00000000..91f82896
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg
new file mode 100644
index 00000000..bf86db77
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg
new file mode 100644
index 00000000..8a5e2503
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg
new file mode 100644
index 00000000..c9c8fea9
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.svg b/1-js/09-classes/02-class-inheritance/this-super-loop.svg
new file mode 100644
index 00000000..342574da
--- /dev/null
+++ b/1-js/09-classes/02-class-inheritance/this-super-loop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class/1-rewrite-to-class/solution.view/index.html b/1-js/09-classes/02-class/1-rewrite-to-class/solution.view/index.html
deleted file mode 100644
index fdee13d0..00000000
--- a/1-js/09-classes/02-class/1-rewrite-to-class/solution.view/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- Console clock
-
-
-
-
-
-
-
-
-
-
diff --git a/1-js/09-classes/02-class/1-rewrite-to-class/source.view/clock.js b/1-js/09-classes/02-class/1-rewrite-to-class/source.view/clock.js
deleted file mode 100644
index 537f7268..00000000
--- a/1-js/09-classes/02-class/1-rewrite-to-class/source.view/clock.js
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-function Clock({ template }) {
- this.template = template;
-}
-
-Clock.prototype.render = function() {
- let date = new Date();
-
- let hours = date.getHours();
- if (hours < 10) hours = '0' + hours;
-
- let mins = date.getMinutes();
- if (mins < 10) mins = '0' + mins;
-
- let secs = date.getSeconds();
- if (secs < 10) secs = '0' + secs;
-
- let output = this.template
- .replace('h', hours)
- .replace('m', mins)
- .replace('s', secs);
-
- console.log(output);
-};
-
-Clock.prototype.stop = function() {
- clearInterval(this.timer);
-};
-
-Clock.prototype.start = function() {
- this.render();
- this.timer = setInterval(() => this.render(), 1000);
-};
diff --git a/1-js/09-classes/02-class/1-rewrite-to-class/source.view/index.html b/1-js/09-classes/02-class/1-rewrite-to-class/source.view/index.html
deleted file mode 100644
index fdee13d0..00000000
--- a/1-js/09-classes/02-class/1-rewrite-to-class/source.view/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- Console clock
-
-
-
-
-
-
-
-
-
-
diff --git a/1-js/09-classes/02-class/article.md b/1-js/09-classes/02-class/article.md
deleted file mode 100644
index dd9284dd..00000000
--- a/1-js/09-classes/02-class/article.md
+++ /dev/null
@@ -1,272 +0,0 @@
-
-# Classes
-
-The "class" construct allows one to define prototype-based classes with a clean, nice-looking syntax. It also introduces great new features which are useful for object-oriented programming.
-
-## The "class" syntax
-
-The `class` syntax is versatile, we'll start with a simple example first.
-
-Here's a prototype-based class `User`:
-
-```js run
-function User(name) {
- this.name = name;
-}
-
-User.prototype.sayHi = function() {
- alert(this.name);
-}
-
-let user = new User("John");
-user.sayHi();
-```
-
-...And here's the same using `class` syntax:
-
-```js run
-class User {
-
- constructor(name) {
- this.name = name;
- }
-
- sayHi() {
- alert(this.name);
- }
-
-}
-
-let user = new User("John");
-user.sayHi();
-```
-
-It's easy to see that these two examples are alike. Be sure to note that methods in a class do not have a comma between them. A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error. The notation here is not to be confused with object literals. Within the class syntactical sugar, no commas are required.
-
-## What is a class?
-
-So, what exactly is a `class`? We may think that it defines a new language-level entity, but that would be wrong.
-
-In Javascript, a class is a kind of a function.
-
-The definition `class User {...}` creates a function under the same name and puts the methods into `User.prototype`. So the structure is similar.
-
-This is demonstrated in the following code, which you can run yourself:
-
-```js run
-class User {
- constructor(name) { this.name = name; }
- sayHi() { alert(this.name); }
-}
-
-*!*
-// proof: User is a function
-alert(typeof User); // function
-*/!*
-
-*!*
-// proof: User is the "constructor" function
-*/!*
-alert(User === User.prototype.constructor); // true
-
-*!*
-// proof: there are two methods in its "prototype"
-*/!*
-alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
-```
-
-Abstractly, we can illustrate this process of `class User` creating a function as:
-
-
-
-`Class` is a special syntax to define a constructor together with its prototype methods. In addition to its basic operation, the `Class` syntax brings many other features with it which we'll explore later.
-
-## Class Expression
-
-Just like functions, classes can be defined inside another expression, passed around, returned etc.
-
-Here's a class-returning function - otherwise known as a "class factory":
-
-```js run
-function makeClass(phrase) {
-*!*
- // declare a class and return it
- return class {
- sayHi() {
- alert(phrase);
- };
- };
-*/!*
-}
-
-let User = makeClass("Hello");
-
-new User().sayHi(); // Hello
-```
-
-That's quite normal if we recall that `class` is just a special form of a function-with-prototype definition.
-
-And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
-
-```js run
-// "Named Class Expression" (alas, no such term, but that's what's going on)
-let User = class *!*MyClass*/!* {
- sayHi() {
- alert(MyClass); // MyClass is visible only inside the class
- }
-};
-
-new User().sayHi(); // works, shows MyClass definition
-
-alert(MyClass); // error, MyClass not visible outside of the class
-```
-
-## Differences between classes and functions
-
-Classes have some differences compared to regular functions:
-
-Constructors require `new`
-: Unlike a regular function, a class `constructor` can't be called without `new`:
-
-```js run
-class User {
- constructor() {}
-}
-
-alert(typeof User); // function
-User(); // Error: Class constructor User cannot be invoked without 'new'
-```
-
-Different string output
-: If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
-
-Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language.
-
-Class methods are non-enumerable
-: A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
-
-Classes have a default `constructor() {}`
-: If there's no `constructor` in the `class` construct, then an empty function is generated, just as if we had written `constructor() {}`.
-
-Classes always `use strict`
-: All code inside the class construct is automatically in strict mode.
-
-
-## Getters/setters, other shorthands
-
-Classes also include getters/setters, generators, computed properties etc.
-
-Here's an example for `user.name` implemented using `get/set`:
-
-```js run
-class User {
-
- constructor(name) {
- // invokes the setter
- this.name = name;
- }
-
-*!*
- get name() {
-*/!*
- return this._name;
- }
-
-*!*
- set name(value) {
-*/!*
- if (value.length < 4) {
- alert("Name is too short.");
- return;
- }
- this._name = value;
- }
-
-}
-
-let user = new User("John");
-alert(user.name); // John
-
-user = new User(""); // Name too short.
-```
-
-Internally, getters and setters are created on `User.prototype`, like this:
-
-```js
-Object.defineProperties(User.prototype, {
- name: {
- get() {
- return this._name
- },
- set(name) {
- // ...
- }
- }
-});
-```
-
-Here's an example with computed properties:
-
-```js run
-function f() { return "sayHi"; }
-
-class User {
- [f()]() {
- alert("Hello");
- }
-
-}
-
-new User().sayHi();
-```
-
-For a generator method, similarly, prepend it with `*`.
-
-## Class properties
-
-```warn header="Old browsers may need a polyfill"
-Class-level properties are a recent addition to the language.
-```
-
-In the example above, `User` only had methods. Let's add a property:
-
-```js run
-class User {
- name = "Anonymous";
-
- sayHi() {
- alert(`Hello, ${this.name}!`);
- }
-}
-
-new User().sayHi();
-```
-
-The property is not placed into `User.prototype`. Instead, it is created by `new`, separately for every object. So, the property will never be shared between different objects of the same class.
-
-
-## Summary
-
-The basic class syntax looks like this:
-
-```js
-class MyClass {
- prop = value;
-
- constructor(...) {
- // ...
- }
-
- method(...) {}
-
- get something(...) {}
- set something(...) {}
-
- [Symbol.iterator]() {}
- // ...
-}
-```
-
-`MyClass` is technically a function, while methods are written to `MyClass.prototype`.
-
-In the next chapters we'll learn more about classes, including inheritance and other features.
diff --git a/1-js/09-classes/02-class/class-user.png b/1-js/09-classes/02-class/class-user.png
deleted file mode 100644
index f090909a..00000000
Binary files a/1-js/09-classes/02-class/class-user.png and /dev/null differ
diff --git a/1-js/09-classes/02-class/class-user@2x.png b/1-js/09-classes/02-class/class-user@2x.png
deleted file mode 100644
index b953f91e..00000000
Binary files a/1-js/09-classes/02-class/class-user@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/3-class-extend-object/rabbit-extends-object.png b/1-js/09-classes/03-class-inheritance/3-class-extend-object/rabbit-extends-object.png
deleted file mode 100644
index b0b1b6ad..00000000
Binary files a/1-js/09-classes/03-class-inheritance/3-class-extend-object/rabbit-extends-object.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png b/1-js/09-classes/03-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png
deleted file mode 100644
index 76cd5877..00000000
Binary files a/1-js/09-classes/03-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/animal-rabbit-extends.png b/1-js/09-classes/03-class-inheritance/animal-rabbit-extends.png
deleted file mode 100644
index ffdb6df7..00000000
Binary files a/1-js/09-classes/03-class-inheritance/animal-rabbit-extends.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/animal-rabbit-extends@2x.png b/1-js/09-classes/03-class-inheritance/animal-rabbit-extends@2x.png
deleted file mode 100644
index e532c182..00000000
Binary files a/1-js/09-classes/03-class-inheritance/animal-rabbit-extends@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/class-inheritance-array-object.png b/1-js/09-classes/03-class-inheritance/class-inheritance-array-object.png
deleted file mode 100644
index 4bb5bb95..00000000
Binary files a/1-js/09-classes/03-class-inheritance/class-inheritance-array-object.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/class-inheritance-array-object@2x.png b/1-js/09-classes/03-class-inheritance/class-inheritance-array-object@2x.png
deleted file mode 100644
index 4741353f..00000000
Binary files a/1-js/09-classes/03-class-inheritance/class-inheritance-array-object@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-animal.png b/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-animal.png
deleted file mode 100644
index 0da6479d..00000000
Binary files a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-animal.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-animal@2x.png b/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-animal@2x.png
deleted file mode 100644
index ebe8c032..00000000
Binary files a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-animal@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-run-animal.png b/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-run-animal.png
deleted file mode 100644
index 387975a9..00000000
Binary files a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-run-animal.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-run-animal@2x.png b/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-run-animal@2x.png
deleted file mode 100644
index ca731359..00000000
Binary files a/1-js/09-classes/03-class-inheritance/class-inheritance-rabbit-run-animal@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/this-super-loop.png b/1-js/09-classes/03-class-inheritance/this-super-loop.png
deleted file mode 100644
index 74d1d88e..00000000
Binary files a/1-js/09-classes/03-class-inheritance/this-super-loop.png and /dev/null differ
diff --git a/1-js/09-classes/03-class-inheritance/this-super-loop@2x.png b/1-js/09-classes/03-class-inheritance/this-super-loop@2x.png
deleted file mode 100644
index 8ce876f1..00000000
Binary files a/1-js/09-classes/03-class-inheritance/this-super-loop@2x.png and /dev/null differ
diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg
new file mode 100644
index 00000000..fab401df
--- /dev/null
+++ b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/04-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md
similarity index 59%
rename from 1-js/09-classes/04-static-properties-methods/article.md
rename to 1-js/09-classes/03-static-properties-methods/article.md
index 760641ea..583fabcf 100644
--- a/1-js/09-classes/04-static-properties-methods/article.md
+++ b/1-js/09-classes/03-static-properties-methods/article.md
@@ -1,9 +1,9 @@
# Static properties and methods
-We can also assign a methods to the class function, not to its `"prototype"`. Such methods are called *static*.
+We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*.
-An example:
+In a class, they are prepended by `static` keyword, like this:
```js run
class User {
@@ -17,21 +17,21 @@ class User {
User.staticMethod(); // true
```
-That actually does the same as assigning it as a function property:
+That actually does the same as assigning it as a property directly:
```js
-function User() { }
+class User() { }
User.staticMethod = function() {
alert(this === User);
};
```
-The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
+The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule).
Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
-For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
+For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this:
```js run
class Article {
@@ -49,8 +49,8 @@ class Article {
// usage
let articles = [
- new Article("Mind", new Date(2019, 1, 1)),
- new Article("Body", new Date(2019, 0, 1)),
+ new Article("HTML", new Date(2019, 1, 1)),
+ new Article("CSS", new Date(2019, 0, 1)),
new Article("JavaScript", new Date(2019, 11, 1))
];
@@ -58,16 +58,16 @@ let articles = [
articles.sort(Article.compare);
*/!*
-alert( articles[0].title ); // Body
+alert( articles[0].title ); // CSS
```
-Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
1. Create by given parameters (`title`, `date` etc).
2. Create an empty article with today's date.
-3. ...
+3. ...or else somehow.
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
@@ -90,7 +90,7 @@ class Article {
let article = Article.createTodays();
-alert( article.title ); // Todays digest
+alert( article.title ); // Today's digest
```
Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class.
@@ -107,7 +107,7 @@ Article.remove({id: 12345});
[recent browser=Chrome]
-Static properties are also possible, just like regular class properties:
+Static properties are also possible, they look like regular class properties, but prepended by `static`:
```js run
class Article {
@@ -123,11 +123,11 @@ That is the same as a direct assignment to `Article`:
Article.publisher = "Ilya Kantor";
```
-## Statics and inheritance
+## Inheritance of static methods
-Statics are inhereted, we can access `Parent.method` as `Child.method`.
+Static methods are inherited.
-For instance, `Animal.compare` in the code below is inhereted and accessible as `Rabbit.compare`:
+For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`:
```js run
class Animal {
@@ -169,36 +169,39 @@ rabbits.sort(Rabbit.compare);
rabbits[0].run(); // Black Rabbit runs with speed 5.
```
-Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called.
+Now when we can call `Rabbit.compare`, the inherited `Animal.compare` will be called.
-How does it work? Again, using prototypes. As you might have already guessed, extends also gives `Rabbit` the `[[Prototype]]` reference to `Animal`.
+How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`.
+
-
+So, `Rabbit extends Animal` creates two `[[Prototype]]` references:
-So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything.
+1. `Rabbit` function prototypally inherits from `Animal` function.
+2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`.
-Here, let's check that:
+As the result, inheritance works both for regular and static methods.
+
+Here, let's check that by code:
```js run
class Animal {}
class Rabbit extends Animal {}
-// for static properties and methods
+// for statics
alert(Rabbit.__proto__ === Animal); // true
-// and the next step is Function.prototype
-alert(Animal.__proto__ === Function.prototype); // true
-
-// that's in addition to the "normal" prototype chain for object methods
+// for regular methods
alert(Rabbit.prototype.__proto__ === Animal.prototype);
```
-This way `Rabbit` has access to all static methods of `Animal`.
-
## Summary
-Static methods are used for the functionality that doesn't relate to a concrete class instance, doesn't require an instance to exist, but rather belongs to the class as a whole, like `Article.compare` -- a generic method to compare two articles.
+Static methods are used for the functionality that belongs to the class "as a whole", doesn't relate to a concrete class instance.
+
+For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`.
+
+They are labeled by the word `static` in class declaration.
Static properties are used when we'd like to store class-level data, also not bound to an instance.
@@ -214,13 +217,13 @@ class MyClass {
}
```
-That's technically the same as assigning to the class itself:
+Technically, static declaration is the same as assigning to the class itself:
```js
MyClass.property = ...
MyClass.method = ...
```
-Static properties are inherited.
+Static properties and methods are inherited.
-Technically, 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`.
+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/05-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md
similarity index 80%
rename from 1-js/09-classes/05-private-protected-properties-methods/article.md
rename to 1-js/09-classes/04-private-protected-properties-methods/article.md
index 82466331..ef0d497a 100644
--- a/1-js/09-classes/05-private-protected-properties-methods/article.md
+++ b/1-js/09-classes/04-private-protected-properties-methods/article.md
@@ -48,16 +48,16 @@ So, all we need to use an object is to know its external interface. We may be co
That was a general introduction.
-In JavaScript, there are three types of properties and members:
+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 properly do the extension.
+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.
+Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated.
-In the next step we'll make a coffee machine in Javascript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could).
+Now we'll make a coffee machine in JavaScript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could).
## Protecting "waterAmount"
@@ -87,7 +87,7 @@ Let's change `waterAmount` property to protected to have more control over it. F
**Protected properties are usually prefixed with an underscore `_`.**
-That is not enforced on the language level, but there's a convention that such properties and methods should not be accessed from the outside. Most programmers follow it.
+That is not enforced on the language level, but there's a well-known convention between programmers that such properties and methods should not be accessed from the outside.
So our property will be called `_waterAmount`:
@@ -164,16 +164,16 @@ class CoffeeMachine {
}
*!*getWaterAmount()*/!* {
- return this.waterAmount;
+ return this._waterAmount;
}
}
new CoffeeMachine().setWaterAmount(100);
```
-That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now). So, for the future, just in case we need to refactor something, functions are a safer choise.
+That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now).
-Surely, there's a tradeoff. On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide.
+On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide.
````
```smart header="Protected fields are inherited"
@@ -186,51 +186,37 @@ So protected fields are naturally inheritable. Unlike private ones that we'll se
[recent browser=none]
-There's a finished Javascript proposal, almost in the standard, that provides language-level support for private properties and methods.
+There's a finished JavaScript proposal, almost in the standard, that provides language-level support for private properties and methods.
Privates should start with `#`. They are only accessible from inside the class.
-For instance, here we add a private `#waterLimit` property and extract the water-checking logic into a separate method:
+For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`:
-```js
+```js run
class CoffeeMachine {
*!*
#waterLimit = 200;
*/!*
*!*
- #checkWater(water) {
+ #checkWater(value) {
if (value < 0) throw new Error("Negative water");
if (value > this.#waterLimit) throw new Error("Too much water");
}
*/!*
- _waterAmount = 0;
-
- set waterAmount(value) {
-*!*
- this.#checkWater(value);
-*/!*
- this._waterAmount = value;
- }
-
- get waterAmount() {
- return this.waterAmount;
- }
-
}
let coffeeMachine = new CoffeeMachine();
*!*
+// can't access privates from outside of the class
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
*/!*
-
-coffeeMachine.waterAmount = 100; // Works
```
-On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inhereting classes.
+On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inheriting classes.
Private fields do not conflict with public ones. We can have both private `#waterAmount` and public `waterAmount` fields at the same time.
@@ -257,12 +243,12 @@ machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
```
-Unlike protected ones, private fields are enforced by the language itselfs. That's a good thing.
+Unlike protected ones, private fields are enforced by the language itself. That's a good thing.
But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter:
```js
-class CoffeeMachine extends CoffeeMachine() {
+class MegaCoffeeMachine extends CoffeeMachine() {
method() {
*!*
alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
@@ -271,19 +257,19 @@ class CoffeeMachine extends CoffeeMachine() {
}
```
-In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used most of the time, even though they are not supported by the language syntax.
+In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax.
-````warn
+````warn header="Private fields are not available as this[name]"
Private fields are special.
-Remember, usually we can access fields by this[name]:
+As we know, usually we can access fields using `this[name]`:
```js
class User {
...
sayHi() {
let fieldName = "name";
- alert(`Hello, ${this[fieldName]}`);
+ alert(`Hello, ${*!*this[fieldName]*/!*}`);
}
}
```
@@ -309,11 +295,11 @@ Protection for users, so that they don't shoot themselves in the feet
Supportable
: The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement.
- **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 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.**
- It's much easier to develop, if you know that certain methods can be 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, but still simple to upgrade if the external interface is the same.
+ 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.
Hiding complexity
: People adore to use things that are simple. At least from outside. What's inside is a different thing.
@@ -322,9 +308,9 @@ Hiding complexity
**It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.**
-To hide internal interface we use either protected or public properties:
+To hide internal interface we use either protected or private properties:
- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it.
-- Private fields start with `#`. Javascript makes sure we only can access those from inside the class.
+- Private fields start with `#`. JavaScript makes sure we only can access those from inside the class.
Right now, private fields are not well-supported among browsers, but can be polyfilled.
diff --git a/1-js/09-classes/05-private-protected-properties-methods/coffee-inside.jpg b/1-js/09-classes/04-private-protected-properties-methods/coffee-inside.jpg
similarity index 100%
rename from 1-js/09-classes/05-private-protected-properties-methods/coffee-inside.jpg
rename to 1-js/09-classes/04-private-protected-properties-methods/coffee-inside.jpg
diff --git a/1-js/09-classes/05-private-protected-properties-methods/coffee.jpg b/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg
similarity index 100%
rename from 1-js/09-classes/05-private-protected-properties-methods/coffee.jpg
rename to 1-js/09-classes/04-private-protected-properties-methods/coffee.jpg
diff --git a/1-js/09-classes/04-static-properties-methods/animal-rabbit-static.png b/1-js/09-classes/04-static-properties-methods/animal-rabbit-static.png
deleted file mode 100644
index f6331e95..00000000
Binary files a/1-js/09-classes/04-static-properties-methods/animal-rabbit-static.png and /dev/null differ
diff --git a/1-js/09-classes/04-static-properties-methods/animal-rabbit-static@2x.png b/1-js/09-classes/04-static-properties-methods/animal-rabbit-static@2x.png
deleted file mode 100644
index d515cb0f..00000000
Binary files a/1-js/09-classes/04-static-properties-methods/animal-rabbit-static@2x.png and /dev/null differ
diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md
new file mode 100644
index 00000000..2dc4902b
--- /dev/null
+++ b/1-js/09-classes/05-extend-natives/article.md
@@ -0,0 +1,89 @@
+
+# Extending built-in classes
+
+Built-in classes like Array, Map and others are extendable also.
+
+For instance, here `PowerArray` inherits from the native `Array`:
+
+```js run
+// add one more method to it (can do more)
+class PowerArray extends Array {
+ isEmpty() {
+ return this.length === 0;
+ }
+}
+
+let arr = new PowerArray(1, 2, 5, 10, 50);
+alert(arr.isEmpty()); // false
+
+let filteredArr = arr.filter(item => item >= 10);
+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 `PowerArray`. Their internal implementation uses object `constructor` property for that.
+
+In the example above,
+```js
+arr.constructor === PowerArray
+```
+
+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.
+
+We can add a special static getter `Symbol.species` to the class. If exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on.
+
+If we'd like built-in methods like `map` or `filter` to return regular arrays, we can return `Array` in `Symbol.species`, like here:
+
+```js run
+class PowerArray extends Array {
+ isEmpty() {
+ return this.length === 0;
+ }
+
+*!*
+ // built-in methods will use this as the constructor
+ static get [Symbol.species]() {
+ return Array;
+ }
+*/!*
+}
+
+let arr = new PowerArray(1, 2, 5, 10, 50);
+alert(arr.isEmpty()); // false
+
+// filter creates new array using arr.constructor[Symbol.species] as constructor
+let filteredArr = arr.filter(item => item >= 10);
+
+*!*
+// filteredArr is not PowerArray, but Array
+*/!*
+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.
+
+As we already know, native classes extend each other. For instance, `Array` extends `Object`.
+
+Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the chapter [](info:static-properties-methods#statics-and-inheritance).
+
+But built-in classes are an exception. They don't inherit statics from each other.
+
+For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no `Array.keys()` and `Date.keys()` static methods.
+
+Here's the picture structure for `Date` and `Object`:
+
+
+
+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/05-extend-natives/object-date-inheritance.svg b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg
new file mode 100644
index 00000000..a0165bcc
--- /dev/null
+++ b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/06-extend-natives/article.md b/1-js/09-classes/06-extend-natives/article.md
deleted file mode 100644
index 24757abe..00000000
--- a/1-js/09-classes/06-extend-natives/article.md
+++ /dev/null
@@ -1,82 +0,0 @@
-
-# Extending build-in classes
-
-Built-in classes like Array, Map and others are extendable also.
-
-For instance, here `PowerArray` inherits from the native `Array`:
-
-```js run
-// add one more method to it (can do more)
-class PowerArray extends Array {
- isEmpty() {
- return this.length === 0;
- }
-}
-
-let arr = new PowerArray(1, 2, 5, 10, 50);
-alert(arr.isEmpty()); // false
-
-let filteredArr = arr.filter(item => item >= 10);
-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.
-
-In the example above,
-```js
-arr.constructor === PowerArray
-```
-
-So when `arr.filter()` is called, it internally creates the new array of results exactly as `new PowerArray`.
-That's actually very cool, because we can keep using `PowerArray` methods further o the result.
-
-Even more, we can customize that behavior.
-
-There's a special static getter `Symbol.species`, if exists, it returns the constructor to use in such cases.
-
-If we'd like built-in methods like `map`, `filter` will return regular arrays, we can return `Array` in `Symbol.species`, like here:
-
-```js run
-class PowerArray extends Array {
- isEmpty() {
- return this.length === 0;
- }
-
-*!*
- // built-in methods will use this as the constructor
- static get [Symbol.species]() {
- return Array;
- }
-*/!*
-}
-
-let arr = new PowerArray(1, 2, 5, 10, 50);
-alert(arr.isEmpty()); // false
-
-// filter creates new array using arr.constructor[Symbol.species] as constructor
-let filteredArr = arr.filter(item => item >= 10);
-
-*!*
-// filteredArr is not PowerArray, but Array
-*/!*
-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.
-
-## No static inheritance in built-ins
-
-Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc.
-
-And we've already been talking about native classes extending each other: `Array.[[Prototype]] = Object`.
-
-But statics are an exception. Built-in classes don't inherit static properties from each other.
-
-In other words, the prototype of build-in constructor `Array` does not point to `Object`. This way `Array` and `Date` do not have `Array.keys` or `Date.keys`. And that feels natural.
-
-Here's the picture structure for `Date` and `Object`:
-
-
-
-Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all.
diff --git a/1-js/09-classes/06-extend-natives/object-date-inheritance.png b/1-js/09-classes/06-extend-natives/object-date-inheritance.png
deleted file mode 100644
index b5f1932c..00000000
Binary files a/1-js/09-classes/06-extend-natives/object-date-inheritance.png and /dev/null differ
diff --git a/1-js/09-classes/06-extend-natives/object-date-inheritance@2x.png b/1-js/09-classes/06-extend-natives/object-date-inheritance@2x.png
deleted file mode 100644
index 38276d45..00000000
Binary files a/1-js/09-classes/06-extend-natives/object-date-inheritance@2x.png and /dev/null differ
diff --git a/1-js/09-classes/07-instanceof/1-strange-instanceof/solution.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md
similarity index 100%
rename from 1-js/09-classes/07-instanceof/1-strange-instanceof/solution.md
rename to 1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md
diff --git a/1-js/09-classes/07-instanceof/1-strange-instanceof/task.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md
similarity index 100%
rename from 1-js/09-classes/07-instanceof/1-strange-instanceof/task.md
rename to 1-js/09-classes/06-instanceof/1-strange-instanceof/task.md
diff --git a/1-js/09-classes/07-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
similarity index 76%
rename from 1-js/09-classes/07-instanceof/article.md
rename to 1-js/09-classes/06-instanceof/article.md
index 702c9e6b..0b02c99b 100644
--- a/1-js/09-classes/07-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,14 +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, and is also fine-tunable using 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 use it. Like this:
+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
- // assume 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;
@@ -61,22 +64,25 @@ The algorithm of `obj instanceof Class` works roughly as follows:
}
let obj = { canEat: true };
+
alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
```
-2. Most classes do not have `Symbol.hasInstance`. In that case, check if `Class.prototype` equals to one of prototypes in the `obj` prototype chain.
+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 {}
@@ -86,19 +92,22 @@ 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`:
-
+
By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`.
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:
@@ -115,9 +124,7 @@ alert( rabbit instanceof Rabbit ); // false
*/!*
```
-That's one of the reasons to avoid changing `prototype`. Just to keep safe.
-
-## Bonus: Object toString for the type
+## Bonus: Object.prototype.toString for the type
We already know that plain objects are converted to string as `[object Object]`:
@@ -150,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`.
@@ -182,7 +189,7 @@ alert( {}.toString.call(user) ); // [object User]
For most environment-specific objects, there is such a property. Here are few browser specific examples:
```js run
-// toStringTag for the envinronment-specific object and class:
+// toStringTag for the environment-specific object and class:
alert( window[Symbol.toStringTag]); // window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
@@ -194,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/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg
new file mode 100644
index 00000000..920be68b
--- /dev/null
+++ b/1-js/09-classes/06-instanceof/instanceof.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/07-instanceof/instanceof.png b/1-js/09-classes/07-instanceof/instanceof.png
deleted file mode 100644
index eb43cb3d..00000000
Binary files a/1-js/09-classes/07-instanceof/instanceof.png and /dev/null differ
diff --git a/1-js/09-classes/07-instanceof/instanceof@2x.png b/1-js/09-classes/07-instanceof/instanceof@2x.png
deleted file mode 100644
index f6e06575..00000000
Binary files a/1-js/09-classes/07-instanceof/instanceof@2x.png and /dev/null differ
diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md
new file mode 100644
index 00000000..d5f1ab83
--- /dev/null
+++ b/1-js/09-classes/07-mixins/article.md
@@ -0,0 +1,208 @@
+# Mixins
+
+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, we have a class `StreetSweeper` and a class `Bicycle`, and want to make their mix: a `StreetSweepingBicycle`.
+
+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".
+
+As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class containing methods that can be used by other classes without a need to inherit from it.
+
+In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.
+
+## A mixin example
+
+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`:
+
+```js run
+*!*
+// mixin
+*/!*
+let sayHiMixin = {
+ sayHi() {
+ alert(`Hello ${this.name}`);
+ },
+ sayBye() {
+ alert(`Bye ${this.name}`);
+ }
+};
+
+*!*
+// usage:
+*/!*
+class User {
+ constructor(name) {
+ this.name = name;
+ }
+}
+
+// copy the methods
+Object.assign(User.prototype, sayHiMixin);
+
+// now User can say hi
+new User("Dude").sayHi(); // Hello Dude!
+```
+
+There's no inheritance, but a simple method copying. So `User` may inherit from another class and also include the mixin to "mix-in" the additional methods, like this:
+
+```js
+class User extends Person {
+ // ...
+}
+
+Object.assign(User.prototype, sayHiMixin);
+```
+
+Mixins can make use of inheritance inside themselves.
+
+For instance, here `sayHiMixin` inherits from `sayMixin`:
+
+```js run
+let sayMixin = {
+ say(phrase) {
+ alert(phrase);
+ }
+};
+
+let sayHiMixin = {
+ __proto__: sayMixin, // (or we could use Object.create to set the prototype here)
+
+ sayHi() {
+ *!*
+ // call parent method
+ */!*
+ super.say(`Hello ${this.name}`); // (*)
+ },
+ sayBye() {
+ super.say(`Bye ${this.name}`); // (*)
+ }
+};
+
+class User {
+ constructor(name) {
+ this.name = name;
+ }
+}
+
+// copy the methods
+Object.assign(User.prototype, sayHiMixin);
+
+// now User can say hi
+new User("Dude").sayHi(); // Hello Dude!
+```
+
+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):
+
+
+
+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]]`.
+
+## EventMixin
+
+Now let's make a mixin for real life.
+
+An important feature of many browser objects (for instance) is that they can generate events. Events is a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows to easily add event-related functions to any class/object.
+
+- The mixin will provide a method `.trigger(name, [...data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, optionally followed by additional arguments with event data.
+- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from `.trigger` call.
+- ...And the method `.off(name, handler)` that removes `handler` listener.
+
+After adding the mixin, an object `user` will become able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen to such events to load the calendar for the logged-in person.
+
+Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may assign handlers to react on that event. And so on.
+
+Here's the code:
+
+```js run
+let eventMixin = {
+ /**
+ * Subscribe to event, usage:
+ * menu.on('select', function(item) { ... }
+ */
+ on(eventName, handler) {
+ if (!this._eventHandlers) this._eventHandlers = {};
+ if (!this._eventHandlers[eventName]) {
+ this._eventHandlers[eventName] = [];
+ }
+ this._eventHandlers[eventName].push(handler);
+ },
+
+ /**
+ * Cancel the subscription, usage:
+ * menu.off('select', handler)
+ */
+ off(eventName, handler) {
+ let handlers = this._eventHandlers && this._eventHandlers[eventName];
+ if (!handlers) return;
+ for (let i = 0; i < handlers.length; i++) {
+ if (handlers[i] === handler) {
+ handlers.splice(i--, 1);
+ }
+ }
+ },
+
+ /**
+ * Generate an event with the given name and data
+ * this.trigger('select', data1, data2);
+ */
+ trigger(eventName, ...args) {
+ if (!this._eventHandlers || !this._eventHandlers[eventName]) {
+ return; // no handlers for that event name
+ }
+
+ // call the handlers
+ this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
+ }
+};
+```
+
+
+- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. Technically, there's `_eventHandlers` property, that stores an array of handlers for each event name. So it just adds it to the list.
+- `.off(eventName, handler)` -- removes the function from the handlers list.
+- `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`.
+
+Usage:
+
+```js run
+// Make a class
+class Menu {
+ choose(value) {
+ this.trigger("select", value);
+ }
+}
+// Add the mixin with event-related methods
+Object.assign(Menu.prototype, eventMixin);
+
+let menu = new Menu();
+
+// add a handler, to be called on selection:
+*!*
+menu.on("select", value => alert(`Value selected: ${value}`));
+*/!*
+
+// triggers the event => the handler above runs and shows:
+// Value selected: 123
+menu.choose("123");
+```
+
+Now if we'd like any code to react on menu selection, we can listen to it with `menu.on(...)`.
+
+And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain.
+
+## Summary
+
+*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes.
+
+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.
+
+Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that.
diff --git a/1-js/09-classes/08-mixins/head.html b/1-js/09-classes/07-mixins/head.html
similarity index 100%
rename from 1-js/09-classes/08-mixins/head.html
rename to 1-js/09-classes/07-mixins/head.html
diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.svg b/1-js/09-classes/07-mixins/mixin-inheritance.svg
new file mode 100644
index 00000000..2e7ba8b3
--- /dev/null
+++ b/1-js/09-classes/07-mixins/mixin-inheritance.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/09-classes/08-mixins/article.md b/1-js/09-classes/08-mixins/article.md
deleted file mode 100644
index bb51395e..00000000
--- a/1-js/09-classes/08-mixins/article.md
+++ /dev/null
@@ -1,205 +0,0 @@
-# Mixins
-
-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`.
-
-Or, talking about programming, we have a class `Renderer` that implements templating and a class `EventEmitter` that implements event handling, and want to merge these functionalities together with a class `Page`, to make a page that can use templates and emit events.
-
-There's a concept that can help here, called "mixins".
-
-As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes.
-
-In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.
-
-## 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.
-
-For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
-
-```js run
-*!*
-// mixin
-*/!*
-let sayHiMixin = {
- sayHi() {
- alert(`Hello ${this.name}`);
- },
- sayBye() {
- alert(`Bye ${this.name}`);
- }
-};
-
-*!*
-// usage:
-*/!*
-class User {
- constructor(name) {
- this.name = name;
- }
-}
-
-// copy the methods
-Object.assign(User.prototype, sayHiMixin);
-
-// now User can say hi
-new User("Dude").sayHi(); // Hello Dude!
-```
-
-There's no inheritance, but a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods, like this:
-
-```js
-class User extends Person {
- // ...
-}
-
-Object.assign(User.prototype, sayHiMixin);
-```
-
-Mixins can make use of inheritance inside themselves.
-
-For instance, here `sayHiMixin` inherits from `sayMixin`:
-
-```js run
-let sayMixin = {
- say(phrase) {
- alert(phrase);
- }
-};
-
-let sayHiMixin = {
- __proto__: sayMixin, // (or we could use Object.create to set the prototype here)
-
- sayHi() {
- *!*
- // call parent method
- */!*
- super.say(`Hello ${this.name}`);
- },
- sayBye() {
- super.say(`Bye ${this.name}`);
- }
-};
-
-class User {
- constructor(name) {
- this.name = name;
- }
-}
-
-// copy the methods
-Object.assign(User.prototype, sayHiMixin);
-
-// now User can say hi
-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.
-
-
-
-That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `super` actually means `sayHiMixin.__proto__`, not `User.__proto__`.
-
-## EventMixin
-
-Now let's make a mixin for real life.
-
-The important feature of many objects is working with events.
-
-That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "listen" to such events.
-
-An event must have a name and, optionally, bundle some additional data.
-
-For instance, an object `user` can generate an event `"login"` when the visitor logs in. And another object `calendar` may want to receive such events to load the calendar for the logged-in person.
-
-Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
-
-Events is a way to "share information" with anyone who wants it. They can be useful in any class, so let's make a mixin for them:
-
-```js run
-let eventMixin = {
- /**
- * Subscribe to event, usage:
- * menu.on('select', function(item) { ... }
- */
- on(eventName, handler) {
- if (!this._eventHandlers) this._eventHandlers = {};
- if (!this._eventHandlers[eventName]) {
- this._eventHandlers[eventName] = [];
- }
- this._eventHandlers[eventName].push(handler);
- },
-
- /**
- * Cancel the subscription, usage:
- * menu.off('select', handler)
- */
- off(eventName, handler) {
- let handlers = this._eventHandlers && this._eventHandlers[eventName];
- if (!handlers) return;
- for (let i = 0; i < handlers.length; i++) {
- if (handlers[i] === handler) {
- handlers.splice(i--, 1);
- }
- }
- },
-
- /**
- * Generate the event and attach the data to it
- * this.trigger('select', data1, data2);
- */
- trigger(eventName, ...args) {
- if (!this._eventHandlers || !this._eventHandlers[eventName]) {
- return; // no handlers for that event name
- }
-
- // call the handlers
- this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
- }
-};
-```
-
-There are 3 methods here:
-
-1. `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. The handlers are stored in the `_eventHandlers` property.
-2. `.off(eventName, handler)` -- removes the function from the handlers list.
-3. `.trigger(eventName, ...args)` -- generates the event: all assigned handlers are called and `args` are passed as arguments to them.
-
-
-Usage:
-
-```js run
-// Make a class
-class Menu {
- choose(value) {
- this.trigger("select", value);
- }
-}
-// Add the mixin
-Object.assign(Menu.prototype, eventMixin);
-
-let menu = new Menu();
-
-// call the handler on selection:
-*!*
-menu.on("select", value => alert(`Value selected: ${value}`));
-*/!*
-
-// triggers the event => shows Value selected: 123
-menu.choose("123"); // value selected
-```
-
-Now if we have the code interested to react on user selection, we can bind it with `menu.on(...)`.
-
-And the `eventMixin` can add such behavior to as many classes as we'd like, without interfering with the inheritance chain.
-
-## Summary
-
-*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 them into the prototype.
-
-We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.
-
-Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming for a mixin, to minimize such possibility.
diff --git a/1-js/09-classes/08-mixins/mixin-inheritance.png b/1-js/09-classes/08-mixins/mixin-inheritance.png
deleted file mode 100644
index 68f9ac27..00000000
Binary files a/1-js/09-classes/08-mixins/mixin-inheritance.png and /dev/null differ
diff --git a/1-js/09-classes/08-mixins/mixin-inheritance@2x.png b/1-js/09-classes/08-mixins/mixin-inheritance@2x.png
deleted file mode 100644
index cd3c3004..00000000
Binary files a/1-js/09-classes/08-mixins/mixin-inheritance@2x.png and /dev/null differ
diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md
index 05ba72e0..303431d6 100644
--- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md
+++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md
@@ -44,4 +44,4 @@ function f() {
f(); // cleanup!
```
-It's `finally` that guarantees the cleanup here. If we just put the code at the end of `f`, it wouldn't run.
+It's `finally` that guarantees the cleanup here. If we just put the code at the end of `f`, it wouldn't run in these situations.
diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
index e8468734..c573cc23 100644
--- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
+++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
@@ -33,6 +33,6 @@ Compare the two code fragments.
*/!*
```
-We definitely need the cleanup after the work has started, doesn't matter if there was an error or not.
+We definitely need the cleanup after the work, doesn't matter if there was an error or not.
Is there an advantage here in using `finally` or both code fragments are equal? If there is such an advantage, then give an example when it matters.
diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md
index dacc2376..fa8ba5e9 100644
--- a/1-js/10-error-handling/1-try-catch/article.md
+++ b/1-js/10-error-handling/1-try-catch/article.md
@@ -15,7 +15,7 @@ try {
// code...
-} catch (err)] {
+} catch (err) {
// error handling
@@ -25,14 +25,14 @@ try {
It works like this:
1. First, the code in `try {...}` is executed.
-2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and then jumps over `catch`.
+2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on skipping `catch`.
3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) contains an error object with details about what's happened.
-
+
So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`.
-Let's see more examples.
+Let's see examples.
- An errorless example: shows `alert` `(1)` and `(2)`:
@@ -50,8 +50,6 @@ Let's see more examples.
alert('Catch is ignored, because there are no errors'); // (3)
}
-
- alert("...Then the execution continues");
```
- An example with an error: shows `(1)` and `(3)`:
@@ -68,11 +66,9 @@ Let's see more examples.
} catch(err) {
- alert(`Error has occured!`); // *!*(3) <--*/!*
+ alert(`Error has occurred!`); // *!*(3) <--*/!*
}
-
- alert("...Then the execution continues");
```
@@ -108,7 +104,7 @@ try {
}
```
-That's because `try..catch` actually wraps the `setTimeout` call that schedules the function. But the function itself is executed later, when the engine has already left the `try..catch` construct.
+That's because the function itself is executed later, when the engine has already left the `try..catch` construct.
To catch an exception inside a scheduled function, `try..catch` must be inside that function:
```js run
@@ -134,10 +130,10 @@ try {
}
```
-For all built-in errors, the error object inside `catch` block has two main properties:
+For all built-in errors, the error object has two main properties:
`name`
-: Error name. For an undefined variable that's `"ReferenceError"`.
+: Error name. For instance, for an undefined variable that's `"ReferenceError"`.
`message`
: Textual message about error details.
@@ -157,7 +153,7 @@ try {
} catch(err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
- alert(err.stack); // ReferenceError: lalala is not defined at ...
+ alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)
// Can also show an error as a whole
// The error is converted to string as "name: message"
@@ -174,8 +170,8 @@ If we don't need error details, `catch` may omit it:
```js
try {
// ...
-} catch {
- // error object omitted
+} catch { // <-- without (err)
+ // ...
}
```
@@ -187,7 +183,7 @@ As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse)
Usually it's used to decode data received over the network, from the server or another source.
-We receive it and call `JSON.parse`, like this:
+We receive it and call `JSON.parse` like this:
```js run
let json = '{"name":"John", "age": 30}'; // data from the server
@@ -302,13 +298,13 @@ try {
*!*
alert(e.name); // SyntaxError
*/!*
- alert(e.message); // Unexpected token o in JSON at position 0
+ alert(e.message); // Unexpected token o in JSON at position 2
}
```
As we can see, that's a `SyntaxError`.
-And in our case, the absence of `name` could be treated as a syntax error also, assuming that users must have a `name`.
+And in our case, the absence of `name` is an error, as users must have a `name`.
So let's throw it:
@@ -338,7 +334,7 @@ Now `catch` became a single place for all error handling: both for `JSON.parse`
## Rethrowing
-In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a variable is undefined or something else, not just that "incorrect data" thing.
+In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just that "incorrect data" thing.
Like this:
@@ -355,7 +351,7 @@ try {
}
```
-Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a crazy bug may be discovered that leads to terrible hacks (like it happened with the `ssh` tool).
+Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks.
In our case, `try..catch` is meant to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug.
@@ -489,7 +485,7 @@ The code has two ways of execution:
1. If you answer "Yes" to "Make an error?", then `try -> catch -> finally`.
2. If you say "No", then `try -> finally`.
-The `finally` clause is often used when we start doing something before `try..catch` and want to finalize it in any case of outcome.
+The `finally` clause is often used when we start doing something and want to finalize it in any case of outcome.
For instance, we want to measure the time that a Fibonacci numbers function `fib(n)` takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there's an error during the function call? In particular, the implementation of `fib(n)` in the code below returns an error for negative or non-integer numbers.
@@ -521,20 +517,20 @@ try {
}
*/!*
-alert(result || "error occured");
+alert(result || "error occurred");
alert( `execution took ${diff}ms` );
```
You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, an the execution will take `0ms`. Both measurements are done correctly.
-In other words, there may be two ways to exit a function: either a `return` or `throw`. The `finally` clause handles them both.
+In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases.
```smart header="Variables are local inside `try..catch..finally`"
Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`.
-Otherwise, if `let` were made inside the `{...}` block, it would only be visible inside of it.
+Otherwise, if we declared `let` in `try` block, it would only be visible inside of it.
```
````smart header="`finally` and `return`"
@@ -565,7 +561,7 @@ alert( func() ); // first works alert from finally, and then this one
````smart header="`try..finally`"
-The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors right here, but want to be sure that processes that we started are finalized.
+The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized.
```js
function func() {
@@ -577,7 +573,7 @@ function func() {
}
}
```
-In the code above, an error inside `try` always falls out, because there's no `catch`. But `finally` works before the execution flow jumps outside.
+In the code above, an error inside `try` always falls out, because there's no `catch`. But `finally` works before the execution flow leaves the function.
````
## Global catch
@@ -590,7 +586,7 @@ Let's imagine we've got a fatal error outside of `try..catch`, and the script di
Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages) etc.
-There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.JS has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error.
+There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error.
The syntax:
@@ -637,13 +633,13 @@ There are also web-services that provide error-logging for such cases, like BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry {}// code...
\ No newline at end of file
diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png b/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png
deleted file mode 100644
index 7515aa8c..00000000
Binary files a/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png and /dev/null differ
diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md
index 5079c746..2414ce7e 100644
--- a/1-js/10-error-handling/2-custom-errors/article.md
+++ b/1-js/10-error-handling/2-custom-errors/article.md
@@ -6,7 +6,7 @@ Our errors should support basic error properties like `message`, `name` and, pre
JavaScript allows to use `throw` with any argument, so technically our custom error classes don't need to inherit from `Error`. But if we inherit, then it becomes possible to use `obj instanceof Error` to identify error objects. So it's better to inherit from it.
-As we build our application, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on.
+As the application grows, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on.
## Extending Error
@@ -17,17 +17,13 @@ Here's an example of how a valid `json` may look:
let json = `{ "name": "John", "age": 30 }`;
```
-Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it throws `SyntaxError`.
-
-But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have `name` and `age` properties that are essential for our users.
+Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it throws `SyntaxError`. But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have `name` and `age` properties that are essential for our users.
Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field.
Our `ValidationError` class should inherit from the built-in `Error` class.
-That class is built-in, but we should have its approximate code before our eyes, to understand what we're extending.
-
-So here you are:
+That class is built-in, here's it approximate code, for us to understand what we're extending:
```js
// The "pseudocode" for the built-in Error class defined by JavaScript itself
@@ -35,12 +31,12 @@ class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (different names for different built-in error classes)
- this.stack = ; // non-standard, but most environments support it
+ this.stack = ; // non-standard, but most environments support it
}
}
```
-Now let's go on and inherit `ValidationError` from it:
+Now let's inherit `ValidationError` from it and try it in action:
```js run untrusted
*!*
@@ -65,10 +61,9 @@ try {
}
```
-Please take a look at the constructor:
+Please note: in the line `(1)` we call the parent constructor. JavaScript requires us to call `super` in the child constructor, so that's obligatory. The parent constructor sets the `message` property.
-1. In the line `(1)` we call the parent constructor. JavaScript requires us to call `super` in the child constructor, so that's obligatory. The parent constructor sets the `message` property.
-2. The parent constructor also sets the `name` property to `"Error"`, so in the line `(2)` we reset it to the right value.
+The parent constructor also sets the `name` property to `"Error"`, so in the line `(2)` we reset it to the right value.
Let's try to use it in `readUser(json)`:
@@ -126,7 +121,7 @@ We could also look at `err.name`, like this:
The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof.
-Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or such) should fall through.
+Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through.
## Further inheritance
@@ -185,7 +180,7 @@ try {
The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor.
-Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` when creating each custom error. But there's a way out. We can make our own "basic error" class that removes this burden from our shoulders by using `this.constructor.name` for `this.name` in the constructor. And then inherit from it.
+Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all ours custom errors from it.
Let's call it `MyError`.
@@ -218,9 +213,9 @@ Now custom errors are much shorter, especially `ValidationError`, as we got rid
## Wrapping exceptions
-The purpose of the function `readUser` in the code above is "to read the user data", right? There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow: the new code will probably generate other kinds of errors.
+The purpose of the function `readUser` in the code above is "to read the user data". There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors.
-The code which calls `readUser` should handle these errors. Right now it uses multiple `if` in the `catch` block to check for different error types and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`?
+The code which calls `readUser` should handle these errors. Right now it uses multiple `if` in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`?
Often the answer is "No": the outer code wants to be "one level above all that". It wants to have some kind of "data reading error". Why exactly it happened -- is often irrelevant (the error message describes it). Or, even better if there is a way to get error details, but only if we need to.
@@ -303,5 +298,5 @@ The approach is called "wrapping exceptions", because we take "low level excepti
## Summary
- We can inherit from `Error` and other built-in error classes normally, just need to take care of `name` property and don't forget to call `super`.
-- Most of the time, we should use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks.
-- Wrapping exceptions is a widespread technique when a function handles low-level exceptions and makes a higher-level object to report about the errors. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required.
+- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks.
+- Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required.
diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md
index a5a91793..c2f67c6c 100644
--- a/1-js/11-async/01-callbacks/article.md
+++ b/1-js/11-async/01-callbacks/article.md
@@ -25,15 +25,16 @@ loadScript('/my/script.js');
The function is called "asynchronously," because the action (script loading) finishes not now, but later.
-The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too.
+If there's a code below `loadScript(…)`, it doesn't wait until the loading finishes.
```js
loadScript('/my/script.js');
-// the code below loadScript doesn't wait for the script loading to finish
+// the code below loadScript
+// doesn't wait for the script loading to finish
// ...
```
-Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them.
+We'd like to use the new script as soon as it loads. It declares new functions, and we want to run them.
But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
@@ -45,7 +46,7 @@ newFunction(); // no such function!
*/!*
```
-Naturally, the browser probably didn't have time to load the script. So the immediate call to the new function fails. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script.
+Naturally, the browser probably didn't have time to load the script. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script.
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
@@ -98,7 +99,7 @@ Here we did it in `loadScript`, but of course, it's a general approach.
## Callback in callback
-How to load two scripts sequentially: the first one, and then the second one after it?
+How can we load two scripts sequentially: the first one, and then the second one after it?
The natural solution would be to put the second `loadScript` call inside the callback, like this:
@@ -140,7 +141,7 @@ So, every new action is inside a callback. That's fine for few actions, but not
## Handling errors
-In examples above we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.
+In the above examples we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.
Here's an improved version of `loadScript` that tracks loading errors:
@@ -222,7 +223,31 @@ As calls become more nested, the code becomes deeper and increasingly more diffi
That's sometimes called "callback hell" or "pyramid of doom."
-
+
+
+
The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control.
@@ -262,7 +287,7 @@ function step3(error, script) {
See? It does the same, and there's no deep nesting now because we made every action a separate top-level function.
-It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that. One needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump.
+It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that one needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump.
Also, the functions named `step*` are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of a namespace cluttering here.
diff --git a/1-js/11-async/01-callbacks/callback-hell.png b/1-js/11-async/01-callbacks/callback-hell.png
deleted file mode 100644
index f42224a2..00000000
Binary files a/1-js/11-async/01-callbacks/callback-hell.png and /dev/null differ
diff --git a/1-js/11-async/01-callbacks/callback-hell.svg b/1-js/11-async/01-callbacks/callback-hell.svg
new file mode 100644
index 00000000..907f62c2
--- /dev/null
+++ b/1-js/11-async/01-callbacks/callback-hell.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/11-async/01-callbacks/callback-hell@2x.png b/1-js/11-async/01-callbacks/callback-hell@2x.png
deleted file mode 100644
index 7c9a3ab5..00000000
Binary files a/1-js/11-async/01-callbacks/callback-hell@2x.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md
index bf70ee13..5c4bcb2a 100644
--- a/1-js/11-async/02-promise-basics/article.md
+++ b/1-js/11-async/02-promise-basics/article.md
@@ -2,13 +2,13 @@
Imagine that you're a top singer, and fans ask day and night for your upcoming single.
-To get some relief, you promise to send it to them when it's published. You give your fans a list to which they can subscribe for updates. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, if plans to publish the song are cancelled, they will still be notified.
+To get some relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, a fire in the studio, so that you can't publish the song, they will still be notified.
-Everyone is happy, because the people don't crowd you any more, and fans, because they won't miss the single.
+Everyone is happy: you, because the people don't crowd you anymore, and fans, because they won't miss the single.
This is a real-life analogy for things we often have in programming:
-1. A "producing code" that does something and takes time. For instance, the code loads a remote script. That's a "singer".
+1. A "producing code" that does something and takes time. For instance, a code that loads the data over a network. That's a "singer".
2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans".
3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready.
@@ -22,45 +22,47 @@ let promise = new Promise(function(resolve, reject) {
});
```
-The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
+The function passed to `new Promise` is called the *executor*. When `new Promise` is created, it runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
-The resulting `promise` object has internal properties:
+Its arguments `resolve` and `reject` are callbacks provided by JavaScript itself. Our code is only inside the executor.
-- `state` — initially "pending", then changes to either "fulfilled" or "rejected",
-- `result` — an arbitrary value of your choosing, initially `undefined`.
+When the executor obtains the result, be it soon or late - doesn't matter, it should call one of these callbacks:
-When the executor finishes the job, it should call one of the functions that it gets as arguments:
+- `resolve(value)` — if the job finished successfully, with result `value`.
+- `reject(error)` — if an error occurred, `error` is the error object.
-- `resolve(value)` — to indicate that the job finished successfully:
- - sets `state` to `"fulfilled"`,
- - sets `result` to `value`.
-- `reject(error)` — to indicate that an error occurred:
- - sets `state` to `"rejected"`,
- - sets `result` to `error`.
+So to summarize: the executor runs automatically, it should do a job and then call either `resolve` or `reject`.
-
+The `promise` object returned by `new Promise` constructor has internal properties:
-Later we'll see how these changes become known to "fans".
+- `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called.
+- `result` — initially `undefined`, then changes to `value` when `resolve(value)` called or `error` when `reject(error)` is called.
-Here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`):
+So the executor eventually moves `promise` to one of these states:
+
+
+
+Later we'll see how "fans" can subscribe to these changes.
+
+Here's an example of a promise constructor and a simple executor function with "producing code" that takes time (via `setTimeout`):
```js run
let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
- // after 1 second signal that the job is done with the result "done!"
- setTimeout(() => *!*resolve("done!")*/!*, 1000);
+ // after 1 second signal that the job is done with the result "done"
+ setTimeout(() => *!*resolve("done")*/!*, 1000);
});
```
We can see two things by running the code above:
-1. The executor is called automatically and immediately (by the `new Promise`).
-2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. Instead, we should write the executor to call them when ready.
+1. The executor is called automatically and immediately (by `new Promise`).
+2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. We only should call one of them when ready.
-After one second of "processing" the executor calls `resolve("done")` to produce the result:
+ After one second of "processing" the executor calls `resolve("done")` to produce the result. This changes the state of the `promise` object:
-
+ 
That was an example of a successful job completion, a "fulfilled promise".
@@ -73,20 +75,24 @@ let promise = new Promise(function(resolve, reject) {
});
```
-
+The call to `reject(...)` moves the promise object to `"rejected"` state:
-To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding Promise object.
+
-The Promise that is either resolved or rejected is called "settled", as opposed to a "pending" Promise.
+To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding promise object.
+
+A promise that is either resolved or rejected is called "settled", as opposed to a initially "pending" promise.
````smart header="There can be only a single result or an error"
-The executor should call only one `resolve` or one `reject`. The promise's state change is final.
+The executor should call only one `resolve` or one `reject`. Any state change is final.
All further calls of `resolve` and `reject` are ignored:
```js
let promise = new Promise(function(resolve, reject) {
+*!*
resolve("done");
+*/!*
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
@@ -99,7 +105,7 @@ Also, `resolve`/`reject` expect only one argument (or none) and will ignore addi
````
```smart header="Reject with `Error` objects"
-In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.
+In case something goes wrong, the executor should call `reject`. That can be done with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.
```
````smart header="Immediately calling `resolve`/`reject`"
@@ -112,13 +118,13 @@ let promise = new Promise(function(resolve, reject) {
});
```
-For instance, this might happen when we start to do a job but then see that everything has already been completed.
+For instance, this might happen when we start to do a job but then see that everything has already been completed and cached.
-That's fine. We immediately have a resolved Promise, nothing wrong with that.
+That's fine. We immediately have a resolved promise.
````
```smart header="The `state` and `result` are internal"
-The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch`/`.finally` for that. They are described below.
+The properties `state` and `result` of the Promise object are internal. We can't directly access them. We can use the methods `.then`/`.catch`/`.finally` for that. They are described below.
```
## Consumers: then, catch, finally
@@ -138,17 +144,11 @@ promise.then(
);
```
-The first argument of `.then` is a function that:
+The first argument of `.then` is a function that runs when the promise is resolved, and receives the result.
-1. runs when the Promise is resolved, and
-2. receives the result.
+The second argument of `.then` is a function that runs when the promise is rejected, and receives the error.
-The second argument of `.then` is a function that:
-
-1. runs when the Promise is rejected, and
-2. receives the error.
-
-For instance, here's a reaction to a successfuly resolved promise:
+For instance, here's a reaction to a successfully resolved promise:
```js run
let promise = new Promise(function(resolve, reject) {
@@ -214,11 +214,11 @@ The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a short
### finally
-Just like there's a finally clause in a regular `try {...} catch {...}`, there's `finally` in promises.
+Just like there's a `finally` clause in a regular `try {...} catch {...}`, there's `finally` in promises.
-The call `.finally(f)` is similar to `.then(f, f)` in the sense that it always runs when the promise is settled: be it resolve or reject.
+The call `.finally(f)` is similar to `.then(f, f)` in the sense that `f` always runs when the promise is settled: be it resolve or reject.
-It is a good handler to perform cleanup, e.g. to stop our loading indicators in `finally`, as they are not needed any more, no matter what the outcome is.
+`finally` is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed anymore, no matter what the outcome is.
Like this:
@@ -233,10 +233,10 @@ new Promise((resolve, reject) => {
.then(result => show result, err => show error)
```
-It's not exactly an alias though. There are several important differences:
+It's not exactly an alias of `then(f,f)` though. There are several important differences:
1. A `finally` handler has no arguments. In `finally` we don't know whether the promise is successful or not. That's all right, as our task is usually to perform "general" finalizing procedures.
-2. Finally passes through results and errors to the next handler.
+2. A `finally` handler passes through results and errors to the next handler.
For instance, here the result is passed through `finally` to `then`:
```js run
@@ -257,14 +257,14 @@ It's not exactly an alias though. There are several important differences:
.catch(err => alert(err)); // <-- .catch handles the error object
```
- That's very convenient, because finally is not meant to process promise results. So it passes them through.
+ That's very convenient, because `finally` is not meant to process a promise result. So it passes it through.
We'll talk more about promise chaining and result-passing between handlers in the next chapter.
-3. The last, but not the least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function.
+3. Last, but not least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function `f`.
````smart header="On settled promises handlers runs immediately"
-If a promise is pending, `.then/catch/finally` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
+If a promise is pending, `.then/catch/finally` handlers wait for it. Otherwise, if a promise has already settled, they execute immediately:
```js run
// an immediately resolved promise
@@ -272,13 +272,11 @@ let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (shows up right now)
```
-
-The good thing is: `.then` handler is guaranteed to run whether the promise takes time or settles it immediately.
````
Next, let's see more practical examples of how promises can help us to write asynchronous code.
-## Example: loadScript
+## Example: loadScript [#loadscript]
We've got the `loadScript` function for loading a script from the previous chapter.
@@ -290,7 +288,7 @@ function loadScript(src, callback) {
script.src = src;
script.onload = () => callback(null, script);
- script.onerror = () => callback(new Error(`Script load error ` + src));
+ script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
@@ -307,7 +305,7 @@ function loadScript(src) {
script.src = src;
script.onload = () => resolve(script);
- script.onerror = () => reject(new Error("Script load error: " + src));
+ script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script);
});
@@ -324,7 +322,7 @@ promise.then(
error => alert(`Error: ${error.message}`)
);
-promise.then(script => alert('One more handler to do something else!'));
+promise.then(script => alert('Another handler...'));
```
We can immediately see a few benefits over the callback-based pattern:
@@ -335,4 +333,4 @@ We can immediately see a few benefits over the callback-based pattern:
| Promises allow us to do things in the natural order. First, we run `loadScript(script)`, and `.then` we write what to do with the result. | We must have a `callback` function at our disposal when calling `loadScript(script, callback)`. In other words, we must know what to do with the result *before* `loadScript` is called. |
| We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next chapter: [](info:promise-chaining). | There can be only one callback. |
-So Promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
+So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
diff --git a/1-js/11-async/02-promise-basics/promise-init.png b/1-js/11-async/02-promise-basics/promise-init.png
deleted file mode 100644
index c54ce05f..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-init.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-init@2x.png b/1-js/11-async/02-promise-basics/promise-init@2x.png
deleted file mode 100644
index 82f0d713..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-init@2x.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-reject-1.png b/1-js/11-async/02-promise-basics/promise-reject-1.png
deleted file mode 100644
index 30692f4e..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-reject-1.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-reject-1.svg b/1-js/11-async/02-promise-basics/promise-reject-1.svg
new file mode 100644
index 00000000..5a94d805
--- /dev/null
+++ b/1-js/11-async/02-promise-basics/promise-reject-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/11-async/02-promise-basics/promise-reject-1@2x.png b/1-js/11-async/02-promise-basics/promise-reject-1@2x.png
deleted file mode 100644
index fd5fb5cf..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-reject-1@2x.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-resolve-1.png b/1-js/11-async/02-promise-basics/promise-resolve-1.png
deleted file mode 100644
index 87cb6b2d..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-resolve-1.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-resolve-1.svg b/1-js/11-async/02-promise-basics/promise-resolve-1.svg
new file mode 100644
index 00000000..f70eec8c
--- /dev/null
+++ b/1-js/11-async/02-promise-basics/promise-resolve-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/11-async/02-promise-basics/promise-resolve-1@2x.png b/1-js/11-async/02-promise-basics/promise-resolve-1@2x.png
deleted file mode 100644
index 3f6bb90f..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-resolve-1@2x.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-resolve-reject.png b/1-js/11-async/02-promise-basics/promise-resolve-reject.png
deleted file mode 100644
index f6c0abd3..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-resolve-reject.png and /dev/null differ
diff --git a/1-js/11-async/02-promise-basics/promise-resolve-reject.svg b/1-js/11-async/02-promise-basics/promise-resolve-reject.svg
new file mode 100644
index 00000000..657da3ff
--- /dev/null
+++ b/1-js/11-async/02-promise-basics/promise-resolve-reject.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/11-async/02-promise-basics/promise-resolve-reject@2x.png b/1-js/11-async/02-promise-basics/promise-resolve-reject@2x.png
deleted file mode 100644
index 6a695361..00000000
Binary files a/1-js/11-async/02-promise-basics/promise-resolve-reject@2x.png and /dev/null differ
diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md
index 54be060a..e35c619e 100644
--- a/1-js/11-async/03-promise-chaining/article.md
+++ b/1-js/11-async/03-promise-chaining/article.md
@@ -1,10 +1,7 @@
# Promises chaining
-Let's return to the problem mentioned in the chapter .
-
-- We have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts.
-- How to code it well?
+Let's return to the problem mentioned in the chapter : we have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts. How can we code it well?
Promises provide a couple of recipes to do that.
@@ -45,30 +42,12 @@ Here the flow is:
As the result is passed along the chain of handlers, we can see a sequence of `alert` calls: `1` -> `2` -> `4`.
-
+
The whole thing works, because a call to `promise.then` returns a promise, so that we can call the next `.then` on it.
When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it.
-To make these words more clear, here's the start of the chain:
-
-```js run
-new Promise(function(resolve, reject) {
-
- setTimeout(() => resolve(1), 1000);
-
-}).then(function(result) {
-
- alert(result);
- return result * 2; // <-- (1)
-
-}) // <-- (2)
-// .then…
-```
-
-The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
-
**A classic newbie error: technically we can also add many `.then` to a single promise. This is not chaining.**
For example:
@@ -97,7 +76,7 @@ What we did here is just several handlers to one promise. They don't pass the re
Here's the picture (compare it with the chaining above):
-
+
All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`.
@@ -105,9 +84,9 @@ In practice we rarely need multiple handlers for one promise. Chaining is used m
## Returning promises
-Normally, a value returned by a `.then` handler is immediately passed to the next handler. But there's an exception.
+A handler, used in `.then(handler)` may create and return a promise.
-If the returned value is a promise, then the further execution is suspended until it settles. After that, the result of that promise is given to the next `.then` handler.
+In that case further handlers wait till it settles, and then get its result.
For instance:
@@ -141,15 +120,15 @@ new Promise(function(resolve, reject) {
});
```
-Here the first `.then` shows `1` returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result*2`) is passed on to handler of the second `.then` in the line `(**)`. It shows `2` and does the same thing.
+Here the first `.then` shows `1` and returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result * 2`) is passed on to handler of the second `.then`. That handler is in the line `(**)`, it shows `2` and does the same thing.
-So the output is again 1 -> 2 -> 4, but now with 1 second delay between `alert` calls.
+So the output is the same as in the previous example: 1 -> 2 -> 4, but now with 1 second delay between `alert` calls.
Returning promises allows us to build chains of asynchronous actions.
## Example: loadScript
-Let's use this feature with `loadScript` to load scripts one by one, in sequence:
+Let's use this feature with the promisified `loadScript`, defined in the [previous chapter](info:promise-basics#loadscript), to load scripts one by one, in sequence:
```js run
loadScript("/article/promise-chaining/one.js")
@@ -187,7 +166,7 @@ Here each `loadScript` call returns a promise, and the next `.then` runs when it
We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom".
-Please note that technically we can add `.then` directly to each `loadScript`, like this:
+Technically, we could add `.then` directly to each `loadScript`, like this:
```js run
loadScript("/article/promise-chaining/one.js").then(script1 => {
@@ -210,9 +189,7 @@ Sometimes it's ok to write `.then` directly, because the nested function has acc
````smart header="Thenables"
-To be precise, `.then` may return an arbitrary "thenable" object, and it will be treated the same way as a promise.
-
-A "thenable" object is any object with a method `.then`.
+To be precise, a handler may return not exactly a promise, but a so-called "thenable" object - an arbitrary object that has method `.then`, and it will be treated the same way as a promise.
The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have extended set of methods, but also be compatible with native promises, because they implement `.then`.
@@ -232,7 +209,9 @@ class Thenable {
new Promise(resolve => resolve(1))
.then(result => {
+*!*
return new Thenable(result); // (*)
+*/!*
})
.then(alert); // shows 2 after 1000ms
```
@@ -247,7 +226,7 @@ This feature allows to integrate custom objects with promise chains without havi
In frontend programming promises are often used for network requests. So let's see an extended example of that.
-We'll use the [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) method to load the information about the user from the remote server. The method is quite complex, it has many optional parameters, but the basic usage is quite simple:
+We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in [separate chapters](info:fetch), but the basic syntax is quite simple:
```js
let promise = fetch(url);
@@ -264,7 +243,7 @@ fetch('/article/promise-chaining/user.json')
// .then below runs when the remote server responds
.then(function(response) {
// response.text() returns a new promise that resolves with the full response text
- // when we finish downloading it
+ // when it loads
return response.text();
})
.then(function(text) {
@@ -281,19 +260,19 @@ We'll also use arrow functions for brevity:
// same as above, but response.json() parses the remote content as JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
- .then(user => alert(user.name)); // iliakan
+ .then(user => alert(user.name)); // iliakan, got user name
```
Now let's do something with the loaded user.
-For instance, we can make one more request to github, load the user profile and show the avatar:
+For instance, we can make one more request to GitHub, load the user profile and show the avatar:
```js run
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
- // Make a request to github
+ // Make a request to GitHub
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
@@ -308,7 +287,7 @@ fetch('/article/promise-chaining/user.json')
});
```
-The code works, see comments about the details, but it should be quite self-descriptive. Although, there's a potential problem in it, a typical error of those who begin to use promises.
+The code works, see comments about the details. Although, there's a potential problem in it, a typical error of those who begin to use promises.
Look at the line `(*)`: how can we do something *after* the avatar has finished showing and gets removed? For instance, we'd like to show a form for editing that user or something else. As of now, there's no way.
@@ -322,7 +301,7 @@ fetch('/article/promise-chaining/user.json')
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
*!*
- .then(githubUser => new Promise(function(resolve, reject) {
+ .then(githubUser => new Promise(function(resolve, reject) { // (*)
*/!*
let img = document.createElement('img');
img.src = githubUser.avatar_url;
@@ -332,7 +311,7 @@ fetch('/article/promise-chaining/user.json')
setTimeout(() => {
img.remove();
*!*
- resolve(githubUser);
+ resolve(githubUser); // (**)
*/!*
}, 3000);
}))
@@ -340,9 +319,11 @@ fetch('/article/promise-chaining/user.json')
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
```
-Now right after `setTimeout` runs `img.remove()`, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data.
+That is, `.then` handler in the line `(*)` now returns `new Promise`, that becomes settled only after the call of `resolve(githubUser)` in `setTimeout` `(**)`.
-As a rule, an asynchronous action should always return a promise.
+The next `.then` in chain will wait for that.
+
+As a good rule, an asynchronous action should always return a promise.
That makes it possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later.
@@ -383,8 +364,8 @@ loadJson('/article/promise-chaining/user.json')
## Summary
-If `.then` (or `catch/finally`, doesn't matter) handler returns a promise, the rest of the chain waits until it settles. When it does, its result (or error) is passed further.
+If a `.then` (or `catch/finally`, doesn't matter) handler returns a promise, the rest of the chain waits until it settles. When it does, its result (or error) is passed further.
Here's a full picture:
-
+
diff --git a/1-js/11-async/03-promise-chaining/head.html b/1-js/11-async/03-promise-chaining/head.html
index 31c6b427..0a0075fb 100644
--- a/1-js/11-async/03-promise-chaining/head.html
+++ b/1-js/11-async/03-promise-chaining/head.html
@@ -10,25 +10,6 @@ function loadScript(src) {
document.head.append(script);
});
}
-
-class HttpError extends Error {
- constructor(response) {
- super(`${response.status} for ${response.url}`);
- this.name = 'HttpError';
- this.response = response;
- }
-}
-
-function loadJson(url) {
- return fetch(url)
- .then(response => {
- if (response.status == 200) {
- return response.json();
- } else {
- throw new HttpError(response);
- }
- })
-}
return valuereturn promisethrow errorstate: "fulfilled"result: valuestate: "rejected"result: error...with the resultof the new promise...state: "pending"result: undefinedthe call of .then(handler) always returns a promise:if handler ends with…that promise settles with:
\ No newline at end of file
diff --git a/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png b/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png
deleted file mode 100644
index 98d0fa46..00000000
Binary files a/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png and /dev/null differ
diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain.png b/1-js/11-async/03-promise-chaining/promise-then-chain.png
deleted file mode 100644
index 52939e5f..00000000
Binary files a/1-js/11-async/03-promise-chaining/promise-then-chain.png and /dev/null differ
diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain.svg b/1-js/11-async/03-promise-chaining/promise-then-chain.svg
new file mode 100644
index 00000000..631ad64a
--- /dev/null
+++ b/1-js/11-async/03-promise-chaining/promise-then-chain.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png b/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png
deleted file mode 100644
index 731f8c93..00000000
Binary files a/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png and /dev/null differ
diff --git a/1-js/11-async/03-promise-chaining/promise-then-many.png b/1-js/11-async/03-promise-chaining/promise-then-many.png
deleted file mode 100644
index c37f6fe0..00000000
Binary files a/1-js/11-async/03-promise-chaining/promise-then-many.png and /dev/null differ
diff --git a/1-js/11-async/03-promise-chaining/promise-then-many.svg b/1-js/11-async/03-promise-chaining/promise-then-many.svg
new file mode 100644
index 00000000..a50359e5
--- /dev/null
+++ b/1-js/11-async/03-promise-chaining/promise-then-many.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/11-async/03-promise-chaining/promise-then-many@2x.png b/1-js/11-async/03-promise-chaining/promise-then-many@2x.png
deleted file mode 100644
index 6fc13c2c..00000000
Binary files a/1-js/11-async/03-promise-chaining/promise-then-many@2x.png and /dev/null differ
diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md
index 6a0c64c0..624c7e81 100644
--- a/1-js/11-async/04-promise-error-handling/article.md
+++ b/1-js/11-async/04-promise-error-handling/article.md
@@ -1,11 +1,9 @@
# Error handling with promises
-Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections).
+Promise chains are great at error handling. When a promise rejects, the control jumps to the closest rejection handler. That's very convenient in practice.
-Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice.
-
-For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error:
+For instance, in the code below the URL to `fetch` is wrong (no such site) and `.catch` handles the error:
```js run
*!*
@@ -15,17 +13,9 @@ fetch('https://no-such-server.blabla') // rejects
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
```
-Or, maybe, everything is all right with the server, but the response is not a valid JSON:
+As you can see, the `.catch` doesn't have to be immediate. It may appear after one or maybe several `.then`.
-```js run
-fetch('/') // fetch works fine now, the server responds successfully
-*!*
- .then(response => response.json()) // rejects: the page is HTML, not a valid json
-*/!*
- .catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
-```
-
-The easiest way to catch all errors is to append `.catch` to the end of chain:
+Or, maybe, everything is all right with the site, but the response is not valid JSON. The easiest way to catch all errors is to append `.catch` to the end of chain:
```js run
fetch('/article/promise-chaining/user.json')
@@ -48,11 +38,11 @@ fetch('/article/promise-chaining/user.json')
*/!*
```
-Normally, `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.
+Normally, such `.catch` doesn't trigger at all. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.
## Implicit try..catch
-The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection.
+The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an exception happens, it gets caught and treated as a rejection.
For instance, this code:
@@ -74,9 +64,9 @@ new Promise((resolve, reject) => {
}).catch(alert); // Error: Whoops!
```
-The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection.
+The "invisible `try..catch`" around the executor automatically catches the error and turns it into rejected promise.
-That's so not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
+This happens not only in the executor function, but in its handlers as well. If we `throw` inside a `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Here's an example:
@@ -90,7 +80,7 @@ new Promise((resolve, reject) => {
}).catch(alert); // Error: Whoops!
```
-That's so not only for `throw`, but for any errors, including programming errors as well:
+This happens for all errors, not just those caused by the `throw` statement. For example, a programming error:
```js run
new Promise((resolve, reject) => {
@@ -102,11 +92,11 @@ new Promise((resolve, reject) => {
}).catch(alert); // ReferenceError: blabla is not defined
```
-As a side effect, the final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
+The final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
## Rethrowing
-As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
+As we already noticed, `.catch` at the end of the chain is similar to `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them.
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises.
@@ -150,7 +140,7 @@ new Promise((resolve, reject) => {
}
}).then(function() {
- /* never runs here */
+ /* doesn't run here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
@@ -159,116 +149,28 @@ new Promise((resolve, reject) => {
});
```
-Then the execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain.
-
-In the section below we'll see a practical example of rethrowing.
-
-## Fetch error handling example
-
-Let's improve error handling for the user-loading example.
-
-The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response.
-
-What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and github returns a page with error 404 at `(**)`?
-
-```js run
-fetch('no-such-user.json') // (*)
- .then(response => response.json())
- .then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
- .then(response => response.json())
- .catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
- // ...
-```
-
-
-As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist.
-
-That's not good, because the error just falls through the chain, without details: what failed and where.
-
-So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error.
-
-```js run
-class HttpError extends Error { // (1)
- constructor(response) {
- super(`${response.status} for ${response.url}`);
- this.name = 'HttpError';
- this.response = response;
- }
-}
-
-function loadJson(url) { // (2)
- return fetch(url)
- .then(response => {
- if (response.status == 200) {
- return response.json();
- } else {
- throw new HttpError(response);
- }
- })
-}
-
-loadJson('no-such-user.json') // (3)
- .catch(alert); // HttpError: 404 for .../no-such-user.json
-```
-
-1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts `response` object and saves it in the error. So error-handling code will be able to access it.
-2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic.
-3. Now `alert` shows better message.
-
-The great thing about having our own class for errors is that we can easily check for it in error-handling code.
-
-For instance, we can make a request, and then if we get 404 -- ask the user to modify the information.
-
-The code below loads a user with the given name from github. If there's no such user, then it asks for the correct name:
-
-```js run
-function demoGithubUser() {
- let name = prompt("Enter a name?", "iliakan");
-
- return loadJson(`https://api.github.com/users/${name}`)
- .then(user => {
- alert(`Full name: ${user.name}.`);
- return user;
- })
- .catch(err => {
-*!*
- if (err instanceof HttpError && err.response.status == 404) {
-*/!*
- alert("No such user, please reenter.");
- return demoGithubUser();
- } else {
- throw err; // (*)
- }
- });
-}
-
-demoGithubUser();
-```
-
-Please note: `.catch` here catches all errors, but it "knows how to handle" only `HttpError 404`. In that particular case it means that there's no such user, and `.catch` just retries in that case.
-
-For other errors, it has no idea what could go wrong. Maybe a programming error or something. So it just rethrows it in the line `(*)`.
+The execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain.
## Unhandled rejections
-What happens when an error is not handled? For instance, after the rethrow `(*)` in the example above.
-
-Or we could just forget to append an error handler to the end of the chain, like here:
+What happens when an error is not handled? For instance, we forgot to append `.catch` to the end of the chain, like here:
```js untrusted run refresh
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
- // zero or many promise handlers
+ // successful promise handlers, one or more
}); // without .catch at the end!
```
-In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck".
+In case of an error, the promise becomes rejected, and the execution should jump to the closest rejection handler. But there is none. So the error gets "stuck". There's no code to handle it.
-In practice, just like with a regular unhandled errors, it means that something terribly gone wrong, the script probably died.
+In practice, just like with regular unhandled errors in code, it means that something has terribly gone wrong.
-Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console.
+What happens when a regular error occurs and is not caught by `try..catch`? The script dies with a message in console. Similar thing happens with unhandled promise rejections.
+
+JavaScript engine tracks such rejections and generates a global error in that case. You can see it in the console if you run the example above.
In the browser we can catch such errors using the event `unhandledrejection`:
@@ -292,52 +194,11 @@ If an error occurs, and there's no `.catch`, the `unhandledrejection` handler tr
Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server.
-In non-browser environments like Node.JS there are other similar ways to track unhandled errors.
-
+In non-browser environments like Node.js there are other ways to track unhandled errors.
## Summary
-- `.catch` handles promise rejections of all kinds: be it a `reject()` call, or an error thrown in a handler.
-- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones.
-- It's normal not to use `.catch` if we don't know how to handle errors (all errors are unrecoverable).
-- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them. So that our app never "just dies".
-
-And finally, if we have load-indication, then `.finally` is a great handler to stop it when the fetch is complete:
-
-```js run
-function demoGithubUser() {
- let name = prompt("Enter a name?", "iliakan");
-
-*!*
- document.body.style.opacity = 0.3; // (1) start the indication
-*/!*
-
- return loadJson(`https://api.github.com/users/${name}`)
-*!*
- .finally(() => { // (2) stop the indication
- document.body.style.opacity = '';
- return new Promise(resolve => setTimeout(resolve, 0)); // (*)
- })
-*/!*
- .then(user => {
- alert(`Full name: ${user.name}.`);
- return user;
- })
- .catch(err => {
- if (err instanceof HttpError && err.response.status == 404) {
- alert("No such user, please reenter.");
- return demoGithubUser();
- } else {
- throw err;
- }
- });
-}
-
-demoGithubUser();
-```
-
-Here on the line `(1)` we indicate loading by dimming the document. The method doesn't matter, could use any type of indication instead.
-
-When the promise is settled, be it a successful fetch or an error, `finally` triggers at the line `(2)` and stops the indication.
-
-There's a little browser trick `(*)` with returning a zero-timeout promise from `finally`. That's because some browsers (like Chrome) need "a bit time" outside promise handlers to paint document changes. So it ensures that the indication is visually stopped before going further on the chain.
+- `.catch` handles errors in promises of all kinds: be it a `reject()` call, or an error thrown in a handler.
+- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones (maybe they are programming mistakes).
+- It's ok not to use `.catch` at all, if there's no way to recover from an error.
+- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them, so that our app never "just dies".
diff --git a/1-js/11-async/04-promise-error-handling/head.html b/1-js/11-async/04-promise-error-handling/head.html
index 31c6b427..a0b74196 100644
--- a/1-js/11-async/04-promise-error-handling/head.html
+++ b/1-js/11-async/04-promise-error-handling/head.html
@@ -1,16 +1,4 @@
-
-
-