+
+Debounced function debounce(handler, 1000) is called on this input:
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
index 4f5867ded..83e75f315 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
@@ -1,28 +1,13 @@
```js demo
-function debounce(f, ms) {
-
- let isCooldown = false;
-
+function debounce(func, ms) {
+ let timeout;
return function() {
- if (isCooldown) return;
-
- f.apply(this, arguments);
-
- isCooldown = true;
-
- setTimeout(() => isCooldown = false, ms);
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, arguments), ms);
};
-
}
-```
-
-A call to `debounce` returns a wrapper. There may be two states:
-- `isCooldown = false` -- ready to run.
-- `isCooldown = true` -- waiting for the timeout.
-
-In the first call `isCooldown` is falsy, so the call proceeds, and the state changes to `true`.
+```
-While `isCooldown` is true, all other calls are ignored.
+A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout.
-Then `setTimeout` reverts it to `false` after the given delay.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
index 2620f1c71..5b0fcc5f8 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
@@ -4,21 +4,48 @@ importance: 5
# Debounce decorator
-The result of `debounce(f, ms)` decorator should be a wrapper that passes the call to `f` at maximum once per `ms` milliseconds.
+The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments.
-In other words, when we call a "debounced" function, it guarantees that all future calls to the function made less than `ms` milliseconds after the previous call will be ignored.
+In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`).
-For instance:
+For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`.
-```js no-beautify
-let f = debounce(alert, 1000);
+Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call.
-f(1); // runs immediately
-f(2); // ignored
+
-setTimeout( () => f(3), 100); // ignored ( only 100 ms passed )
-setTimeout( () => f(4), 1100); // runs
-setTimeout( () => f(5), 1500); // ignored (less than 1000 ms from the last run)
+...And it will get the arguments of the very last call, other calls are ignored.
+
+Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)):
+
+```js
+let f = _.debounce(alert, 1000);
+
+f("a");
+setTimeout( () => f("b"), 200);
+setTimeout( () => f("c"), 500);
+// debounced function waits 1000ms after the last call and then runs: alert("c")
+```
+
+Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished.
+
+There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result.
+
+In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input.
+
+```online
+
+In this live example, the handler puts the result into a box below, try it:
+
+[iframe border=1 src="debounce" height=200]
+
+See? The second input calls the debounced function, so its content is processed after 1000ms from the last input.
```
-In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources.
+So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else.
+
+It waits the given time after the last call, and then runs its function, that can process the result.
+
+The task is to implement `debounce` decorator.
+
+Hint: that's just a few lines if you think about it :)
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js
index d2cf8e151..e671438f6 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js
@@ -7,8 +7,8 @@ describe("throttle(f, 1000)", function() {
}
before(function() {
- f1000 = throttle(f, 1000);
this.clock = sinon.useFakeTimers();
+ f1000 = throttle(f, 1000);
});
it("the first call runs now", function() {
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
index cf851f771..6950664be 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
@@ -12,11 +12,10 @@ function throttle(func, ms) {
savedThis = this;
return;
}
+ isThrottled = true;
func.apply(this, arguments); // (1)
- isThrottled = true;
-
setTimeout(function() {
isThrottled = false; // (3)
if (savedArgs) {
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 9e08874af..6df7af132 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
@@ -4,16 +4,21 @@ importance: 5
# Throttle decorator
-Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper, passing the call to `f` at maximum once per `ms` milliseconds. Those calls that fall into the "cooldown" period, are ignored.
+Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
-**The difference with `debounce` -- if an ignored call is the last during the cooldown, then it executes at the end of the delay.**
+When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
+
+The difference with debounce is that it's completely different decorator:
+- `debounce` runs the function once after the "cooldown" period. Good for processing the final result.
+- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
+
+In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds.
Let's check the real-life application to better understand that requirement and to see where it comes from.
**For instance, we want to track mouse movements.**
In a 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).
-
**We'd like to update some information on the web-page when the pointer moves.**
...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.
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 373500e13..c5d785493 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
@@ -36,11 +36,11 @@ function cachingDecorator(func) {
slow = cachingDecorator(slow);
-alert( slow(1) ); // slow(1) is cached
-alert( "Again: " + slow(1) ); // the same
+alert( slow(1) ); // slow(1) is cached and the result returned
+alert( "Again: " + slow(1) ); // slow(1) result returned from cache
-alert( slow(2) ); // slow(2) is cached
-alert( "Again: " + slow(2) ); // the same as the previous line
+alert( slow(2) ); // slow(2) is cached and the result returned
+alert( "Again: " + slow(2) ); // slow(2) result returned from cache
```
In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior.
@@ -149,8 +149,8 @@ let user = { name: "John" };
let admin = { name: "Admin" };
// use call to pass different objects as "this"
-sayHi.call( user ); // this = John
-sayHi.call( admin ); // this = Admin
+sayHi.call( user ); // John
+sayHi.call( admin ); // Admin
```
And here we use `call` to call `say` with the given context and phrase:
@@ -209,7 +209,7 @@ To make it all clear, let's see more deeply how `this` is passed along:
2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot).
3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method.
-## Going multi-argument with "func.apply"
+## Going multi-argument
Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions.
@@ -236,7 +236,7 @@ There are many solutions possible:
For many practical applications, the 3rd variant is good enough, so we'll stick to 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.
+Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`.
Here's a more powerful `cachingDecorator`:
@@ -284,6 +284,8 @@ 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.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function.
+## func.apply
+
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:
@@ -299,18 +301,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l
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
+func.call(context, ...args);
+func.apply(context, args);
```
-There's only a minor difference:
+They perform the same call of `func` with given context and arguments.
-- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
-- The `apply` accepts only *array-like* `args`.
+There's only a subtle difference regarding `args`:
-So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
+- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`.
+- The `apply` accepts only *array-like* `args`.
-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.
+...And for objects that are both iterable and array-like, such as a real array, we can 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*.
@@ -344,7 +346,7 @@ function hash(args) {
}
```
-...Unfortunately, that won't work. Because we are calling `hash(arguments)` and `arguments` object is both iterable and array-like, but not a real array.
+...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array.
So calling `join` on it would fail, as we can see below:
diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md
index 16a50942d..9d705cdcd 100644
--- a/1-js/06-advanced-functions/10-bind/article.md
+++ b/1-js/06-advanced-functions/10-bind/article.md
@@ -167,7 +167,7 @@ sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
// even if the value of user changes within 1 second
-// sayHi uses the pre-bound value
+// sayHi uses the pre-bound value which is reference to the old user object
user = {
sayHi() { alert("Another user in setTimeout!"); }
};
@@ -187,8 +187,8 @@ let user = {
let say = user.say.bind(user);
-say("Hello"); // Hello, John ("Hello" argument is passed to say)
-say("Bye"); // Bye, John ("Bye" is passed to say)
+say("Hello"); // Hello, John! ("Hello" argument is passed to say)
+say("Bye"); // Bye, John! ("Bye" is passed to say)
```
````smart header="Convenience method: `bindAll`"
@@ -202,7 +202,7 @@ 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.
+JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) in lodash.
````
## Partial functions
@@ -247,7 +247,7 @@ The call to `mul.bind(null, 2)` creates a new function `double` that passes call
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`.
+Please note that 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:
@@ -279,7 +279,7 @@ What if we'd like to fix some arguments, but not the context `this`? For example
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.
+Fortunately, a function `partial` for binding only arguments can be easily implemented.
Like this:
@@ -313,7 +313,7 @@ The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that call
- 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?
+So easy to do it with the spread syntax, right?
Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library.
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 f5caeaece..8730277ad 100644
--- a/1-js/06-advanced-functions/12-arrow-functions/article.md
+++ b/1-js/06-advanced-functions/12-arrow-functions/article.md
@@ -52,7 +52,7 @@ let group = {
*!*
this.students.forEach(function(student) {
// Error: Cannot read property 'title' of undefined
- alert(this.title + ': ' + student)
+ alert(this.title + ': ' + student);
});
*/!*
}
@@ -87,7 +87,7 @@ For instance, `defer(f, ms)` gets a function and returns a wrapper around it tha
```js run
function defer(f, ms) {
return function() {
- setTimeout(() => f.apply(this, arguments), ms)
+ setTimeout(() => f.apply(this, arguments), ms);
};
}
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 e894f0662..bdc693418 100644
--- a/1-js/07-object-properties/01-property-descriptors/article.md
+++ b/1-js/07-object-properties/01-property-descriptors/article.md
@@ -19,7 +19,7 @@ We didn't see them yet, because generally they do not show up. When we create a
First, let's see how to get those flags.
-The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
+The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
The syntax is:
```js
@@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) );
*/
```
-To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty).
+To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
The syntax is:
@@ -66,7 +66,7 @@ Object.defineProperty(obj, propertyName, descriptor)
: The object and its property to apply the descriptor.
`descriptor`
-: Property descriptor to apply.
+: Property descriptor object to apply.
If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`.
@@ -194,7 +194,7 @@ alert(Object.keys(user)); // name
The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties.
-A non-configurable property can not be deleted.
+A non-configurable property can't be deleted, its attributes can't be modified.
For instance, `Math.PI` is non-writable, non-enumerable and non-configurable:
@@ -214,49 +214,67 @@ alert( JSON.stringify(descriptor, null, 2 ) );
So, a programmer is unable to change the value of `Math.PI` or overwrite it.
```js run
-Math.PI = 3; // Error
+Math.PI = 3; // Error, because it has writable: false
// delete Math.PI won't work either
```
+We also can't change `Math.PI` to be `writable` again:
+
+```js run
+// Error, because of configurable: false
+Object.defineProperty(Math, "PI", { writable: true });
+```
+
+There's absolutely nothing we can do with `Math.PI`.
+
Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`.
-To be precise, non-configurability imposes several restrictions on `defineProperty`:
-1. Can't change `configurable` flag.
-2. Can't change `enumerable` flag.
-3. Can't change `writable: false` to `true` (the other way round works).
-4. Can't change `get/set` for an accessor property (but can assign them if absent).
+**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.**
-Here we are making `user.name` a "forever sealed" constant:
+Here `user.name` is non-configurable, but we can still change it (as it's writable):
```js run
-let user = { };
+let user = {
+ name: "John"
+};
+
+Object.defineProperty(user, "name", {
+ configurable: false
+});
+
+user.name = "Pete"; // works fine
+delete user.name; // Error
+```
+
+And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`:
+
+```js run
+let user = {
+ name: "John"
+};
Object.defineProperty(user, "name", {
- value: "John",
writable: false,
configurable: false
});
-*!*
// won't be able to change user.name or its flags
// all this won't work:
-// user.name = "Pete"
-// delete user.name
-// defineProperty(user, "name", { value: "Pete" })
-Object.defineProperty(user, "name", {writable: true}); // Error
-*/!*
+user.name = "Pete";
+delete user.name;
+Object.defineProperty(user, "name", { value: "Pete" });
```
-```smart header="\"Non-configurable\" doesn't mean \"non-writable\""
-Notable exception: a value of non-configurable, but writable property can be changed.
+```smart header="The only attribute change possible: writable true -> false"
+There's a minor exception about changing flags.
-The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value.
+We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though.
```
## Object.defineProperties
-There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
+There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once.
The syntax is:
@@ -282,7 +300,7 @@ So, we can set many properties at once.
## Object.getOwnPropertyDescriptors
-To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
+To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors).
Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object:
@@ -300,7 +318,7 @@ for (let key in user) {
...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred.
-Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones.
+Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones.
## Sealing an object globally
@@ -308,24 +326,24 @@ 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)
+[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)
: Forbids the addition of new properties to the object.
-[Object.seal(obj)](mdn:js/Object/seal)
+[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal)
: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties.
-[Object.freeze(obj)](mdn:js/Object/freeze)
+[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
: 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)
+[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible)
: Returns `false` if adding properties is forbidden, otherwise `true`.
-[Object.isSealed(obj)](mdn:js/Object/isSealed)
+[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed)
: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`.
-[Object.isFrozen(obj)](mdn:js/Object/isFrozen)
+[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen)
: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`.
These methods are rarely used in practice.
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 726529c5b..45b9e70ed 100644
--- a/1-js/07-object-properties/02-property-accessors/article.md
+++ b/1-js/07-object-properties/02-property-accessors/article.md
@@ -1,11 +1,11 @@
# Property getters and setters
-There are two kinds of properties.
+There are two kinds of object properties.
The first kind is *data properties*. We already know how to work with them. All properties that we've been using until 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.
+The second type of properties is something new. It's *accessor properties*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.
## Getters and setters
@@ -53,7 +53,7 @@ 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.
+From the 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:
@@ -94,11 +94,7 @@ alert(user.name); // Alice
alert(user.surname); // Cooper
```
-As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist.
-
-```smart header="No way to handle `delete`"
-There's no similar method to handle deletion of an accessor property. Only getter/setter methods may exist.
-```
+As the result, we have a "virtual" property `fullName`. It is readable and writable.
## Accessor descriptors
@@ -138,7 +134,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 (has `get/set` methods) or a data property (has a `value`), not both.
+Please note 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:
diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md
index 69e7c5f5c..8cb301c80 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/article.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/article.md
@@ -12,11 +12,11 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named

-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.
+When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it.
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
-One of them is to use `__proto__`, like this:
+One of them is to use the special name `__proto__`, like this:
```js run
let animal = {
@@ -27,19 +27,11 @@ let rabbit = {
};
*!*
-rabbit.__proto__ = animal;
+rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal
*/!*
```
-```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
-Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it.
-
-It exists for historical reasons. In modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later.
-
-By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples.
-```
-
-If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`.
+Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`.
For instance:
@@ -62,7 +54,7 @@ alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true
```
-Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
+Here the line `(*)` sets `animal` to be the 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):
@@ -130,6 +122,8 @@ alert(longEar.jumps); // true (from rabbit)

+Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`.
+
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.
@@ -137,6 +131,19 @@ There are only two limitations:
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
+
+```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
+It's a common mistake of novice developers not to know the difference between these two.
+
+Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language.
+
+The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later.
+
+By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it.
+
+As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples.
+```
+
## Writing doesn't use prototype
The prototype is only used for reading properties.
@@ -197,6 +204,9 @@ alert(admin.fullName); // John Smith (*)
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
+
+alert(admin.fullName); // Alice Cooper, state of admin modified
+alert(user.fullName); // John Smith, state of user protected
```
Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called.
@@ -277,7 +287,7 @@ 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`.
+If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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):
diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
index 0073e252e..372d50dd6 100644
--- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
+++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
@@ -38,7 +38,12 @@ Why `user2.name` is `undefined`?
Here's how `new user.constructor('Pete')` works:
1. First, it looks for `constructor` in `user`. Nothing.
-2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing.
-3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used.
+2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!).
+3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`.
+4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used.
-At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all.
+Finally, at the end, we have `let user2 = new Object('Pete')`.
+
+Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`.
+
+(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all).
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md
index c106d1d90..b1ef51826 100644
--- a/1-js/08-prototypes/02-function-prototype/article.md
+++ b/1-js/08-prototypes/02-function-prototype/article.md
@@ -41,7 +41,7 @@ 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` 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".
+`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object.
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.
```
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index 378936c9a..6cf7aebb4 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -33,7 +33,9 @@ We can check it like this:
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
-// obj.toString === obj.__proto__.toString == Object.prototype.toString
+
+alert(obj.toString === obj.__proto__.toString); //true
+alert(obj.toString === Object.prototype.toString); //true
```
Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`:
diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md
index 80f5a956a..a4ce2646c 100644
--- a/1-js/08-prototypes/04-prototype-methods/article.md
+++ b/1-js/08-prototypes/04-prototype-methods/article.md
@@ -7,7 +7,7 @@ The `__proto__` is considered outdated and somewhat deprecated (in browser-only
The modern methods are:
-- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
+- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`.
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`.
@@ -57,7 +57,6 @@ The descriptors are in the same format as described in the chapter = " in the declaration, and that's it.
-alert(User.prototype.sayHi); // placed in User.prototype
-alert(User.prototype.name); // undefined, not placed in User.prototype
+The important difference of class fields is that they are set on individual objects, not `User.prototype`:
+
+```js run
+class User {
+*!*
+ name = "John";
+*/!*
+}
+
+let user = new User();
+alert(user.name); // John
+alert(User.prototype.name); // undefined
```
-The property `name` is not placed into `User.prototype`. Instead, it is created by `new` before calling the constructor, it's a property of the object itself.
+We can also assign values using more complex expressions and function calls:
+
+```js run
+class User {
+*!*
+ name = prompt("Name, please?", "John");
+*/!*
+}
+
+let user = new User();
+alert(user.name); // John
+```
+
+
+### Making bound methods with class fields
+
+As demonstrated in the chapter functions in JavaScript have a dynamic `this`. It depends on the context of the call.
+
+So if an object method is passed around and called in another context, `this` won't be a reference to its object any more.
+
+For instance, this code will show `undefined`:
+
+```js run
+class Button {
+ constructor(value) {
+ this.value = value;
+ }
+
+ click() {
+ alert(this.value);
+ }
+}
+
+let button = new Button("hello");
+
+*!*
+setTimeout(button.click, 1000); // undefined
+*/!*
+```
+
+The problem is called "losing `this`".
+
+There are two approaches to fixing it, as discussed in the chapter :
+
+1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`.
+2. Bind the method to object, e.g. in the constructor.
+
+Class fields provide another, quite elegant syntax:
+
+```js run
+class Button {
+ constructor(value) {
+ this.value = value;
+ }
+*!*
+ click = () => {
+ alert(this.value);
+ }
+*/!*
+}
+
+let button = new Button("hello");
+
+setTimeout(button.click, 1000); // hello
+```
+
+The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct.
+
+That's especially useful in browser environment, for event listeners.
## Summary
diff --git a/1-js/09-classes/02-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
index ca613ca5e..be2053cfc 100644
--- a/1-js/09-classes/02-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
@@ -1,7 +1,7 @@
class ExtendedClock extends Clock {
constructor(options) {
super(options);
- let { precision=1000 } = options;
+ let { precision = 1000 } = options;
this.precision = precision;
}
diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md
index 1000fdd84..48c9e468e 100644
--- a/1-js/09-classes/02-class-inheritance/article.md
+++ b/1-js/09-classes/02-class-inheritance/article.md
@@ -16,7 +16,7 @@ class Animal {
this.name = name;
}
run(speed) {
- this.speed += speed;
+ this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
@@ -55,7 +55,7 @@ rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
```
-Object of `Rabbit` class have access to both `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`.
+Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`.
Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`.
@@ -76,8 +76,8 @@ For instance, a function call that generates the parent class:
```js run
function f(phrase) {
return class {
- sayHi() { alert(phrase) }
- }
+ sayHi() { alert(phrase); }
+ };
}
*!*
@@ -124,7 +124,7 @@ class Animal {
}
run(speed) {
- this.speed += speed;
+ this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
@@ -151,7 +151,7 @@ class Rabbit extends Animal {
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
-rabbit.stop(); // White Rabbit stands still. 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.
@@ -230,7 +230,9 @@ let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
Whoops! We've got an error. Now we can't create rabbits. What went wrong?
-The short answer is: constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`.
+The short answer is:
+
+- **Constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`.**
...But why? What's going on here? Indeed, the requirement seems strange.
@@ -243,7 +245,7 @@ That label affects its behavior with `new`.
- When a regular function is executed with `new`, 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 a derived constructor must call `super` in order to execute its parent (non-derived) constructor, otherwise the object for `this` won't be created. And we'll get an error.
+So a derived constructor must call `super` in order to execute its parent (base) constructor, otherwise the object for `this` won't be created. And we'll get an error.
For the `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here:
@@ -279,6 +281,102 @@ alert(rabbit.earLength); // 10
```
+
+### Overriding class fields: a tricky note
+
+```warn header="Advanced note"
+This note assumes you have a certain experience with classes, maybe in other programming languages.
+
+It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often).
+
+If you find it difficult to understand, just go on, continue reading, then return to it some time later.
+```
+
+We can override not only methods, but also class fields.
+
+Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages.
+
+Consider this example:
+
+```js run
+class Animal {
+ name = 'animal';
+
+ constructor() {
+ alert(this.name); // (*)
+ }
+}
+
+class Rabbit extends Animal {
+ name = 'rabbit';
+}
+
+new Animal(); // animal
+*!*
+new Rabbit(); // animal
+*/!*
+```
+
+Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value.
+
+There's no own constructor in `Rabbit`, so `Animal` constructor is called.
+
+What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`.
+
+**In other words, the parent constructor always uses its own field value, not the overridden one.**
+
+What's odd about it?
+
+If it's not clear yet, please compare with methods.
+
+Here's the same code, but instead of `this.name` field we call `this.showName()` method:
+
+```js run
+class Animal {
+ showName() { // instead of this.name = 'animal'
+ alert('animal');
+ }
+
+ constructor() {
+ this.showName(); // instead of alert(this.name);
+ }
+}
+
+class Rabbit extends Animal {
+ showName() {
+ alert('rabbit');
+ }
+}
+
+new Animal(); // animal
+*!*
+new Rabbit(); // rabbit
+*/!*
+```
+
+Please note: now the output is different.
+
+And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method.
+
+...But for class fields it's not so. As said, the parent constructor always uses the parent field.
+
+Why is there a difference?
+
+Well, the reason is the field initialization order. The class field is initialized:
+- Before constructor for the base class (that doesn't extend anything),
+- Immediately after `super()` for the derived class.
+
+In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`.
+
+So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used.
+
+This subtle difference between fields and methods is specific to JavaScript
+
+Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here.
+
+If it becomes a problem, one can fix it by using methods or getters/setters instead of fields.
+
+
## Super: internals, [[HomeObject]]
```warn header="Advanced information"
@@ -438,7 +536,7 @@ It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `long
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 very existence 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.
@@ -447,7 +545,7 @@ Here's the demo of a wrong `super` result after copying:
```js run
let animal = {
sayHi() {
- console.log(`I'm an animal`);
+ alert(`I'm an animal`);
}
};
@@ -461,7 +559,7 @@ let rabbit = {
let plant = {
sayHi() {
- console.log("I'm a plant");
+ alert("I'm a plant");
}
};
@@ -478,7 +576,7 @@ tree.sayHi(); // I'm an animal (?!?)
*/!*
```
-A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong.
+A call to `tree.sayHi()` shows "I'm an animal". Definitely wrong.
The reason is simple:
- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication?
@@ -499,7 +597,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]]
```js run
let animal = {
- eat: function() { // intentially writing like this instead of eat() {...
+ eat: function() { // intentionally writing like this instead of eat() {...
// ...
}
};
diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg
similarity index 59%
rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg
rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg
index 34d783b4d..915ab9aa6 100644
--- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg
+++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
similarity index 100%
rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md
rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md
similarity index 92%
rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md
rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md
index b82a4255e..1d0f98a74 100644
--- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md
+++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md
@@ -1,4 +1,4 @@
-importance: 5
+importance: 3
---
@@ -38,5 +38,5 @@ class Rabbit extends Object {
let rabbit = new Rabbit("Rab");
-alert( rabbit.hasOwnProperty('name') ); // true
+alert( rabbit.hasOwnProperty('name') ); // Error
```
diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md
index dd09a0262..c75ec257f 100644
--- a/1-js/09-classes/03-static-properties-methods/article.md
+++ b/1-js/09-classes/03-static-properties-methods/article.md
@@ -20,7 +20,7 @@ User.staticMethod(); // true
That actually does the same as assigning it as a property directly:
```js run
-class User() { }
+class User { }
User.staticMethod = function() {
alert(this === User);
@@ -125,7 +125,7 @@ That is the same as a direct assignment to `Article`:
Article.publisher = "Ilya Kantor";
```
-## Inheritance of static properties and methods
+## Inheritance of static properties and methods [#statics-and-inheritance]
Static properties and methods are inherited.
diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md
index 6d77e8ed7..91efb89ee 100644
--- a/1-js/09-classes/04-private-protected-properties-methods/article.md
+++ b/1-js/09-classes/04-private-protected-properties-methods/article.md
@@ -96,7 +96,9 @@ class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
- if (value < 0) throw new Error("Negative water");
+ if (value < 0) {
+ value = 0;
+ }
this._waterAmount = value;
}
@@ -114,10 +116,10 @@ class CoffeeMachine {
let coffeeMachine = new CoffeeMachine(100);
// add water
-coffeeMachine.waterAmount = -10; // Error: Negative water
+coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10
```
-Now the access is under control, so setting the water below zero fails.
+Now the access is under control, so setting the water amount below zero becomes impossible.
## Read-only "power"
@@ -159,7 +161,7 @@ class CoffeeMachine {
_waterAmount = 0;
*!*setWaterAmount(value)*/!* {
- if (value < 0) throw new Error("Negative water");
+ if (value < 0) value = 0;
this._waterAmount = value;
}
@@ -190,7 +192,7 @@ There's a finished JavaScript proposal, almost in the standard, that provides la
Privates should start with `#`. They are only accessible from inside the class.
-For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`:
+For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`:
```js run
class CoffeeMachine {
@@ -199,19 +201,23 @@ class CoffeeMachine {
*/!*
*!*
- #checkWater(value) {
- if (value < 0) throw new Error("Negative water");
- if (value > this.#waterLimit) throw new Error("Too much water");
+ #fixWaterAmount(value) {
+ if (value < 0) return 0;
+ if (value > this.#waterLimit) return this.#waterLimit;
}
*/!*
+ setWaterAmount(value) {
+ this.#waterLimit = this.#fixWaterAmount(value);
+ }
+
}
let coffeeMachine = new CoffeeMachine();
*!*
// can't access privates from outside of the class
-coffeeMachine.#checkWater(); // Error
+coffeeMachine.#fixWaterAmount(123); // Error
coffeeMachine.#waterLimit = 1000; // Error
*/!*
```
@@ -232,7 +238,7 @@ class CoffeeMachine {
}
set waterAmount(value) {
- if (value < 0) throw new Error("Negative water");
+ if (value < 0) value = 0;
this.#waterAmount = value;
}
}
@@ -279,7 +285,7 @@ With private fields that's impossible: `this['#name']` doesn't work. That's a sy
## Summary
-In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)").
+In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)).
It gives the following benefits:
diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
index aa973da06..f9db989ca 100644
--- a/1-js/09-classes/06-instanceof/article.md
+++ b/1-js/09-classes/06-instanceof/article.md
@@ -2,7 +2,7 @@
The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account.
-Such a check may be necessary in many cases. Here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type.
+Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type.
## The instanceof operator [#ref-instanceof]
@@ -93,7 +93,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
alert(rabbit instanceof Animal); // true
*/!*
- // rabbit.__proto__ === Rabbit.prototype
+ // rabbit.__proto__ === Animal.prototype (no match)
*!*
// rabbit.__proto__.__proto__ === Animal.prototype (match!)
*/!*
@@ -190,7 +190,7 @@ For most environment-specific objects, there is such a property. Here are some b
```js run
// toStringTag for the environment-specific object and class:
-alert( window[Symbol.toStringTag]); // window
+alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md
index 2ec196105..06001d900 100644
--- a/1-js/09-classes/07-mixins/article.md
+++ b/1-js/09-classes/07-mixins/article.md
@@ -69,7 +69,7 @@ let sayMixin = {
};
let sayHiMixin = {
- __proto__: sayMixin, // (or we could use Object.create to set the prototype here)
+ __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here)
sayHi() {
*!*
@@ -140,7 +140,7 @@ let eventMixin = {
* menu.off('select', handler)
*/
off(eventName, handler) {
- let handlers = this._eventHandlers && this._eventHandlers[eventName];
+ let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
@@ -154,7 +154,7 @@ let eventMixin = {
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
- if (!this._eventHandlers || !this._eventHandlers[eventName]) {
+ if (!this._eventHandlers?.[eventName]) {
return; // no handlers for that event name
}
diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html
index 77ea38b20..20e3a6354 100644
--- a/1-js/09-classes/07-mixins/head.html
+++ b/1-js/09-classes/07-mixins/head.html
@@ -18,7 +18,7 @@
* menu.off('select', handler)
*/
off(eventName, handler) {
- let handlers = this._eventHandlers && this._eventHandlers[eventName];
+ let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for(let i = 0; i < handlers.length; i++) {
if (handlers[i] == handler) {
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 303431d6d..ec0dabc9a 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
@@ -1,8 +1,8 @@
The difference becomes obvious when we look at the code inside a function.
-The behavior is different if there's a "jump out" of `try..catch`.
+The behavior is different if there's a "jump out" of `try...catch`.
-For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control.
+For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control.
```js run
function f() {
@@ -11,7 +11,7 @@ function f() {
*!*
return "result";
*/!*
- } catch (e) {
+ } catch (err) {
/// ...
} finally {
alert('cleanup!');
@@ -28,11 +28,11 @@ function f() {
try {
alert('start');
throw new Error("an error");
- } catch (e) {
+ } catch (err) {
// ...
if("can't handle the error") {
*!*
- throw e;
+ throw err;
*/!*
}
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 c573cc232..b6dc81326 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
@@ -6,12 +6,12 @@ importance: 5
Compare the two code fragments.
-1. The first one uses `finally` to execute the code after `try..catch`:
+1. The first one uses `finally` to execute the code after `try...catch`:
```js
try {
work work
- } catch (e) {
+ } catch (err) {
handle errors
} finally {
*!*
@@ -19,12 +19,12 @@ Compare the two code fragments.
*/!*
}
```
-2. The second fragment puts the cleaning right after `try..catch`:
+2. The second fragment puts the cleaning right after `try...catch`:
```js
try {
work work
- } catch (e) {
+ } catch (err) {
handle errors
}
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 09b1ee398..a928da289 100644
--- a/1-js/10-error-handling/1-try-catch/article.md
+++ b/1-js/10-error-handling/1-try-catch/article.md
@@ -1,14 +1,14 @@
-# Error handling, "try..catch"
+# Error handling, "try...catch"
No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons.
Usually, a script "dies" (immediately stops) in case of an error, printing it to console.
-But there's a syntax construct `try..catch` that allows to "catch" errors and, instead of dying, do something more reasonable.
+But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable.
-## The "try..catch" syntax
+## The "try...catch" syntax
-The `try..catch` construct has two main blocks: `try`, and then `catch`:
+The `try...catch` construct has two main blocks: `try`, and then `catch`:
```js
try {
@@ -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 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) will contain an error object with details about what happened.
+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 the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened.

-So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`.
+So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`.
-Let's see examples.
+Let's look at some examples.
- An errorless example: shows `alert` `(1)` and `(2)`:
@@ -45,7 +45,7 @@ Let's see examples.
alert('End of try runs'); // *!*(2) <--*/!*
- } catch(err) {
+ } catch (err) {
alert('Catch is ignored, because there are no errors'); // (3)
@@ -64,7 +64,7 @@ Let's see examples.
alert('End of try (never reached)'); // (2)
- } catch(err) {
+ } catch (err) {
alert(`Error has occurred!`); // *!*(3) <--*/!*
@@ -72,45 +72,45 @@ Let's see examples.
```
-````warn header="`try..catch` only works for runtime errors"
-For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript.
+````warn header="`try...catch` only works for runtime errors"
+For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript.
It won't work if the code is syntactically wrong, for instance it has unmatched curly braces:
```js run
try {
{{{{{{{{{{{{
-} catch(e) {
+} catch (err) {
alert("The engine can't understand this code, it's invalid");
}
```
The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code.
-So, `try..catch` can only handle errors that occur in the valid code. Such errors are called "runtime errors" or, sometimes, "exceptions".
+So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions".
````
-````warn header="`try..catch` works synchronously"
-If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it:
+````warn header="`try...catch` works synchronously"
+If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it:
```js run
try {
setTimeout(function() {
noSuchVariable; // script will die here
}, 1000);
-} catch (e) {
+} catch (err) {
alert( "won't work" );
}
```
-That's because 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:
+To catch an exception inside a scheduled function, `try...catch` must be inside that function:
```js run
setTimeout(function() {
try {
- noSuchVariable; // try..catch handles the error!
+ noSuchVariable; // try...catch handles the error!
} catch {
alert( "error is caught here!" );
}
@@ -125,7 +125,7 @@ When an error occurs, JavaScript generates an object containing the details abou
```js
try {
// ...
-} catch(err) { // <-- the "error object", could use another word instead of err
+} catch (err) { // <-- the "error object", could use another word instead of err
// ...
}
```
@@ -150,7 +150,7 @@ try {
*!*
lalala; // error, variable is not defined!
*/!*
-} catch(err) {
+} catch (err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)
@@ -175,9 +175,9 @@ try {
}
```
-## Using "try..catch"
+## Using "try...catch"
-Let's explore a real-life use case of `try..catch`.
+Let's explore a real-life use case of `try...catch`.
As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values.
@@ -205,7 +205,7 @@ Should we be satisfied with that? Of course not!
This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message.
-Let's use `try..catch` to handle the error:
+Let's use `try...catch` to handle the error:
```js run
let json = "{ bad json }";
@@ -217,12 +217,12 @@ try {
*/!*
alert( user.name ); // doesn't work
-} catch (e) {
+} catch (err) {
*!*
// ...the execution jumps here
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
- alert( e.name );
- alert( e.message );
+ alert( err.name );
+ alert( err.message );
*/!*
}
```
@@ -245,7 +245,7 @@ try {
alert( user.name ); // no name!
*/!*
-} catch (e) {
+} catch (err) {
alert( "doesn't execute" );
}
```
@@ -294,11 +294,11 @@ Let's see what kind of error `JSON.parse` generates:
```js run
try {
JSON.parse("{ bad json o_O }");
-} catch(e) {
+} catch (err) {
*!*
- alert(e.name); // SyntaxError
+ alert(err.name); // SyntaxError
*/!*
- alert(e.message); // Unexpected token o in JSON at position 2
+ alert(err.message); // Unexpected token b in JSON at position 2
}
```
@@ -323,8 +323,8 @@ try {
alert( user.name );
-} catch(e) {
- alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name
+} catch (err) {
+ alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name
}
```
@@ -334,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 programming error (variable is not defined) or something else, not just this "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 this "incorrect data" thing.
For example:
@@ -345,7 +345,7 @@ try {
user = JSON.parse(json); // <-- forgot to put "let" before user
// ...
-} catch(err) {
+} catch (err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (no JSON Error actually)
}
@@ -353,29 +353,33 @@ try {
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.
+In our case, `try...catch` is placed 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.
+
+To avoid such problems, we can employ the "rethrowing" technique. The rule is simple:
+
+**Catch should only process errors that it knows and "rethrow" all others.**
+
+The "rethrowing" technique can be explained in more detail as:
+
+1. Catch gets all errors.
+2. In the `catch (err) {...}` block we analyze the error object `err`.
+3. If we don't know how to handle it, we do `throw err`.
-Fortunately, we can find out which error we get, for instance from its `name`:
+Usually, we can check the error type using the `instanceof` operator:
```js run
try {
user = { /*...*/ };
-} catch(e) {
+} catch (err) {
*!*
- alert(e.name); // "ReferenceError" for accessing an undefined variable
+ if (err instanceof ReferenceError) {
*/!*
+ alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable
+ }
}
```
-The rule is simple:
-
-**Catch should only process errors that it knows and "rethrow" all others.**
-
-The "rethrowing" technique can be explained in more detail as:
-
-1. Catch gets all errors.
-2. In the `catch(err) {...}` block we analyze the error object `err`.
-2. If we don't know how to handle it, we do `throw err`.
+We can also get the error class name from `err.name` property. All native errors have it. Another option is to read `err.constructor.name`.
In the code below, we use rethrowing so that `catch` only handles `SyntaxError`:
@@ -395,24 +399,24 @@ try {
alert( user.name );
-} catch(e) {
+} catch (err) {
*!*
- if (e.name == "SyntaxError") {
- alert( "JSON Error: " + e.message );
+ if (err instanceof SyntaxError) {
+ alert( "JSON Error: " + err.message );
} else {
- throw e; // rethrow (*)
+ throw err; // rethrow (*)
}
*/!*
}
```
-The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script.
+The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script.
So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others.
-The example below demonstrates how such errors can be caught by one more level of `try..catch`:
+The example below demonstrates how such errors can be caught by one more level of `try...catch`:
```js run
function readData() {
@@ -423,11 +427,11 @@ function readData() {
*!*
blabla(); // error!
*/!*
- } catch (e) {
+ } catch (err) {
// ...
- if (e.name != 'SyntaxError') {
+ if (!(err instanceof SyntaxError)) {
*!*
- throw e; // rethrow (don't know how to deal with it)
+ throw err; // rethrow (don't know how to deal with it)
*/!*
}
}
@@ -435,20 +439,20 @@ function readData() {
try {
readData();
-} catch (e) {
+} catch (err) {
*!*
- alert( "External catch got: " + e ); // caught it!
+ alert( "External catch got: " + err ); // caught it!
*/!*
}
```
-Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything.
+Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything.
-## try..catch..finally
+## try...catch...finally
Wait, that's not all.
-The `try..catch` construct may have one more code clause: `finally`.
+The `try...catch` construct may have one more code clause: `finally`.
If it exists, it runs in all cases:
@@ -460,7 +464,7 @@ The extended syntax looks like this:
```js
*!*try*/!* {
... try to execute the code ...
-} *!*catch*/!*(e) {
+} *!*catch*/!* (err) {
... handle errors ...
} *!*finally*/!* {
... execute always ...
@@ -473,7 +477,7 @@ Try running this code:
try {
alert( 'try' );
if (confirm('Make an error?')) BAD_CODE();
-} catch (e) {
+} catch (err) {
alert( 'catch' );
} finally {
alert( 'finally' );
@@ -509,7 +513,7 @@ let start = Date.now();
try {
result = fib(num);
-} catch (e) {
+} catch (err) {
result = 0;
*!*
} finally {
@@ -527,14 +531,14 @@ You can check by running the code with entering `35` into `prompt` -- it execute
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`.
+```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 we declared `let` in `try` block, it would only be visible inside of it.
```
````smart header="`finally` and `return`"
-The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`.
+The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`.
In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code.
@@ -546,7 +550,7 @@ function func() {
return 1;
*/!*
- } catch (e) {
+ } catch (err) {
/* ... */
} finally {
*!*
@@ -559,9 +563,9 @@ alert( func() ); // first works alert from finally, and then this one
```
````
-````smart header="`try..finally`"
+````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 here (let them fall through), 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() {
@@ -582,7 +586,7 @@ In the code above, an error inside `try` always falls out, because there's no `c
The information from this section is not a part of the core JavaScript.
```
-Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or some other terrible thing.
+Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing.
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.
@@ -639,14 +643,14 @@ They work like this:
## Summary
-The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it.
+The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it.
The syntax is:
```js
try {
// run this code
-} catch(err) {
+} catch (err) {
// if an error happened, then jump here
// err is the error object
} finally {
@@ -654,7 +658,7 @@ try {
}
```
-There may be no `catch` section or no `finally`, so shorter constructs `try..catch` and `try..finally` are also valid.
+There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid.
Error objects have following properties:
@@ -662,10 +666,10 @@ Error objects have following properties:
- `name` -- the string with error name (error constructor name).
- `stack` (non-standard, but well-supported) -- the stack at the moment of error creation.
-If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`.
+If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`.
We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter.
*Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know.
-Even if we don't have `try..catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`.
+Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`.
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 b48313322..918289319 100644
--- a/1-js/10-error-handling/2-custom-errors/article.md
+++ b/1-js/10-error-handling/2-custom-errors/article.md
@@ -21,9 +21,9 @@ Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it thr
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.
+Our `ValidationError` class should inherit from the `Error` class.
-That class is built-in, but here's its approximate code so we can understand what we're extending:
+The `Error` class is built-in, but here's its approximate code so we can understand what we're extending:
```js
// The "pseudocode" for the built-in Error class defined by JavaScript itself
@@ -117,15 +117,15 @@ We could also look at `err.name`, like this:
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...
-```
+```
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` 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.
+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 (caused by a typo in the code or other unknown reasons) should fall through.
## Further inheritance
-The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
+The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
```js run
class ValidationError extends Error {
@@ -215,11 +215,39 @@ Now custom errors are much shorter, especially `ValidationError`, as we got rid
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`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if the `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`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones.
+
+The scheme is like this:
+
+```js
+try {
+ ...
+ readUser() // the potential error source
+ ...
+} catch (err) {
+ if (err instanceof ValidationError) {
+ // handle validation errors
+ } else if (err instanceof SyntaxError) {
+ // handle syntax errors
+ } else {
+ throw err; // unknown error, rethrow it
+ }
+}
+```
+
+In the code above we can see two types of errors, but there can be more.
+
+If the `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 every time?
+
+Often the answer is "No": we'd like to be "one level above all that". We just want to know if there was a "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, we'd like to have a way to get the error details, but only if we need to.
+
+The technique that we describe here is called "wrapping exceptions".
-Often the answer is "No": the outer code wants to be "one level above all that", it just wants to have some kind of "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, it could have a way to get the error details, but only if we need to.
+1. We'll make a new class `ReadError` to represent a generic "data reading" error.
+2. The function `readUser` will catch data reading errors that occur inside it, such as `ValidationError` and `SyntaxError`, and generate a `ReadError` instead.
+3. The `ReadError` object will keep the reference to the original error in its `cause` property.
-So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in its `cause` property. Then the outer code will only have to check for `ReadError`.
+Then the code that calls `readUser` will only have to check for `ReadError`, not for every kind of data reading errors. And if it needs more details of an error, it can check its `cause` property.
Here's the code that defines `ReadError` and demonstrates its use in `readUser` and `try..catch`:
@@ -293,7 +321,7 @@ In the code above, `readUser` works exactly as described -- catches syntax and v
So the outer code checks `instanceof ReadError` and that's it. No need to list all possible error types.
-The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError` that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming.
+The approach is called "wrapping exceptions", because we take "low level" exceptions and "wrap" them into `ReadError` that is more abstract. It is widely used in object-oriented programming.
## Summary
diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md
index daab93316..5950df051 100644
--- a/1-js/11-async/01-callbacks/article.md
+++ b/1-js/11-async/01-callbacks/article.md
@@ -2,15 +2,17 @@
# Introduction: callbacks
-```warn header="We use browser methods here"
-To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods; specifically, loading scripts and performing simple document manipulations.
+```warn header="We use browser methods in examples here"
+To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations.
-If you're not familiar with these methods, and their usage in the examples is confusing, or if you would just like to understand them better, you may want to read a few chapters from the [next part](/document) of the tutorial.
+If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial.
+
+Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise.
```
-Many actions in JavaScript are *asynchronous*. In other words, we initiate them now, but they finish later.
+Many functions are provided by JavaScript host environments that allow you to schedule *asynchronous* actions. In other words, actions that we initiate now, but they finish later.
-For instance, we can schedule such actions using `setTimeout`.
+For instance, one such function is the `setTimeout` function.
There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters).
@@ -18,13 +20,15 @@ Take a look at the function `loadScript(src)`, that loads a script with the give
```js
function loadScript(src) {
+ // creates a
```
-If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason.
+```smart
+In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`.
+
+Then all scripts will see it, both with `type="module"` and without it.
+
+That said, making such global variables is frowned upon. Please try to avoid them.
+```
### A module code is evaluated only the first time when imported
-If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers.
+If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers.
-That has important consequences. Let's see that on examples.
+The one-time evaluation has important consequences, that we should be aware of.
+
+Let's see a couple of examples.
First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time:
@@ -129,9 +146,11 @@ import `./alert.js`; // Module is evaluated!
import `./alert.js`; // (shows nothing)
```
-In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it.
+The second import shows nothing, because the module has already been evaluated.
+
+There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above.
-Now, a more advanced example.
+Now, let's consider a deeper example.
Let's say, a module exports an object:
@@ -156,54 +175,67 @@ import {admin} from './admin.js';
alert(admin.name); // Pete
*!*
-// Both 1.js and 2.js imported the same object
+// Both 1.js and 2.js reference the same admin object
// Changes made in 1.js are visible in 2.js
*/!*
```
-So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that.
+As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`.
-Such behavior allows to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready.
+That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that.
-For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside:
+**Such behavior is actually very convenient, because it allows us to *configure* modules.**
+
+In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it.
+
+Here's the classical pattern:
+1. A module exports some means of configuration, e.g. a configuration object.
+2. On the first import we initialize it, write to its properties. The top-level application script may do that.
+3. Further imports use the module.
+
+For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside:
```js
// 📁 admin.js
-export let admin = { };
+export let config = { };
export function sayHi() {
- alert(`Ready to serve, ${admin.name}!`);
+ alert(`Ready to serve, ${config.user}!`);
}
```
-In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself:
+Here, `admin.js` exports the `config` object (initially empty, but may have default properties too).
+
+Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`:
```js
// 📁 init.js
-import {admin} from './admin.js';
-admin.name = "Pete";
+import {config} from './admin.js';
+config.user = "Pete";
```
-Another module can also see `admin.name`:
+...Now the module `admin.js` is configured.
-```js
-// 📁 other.js
-import {admin, sayHi} from './admin.js';
+Further importers can call it, and it correctly shows the current user:
-alert(admin.name); // *!*Pete*/!*
+```js
+// 📁 another.js
+import {sayHi} from './admin.js';
sayHi(); // Ready to serve, *!*Pete*/!*!
```
+
### import.meta
The object `import.meta` contains the information about the current module.
-Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML:
+Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML:
```html run height=0
```
@@ -229,7 +261,7 @@ Compare it to non-module scripts, where `this` is a global object:
There are also several browser-specific differences of scripts with `type="module"` compared to regular ones.
-You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser.
+You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser.
### Module scripts are deferred
@@ -256,7 +288,7 @@ Compare to regular script below:
@@ -268,11 +300,11 @@ Please note: the second script actually runs before the first! So we'll see `und
That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first.
-When using modules, we should be aware that HTML-page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that.
+When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that.
### Async works on inline scripts
-For non-module scripts, `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document.
+For non-module scripts, the `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document.
For module scripts, it works on inline scripts as well.
diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md
index f5465831d..10e47820f 100644
--- a/1-js/13-modules/02-import-export/article.md
+++ b/1-js/13-modules/02-import-export/article.md
@@ -2,7 +2,7 @@
Export and import directives have several syntax variants.
-In the previous chapter we saw a simple use, now let's explore more examples.
+In the previous article we saw a simple use, now let's explore more examples.
## Export before declarations
@@ -26,7 +26,7 @@ For instance, here all exports are valid:
```
````smart header="No semicolons after export class/function"
-Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions-arrows). It's still a function declaration, albeit exported.
+Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions). It's still a function declaration, albeit exported.
Most JavaScript style guides don't recommend semicolons after function and class declarations.
@@ -162,7 +162,7 @@ Mostly, the second approach is preferred, so that every "thing" resides in its o
Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier if files are well-named and structured into folders.
-Modules provide special `export default` ("the default export") syntax to make the "one thing per module" way look better.
+Modules provide a special `export default` ("the default export") syntax to make the "one thing per module" way look better.
Put `export default` before the entity to export:
@@ -216,9 +216,9 @@ export default function(user) { // no function name
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
```
-Not giving a name is fine, because `export default` is only one per file, so `import` without curly braces knows what to import.
+Not giving a name is fine, because there is only one `export default` per file, so `import` without curly braces knows what to import.
-Without `default`, such export would give an error:
+Without `default`, such an export would give an error:
```js
export class { // Error! (non-default export needs a name)
@@ -241,7 +241,7 @@ function sayHi(user) {
export {sayHi as default};
```
-Or, another situation, let's say a module `user.js` exports one main "default" thing and a few named ones (rarely the case, but happens):
+Or, another situation, let's say a module `user.js` exports one main "default" thing, and a few named ones (rarely the case, but it happens):
```js
// 📁 user.js
@@ -277,9 +277,9 @@ new User('John');
### A word against default exports
-Named exports are explicit. They exactly name what they import, so we have that information from them, that's a good thing.
+Named exports are explicit. They exactly name what they import, so we have that information from them; that's a good thing.
-Named exports enforce us to use exactly the right name to import:
+Named exports force us to use exactly the right name to import:
```js
import {User} from './user.js';
@@ -321,7 +321,7 @@ export {default as User} from './user.js'; // re-export default
Why would that be needed? Let's see a practical use case.
-Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow to publish and distribute such packages), and many modules are just "helpers", for the internal use in other package modules.
+Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules.
The file structure could be like this:
```
@@ -337,13 +337,19 @@ auth/
...
```
-We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this:
+We'd like to expose the package functionality via a single entry point.
+
+In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`.
+
+Like this:
```js
import {login, logout} from 'auth/index.js'
```
-The idea is that outsiders, developers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
+The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package.
+
+The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
As the actual exported functionality is scattered among the package, we can import it into `auth/index.js` and export from it:
@@ -366,19 +372,21 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo
```js
// 📁 auth/index.js
-// import login/logout and immediately export them
+// re-export login/logout
export {login, logout} from './helpers.js';
-// import default as User and export it
+// re-export the default export as User
export {default as User} from './user.js';
...
```
+The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions.
+
### Re-exporting the default export
The default export needs separate handling when re-exporting.
-Let's say we have `user.js`, and we'd like to re-export class `User` from it:
+Let's say we have `user.js` with the `export default class User` and would like to re-export it:
```js
// 📁 user.js
@@ -387,7 +395,9 @@ export default class User {
}
```
-1. `export User from './user.js'` won't work. What can go wrong?... But that's a syntax error!
+We can come across two problems with it:
+
+1. `export User from './user.js'` won't work. That would lead to a syntax error.
To re-export the default export, we have to write `export {default as User}`, as in the example above.
@@ -399,11 +409,11 @@ export default class User {
export {default} from './user.js'; // to re-export the default export
```
-Such oddities of re-exporting the default export are one of the reasons why some developers don't like them.
+Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones.
## Summary
-Here are all types of `export` that we covered in this and previous chapters.
+Here are all types of `export` that we covered in this and previous articles.
You can check yourself by reading them and recalling what they mean:
@@ -418,14 +428,14 @@ You can check yourself by reading them and recalling what they mean:
Import:
-- Named exports from module:
+- Importing named exports:
- `import {x [as y], ...} from "module"`
-- Default export:
+- Importing the default export:
- `import x from "module"`
- `import {default as x} from "module"`
-- Everything:
+- Import all:
- `import * as obj from "module"`
-- Import the module (its code runs), but do not assign it to a variable:
+- Import the module (its code runs), but do not assign any of its exports to variables:
- `import "module"`
We can put `import/export` statements at the top or at the bottom of a script, that doesn't matter.
@@ -439,7 +449,7 @@ sayHi();
import {sayHi} from './say.js'; // import at the end of the file
```
-In practice imports are usually at the start of the file, but that's only for better convenience.
+In practice imports are usually at the start of the file, but that's only for more convenience.
**Please note that import/export statements don't work if inside `{...}`.**
@@ -452,4 +462,4 @@ if (something) {
...But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it's really needed?
-We'll see dynamic imports in the next chapter.
+We'll see dynamic imports in the next article.
diff --git a/1-js/13-modules/03-modules-dynamic-imports/article.md b/1-js/13-modules/03-modules-dynamic-imports/article.md
index b638fd347..e48144a3e 100644
--- a/1-js/13-modules/03-modules-dynamic-imports/article.md
+++ b/1-js/13-modules/03-modules-dynamic-imports/article.md
@@ -94,5 +94,5 @@ Dynamic imports work in regular scripts, they don't require `script type="module
```smart
Although `import()` looks like a function call, it's a special syntax that just happens to use parentheses (similar to `super()`).
-So we can't copy `import` to a variable or use `call/apply` with it. That's not a function.
+So we can't copy `import` to a variable or use `call/apply` with it. It's not a function.
```
diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md
index 648958530..9db69cb2f 100644
--- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md
+++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md
@@ -19,5 +19,5 @@ function wrap(target) {
user = wrap(user);
alert(user.name); // John
-alert(user.age); // ReferenceError: Property doesn't exist
+alert(user.age); // ReferenceError: Property doesn't exist: "age"
```
diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md
index f890256ce..47985e1a7 100644
--- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md
+++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md
@@ -1,8 +1,8 @@
-# Error on reading non-existant property
+# Error on reading non-existent property
-Usually, an attempt to read a non-existant property returns `undefined`.
+Usually, an attempt to read a non-existent property returns `undefined`.
-Create a proxy that throws an error for an attempt to read of a non-existant property instead.
+Create a proxy that throws an error for an attempt to read of a non-existent property instead.
That can help to detect programming mistakes early.
@@ -27,6 +27,6 @@ user = wrap(user);
alert(user.name); // John
*!*
-alert(user.age); // Error: Property doesn't exist
+alert(user.age); // ReferenceError: Property doesn't exist: "age"
*/!*
```
diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md
index 0ae375f77..1f84912e5 100644
--- a/1-js/99-js-misc/01-proxy/article.md
+++ b/1-js/99-js-misc/01-proxy/article.md
@@ -2,7 +2,9 @@
A `Proxy` object wraps another object and intercepts operations, like reading/writing properties and others, optionally handling them on its own, or transparently allowing the object to handle them.
-Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this chapter.
+Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this article.
+
+## Proxy
The syntax:
@@ -37,7 +39,7 @@ As there are no traps, all operations on `proxy` are forwarded to `target`.
As we can see, without any traps, `proxy` is a transparent wrapper around `target`.
-
+
`Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`.
@@ -59,13 +61,13 @@ For every internal method, there's a trap in this table: the name of the method
| `[[Delete]]` | `deleteProperty` | `delete` operator |
| `[[Call]]` | `apply` | function call |
| `[[Construct]]` | `construct` | `new` operator |
-| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) |
-| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) |
-| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) |
-| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) |
-| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) |
-| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` |
-| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` |
+| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) |
+| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) |
+| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) |
+| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) |
+| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) |
+| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` |
+| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` |
```warn header="Invariants"
JavaScript enforces some invariants -- conditions that must be fulfilled by internal methods and traps.
@@ -244,7 +246,7 @@ If we forget to do it or return any falsy value, the operation triggers `TypeErr
Such methods differ in details:
- `Object.getOwnPropertyNames(obj)` returns non-symbol keys.
- `Object.getOwnPropertySymbols(obj)` returns symbol keys.
-- `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the chapter ).
+- `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the article ).
- `for..in` loops over non-symbol keys with `enumerable` flag, and also prototype keys.
...But all of them start with that list.
@@ -333,7 +335,7 @@ let user = {
_password: "secret"
};
-alert(user._password); // secret
+alert(user._password); // secret
```
Let's use proxies to prevent any access to properties starting with `_`.
@@ -374,7 +376,7 @@ user = new Proxy(user, {
},
*!*
deleteProperty(target, prop) { // to intercept property deletion
-*/!*
+*/!*
if (prop.startsWith('_')) {
throw new Error("Access denied");
} else {
@@ -435,7 +437,7 @@ user = {
```
-A call to `user.checkPassword()` call gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error.
+A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error.
So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps.
@@ -446,7 +448,7 @@ Besides, an object may be proxied multiple times (multiple proxies may add diffe
So, such a proxy shouldn't be used everywhere.
```smart header="Private properties of a class"
-Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the chapter . No proxies required.
+Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the article . No proxies required.
Such properties have their own issues though. In particular, they are not inherited.
```
@@ -485,7 +487,7 @@ range = new Proxy(range, {
*!*
has(target, prop) {
*/!*
- return prop >= target.start && prop <= target.end
+ return prop >= target.start && prop <= target.end;
}
});
@@ -507,9 +509,9 @@ The `apply(target, thisArg, args)` trap handles calling a proxy as function:
- `thisArg` is the value of `this`.
- `args` is a list of arguments.
-For example, let's recall `delay(f, ms)` decorator, that we did in the chapter .
+For example, let's recall `delay(f, ms)` decorator, that we did in the article .
-In that chapter we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds.
+In that article we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds.
Here's the previous, function-based implementation:
@@ -587,7 +589,7 @@ The result is the same, but now not only calls, but all operations on the proxy
We've got a "richer" wrapper.
-Other traps exist: the full list is in the beginning of this chapter. Their usage pattern is similar to the above.
+Other traps exist: the full list is in the beginning of this article. Their usage pattern is similar to the above.
## Reflect
@@ -619,7 +621,7 @@ alert(user.name); // John
In particular, `Reflect` allows us to call operators (`new`, `delete`...) as functions (`Reflect.construct`, `Reflect.deleteProperty`, ...). That's an interesting capability, but here another thing is important.
-**For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as `Proxy` trap.**
+**For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as the `Proxy` trap.**
So we can use `Reflect` to forward an operation to the original object.
@@ -660,7 +662,7 @@ In most cases we can do the same without `Reflect`, for instance, reading a prop
### Proxying a getter
-Let's see an example that demonstrates why `Reflect.get` is better. And we'll also see why `get/set` have the fourth argument `receiver`, that we didn't use before.
+Let's see an example that demonstrates why `Reflect.get` is better. And we'll also see why `get/set` have the third argument `receiver`, that we didn't use before.
We have an object `user` with `_name` property and a getter for it.
@@ -838,7 +840,7 @@ So there's no such problem when proxying an array.
### Private fields
-The similar thing happens with private class fields.
+A similar thing happens with private class fields.
For example, `getName()` method accesses the private `#name` property and breaks after proxying:
@@ -961,9 +963,13 @@ revoke();
alert(proxy.data); // Error
```
-A call to `revoke()` removes all internal references to the target object from the proxy, so they are no more connected. The target object can be garbage-collected after that.
+A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected.
+
+Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope.
-We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object:
+We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`.
+
+Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy:
```js run
*!*
@@ -978,21 +984,19 @@ let {proxy, revoke} = Proxy.revocable(object, {});
revokes.set(proxy, revoke);
-// ..later in our code..
+// ..somewhere else in our code..
revoke = revokes.get(proxy);
revoke();
alert(proxy.data); // Error (revoked)
```
-The benefit of such an approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needed.
-
We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more.
## References
- Specification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots).
-- MDN: [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
+- MDN: [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy).
## Summary
@@ -1014,13 +1018,13 @@ We can trap:
- Reading (`get`), writing (`set`), deleting (`deleteProperty`) a property (even a non-existing one).
- Calling a function (`apply` trap).
- The `new` operator (`construct` trap).
-- Many other operations (the full list is at the beginning of the article and in the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)).
+- Many other operations (the full list is at the beginning of the article and in the [docs](mdn:/JavaScript/Reference/Global_Objects/Proxy)).
That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more.
We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality.
-The [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects.
+The [Reflect](mdn:/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects.
Proxies have some limitations:
diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md
index 1ae2a12a0..d71ac23f8 100644
--- a/1-js/99-js-misc/03-currying-partials/article.md
+++ b/1-js/99-js-misc/03-currying-partials/article.md
@@ -39,7 +39,7 @@ alert( curriedSum(1)(2) ); // 3
As you can see, the implementation is straightforward: it's just two 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)`.
+- When it is called like `curriedSum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`.
- Then this wrapper is called with `2` as an argument, and it passes the call to the original `sum`.
More advanced implementations of currying, such as [_.curry](https://lodash.com/docs#curry) from lodash library, return a wrapper that allows a function to be called both normally and partially:
@@ -73,7 +73,7 @@ Let's curry it!
log = _.curry(log);
```
-After that `log` work normally:
+After that `log` works normally:
```js
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
@@ -111,7 +111,7 @@ So:
## Advanced curry implementation
-In case you'd like to get in details, here's the "advanced" curry implementation for multi-argument functions that we could use above.
+In case you'd like to get in to the details, here's the "advanced" curry implementation for multi-argument functions that we could use above.
It's pretty short:
@@ -155,7 +155,7 @@ function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
- return function pass(...args2) { // (2)
+ return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
@@ -164,18 +164,10 @@ function curried(...args) {
When we run it, there are two `if` execution 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.
+1. If passed `args` count is the same or more than the original function has in its definition (`func.length`) , then just pass the call to it using `func.apply`.
+2. Otherwise, get a partial: we don't call `func` just yet. Instead, another wrapper is returned, that will re-apply `curried` providing previous arguments together with the new ones.
-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.
+Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result.
```smart header="Fixed-length functions only"
The currying requires the function to have a fixed number of arguments.
@@ -191,6 +183,6 @@ But most implementations of currying in JavaScript are advanced, as described: t
## Summary
-*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 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 the arguments count is not enough.
-Currying allows to easily get 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)`.
+Currying allows us to easily get partials. As we've seen in the logging example, after currying the three argument universal function `log(date, importance, message)` gives us partials when called with one argument (like `log(date)`) or two arguments (like `log(date, importance)`).
diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md b/1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md
similarity index 81%
rename from 1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md
rename to 1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md
index 0534202a8..ba5d3bf04 100644
--- a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md
+++ b/1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md
@@ -34,4 +34,4 @@ let user = {
(user.go)() // John
```
-Please note that brackets around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters.
+Please note that parentheses around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters.
diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/task.md b/1-js/99-js-misc/04-reference-type/2-check-syntax/task.md
similarity index 100%
rename from 1-js/04-object-basics/04-object-methods/2-check-syntax/task.md
rename to 1-js/99-js-misc/04-reference-type/2-check-syntax/task.md
diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md
similarity index 65%
rename from 1-js/04-object-basics/04-object-methods/3-why-this/solution.md
rename to 1-js/99-js-misc/04-reference-type/3-why-this/solution.md
index 89bc0d722..e4ee78748 100644
--- a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md
+++ b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md
@@ -3,9 +3,9 @@ Here's the explanations.
1. That's a regular object method call.
-2. The same, brackets do not change the order of operations here, the dot is first anyway.
+2. The same, parentheses do not change the order of operations here, the dot is first anyway.
-3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
+3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines:
```js no-beautify
f = obj.go; // calculate the expression
@@ -14,7 +14,7 @@ Here's the explanations.
Here `f()` is executed as a function, without `this`.
-4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
+4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression.
To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type.
diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/task.md b/1-js/99-js-misc/04-reference-type/3-why-this/task.md
similarity index 100%
rename from 1-js/04-object-basics/04-object-methods/3-why-this/task.md
rename to 1-js/99-js-misc/04-reference-type/3-why-this/task.md
diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md
new file mode 100644
index 000000000..1ec378059
--- /dev/null
+++ b/1-js/99-js-misc/04-reference-type/article.md
@@ -0,0 +1,108 @@
+
+# Reference Type
+
+```warn header="In-depth language feature"
+This article covers an advanced topic, to understand certain edge-cases better.
+
+It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood.
+```
+
+A dynamically evaluated method call can lose `this`.
+
+For instance:
+
+```js run
+let user = {
+ name: "John",
+ hi() { alert(this.name); },
+ bye() { alert("Bye"); }
+};
+
+user.hi(); // works
+
+// now let's call user.hi or user.bye depending on the name
+*!*
+(user.name == "John" ? user.hi : user.bye)(); // Error!
+*/!*
+```
+
+On the last line there is a conditional operator that chooses either `user.hi` or `user.bye`. In this case the result is `user.hi`.
+
+Then the method is immediately called with parentheses `()`. But it doesn't work correctly!
+
+As you can see, the call results in an error, because the value of `"this"` inside the call becomes `undefined`.
+
+This works (object dot method):
+```js
+user.hi();
+```
+
+This doesn't (evaluated method):
+```js
+(user.name == "John" ? user.hi : user.bye)(); // Error!
+```
+
+Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works.
+
+## Reference type explained
+
+Looking closely, we may notice two operations in `obj.method()` statement:
+
+1. First, the dot `'.'` retrieves the property `obj.method`.
+2. Then parentheses `()` execute it.
+
+So, how does the information about `this` get passed from the first part to the second one?
+
+If we put these operations on separate lines, then `this` will be lost for sure:
+
+```js run
+let user = {
+ name: "John",
+ hi() { alert(this.name); }
+}
+
+*!*
+// split getting and calling the method in two lines
+let hi = user.hi;
+hi(); // Error, because this is undefined
+*/!*
+```
+
+Here `hi = user.hi` puts the function into the variable, and then on the last line it is completely standalone, and so there's no `this`.
+
+**To make `user.hi()` calls work, JavaScript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).**
+
+The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language.
+
+The value of Reference Type is a three-value combination `(base, name, strict)`, where:
+
+- `base` is the object.
+- `name` is the property name.
+- `strict` is true if `use strict` is in effect.
+
+The result of a property access `user.hi` is not a function, but a value of Reference Type. For `user.hi` in strict mode it is:
+
+```js
+// Reference Type value
+(user, "hi", true)
+```
+
+When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case).
+
+Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`.
+
+Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`.
+
+So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind).
+
+## Summary
+
+Reference Type is an internal type of the language.
+
+Reading a property, such as with dot `.` in `obj.method()` returns not exactly the property value, but a special "reference type" value that stores both the property value and the object it was taken from.
+
+That's for the subsequent method call `()` to get the object and set `this` to it.
+
+For all other operations, the reference type automatically becomes the property value (a function in our case).
+
+The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression.
diff --git a/1-js/99-js-misc/05-bigint/article.md b/1-js/99-js-misc/05-bigint/article.md
new file mode 100644
index 000000000..2a1cfc843
--- /dev/null
+++ b/1-js/99-js-misc/05-bigint/article.md
@@ -0,0 +1,130 @@
+# BigInt
+
+[recent caniuse="bigint"]
+
+`BigInt` is a special numeric type that provides support for integers of arbitrary length.
+
+A bigint is created by appending `n` to the end of an integer literal or by calling the function `BigInt` that creates bigints from strings, numbers etc.
+
+```js
+const bigint = 1234567890123456789012345678901234567890n;
+
+const sameBigint = BigInt("1234567890123456789012345678901234567890");
+
+const bigintFromNumber = BigInt(10); // same as 10n
+```
+
+## Math operators
+
+`BigInt` can mostly be used like a regular number, for example:
+
+```js run
+alert(1n + 2n); // 3
+
+alert(5n / 2n); // 2
+```
+
+Please note: the division `5/2` returns the result rounded towards zero, without the decimal part. All operations on bigints return bigints.
+
+We can't mix bigints and regular numbers:
+
+```js run
+alert(1n + 2); // Error: Cannot mix BigInt and other types
+```
+
+We should explicitly convert them if needed: using either `BigInt()` or `Number()`, like this:
+
+```js run
+let bigint = 1n;
+let number = 2;
+
+// number to bigint
+alert(bigint + BigInt(number)); // 3
+
+// bigint to number
+alert(Number(bigint) + number); // 3
+```
+
+The conversion operations are always silent, never give errors, but if the bigint is too huge and won't fit the number type, then extra bits will be cut off, so we should be careful doing such conversion.
+
+````smart header="The unary plus is not supported on bigints"
+The unary plus operator `+value` is a well-known way to convert `value` to a number.
+
+In order to avoid confusion, it's not supported on bigints:
+```js run
+let bigint = 1n;
+
+alert( +bigint ); // error
+```
+So we should use `Number()` to convert a bigint to a number.
+````
+
+## Comparisons
+
+Comparisons, such as `<`, `>` work with bigints and numbers just fine:
+
+```js run
+alert( 2n > 1n ); // true
+
+alert( 2n > 1 ); // true
+```
+
+Please note though, as numbers and bigints belong to different types, they can be equal `==`, but not strictly equal `===`:
+
+```js run
+alert( 1 == 1n ); // true
+
+alert( 1 === 1n ); // false
+```
+
+## Boolean operations
+
+When inside `if` or other boolean operations, bigints behave like numbers.
+
+For instance, in `if`, bigint `0n` is falsy, other values are truthy:
+
+```js run
+if (0n) {
+ // never executes
+}
+```
+
+Boolean operators, such as `||`, `&&` and others also work with bigints similar to numbers:
+
+```js run
+alert( 1n || 2 ); // 1 (1n is considered truthy)
+
+alert( 0n || 2 ); // 2 (0n is considered falsy)
+```
+
+## Polyfills
+
+Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as `+`, `-` and so on behave differently with bigints compared to regular numbers.
+
+For example, division of bigints always returns a bigint (rounded if necessary).
+
+To emulate such behavior, a polyfill would need to analyze the code and replace all such operators with its functions. But doing so is cumbersome and would cost a lot of performance.
+
+So, there's no well-known good polyfill.
+
+Although, the other way around is proposed by the developers of [JSBI](https://github.com/GoogleChromeLabs/jsbi) library.
+
+This library implements big numbers using its own methods. We can use them instead of native bigints:
+
+| Operation | native `BigInt` | JSBI |
+|-----------|-----------------|------|
+| Creation from Number | `a = BigInt(789)` | `a = JSBI.BigInt(789)` |
+| Addition | `c = a + b` | `c = JSBI.add(a, b)` |
+| Subtraction | `c = a - b` | `c = JSBI.subtract(a, b)` |
+| ... | ... | ... |
+
+...And then use the polyfill (Babel plugin) to convert JSBI calls to native bigints for those browsers that support them.
+
+In other words, this approach suggests that we write code in JSBI instead of native bigints. But JSBI works with numbers as with bigints internally, emulates them closely following the specification, so the code will be "bigint-ready".
+
+We can use such JSBI code "as is" for engines that don't support bigints and for those that do support - the polyfill will convert the calls to native bigints.
+
+## References
+
+- [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt).
+- [Specification](https://tc39.es/ecma262/#sec-bigint-objects).
diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md
index f680554dd..43dec976a 100644
--- a/2-ui/1-document/01-browser-environment/article.md
+++ b/2-ui/1-document/01-browser-environment/article.md
@@ -2,11 +2,11 @@
The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms.
-A platform may be a browser, or a web-server or another *host*, even a coffee machine. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
+A platform may be a browser, or a web-server or another *host*, even a "smart" coffee machine, if it can run JavaScript. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.
-Here's a bird's-eye view of what we have when JavaScript runs in a web-browser:
+Here's a bird's-eye view of what we have when JavaScript runs in a web browser:

@@ -17,7 +17,7 @@ There's a "root" object called `window`. It has two roles:
For instance, here we use it as a global object:
-```js run
+```js run global
function sayHi() {
alert("Hello");
}
@@ -49,9 +49,7 @@ document.body.style.background = "red";
setTimeout(() => document.body.style.background = "", 1000);
```
-Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification:
-
-- **DOM Living Standard** at
+Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org).
```smart header="DOM is not only for browsers"
The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too.
@@ -60,9 +58,9 @@ For instance, server-side scripts that download HTML pages and process them can
```
```smart header="CSSOM for styling"
-CSS rules and stylesheets are structured in a different way than HTML. There's a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/), that explains how they are represented as objects, and how to read and write them.
+There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them.
-CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, but that's also possible.
+CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible.
```
## BOM (Browser Object Model)
diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md
index 019398be9..e18335f38 100644
--- a/2-ui/1-document/02-dom-nodes/article.md
+++ b/2-ui/1-document/02-dom-nodes/article.md
@@ -51,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -143,7 +143,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360);
````warn header="Tables always have ``"
-An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in the DOM automatically.
+An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically.
For the HTML:
@@ -160,7 +160,7 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType":
drawHtmlTree(node5, 'div.domtree', 600, 200);
-You see? The `` appeared out of nowhere. You should keep this in mind while working with tables to avoid surprises.
+You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises.
````
## Other node types
@@ -188,7 +188,7 @@ For example, comments:
@@ -199,7 +199,7 @@ We may think -- why is a comment added to the DOM? It doesn't affect the visual
**Everything in HTML, even comments, becomes a part of the DOM.**
-Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. We are not going to touch that node, we even don't draw it on diagrams for that reason, but it's there.
+Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there.
The `document` object that represents the whole document is, formally, a DOM node as well.
diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md
index 332f57827..b5f03098c 100644
--- a/2-ui/1-document/03-dom-navigation/article.md
+++ b/2-ui/1-document/03-dom-navigation/article.md
@@ -201,7 +201,7 @@ The parent is available as `parentNode`.
For example:
-```js
+```js run
// parent of is
alert( document.body.parentNode === document.documentElement ); // true
@@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement
## Element-only navigation
-Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist.
+Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist.
But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page.
diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md
index cc878009f..5af6435ce 100644
--- a/2-ui/1-document/04-searching-elements-dom/article.md
+++ b/2-ui/1-document/04-searching-elements-dom/article.md
@@ -71,7 +71,7 @@ If there are multiple elements with the same `id`, then the behavior of methods
```
```warn header="Only `document.getElementById`, not `anyElem.getElementById`"
-The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document.
+The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document.
```
## querySelectorAll [#querySelectorAll]
@@ -103,7 +103,7 @@ Here we look for all `
` elements that are last children:
This method is indeed powerful, because any CSS selector can be used.
```smart header="Can use pseudo-classes as well"
-Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one).
+Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one).
```
## querySelector [#querySelector]
@@ -142,7 +142,7 @@ For instance:
*Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top.
-The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search.
+The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search.
In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned.
@@ -178,7 +178,7 @@ So here we cover them mainly for completeness, while you can still find them in
- `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags".
- `elem.getElementsByClassName(className)` returns elements that have the given CSS class.
-- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. very rarely used.
+- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. Very rarely used.
For instance:
```js
@@ -363,7 +363,7 @@ There are 6 main methods to search for nodes in DOM:
```
-If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`!
+If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`!

@@ -125,7 +125,7 @@ If there are some actions upon leaving the parent element, e.g. an animation run
To avoid it, we can check `relatedTarget` in the handler and, if the mouse is still inside the element, then ignore such event.
-Alternatively we can use other events: `mouseenter` и `mouseleave`, that we'll be covering now, as they don't have such problems.
+Alternatively we can use other events: `mouseenter` and `mouseleave`, that we'll be covering now, as they don't have such problems.
## Events mouseenter and mouseleave
@@ -145,7 +145,7 @@ When the pointer leaves an element -- `mouseleave` triggers.
```online
This example is similar to the one above, but now the top element has `mouseenter/mouseleave` instead of `mouseover/mouseout`.
-As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignores
+As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignored
[codetabs height=340 src="mouseleave"]
```
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js
index 6d87199c2..5752e83ae 100755
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js
@@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler;
function handler(event) {
let type = event.type;
- while (type < 11) type += ' ';
+ while (type.length < 11) type += ' ';
log(type + " target=" + event.target.id)
return false;
diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js
index 1c1443a7e..10ae2eeed 100644
--- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js
+++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js
@@ -7,7 +7,7 @@ document.addEventListener('mousedown', function(event) {
if (!dragElement) return;
event.preventDefault();
-
+
dragElement.ondragstart = function() {
return false;
};
@@ -19,7 +19,7 @@ document.addEventListener('mousedown', function(event) {
function onMouseUp(event) {
finishDrag();
};
-
+
function onMouseMove(event) {
moveAt(event.clientX, event.clientY);
}
@@ -31,9 +31,9 @@ document.addEventListener('mousedown', function(event) {
if(isDragging) {
return;
}
-
+
isDragging = true;
-
+
document.addEventListener('mousemove', onMouseMove);
element.addEventListener('mouseup', onMouseUp);
@@ -50,10 +50,10 @@ document.addEventListener('mousedown', function(event) {
if(!isDragging) {
return;
}
-
+
isDragging = false;
- dragElement.style.top = parseInt(dragElement.style.top) + pageYOffset + 'px';
+ dragElement.style.top = parseInt(dragElement.style.top) + window.pageYOffset + 'px';
dragElement.style.position = 'absolute';
document.removeEventListener('mousemove', onMouseMove);
@@ -113,4 +113,4 @@ document.addEventListener('mousedown', function(event) {
dragElement.style.top = newY + 'px';
}
-});
\ No newline at end of file
+});
diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md
index 2418ffd66..49ab88be2 100644
--- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md
+++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md
@@ -1,12 +1,12 @@
# Drag'n'Drop with mouse events
-Drag'n'Drop is a great interface solution. Taking something, dragging and dropping is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (drop into cart).
+Drag'n'Drop is a great interface solution. Taking something and dragging and dropping it is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (dropping items into a cart).
-In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend` and so on.
+In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend`, and so on.
-They are interesting because they allow to solve simple tasks easily, and also allow to handle drag'n'drop of "external" files into the browser. So we can take a file in the OS file-manager and drop it into the browser window. Then JavaScript gains access to its contents.
+These events allow us to support special kinds of drag'n'drop, such as handling dragging a file from OS file-manager and dropping it into the browser window. Then JavaScript can access the contents of such files.
-But native Drag Events also have limitations. For instance, we can't limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. There are other drag'n'drop tasks that can't be done using that API. Besides, mobile devices support for such events is almost non-existant.
+But native Drag Events also have limitations. For instance, we can't prevent dragging from a certain area. Also we can't make the dragging "horizontal" or "vertical" only. And there are many other drag'n'drop tasks that can't be done using them. Also, mobile device support for such events is very weak.
So here we'll see how to implement Drag'n'Drop using mouse events.
@@ -14,26 +14,23 @@ So here we'll see how to implement Drag'n'Drop using mouse events.
The basic Drag'n'Drop algorithm looks like this:
-1. On `mousedown` - prepare the element for moving, if needed (maybe create a copy of it).
-2. Then on `mousemove` move it by changing `left/top` and `position:absolute`.
-3. On `mouseup` - perform all actions related to a finished Drag'n'Drop.
+1. On `mousedown` - prepare the element for moving, if needed (maybe create a clone of it, add a class to it or whatever).
+2. Then on `mousemove` move it by changing `left/top` with `position:absolute`.
+3. On `mouseup` - perform all actions related to finishing the drag'n'drop.
-These are the basics. Later we can extend it, for instance, by highlighting droppable (available for the drop) elements when hovering over them.
+These are the basics. Later we'll see how to other features, such as highlighting current underlying elements while we drag over them.
-Here's the algorithm for drag'n'drop of a ball:
+Here's the implementation of dragging a ball:
```js
-ball.onmousedown = function(event) { // (1) start the process
-
- // (2) prepare to moving: make absolute and on top by z-index
+ball.onmousedown = function(event) {
+ // (1) prepare to moving: make absolute and on top by z-index
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
+
// move it out of any current parents directly into body
// to make it positioned relative to the body
document.body.append(ball);
- // ...and put that absolutely positioned ball under the pointer
-
- moveAt(event.pageX, event.pageY);
// centers the ball at (pageX, pageY) coordinates
function moveAt(pageX, pageY) {
@@ -41,14 +38,17 @@ ball.onmousedown = function(event) { // (1) start the process
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
}
+ // move our absolutely positioned ball under the pointer
+ moveAt(event.pageX, event.pageY);
+
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
}
- // (3) move the ball on mousemove
+ // (2) move the ball on mousemove
document.addEventListener('mousemove', onMouseMove);
- // (4) drop the ball, remove unneeded handlers
+ // (3) drop the ball, remove unneeded handlers
ball.onmouseup = function() {
document.removeEventListener('mousemove', onMouseMove);
ball.onmouseup = null;
@@ -64,10 +64,10 @@ Here's an example in action:
[iframe src="ball" height=230]
-Try to drag'n'drop the mouse and you'll see such behavior.
+Try to drag'n'drop with the mouse and you'll see such behavior.
```
-That's because the browser has its own Drag'n'Drop for images and some other elements that runs automatically and conflicts with ours.
+That's because the browser has its own drag'n'drop support for images and some other elements. It runs automatically and conflicts with ours.
To disable it:
@@ -124,7 +124,7 @@ Let's update our algorithm:
```js
// onmousemove
- // у мяча ball стоит position:absoute
+ // ball has position:absolute
ball.style.left = event.pageX - *!*shiftX*/!* + 'px';
ball.style.top = event.pageY - *!*shiftY*/!* + 'px';
```
@@ -276,7 +276,7 @@ function onMouseMove(event) {
}
```
-In the example below when the ball is dragged over the soccer gate, the gate is highlighted.
+In the example below when the ball is dragged over the soccer goal, the goal is highlighted.
[codetabs height=250 src="ball4"]
@@ -300,4 +300,4 @@ We can lay a lot on this foundation.
- We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements.
- And so on.
-There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to described above, so it should be easy to understand them now. Or roll our own, as you can see that's easy enough to do, sometimes easier than adapting a third-part solution.
+There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution.
diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html
index 3fdd7fe76..8751c70ad 100644
--- a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html
+++ b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html
@@ -13,16 +13,13 @@
+
+
+
diff --git a/2-ui/3-event-details/6-pointer-events/ball.view/index.html b/2-ui/3-event-details/6-pointer-events/ball.view/index.html
new file mode 100644
index 000000000..8bbef8f63
--- /dev/null
+++ b/2-ui/3-event-details/6-pointer-events/ball.view/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css
new file mode 100644
index 000000000..a84cd5e7e
--- /dev/null
+++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css
@@ -0,0 +1,20 @@
+.slider {
+ border-radius: 5px;
+ background: #E0E0E0;
+ background: linear-gradient(left top, #E0E0E0, #EEEEEE);
+ width: 310px;
+ height: 15px;
+ margin: 5px;
+}
+
+.thumb {
+ touch-action: none;
+ width: 10px;
+ height: 25px;
+ border-radius: 3px;
+ position: relative;
+ left: 10px;
+ top: -5px;
+ background: blue;
+ cursor: pointer;
+}
diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md
similarity index 100%
rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md
rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md
diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html
similarity index 100%
rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html
rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html
diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md
similarity index 100%
rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md
rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md
diff --git a/2-ui/3-event-details/5-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md
similarity index 81%
rename from 2-ui/3-event-details/5-keyboard-events/article.md
rename to 2-ui/3-event-details/7-keyboard-events/article.md
index 617852ccf..86e9b83fd 100644
--- a/2-ui/3-event-details/5-keyboard-events/article.md
+++ b/2-ui/3-event-details/7-keyboard-events/article.md
@@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters
To reliably track layout-dependent characters, `event.key` may be a better way.
-On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch.
+On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch.
Do we want to handle layout-dependant keys? Then `event.key` is the way to go.
@@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept
```html autorun height=60 run
```
-Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`.
+The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`.
-Let's relax it a little bit:
+As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters)
+Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. These keys make it return `false`.
+
+Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`:
```html autorun height=60 run
@@ -162,7 +165,9 @@ function checkPhoneKey(key) {
Now arrows and deletion works well.
-...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid.
+Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable.
+
+The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together.
## Legacy
@@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic
There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more.
+## Mobile Keyboards
+
+When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values).
+
+While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices.
+
## Summary
Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS.
diff --git a/2-ui/3-event-details/7-keyboard-events/german-layout.svg b/2-ui/3-event-details/7-keyboard-events/german-layout.svg
new file mode 100644
index 000000000..7ac9a4008
--- /dev/null
+++ b/2-ui/3-event-details/7-keyboard-events/german-layout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html
similarity index 95%
rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html
rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html
index 401062830..a0d5a4f40 100644
--- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html
+++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html
@@ -28,7 +28,7 @@
-
+
diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js
similarity index 96%
rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js
rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js
index 5eba24c7a..d97f7a7b5 100644
--- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js
+++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js
@@ -5,6 +5,8 @@ let lastTime = Date.now();
function handle(e) {
if (form.elements[e.type + 'Ignore'].checked) return;
+ area.scrollTop = 1e6;
+
let text = e.type +
' key=' + e.key +
' code=' + e.code +
diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css
similarity index 100%
rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css
rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css
diff --git a/2-ui/3-event-details/7-keyboard-events/us-layout.svg b/2-ui/3-event-details/7-keyboard-events/us-layout.svg
new file mode 100644
index 000000000..353f225f1
--- /dev/null
+++ b/2-ui/3-event-details/7-keyboard-events/us-layout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md
index 10945ccd7..54c101193 100644
--- a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md
+++ b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md
@@ -55,11 +55,11 @@ function populate() {
// document bottom
let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
- // if the user scrolled far enough (<100px to the end)
- if (windowRelativeBottom < document.documentElement.clientHeight + 100) {
- // let's add more data
- document.body.insertAdjacentHTML("beforeend", `
Date: ${new Date()}
`);
- }
+ // if the user hasn't scrolled far enough (>100px to the end)
+ if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;
+
+ // let's add more data
+ document.body.insertAdjacentHTML("beforeend", `
* It's enough that the top or bottom edge of the element are visible
*/
function isVisible(elem) {
-
- let coords = elem.getBoundingClientRect();
-
- let windowHeight = document.documentElement.clientHeight;
-
- // top elem edge is visible OR bottom elem edge is visible
- let topVisible = coords.top > 0 && coords.top < windowHeight;
- let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
-
- return topVisible || bottomVisible;
- }
-
- /**
- A variant of the test that considers the element visible if it's no more than
- one page after/behind the current screen.
-
- function isVisible(elem) {
-
- let coords = elem.getBoundingClientRect();
-
- let windowHeight = document.documentElement.clientHeight;
-
- let extendedTop = -windowHeight;
- let extendedBottom = 2 * windowHeight;
-
- // top visible || bottom visible
- let topVisible = coords.top > extendedTop && coords.top < extendedBottom;
- let bottomVisible = coords.bottom < extendedBottom && coords.bottom > extendedTop;
-
- return topVisible || bottomVisible;
+ // todo: your code
}
- */
function showVisible() {
for (let img of document.querySelectorAll('img')) {
diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md
index 22ff8d859..734bd84c6 100644
--- a/2-ui/3-event-details/8-onscroll/article.md
+++ b/2-ui/3-event-details/8-onscroll/article.md
@@ -1,6 +1,6 @@
# Scrolling
-The `scroll` event allows to react on a page or element scrolling. There are quite a few good things we can do here.
+The `scroll` event allows reacting to a page or element scrolling. There are quite a few good things we can do here.
For instance:
- Show/hide additional controls or information depending on where in the document the user is.
@@ -10,7 +10,7 @@ Here's a small function to show the current scroll:
```js autorun
window.addEventListener('scroll', function() {
- document.getElementById('showScroll').innerHTML = pageYOffset + 'px';
+ document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px';
});
```
@@ -34,4 +34,4 @@ If we add an event handler to these events and `event.preventDefault()` in it, t
There are many ways to initiate a scroll, so it's more reliable to use CSS, `overflow` property.
-Here are few tasks that you can solve or look through to see the applications on `onscroll`.
+Here are few tasks that you can solve or look through to see applications of `onscroll`.
diff --git a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md
index 9b218aa7f..a0e74da57 100644
--- a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md
+++ b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md
@@ -18,3 +18,5 @@ Use JavaScript to:
1. Show the value and the text of the selected option.
2. Add an option: ``.
3. Make it selected.
+
+Note, if you've done everything right, your alert should show `blues`.
diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md
index d989a5fb8..689301e4d 100644
--- a/2-ui/4-forms-controls/1-form-elements/article.md
+++ b/2-ui/4-forms-controls/1-form-elements/article.md
@@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them.
Document forms are members of the special collection `document.forms`.
-That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form.
+That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form.
```js no-beautify
-document.forms.my - the form with name="my"
-document.forms[0] - the first form in the document
+document.forms.my; // the form with name="my"
+document.forms[0]; // the first form in the document
```
When we have a form, then any element is available in the named collection `form.elements`.
@@ -36,9 +36,9 @@ For instance:
```
-There may be multiple elements with the same name, that's often the case with radio buttons.
+There may be multiple elements with the same name. This is typical with radio buttons and checkboxes.
-In that case `form.elements[name]` is a collection, for instance:
+In that case, `form.elements[name]` is a *collection*. For instance:
```html run height=40