+
+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 466c6bc3f..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 other future in the closest `ms` milliseconds 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.
\ No newline at end of file
+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 5339c8d11..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() {
@@ -44,4 +44,20 @@ describe("throttle(f, 1000)", function() {
this.clock.restore();
});
-});
\ No newline at end of file
+});
+
+describe('throttle', () => {
+
+ it('runs a forwarded call once', done => {
+ let log = '';
+ const f = str => log += str;
+ const f10 = throttle(f, 10);
+ f10('once');
+
+ setTimeout(() => {
+ assert.equal(log, 'once');
+ done();
+ }, 20);
+ });
+
+});
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 c844016d3..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) {
@@ -33,7 +32,7 @@ function throttle(func, ms) {
A call to `throttle(func, ms)` returns `wrapper`.
1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`).
-2. In this state all calls memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call.
-3. ...Then after `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`). And if we had ignored calls, then `wrapper` is executed with last memorized arguments and context.
+2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call.
+3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context.
The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it.
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 567c9ce7a..cbd473196 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.
+
+Compared to the debounce decorator, the behavior is completely different:
+- `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 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).
-
+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.
@@ -31,8 +36,8 @@ A code example:
```js
function f(a) {
- console.log(a)
-};
+ console.log(a);
+}
// f1000 passes calls to f at maximum once per 1000 ms
let f1000 = throttle(f, 1000);
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 ada01f91e..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.
@@ -58,10 +58,9 @@ From an outside code, the wrapped `slow` function still does the same. It just g
To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself:
- The `cachingDecorator` is reusable. We can apply it to another function.
-- The caching logic is separate, it did not increase the complexity of `slow` itself (if there were any).
+- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any).
- We can combine multiple decorators if needed (other decorators will follow).
-
## Using "func.call" for the context
The caching decorator mentioned above is not suited to work with object methods.
@@ -76,7 +75,7 @@ let worker = {
},
slow(x) {
- // actually, there can be a scary CPU-heavy task here
+ // scary CPU-heavy task here
alert("Called with " + x);
return x * this.someMethod(); // (*)
}
@@ -150,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:
@@ -168,10 +167,8 @@ let user = { name: "John" };
say.call( user, "Hello" ); // John: Hello
```
-
In our case, we can use `call` in the wrapper to pass the context to the original function:
-
```js run
let worker = {
someMethod() {
@@ -212,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.
@@ -239,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`:
@@ -280,13 +277,15 @@ alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)
```
-Now it works with any number of arguments.
+Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below).
There are two changes:
- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions.
- Then `(**)` uses `func.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:
@@ -302,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*.
@@ -347,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:
@@ -375,7 +374,7 @@ hash(1, 2);
The trick is called *method borrowing*.
-We take (borrow) a join method from a regular array `[].join`. And use `[].join.call` to run it in the context of `arguments`.
+We take (borrow) a join method from a regular array (`[].join`) and use `[].join.call` to run it in the context of `arguments`.
Why does it work?
@@ -393,12 +392,20 @@ Taken from the specification almost "as-is":
So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`.
+## Decorators and function properties
+
+It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them.
+
+E.g. in the example above if `slow` function had any properties on it, then `cachingDecorator(slow)` is a wrapper without them.
+
+Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties.
+
+There exists a way to create decorators that keep access to function properties, but this requires using a special `Proxy` object to wrap a function. We'll discuss it later in the article .
+
## Summary
*Decorator* is a wrapper around a function that alters its behavior. The main job is still carried out by the function.
-It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. Some decorators provide their own properties.
-
Decorators can be seen as "features" or "aspects" that can be added to a function. We can add one or add many. And all this without changing its code!
To implement `cachingDecorator`, we studied methods:
diff --git a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md
index 8cd18ec56..d6cfb44bf 100644
--- a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md
+++ b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md
@@ -4,7 +4,7 @@ importance: 5
# Function property after bind
-There's a value in the property of a function. Will it change after `bind`? Why, elaborate?
+There's a value in the property of a function. Will it change after `bind`? Why, or why not?
```js run
function sayHi() {
diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
index 403107ca6..4a381c0b4 100644
--- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
+++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
@@ -1,5 +1,5 @@
-The error occurs because `ask` gets functions `loginOk/loginFail` without the object.
+The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object.
When it calls them, they naturally assume `this=undefined`.
diff --git a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
index f8b83d7a2..c90851c2b 100644
--- a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
+++ b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md
@@ -8,7 +8,7 @@ The task is a little more complex variant of .
The `user` object was modified. Now instead of two functions `loginOk/loginFail`, it has a single function `user.login(true/false)`.
-What to pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`?
+What should we pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`?
```js
function askPassword(ok, fail) {
diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md
index ce0c94a50..7a6e47b90 100644
--- a/1-js/06-advanced-functions/10-bind/article.md
+++ b/1-js/06-advanced-functions/10-bind/article.md
@@ -83,10 +83,12 @@ let user = {
setTimeout(() => user.sayHi(), 1000);
-// ...within 1 second
-user = { sayHi() { alert("Another user in setTimeout!"); } };
+// ...the value of user changes within 1 second
+user = {
+ sayHi() { alert("Another user in setTimeout!"); }
+};
-// Another user in setTimeout?!?
+// Another user in setTimeout!
```
The next solution guarantees that such thing won't happen.
@@ -98,7 +100,7 @@ Functions provide a built-in method [bind](mdn:js/Function/bind) that allows to
The basic syntax is:
```js
-// more complex syntax will be little later
+// more complex syntax will come a little later
let boundFunc = func.bind(context);
```
@@ -123,7 +125,7 @@ funcUser(); // John
*/!*
```
-Here `func.bind(user)` as a "bound variant" of `func`, with fixed `this=user`.
+Here `func.bind(user)` is a "bound variant" of `func`, with fixed `this=user`.
All arguments are passed to the original `func` "as is", for instance:
@@ -159,9 +161,16 @@ let user = {
let sayHi = user.sayHi.bind(user); // (*)
*/!*
+// can run it without an object
sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
+
+// even if the value of user changes within 1 second
+// sayHi uses the pre-bound value which is reference to the old user object
+user = {
+ sayHi() { alert("Another user in setTimeout!"); }
+};
```
In the line `(*)` we take the method `user.sayHi` and bind it to `user`. The `sayHi` is a "bound" function, that can be called alone or passed to `setTimeout` -- doesn't matter, the context will be right.
@@ -178,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`"
@@ -193,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)](https://lodash.com/docs#bindAll) in lodash.
````
## Partial functions
@@ -238,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:
@@ -258,7 +267,7 @@ alert( triple(5) ); // = mul(3, 5) = 15
Why do we usually make a partial function?
-The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide first argument of every time as it's fixed with `bind`.
+The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide the first argument every time as it's fixed with `bind`.
In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience.
@@ -270,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:
@@ -304,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 abc5dd80a..8730277ad 100644
--- a/1-js/06-advanced-functions/12-arrow-functions/article.md
+++ b/1-js/06-advanced-functions/12-arrow-functions/article.md
@@ -4,7 +4,7 @@ Let's revisit arrow functions.
Arrow functions are not just a "shorthand" for writing small stuff. They have some very specific and useful features.
-JavaScript is full of situations where we need to write a small function, that's executed somewhere else.
+JavaScript is full of situations where we need to write a small function that's executed somewhere else.
For instance:
@@ -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);
};
}
@@ -118,9 +118,9 @@ Here we had to create additional variables `args` and `ctx` so that the function
Arrow functions:
-- Do not have `this`.
-- Do not have `arguments`.
-- Can't be called with `new`.
-- (They also don't have `super`, but we didn't study it. Will be in the chapter ).
+- Do not have `this`
+- Do not have `arguments`
+- Can't be called with `new`
+- They also don't have `super`, but we didn't study it yet. We will on the chapter
-That's because they are meant for short pieces of code that do not have their own "context", but rather works in the current one. And they really shine in that use case.
+That's because they are meant for short pieces of code that do not have their own "context", but rather work in the current one. And they really shine in that use case.
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 8ac5fd0d4..0a945b377 100644
--- a/1-js/07-object-properties/01-property-descriptors/article.md
+++ b/1-js/07-object-properties/01-property-descriptors/article.md
@@ -3,7 +3,7 @@
As we know, objects can store properties.
-Till now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing.
+Until now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing.
In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions.
@@ -11,7 +11,7 @@ In this chapter we'll study additional configuration options, and in the next we
Object properties, besides a **`value`**, have three special attributes (so-called "flags"):
-- **`writable`** -- if `true`, can be changed, otherwise it's read-only.
+- **`writable`** -- if `true`, the value can be changed, otherwise it's read-only.
- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed.
- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not.
@@ -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`.
@@ -100,9 +100,9 @@ Compare it with "normally created" `user.name` above: now all flags are falsy. I
Now let's see effects of the flags by example.
-## Read-only
+## Non-writable
-Let's make `user.name` read-only by changing `writable` flag:
+Let's make `user.name` non-writable (can't be reassigned) by changing `writable` flag:
```js run
let user = {
@@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name'
Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours.
```smart header="Errors appear only in strict mode"
-In the non-strict mode, no errors occur when writing to read-only properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
+In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
```
Here's the same example, but the property is created from scratch:
@@ -134,7 +134,7 @@ let user = { };
Object.defineProperty(user, "name", {
*!*
value: "John",
- // for new properties need to explicitly list what's true
+ // for new properties we need to explicitly list what's true
enumerable: true,
configurable: true
*/!*
@@ -148,7 +148,7 @@ user.name = "Pete"; // Error
Now let's add a custom `toString` to `user`.
-Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add `toString` of our own, then by default it shows up in `for..in`, like this:
+Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add a `toString` of our own, then by default it shows up in `for..in`, like this:
```js run
let user = {
@@ -162,7 +162,7 @@ let user = {
for (let key in user) alert(key); // name, toString
```
-If we don't like it, then we can set `enumerable:false`. Then it won't appear in `for..in` loop, just like the built-in one:
+If we don't like it, then we can set `enumerable:false`. Then it won't appear in a `for..in` loop, just like the built-in one:
```js run
let user = {
@@ -194,9 +194,9 @@ 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 or altered with `defineProperty`.
+A non-configurable property can't be deleted, its attributes can't be modified.
-For instance, `Math.PI` is read-only, non-enumerable and non-configurable:
+For instance, `Math.PI` is non-writable, non-enumerable and non-configurable:
```js run
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
@@ -214,37 +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
```
-Making a property non-configurable is a one-way road. We cannot change it back, because `defineProperty` doesn't work on non-configurable properties.
+We also can't change `Math.PI` to be `writable` again:
-Here we are making `user.name` a "forever sealed" constant:
+```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`.
+
+**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.**
+
+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", ...)
-Object.defineProperty(user, "name", {writable: true}); // Error
-*/!*
+user.name = "Pete";
+delete user.name;
+Object.defineProperty(user, "name", { value: "Pete" });
+```
+
+```smart header="The only attribute change possible: writable true -> false"
+There's a minor exception about changing flags.
+
+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:
@@ -270,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:
@@ -288,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
@@ -296,23 +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 78fab9a63..c2aa35d53 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 till now were data properties.
+The first kind is *data properties*. We already know how to work with them. All properties that we've been using 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 property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.
## Getters and setters
@@ -27,7 +27,7 @@ The getter works when `obj.propName` is read, the setter -- when it is assigned.
For instance, we have a `user` object with `name` and `surname`:
-```js run
+```js
let user = {
name: "John",
surname: "Smith"
@@ -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,18 +94,13 @@ 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 support for `delete`"
-An attempt to `delete` on accessor property causes an error.
-```
-
+As the result, we have a "virtual" property `fullName`. It is readable and writable.
## Accessor descriptors
-Descriptors for accessor properties are different -- as compared with data properties.
+Descriptors for accessor properties are different from those for data properties.
-For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions.
+For accessor properties, there is no `value` or `writable`, but instead there are `get` and `set` functions.
That is, an accessor descriptor may have:
@@ -139,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:
@@ -190,9 +185,9 @@ Technically, external code is able to access the name directly by using `user._n
## Using for compatibility
-One of the great uses of accessors -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior.
+One of the great uses of accessors is that they allow to take control over a "regular" data property at any moment by replacing it with a getter and a setter and tweak its behavior.
-Imagine, we started implementing user objects using data properties `name` and `age`:
+Imagine we started implementing user objects using data properties `name` and `age`:
```js
function User(name, age) {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md
index 421b57e0a..bc2db47fe 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md
@@ -6,7 +6,7 @@ importance: 5
The task has two parts.
-We have objects:
+Given the following objects:
```js
let head = {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md
index b37499bad..ed8482c07 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md
@@ -2,7 +2,7 @@ importance: 5
---
-# Where it writes?
+# Where does it write?
We have `rabbit` inheriting from `animal`.
diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md
index bd412f126..c141b2ecd 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md
@@ -44,7 +44,7 @@ alert( lazy.stomach ); //
Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object.
-Also we can totally evade the problem by making sure that each hamster has their own stomach:
+Also we can totally avoid the problem by making sure that each hamster has their own stomach:
```js run
let hamster = {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md
index 6f9fb279e..50171123d 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md
@@ -2,11 +2,11 @@ importance: 5
---
-# Why two hamsters are full?
+# Why are both hamsters full?
We have two hamsters: `speedy` and `lazy` inheriting from the general `hamster` object.
-When we feed one of them, the other one is also full. Why? How to fix it?
+When we feed one of them, the other one is also full. Why? How can we fix it?
```js run
let hamster = {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md
index 5895a0b37..ef6c7ffeb 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,23 +27,15 @@ 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:
-```js run
+```js
let animal = {
eats: true
};
@@ -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,18 @@ 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,13 +203,16 @@ 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.
## The value of "this"
-An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: into `user` or `admin`?
+An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where are the properties `this.name` and `this.surname` written: into `user` or `admin`?
The answer is simple: `this` is not affected by prototypes at all.
@@ -211,7 +220,7 @@ The answer is simple: `this` is not affected by prototypes at all.
So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`.
-That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then inherited objects can run its methods, and they will modify the state of these objects, not the big one.
+That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object.
For instance, here `animal` represents a "method storage", and `rabbit` makes use of it.
@@ -246,13 +255,13 @@ The resulting picture:

-If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects.
+If we had other objects, like `bird`, `snake`, etc., inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects.
As a result, methods are shared, but the object state is not.
## for..in loop
-The `for..in` loops over inherited properties too.
+The `for..in` loop iterates over inherited properties too.
For instance:
@@ -267,7 +276,7 @@ let rabbit = {
};
*!*
-// Object.keys only return own keys
+// Object.keys only returns own keys
alert(Object.keys(rabbit)); // jumps
*/!*
@@ -277,7 +286,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):
@@ -308,9 +317,9 @@ Here we have the following inheritance chain: `rabbit` inherits from `animal`, t
Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? We did not define it. Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited.
-...But why `hasOwnProperty` does not appear in `for..in` loop, like `eats` and `jumps`, if it lists all inherited properties.
+...But why does `hasOwnProperty` not appear in the `for..in` loop like `eats` and `jumps` do, if `for..in` lists inherited properties?
-The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. That's why they are not listed.
+The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. And `for..in` only lists enumerable properties. That's why it and the rest of the `Object.prototype` properties are not listed.
```smart header="Almost all other key/value-getting methods ignore inherited properties"
Almost all other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties.
@@ -324,6 +333,6 @@ They only operate on the object itself. Properties from the prototype are *not*
- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon).
- The object referenced by `[[Prototype]]` is called a "prototype".
- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype.
-- Write/delete operations for act directly on the object, they don't use the prototype (assuming it's a data property, not is a setter).
+- Write/delete operations act directly on the object, they don't use the prototype (assuming it's a data property, not a setter).
- If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited.
-- The `for..in` loop iterates over both own and inherited properties. All other key/value-getting methods only operate on the object itself.
+- The `for..in` loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself.
diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md
index 771e3061c..ebbdf3a7c 100644
--- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md
+++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md
@@ -7,7 +7,7 @@ Answers:
2. `false`.
- Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object is referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`.
+ Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`.
So when we change its content through one reference, it is visible through the other one.
diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md
index 4b8522d3d..2838c125a 100644
--- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md
+++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md
@@ -20,7 +20,7 @@ alert( rabbit.eats ); // true
```
-1. We added one more string (emphasized), what `alert` shows now?
+1. We added one more string (emphasized). What will `alert` show now?
```js
function Rabbit() {}
@@ -54,7 +54,7 @@ alert( rabbit.eats ); // true
alert( rabbit.eats ); // ?
```
-3. Like this (replaced one line)?
+3. And like this (replaced one line)?
```js
function Rabbit() {}
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 43190e163..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
@@ -15,7 +15,7 @@ alert( user2.name ); // Pete (worked!)
It worked, because `User.prototype.constructor == User`.
-..But if someone, so to say, overwrites `User.prototype` and forgets to recreate `"constructor"`, then it would fail.
+..But if someone, so to speak, overwrites `User.prototype` and forgets to recreate `constructor` to reference `User`, then it would fail.
For instance:
@@ -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 -- 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 29b3773eb..b1ef51826 100644
--- a/1-js/08-prototypes/02-function-prototype/article.md
+++ b/1-js/08-prototypes/02-function-prototype/article.md
@@ -2,7 +2,7 @@
Remember, new objects can be created with a constructor function, like `new F()`.
-If `F.prototype` is an object, then `new` operator uses it to set `[[Prototype]]` for the new object.
+If `F.prototype` is an object, then the `new` operator uses it to set `[[Prototype]]` for the new object.
```smart
JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language.
@@ -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.
```
@@ -158,9 +158,9 @@ Rabbit.prototype = {
In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it.
-Everything is quite simple, just few notes to make things clear:
+Everything is quite simple, just a few notes to make things clear:
-- The `F.prototype` property (don't mess with `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called.
+- The `F.prototype` property (don't mistake it for `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called.
- The value of `F.prototype` should be either an object or `null`: other values won't work.
- The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`.
diff --git a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md
index e3651683f..99c358c9b 100644
--- a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md
+++ b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md
@@ -15,3 +15,27 @@ function f(a, b) {
f.defer(1000)(1, 2); // shows 3 after 1 sec
```
+
+Please note: we use `this` in `f.apply` to make our decoration work for object methods.
+
+So if the wrapper function is called as an object method, then `this` is passed to the original method `f`.
+
+```js run
+Function.prototype.defer = function(ms) {
+ let f = this;
+ return function(...args) {
+ setTimeout(() => f.apply(this, args), ms);
+ }
+};
+
+let user = {
+ name: "John",
+ sayHi() {
+ alert(this.name);
+ }
+}
+
+user.sayHi = user.sayHi.defer(1000);
+
+user.sayHi();
+```
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index 66be00ca1..bdfc86dd8 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -2,7 +2,7 @@
The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
-First we'll see at the details, and then how to use it for adding new capabilities to built-in objects.
+First we'll look at the details, and then how to use it for adding new capabilities to built-in objects.
## Object.prototype
@@ -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`:
@@ -99,12 +101,12 @@ alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects
The most intricate thing happens with strings, numbers and booleans.
-As we remember, they are not objects. But if we try to access their properties, then temporary wrapper objects are created using built-in constructors `String`, `Number`, `Boolean`, they provide the methods and disappear.
+As we remember, they are not objects. But if we try to access their properties, temporary wrapper objects are created using built-in constructors `String`, `Number` and `Boolean`. They provide the methods and disappear.
These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as `String.prototype`, `Number.prototype` and `Boolean.prototype`.
```warn header="Values `null` and `undefined` have no object wrappers"
-Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes too.
+Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes either.
```
## Changing native prototypes [#native-prototype-change]
@@ -129,9 +131,9 @@ So, generally, modifying a native prototype is considered a bad idea.
**In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.**
-Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but not yet supported by current JavaScript engine.
+Polyfilling is a term for making a substitute for a method that exists in the JavaScript specification, but is not yet supported by a particular JavaScript engine.
-Then we may implement it manually and populate the built-in prototype with it.
+We may then implement it manually and populate the built-in prototype with it.
For instance:
@@ -144,7 +146,7 @@ if (!String.prototype.repeat) { // if there's no such method
// actually, the code should be a little bit more complex than that
// (the full algorithm is in the specification)
- // but even an imperfect polyfill is often considered good enough for use
+ // but even an imperfect polyfill is often considered good enough
return new Array(n + 1).join(this);
};
}
@@ -179,18 +181,18 @@ obj.join = Array.prototype.join;
alert( obj.join(',') ); // Hello,world!
```
-It works, because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property, it doesn't check that the object is indeed the array. And many built-in methods are like that.
+It works because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property. It doesn't check if the object is indeed an array. Many built-in methods are like that.
Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, so all `Array` methods are automatically available in `obj`.
But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time.
-Borrowing methods is flexible, it allows to mix functionality from different objects if needed.
+Borrowing methods is flexible, it allows to mix functionalities from different objects if needed.
## Summary
- All built-in objects follow the same pattern:
- - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype` etc).
- - The object itself stores only the data (array items, object properties, the date).
-- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects.
-- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable cause is when we add-in a new standard, but not yet supported by the engine JavaScript method.
+ - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype`, etc.)
+ - The object itself stores only the data (array items, object properties, the date)
+- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype` and `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects
+- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. The only allowable case is probably when we add-in a new standard, but it's not yet supported by the JavaScript engine
diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
index a92e17900..f3c9cf0e5 100644
--- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
+++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
@@ -28,4 +28,4 @@ alert(dictionary); // "apple,__proto__"
When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable.
-See the the chapter [](info:property-descriptors) for review.
+See the chapter [](info:property-descriptors) for review.
diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md
index 8a71dbf1c..9c5f1eb3d 100644
--- a/1-js/08-prototypes/04-prototype-methods/article.md
+++ b/1-js/08-prototypes/04-prototype-methods/article.md
@@ -3,15 +3,18 @@
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
-The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).
+Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only).
-The modern methods are:
+The modern methods to get/set a prototype are:
-- [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`.
-These should be used instead of `__proto__`.
+The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`.
+
+Although, there's a special method for this too:
+
+- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
For instance:
@@ -22,13 +25,13 @@ let animal = {
// create a new object with animal as a prototype
*!*
-let rabbit = Object.create(animal);
+let rabbit = Object.create(animal); // same as {__proto__: animal}
*/!*
alert(rabbit.eats); // true
*!*
-alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
+alert(Object.getPrototypeOf(rabbit) === animal); // true
*/!*
*!*
@@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
*/!*
```
-`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
+The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors.
+
+We can provide additional properties to the new object there, like this:
```js run
let animal = {
@@ -57,32 +62,39 @@ The descriptors are in the same format as described in the chapter . So the object has access to class methods.
+After `new User` object is created, when we call its method, it's taken from the prototype, just as described in the chapter . So the object has access to class methods.
We can illustrate the result of `class User` declaration as:
@@ -110,15 +110,15 @@ alert(typeof User); // function
alert(User === User.prototype.constructor); // true
// The methods are in User.prototype, e.g:
-alert(User.prototype.sayHi); // alert(this.name);
+alert(User.prototype.sayHi); // the code of the sayHi method
// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
```
-## Not just a syntax sugar
+## Not just a syntactic sugar
-Sometimes people say that `class` is a "syntax sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without `class` keyword at all:
+Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same thing without using the `class` keyword at all:
```js run
// rewriting class User in pure functions
@@ -127,7 +127,7 @@ Sometimes people say that `class` is a "syntax sugar" (syntax that is designed t
function User(name) {
this.name = name;
}
-// any function prototype has constructor property by default,
+// a function prototype has "constructor" property by default,
// so we don't need to create it
// 2. Add the method to prototype
@@ -140,13 +140,13 @@ let user = new User("John");
user.sayHi();
```
-The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods.
+The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntactic sugar to define a constructor together with its prototype methods.
-Although, there are important differences.
+Still, there are important differences.
-1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually.
+1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually.
- Unlike a regular function, a class constructor must be called with `new`:
+ The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`:
```js run
class User {
@@ -166,6 +166,7 @@ Although, there are important differences.
alert(User); // class User { ... }
```
+ There are other differences, we'll see them soon.
2. Class methods are non-enumerable.
A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`.
@@ -179,7 +180,7 @@ Besides, `class` syntax brings many other features that we'll explore later.
## Class Expression
-Just like functions, classes can be defined inside another expression, passed around, returned, assigned etc.
+Just like functions, classes can be defined inside another expression, passed around, returned, assigned, etc.
Here's an example of a class expression:
@@ -209,7 +210,6 @@ new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass name isn't visible outside of the class
```
-
We can even make classes dynamically "on-demand", like this:
```js run
@@ -218,7 +218,7 @@ function makeClass(phrase) {
return class {
sayHi() {
alert(phrase);
- };
+ }
};
}
@@ -229,9 +229,9 @@ new User().sayHi(); // Hello
```
-## Getters/setters, other shorthands
+## Getters/setters
-Just like literal objects, classes may include getters/setters, generators, computed properties etc.
+Just like literal objects, classes may include getters/setters, computed properties etc.
Here's an example for `user.name` implemented using `get/set`:
@@ -264,25 +264,14 @@ class User {
let user = new User("John");
alert(user.name); // John
-user = new User(""); // Name too short.
+user = new User(""); // Name is too short.
```
-The class declaration creates getters and setters in `User.prototype`, like this:
+Technically, such class declaration works by creating getters and setters in `User.prototype`.
-```js
-Object.defineProperties(User.prototype, {
- name: {
- get() {
- return this._name
- },
- set(name) {
- // ...
- }
- }
-});
-```
+## Computed names [...]
-Here's an example with a computed property in brackets `[...]`:
+Here's an example with a computed method name using brackets `[...]`:
```js run
class User {
@@ -298,20 +287,24 @@ class User {
new User().sayHi();
```
-For a generator method, similarly, prepend it with `*`.
+Such features are easy to remember, as they resemble that of literal objects.
-## Class properties
+## Class fields
```warn header="Old browsers may need a polyfill"
-Class-level properties are a recent addition to the language.
+Class fields are a recent addition to the language.
```
-In the example above, `User` only had methods. Let's add a property:
+Previously, our classes only had methods.
+
+"Class fields" is a syntax that allows to add any properties.
+
+For instance, let's add `name` property to `class User`:
```js run
class User {
*!*
- name = "Anonymous";
+ name = "John";
*/!*
sayHi() {
@@ -319,10 +312,94 @@ class User {
}
}
-new User().sayHi();
+new User().sayHi(); // Hello, John!
```
-The property `name` is not placed into `User.prototype`. Instead, it is created by `new` before calling constructor, it's the property of the object itself.
+So, we just write " = " in the declaration, and that's it.
+
+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
+```
+
+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
@@ -346,6 +423,6 @@ class MyClass {
}
```
-`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and settors are written to `MyClass.prototype`.
+`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and setters are written to `MyClass.prototype`.
In the next chapters we'll learn more about classes, including inheritance and other features.
diff --git a/1-js/09-classes/02-class-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 108cc11f2..464042d82 100644
--- a/1-js/09-classes/02-class-inheritance/article.md
+++ b/1-js/09-classes/02-class-inheritance/article.md
@@ -1,9 +1,13 @@
# Class inheritance
-Let's say we have two classes.
+Class inheritance is a way for one class to extend another class.
-`Animal`:
+So we can create new functionality on top of the existing.
+
+## The "extends" keyword
+
+Let's say we have class `Animal`:
```js
class Animal {
@@ -12,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() {
@@ -24,52 +28,19 @@ class Animal {
let animal = new Animal("My animal");
```
-
-
-
-...And `Rabbit`:
-
-```js
-class Rabbit {
- constructor(name) {
- this.name = name;
- }
- hide() {
- alert(`${this.name} hides!`);
- }
-}
-
-let rabbit = new Rabbit("My rabbit");
-```
-
-
+Here's how we can represent `animal` object and `Animal` class graphically:
+
-Right now they are fully independent.
+...And we would like to create another `class Rabbit`.
-But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
+As rabbits are animals, `Rabbit` class should be based on `Animal`, have access to animal methods, so that rabbits can do what "generic" animals can do.
-To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`.
+The syntax to extend another class is: `class Child extends Parent`.
-Here `Rabbit` inherits from `Animal`:
+Let's create `class Rabbit` that inherits from `Animal`:
-```js run
-class Animal {
- constructor(name) {
- this.speed = 0;
- this.name = name;
- }
- run(speed) {
- this.speed += speed;
- alert(`${this.name} runs with speed ${this.speed}.`);
- }
- stop() {
- this.speed = 0;
- alert(`${this.name} stands still.`);
- }
-}
-
-// Inherit from Animal by specifying "extends Animal"
+```js
*!*
class Rabbit extends Animal {
*/!*
@@ -84,15 +55,18 @@ rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
```
-Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do.
+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 adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`:
+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`.

-So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`.
+For instance, to find `rabbit.run` method, the engine checks (bottom-up on the picture):
+1. The `rabbit` object (has no `run`).
+2. Its prototype, that is `Rabbit.prototype` (has `hide`, but not `run`).
+3. Its prototype, that is (due to `extends`) `Animal.prototype`, that finally has the `run` method.
-As we can recall from the chapter , JavaScript uses prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods.
+As we can recall from the chapter , JavaScript itself uses prototypal inheritance for built-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`. That's why dates have access to generic object methods.
````smart header="Any expression is allowed after `extends`"
Class syntax allows to specify not just a class, but any expression after `extends`.
@@ -102,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); }
+ };
}
*!*
@@ -119,19 +93,20 @@ That may be useful for advanced programming patterns when we use functions to ge
## Overriding a method
-Now let's move forward and override a method. As of now, `Rabbit` inherits the `stop` method that sets `this.speed = 0` from `Animal`.
+Now let's move forward and override a method. By default, all methods that are not specified in `class Rabbit` are taken directly "as is" from `class Animal`.
-If we specify our own `stop` in `Rabbit`, then it will be used instead:
+But if we specify our own method in `Rabbit`, such as `stop()` then it will be used instead:
```js
class Rabbit extends Animal {
stop() {
- // ...this will be used for rabbit.stop()
+ // ...now this will be used for rabbit.stop()
+ // instead of stop() from class Animal
}
}
```
-...But usually we don't want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.
+Usually, however, we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.
Classes provide `"super"` keyword for that.
@@ -149,7 +124,7 @@ class Animal {
}
run(speed) {
- this.speed += speed;
+ this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
@@ -176,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.
@@ -185,6 +160,7 @@ Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the p
As was mentioned in the chapter , arrow functions do not have `super`.
If accessed, it's taken from the outer function. For instance:
+
```js
class Rabbit extends Animal {
stop() {
@@ -201,12 +177,11 @@ setTimeout(function() { super.stop() }, 1000);
```
````
-
## Overriding constructor
With constructors it gets a little bit tricky.
-Till now, `Rabbit` did not have its own `constructor`.
+Until now, `Rabbit` did not have its own `constructor`.
According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated:
@@ -255,22 +230,24 @@ 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.
-Of course, there's an explanation. Let's get into details, so you'd really understand what's going on.
+Of course, there's an explanation. Let's get into details, so you'll really understand what's going on.
-In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property `[[ConstructorKind]]:"derived"`.
+In JavaScript, there's a distinction between a constructor function of an inheriting class (so-called "derived constructor") and other functions. A derived constructor has a special internal property `[[ConstructorKind]]:"derived"`. That's a special internal label.
-The difference is:
+That label affects its behavior with `new`.
-- When a normal constructor runs, it creates an empty object and assigns it to `this`.
+- 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 if we're making a constructor of our own, then we must call `super`, because otherwise the object for `this` won't be created. And we'll get an error.
+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 `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here:
+For the `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here:
```js run
class Animal {
@@ -303,6 +280,99 @@ 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]]
@@ -312,7 +382,7 @@ If you're reading the tutorial for the first time - this section may be skipped.
It's about the internal mechanisms behind inheritance and `super`.
```
-Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way.
+Let's get a little deeper under the hood of `super`. We'll see some interesting things along the way.
First to say, from all that we've learned till now, it's impossible for `super` to work at all!
@@ -463,7 +533,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.
@@ -472,7 +542,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`);
}
};
@@ -486,7 +556,7 @@ let rabbit = {
let plant = {
sayHi() {
- console.log("I'm a plant");
+ alert("I'm a plant");
}
};
@@ -503,7 +573,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?
@@ -524,7 +594,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]]
```js run
let animal = {
- eat: function() { // should be the short syntax: eat() {...}
+ eat: function() { // intentionally writing like this instead of eat() {...
// ...
}
};
@@ -554,4 +624,4 @@ rabbit.eat(); // Error calling super (because there's no [[HomeObject]])
- So it's not safe to copy a method with `super` from one object to another.
Also:
-- Arrow functions don't have own `this` or `super`, so they transparently fit into the surrounding context.
+- Arrow functions don't have their own `this` or `super`, so they transparently fit into the surrounding context.
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 54%
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 0a1f4382c..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 76%
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
index fa26ec834..cb9829ce0 100644
--- 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
@@ -21,14 +21,14 @@ alert( rabbit.hasOwnProperty('name') ); // true
But that's not all yet.
-Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`.
+Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`.
As we know, the "extends" syntax sets up two prototypes:
1. Between `"prototype"` of the constructor functions (for methods).
-2. Between the constructor functions itself (for static methods).
+2. Between the constructor functions themselves (for static methods).
-In our case, for `class Rabbit extends Object` it means:
+In the case of `class Rabbit extends Object` it means:
```js run
class Rabbit extends Object {}
@@ -37,7 +37,7 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true
```
-So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this:
+So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this:
```js run
class Rabbit extends Object {}
@@ -67,7 +67,7 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
So `Rabbit` doesn't provide access to static methods of `Object` in that case.
-By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
+By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
Here's the picture:
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 88%
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 ca6628edf..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
---
@@ -19,7 +19,6 @@ let rabbit = new Rabbit("Rab");
*!*
// hasOwnProperty method is from Object.prototype
-// rabbit.__proto__ === Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true
*/!*
```
@@ -39,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 5abe494ea..4b493a5e8 100644
--- a/1-js/09-classes/03-static-properties-methods/article.md
+++ b/1-js/09-classes/03-static-properties-methods/article.md
@@ -1,9 +1,9 @@
# Static properties and methods
-We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*.
+We can also assign a method to the class as a whole. Such methods are called *static*.
-In a class, they are prepended by `static` keyword, like this:
+In a class declaration, they are prepended by `static` keyword, like this:
```js run
class User {
@@ -19,19 +19,23 @@ User.staticMethod(); // true
That actually does the same as assigning it as a property directly:
-```js
-class User() { }
+```js run
+class User { }
User.staticMethod = function() {
alert(this === User);
};
+
+User.staticMethod(); // true
```
The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule).
-Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
+Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it.
-For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this:
+For instance, we have `Article` objects and need a function to compare them.
+
+A natural solution would be to add `Article.compare` static method:
```js run
class Article {
@@ -61,9 +65,11 @@ articles.sort(Article.compare);
alert( articles[0].title ); // CSS
```
-Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+
+Another example would be a so-called "factory" method.
-Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
+Let's say, we need multiple ways to create an article:
1. Create by given parameters (`title`, `date` etc).
2. Create an empty article with today's date.
@@ -71,7 +77,7 @@ Another example would be a so-called "factory" method. Imagine, we need few ways
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
-Like `Article.createTodays()` here:
+Such as `Article.createTodays()` here:
```js run
class Article {
@@ -90,7 +96,7 @@ class Article {
let article = Article.createTodays();
-alert( article.title ); // Todays digest
+alert( article.title ); // Today's digest
```
Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class.
@@ -99,10 +105,21 @@ Static methods are also used in database-related classes to search/save/remove e
```js
// assuming Article is a special class for managing articles
-// static method to remove the article:
+// static method to remove the article by id:
Article.remove({id: 12345});
```
+````warn header="Static methods aren't available for individual objects"
+Static methods are callable on classes, not on individual objects.
+
+E.g. such code won't work:
+
+```js
+// ...
+article.createTodays(); /// Error: article.createTodays is not a function
+```
+````
+
## Static properties
[recent browser=Chrome]
@@ -123,14 +140,15 @@ That is the same as a direct assignment to `Article`:
Article.publisher = "Ilya Kantor";
```
-## Inheritance of static methods
+## Inheritance of static properties and methods [#statics-and-inheritance]
-Static methods are inherited.
+Static properties and methods are inherited.
-For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`:
+For instance, `Animal.compare` and `Animal.planet` in the code below are inherited and accessible as `Rabbit.compare` and `Rabbit.planet`:
```js run
class Animal {
+ static planet = "Earth";
constructor(name, speed) {
this.speed = speed;
@@ -167,9 +185,11 @@ rabbits.sort(Rabbit.compare);
*/!*
rabbits[0].run(); // Black Rabbit runs with speed 5.
+
+alert(Rabbit.planet); // Earth
```
-Now when we can call `Rabbit.compare`, the inherited `Animal.compare` will be called.
+Now when we call `Rabbit.compare`, the inherited `Animal.compare` will be called.
How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`.
@@ -180,7 +200,7 @@ So, `Rabbit extends Animal` creates two `[[Prototype]]` references:
1. `Rabbit` function prototypally inherits from `Animal` function.
2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`.
-As the result, inheritance works both for regular and static methods.
+As a result, inheritance works both for regular and static methods.
Here, let's check that by code:
@@ -192,12 +212,12 @@ class Rabbit extends Animal {}
alert(Rabbit.__proto__ === Animal); // true
// for regular methods
-alert(Rabbit.prototype.__proto__ === Animal.prototype);
+alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
```
## Summary
-Static methods are used for the functionality that belongs to the class "as a whole", doesn't relate to a concrete class instance.
+Static methods are used for the functionality that belongs to the class "as a whole". It doesn't relate to a concrete class instance.
For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`.
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 ef0d497a8..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
@@ -50,7 +50,7 @@ That was a general introduction.
In JavaScript, there are two types of object fields (properties and methods):
-- Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods.
+- Public: accessible from anywhere. They comprise the external interface. Until now we were only using public properties and methods.
- Private: accessible only from inside the class. These are for the internal interface.
In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them.
@@ -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;
}
}
@@ -248,7 +254,7 @@ Unlike protected ones, private fields are enforced by the language itself. That'
But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter:
```js
-class MegaCoffeeMachine extends CoffeeMachine() {
+class MegaCoffeeMachine extends CoffeeMachine {
method() {
*!*
alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
@@ -257,7 +263,7 @@ class MegaCoffeeMachine extends CoffeeMachine() {
}
```
-In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax.
+In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reasons to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax.
````warn header="Private fields are not available as this[name]"
Private fields are special.
@@ -279,11 +285,11 @@ 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:
-Protection for users, so that they don't shoot themselves in the feet
+Protection for users, so that they don't shoot themselves in the foot
: Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed.
All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later.
@@ -302,15 +308,15 @@ Supportable
For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same.
Hiding complexity
-: People adore to use things that are simple. At least from outside. What's inside is a different thing.
+: People adore using things that are simple. At least from outside. What's inside is a different thing.
Programmers are not an exception.
**It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.**
-To hide internal interface we use either protected or private properties:
+To hide an internal interface we use either protected or private properties:
- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it.
-- Private fields start with `#`. JavaScript makes sure we only can access those from inside the class.
+- Private fields start with `#`. JavaScript makes sure we can only access those from inside the class.
Right now, private fields are not well-supported among browsers, but can be polyfilled.
diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md
index 2dc4902b5..28b4c6eb6 100644
--- a/1-js/09-classes/05-extend-natives/article.md
+++ b/1-js/09-classes/05-extend-natives/article.md
@@ -21,7 +21,7 @@ alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
```
-Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses object `constructor` property for that.
+Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses the object's `constructor` property for that.
In the example above,
```js
@@ -32,7 +32,7 @@ When `arr.filter()` is called, it internally creates the new array of results us
Even more, we can customize that behavior.
-We can add a special static getter `Symbol.species` to the class. If exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on.
+We can add a special static getter `Symbol.species` to the class. If it exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on.
If we'd like built-in methods like `map` or `filter` to return regular arrays, we can return `Array` in `Symbol.species`, like here:
@@ -74,11 +74,11 @@ Built-in objects have their own static methods, for instance `Object.keys`, `Arr
As we already know, native classes extend each other. For instance, `Array` extends `Object`.
-Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the chapter [](info:static-properties-methods#statics-and-inheritance).
+Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the article [](info:static-properties-methods#statics-and-inheritance).
But built-in classes are an exception. They don't inherit statics from each other.
-For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no `Array.keys()` and `Date.keys()` static methods.
+For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no, for instance, `Array.keys()` (or `Date.keys()`) static method.
Here's the picture structure for `Date` and `Object`:
diff --git a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md
index e9481912a..5b8dc7de3 100644
--- a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md
+++ b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md
@@ -4,7 +4,7 @@ importance: 5
# Strange instanceof
-Why `instanceof` below returns `true`? We can easily see that `a` is not created by `B()`.
+In the code below, why does `instanceof` return `true`? We can easily see that `a` is not created by `B()`.
```js run
function A() {}
diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
index 0b02c99be..00aee3376 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]
@@ -44,9 +44,9 @@ alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
```
-Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypally inherits from `Object`.
+Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypically inherits from `Object`.
-Normally, `instanceof` operator examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`.
+Normally, `instanceof` examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`.
The algorithm of `obj instanceof Class` works roughly as follows:
@@ -55,7 +55,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
For example:
```js run
- // setup instanceOf check that assumes that
+ // set up instanceof check that assumes that
// anything with canEat property is an animal
class Animal {
static [Symbol.hasInstance](obj) {
@@ -68,7 +68,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
```
-2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain.
+2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceof Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain.
In other words, compare one after another:
```js
@@ -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!)
*/!*
@@ -105,9 +105,9 @@ Here's the illustration of what `rabbit instanceof Animal` compares with `Animal
By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`.
-That's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters.
+It's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters.
-That can lead to interesting consequences when `prototype` property is changed after the object is created.
+That can lead to interesting consequences when a `prototype` property is changed after the object is created.
Like here:
@@ -186,11 +186,11 @@ let user = {
alert( {}.toString.call(user) ); // [object User]
```
-For most environment-specific objects, there is such a property. Here are few browser specific examples:
+For most environment-specific objects, there is such a property. Here are some browser specific examples:
```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 d5f1ab832..526b832ef 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() {
*!*
@@ -101,21 +101,21 @@ Here's the diagram (see the right part):

-That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above.
+That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above.
-As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`.
+As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`.
## EventMixin
Now let's make a mixin for real life.
-An important feature of many browser objects (for instance) is that they can generate events. Events is a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows to easily add event-related functions to any class/object.
+An important feature of many browser objects (for instance) is that they can generate events. Events are a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows us to easily add event-related functions to any class/object.
- The mixin will provide a method `.trigger(name, [...data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, optionally followed by additional arguments with event data.
-- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from `.trigger` call.
-- ...And the method `.off(name, handler)` that removes `handler` listener.
+- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from the `.trigger` call.
+- ...And the method `.off(name, handler)` that removes the `handler` listener.
-After adding the mixin, an object `user` will become able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen to such events to load the calendar for the logged-in person.
+After adding the mixin, an object `user` will be able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen for such events to load the calendar for the logged-in person.
Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may assign handlers to react on that event. And so on.
@@ -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
}
@@ -165,7 +165,7 @@ let eventMixin = {
```
-- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. Technically, there's `_eventHandlers` property, that stores an array of handlers for each event name. So it just adds it to the list.
+- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name occurs. Technically, there's an `_eventHandlers` property that stores an array of handlers for each event name, and it just adds it to the list.
- `.off(eventName, handler)` -- removes the function from the handlers list.
- `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`.
@@ -193,7 +193,7 @@ menu.on("select", value => alert(`Value selected: ${value}`));
menu.choose("123");
```
-Now if we'd like any code to react on menu selection, we can listen to it with `menu.on(...)`.
+Now, if we'd like any code to react to a menu selection, we can listen for it with `menu.on(...)`.
And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain.
@@ -201,8 +201,8 @@ And `eventMixin` mixin makes it easy to add such behavior to as many classes as
*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes.
-Some other languages like allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.
+Some other languages allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.
-We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.
+We can use mixins as a way to augment a class by adding multiple behaviors, like event-handling as we have seen above.
-Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that.
+Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that happening.
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 fa8ba5e93..cad2e1a3e 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 of other reasons.
+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) contains an error object with details about what's 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 phrase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code.
+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.
@@ -201,11 +201,11 @@ You can find more detailed information about JSON in the chapter.
**If `json` is malformed, `JSON.parse` generates an error, so the script "dies".**
-Should we be satisfied with that? Of course, not!
+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,9 +334,9 @@ 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 that "incorrect data" thing.
+In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing.
-Like this:
+For example:
```js run
let json = '{ "age": 30 }'; // incomplete data
@@ -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.**
-Fortunately, we can find out which error we get, for instance from its `name`:
+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`.
+
+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 `catch(err) {...}` block we analyze the error object `err`.
-2. If we don't know how to handle it, then 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 {
@@ -522,19 +526,19 @@ alert(result || "error occurred");
alert( `execution took ${diff}ms` );
```
-You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, an the execution will take `0ms`. Both measurements are done correctly.
+You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, and the execution will take `0ms`. Both measurements are done correctly.
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,11 +586,11 @@ 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 something else terrible.
+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.
+Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc.
-There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error.
+There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to the special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error.
The syntax:
@@ -628,7 +632,7 @@ For instance:
The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers.
-There are also web-services that provide error-logging for such cases, like or .
+There are also web-services that provide error-logging for such cases, like or .
They work like this:
@@ -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 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/1-format-error/solution.md b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md
index bb6b74cfa..754e68f9a 100644
--- a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md
+++ b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md
@@ -2,7 +2,7 @@
class FormatError extends SyntaxError {
constructor(message) {
super(message);
- this.name = "FormatError";
+ this.name = this.constructor.name;
}
}
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 2414ce7ef..d28b07439 100644
--- a/1-js/10-error-handling/2-custom-errors/article.md
+++ b/1-js/10-error-handling/2-custom-errors/article.md
@@ -2,11 +2,11 @@
When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need `HttpError`, for database operations `DbError`, for searching operations `NotFoundError` and so on.
-Our errors should support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have `statusCode` property with a value like `404` or `403` or `500`.
+Our errors should support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have a `statusCode` property with a value like `404` or `403` or `500`.
JavaScript allows to use `throw` with any argument, so technically our custom error classes don't need to inherit from `Error`. But if we inherit, then it becomes possible to use `obj instanceof Error` to identify error objects. So it's better to inherit from it.
-As the application grows, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on.
+As the application grows, our own errors naturally form a hierarchy. For instance, `HttpTimeoutError` may inherit from `HttpError`, and so on.
## Extending Error
@@ -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, here's it approximate code, for us to 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
@@ -38,7 +38,7 @@ class Error {
Now let's inherit `ValidationError` from it and try it in action:
-```js run untrusted
+```js run
*!*
class ValidationError extends Error {
*/!*
@@ -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 {
@@ -180,7 +180,7 @@ try {
The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor.
-Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all ours custom errors from it.
+Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all our custom errors from it.
Let's call it `MyError`.
@@ -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` in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`?
+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 wants to have some kind of "data reading error". Why exactly it happened -- is often irrelevant (the error message describes it). Or, even better if there is a way to get error details, but only if we need to.
+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`:
@@ -291,12 +319,12 @@ try {
In the code above, `readUser` works exactly as described -- catches syntax and validation errors and throws `ReadError` errors instead (unknown errors are rethrown as usual).
-So the outer code checks `instanceof ReadError` and that's it. No need to list possible all error types.
+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
-- We can inherit from `Error` and other built-in error classes normally, just need to take care of `name` property and don't forget to call `super`.
-- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks.
+- We can inherit from `Error` and other built-in error classes normally. We just need to take care of the `name` property and don't forget to call `super`.
+- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from a 3rd-party library and there's no easy way to get its class. Then `name` property can be used for such checks.
- Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required.
diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md
index c2f67c6cc..57115a909 100644
--- a/1-js/11-async/01-callbacks/article.md
+++ b/1-js/11-async/01-callbacks/article.md
@@ -2,30 +2,44 @@
# Introduction: callbacks
-Many actions in JavaScript are *asynchronous*.
+```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.
-For instance, take a look at the function `loadScript(src)`:
+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 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, 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).
+
+Take a look at the function `loadScript(src)`, that loads a script with the given `src`:
```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 importers will see that.
-For instance, `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,18 +261,18 @@ 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
Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:script-async-defer)), for both external and inline scripts.
In other words:
-- downloading of external module scripts `
@@ -264,21 +296,21 @@ Compare to regular script below:
```
-Please note: the second script actually works before the first! So we'll see `undefined` first, and then `object`.
+Please note: the second script actually runs before the first! So we'll see `undefined` first, and then `object`.
-That's because modules are deferred, so way wait for the document to be processed. The regular scripts runs immediately, so we saw its output first.
+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 any scripts.
+For module scripts, it works on inline scripts as well.
-For example, the script below has `async`, so it doesn't wait for anyone.
+For example, the inline script below has `async`, so it doesn't wait for anything.
-It performs the import (fetches `./analytics.js`) and runs when ready, even if HTML document is not finished yet, or if other scripts are still pending.
+It performs the import (fetches `./analytics.js`) and runs when ready, even if the HTML document is not finished yet, or if other scripts are still pending.
That's good for functionality that doesn't depend on anything, like counters, ads, document-level event listeners.
@@ -296,7 +328,7 @@ That's good for functionality that doesn't depend on anything, like counters, ad
External scripts that have `type="module"` are different in two aspects:
-1. External scripts with same `src` run only once:
+1. External scripts with the same `src` run only once:
```html
@@ -322,11 +354,11 @@ import {sayHi} from 'sayHi'; // Error, "bare" module
// the module must have a path, e.g. './sayHi.js' or wherever the module is
```
-Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet.
+Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet.
### Compatibility, "nomodule"
-Old browsers do not understand `type="module"`. Scripts of the unknown type are just ignored. For them, it's possible to provide a fallback using `nomodule` attribute:
+Old browsers do not understand `type="module"`. Scripts of an unknown type are just ignored. For them, it's possible to provide a fallback using the `nomodule` attribute:
```html run
';
+};
+
+const downloadCollage = () => {
+ const date = new Date();
+ const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`;
+ const img = canvasEl.toDataURL("image/png");
+ const link = document.createElement("a");
+ link.download = fileName;
+ link.href = img;
+ link.click();
+ link.remove();
+};
+
+const changeLayout = ({ target }) => {
+ state.currentLayout = JSON.parse(target.value);
+};
+
+// Listeners.
+selectEl.addEventListener("change", changeLayout);
+createCollageBtn.addEventListener("click", createCollage);
+startOverBtn.addEventListener("click", startOver);
+downloadBtn.addEventListener("click", downloadCollage);
diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js
new file mode 100644
index 000000000..f0140c116
--- /dev/null
+++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js
@@ -0,0 +1,321 @@
+const loggerContainerEl = document.querySelector(".loggerContainer");
+
+export const images = [
+ {
+ img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1533105079780-92b9be482077",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1518684079-3c830dcef090",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1505832018823-50331d70d237",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1499363536502-87642509e31b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1545389336-cf090694435e",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506125840744-167167210587",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1510662145379-13537db782dc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1573790387438-4da905039392",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1512757776214-26d36777b513",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a",
+ },
+];
+export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format";
+
+// Console styles.
+export const CONSOLE_BASE_STYLES = [
+ "font-size: 12px",
+ "padding: 4px",
+ "border: 2px solid #5a5a5a",
+ "color: white",
+].join(";");
+export const CONSOLE_PRIMARY = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #13315a",
+].join(";");
+export const CONSOLE_SUCCESS = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #385a4e",
+].join(";");
+export const CONSOLE_ERROR = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #5a1a24",
+].join(";");
+
+// Layouts.
+export const LAYOUT_4_COLUMNS = {
+ name: "Layout 4 columns",
+ columns: 4,
+ itemWidth: 240,
+ itemHeight: 240,
+};
+export const LAYOUT_8_COLUMNS = {
+ name: "Layout 8 columns",
+ columns: 8,
+ itemWidth: 240,
+ itemHeight: 240,
+};
+export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS];
+
+export const createImageFile = async (src) =>
+ new Promise((resolve, reject) => {
+ const img = new Image();
+ img.src = src;
+ img.onload = () => resolve(img);
+ img.onerror = () => reject(new Error("Failed to construct image."));
+ });
+
+export const loadImage = async (url) => {
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(String(response.status));
+ }
+
+ return await response.blob();
+ } catch (e) {
+ console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR);
+ }
+};
+
+export const weakRefCache = (fetchImg) => {
+ const imgCache = new Map();
+ const registry = new FinalizationRegistry(({ imgName, size, type }) => {
+ const cachedImg = imgCache.get(imgName);
+ if (cachedImg && !cachedImg.deref()) {
+ imgCache.delete(imgName);
+ console.log(
+ `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`,
+ CONSOLE_ERROR,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--error");
+ logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+ }
+ });
+
+ return async (imgName) => {
+ const cachedImg = imgCache.get(imgName);
+
+ if (cachedImg?.deref() !== undefined) {
+ console.log(
+ `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`,
+ CONSOLE_SUCCESS,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--success");
+ logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+
+ return cachedImg?.deref();
+ }
+
+ const newImg = await fetchImg(imgName);
+ console.log(
+ `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`,
+ CONSOLE_PRIMARY,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--primary");
+ logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+
+ imgCache.set(imgName, new WeakRef(newImg));
+ registry.register(newImg, {
+ imgName,
+ size: newImg.size,
+ type: newImg.type,
+ });
+
+ return newImg;
+ };
+};
+
+export const stateObj = {
+ loading: false,
+ drawing: true,
+ collageRendered: false,
+ currentLayout: LAYOUTS[0],
+ selectedImages: new Set(),
+};
diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md
index a8a3fd110..eedc28fb3 100644
--- a/2-ui/1-document/01-browser-environment/article.md
+++ b/2-ui/1-document/01-browser-environment/article.md
@@ -1,12 +1,12 @@
# Browser environment, specs
-The JavaScript language was initially created for web browsers. Since then, it has evolved and become a language with many uses and platforms.
+The JavaScript language was initially created for web browsers. Since then, it has evolved into 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*, or even a "smart" coffee machine if it can run JavaScript. Each of these 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.
+A host environment provides its own objects and functions in addition 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:

@@ -15,9 +15,9 @@ There's a "root" object called `window`. It has two roles:
1. First, it is a global object for JavaScript code, as described in the chapter .
2. Second, it represents the "browser window" and provides methods to control it.
-For instance, here we use it as a global object:
+For instance, we can use it as a global object:
-```js run
+```js run global
function sayHi() {
alert("Hello");
}
@@ -26,17 +26,17 @@ function sayHi() {
window.sayHi();
```
-And here we use it as a browser window, to see the window height:
+And we can use it as a browser window, to show the window height:
```js run
alert(window.innerHeight); // inner window height
```
-There are more window-specific methods and properties, we'll cover them later.
+There are more window-specific methods and properties, which we'll cover later.
## DOM (Document Object Model)
-Document Object Model, or DOM for short, represents all page content as objects that can be modified.
+The Document Object Model, or DOM for short, represents all page content as objects that can be modified.
The `document` object is the main "entry point" to the page. We can change or create anything on the page using it.
@@ -49,29 +49,27 @@ 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.
-For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though.
+For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though.
```
```smart header="CSSOM for styling"
-CSS rules and stylesheets are structured in a different way than HTML. There's a separate specification [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.
+The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the 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)
+## BOM (Browser Object Model)
-Browser Object Model (BOM) are additional objects provided by the browser (host environment) to work with everything except the document.
+The Browser Object Model (BOM) represents additional objects provided by the browser (host environment) for working with everything except the document.
For instance:
-- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc).
+- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc).
- The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one.
Here's how we can use the `location` object:
@@ -83,12 +81,12 @@ if (confirm("Go to Wikipedia?")) {
}
```
-Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user.
+The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user.
```smart header="Specifications"
-BOM is the part of the general [HTML specification](https://html.spec.whatwg.org).
+The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org).
-Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at .
+Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at .
```
## Summary
@@ -96,20 +94,20 @@ Yes, you heard that right. The HTML spec at is no
Talking about standards, we have:
DOM specification
-: Describes the document structure, manipulations and events, see .
+: Describes the document structure, manipulations, and events, see .
CSSOM specification
-: Describes stylesheets and style rules, manipulations with them and their binding to documents, see .
+: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see .
HTML specification
: Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods.
Additionally, some classes are described separately at .
-Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything.
+Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all.
-When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.
+When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.
To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g , .
-Now we'll get down to learning DOM, because the document plays the central role in the UI.
+Now, we'll get down to learning the DOM, because the document plays the central role in the UI.
diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md
index e961eff3a..f7f2be91d 100644
--- a/2-ui/1-document/02-dom-nodes/article.md
+++ b/2-ui/1-document/02-dom-nodes/article.md
@@ -6,13 +6,13 @@ libs:
# DOM tree
-The backbone of an HTML document are tags.
+The backbone of an HTML document is tags.
-According to Document Object Model (DOM), every HTML-tag is an object. Nested tags are "children" of the enclosing one. The text inside a tag it is an object as well.
+According to the Document Object Model (DOM), every HTML tag is an object. Nested tags are "children" of the enclosing one. The text inside a tag is an object as well.
-All these objects are accessible using JavaScript, we can use them to modify the page.
+All these objects are accessible using JavaScript, and we can use them to modify the page.
-For example, `document.body` is the object representing `` tag.
+For example, `document.body` is the object representing the `` tag.
Running this code will make the `` red for 3 seconds:
@@ -22,20 +22,26 @@ document.body.style.background = 'red'; // make the background red
setTimeout(() => document.body.style.background = '', 3000); // return back
```
-That was just a glimpse of DOM power. Soon we'll learn more ways to manipulate DOM, but first we need to know about its structure.
+Here we used `style.background` to change the background color of `document.body`, but there are many other properties, such as:
-## An example of DOM
+- `innerHTML` -- HTML contents of the node.
+- `offsetWidth` -- the node width (in pixels)
+- ...and so on.
+
+Soon we'll learn more ways to manipulate the DOM, but first we need to know about its structure.
+
+## An example of the DOM
-Let's start with the following simple docment:
+Let's start with the following simple document:
```html run no-beautify
- About elks
+ About elk
- The truth about elks.
+ The truth about elk.
```
@@ -45,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -56,36 +62,36 @@ On the picture above, you can click on element nodes and their children will ope
Every tree node is an object.
-Tags are *element nodes* (or just elements), they form the tree structure: `` is at the root, then `` and `` are its children, etc.
+Tags are *element nodes* (or just elements) and form the tree structure: `` is at the root, then `` and `` are its children, etc.
The text inside elements forms *text nodes*, labelled as `#text`. A text node contains only a string. It may not have children and is always a leaf of the tree.
-For instance, the `` tag has the text `"About elks"`.
+For instance, the `` tag has the text `"About elk"`.
Please note the special characters in text nodes:
- a newline: `âµ` (in JavaScript known as `\n`)
- a space: `â£`
-Spaces and newlines -- are totally valid characters, like letters and digits. They form text nodes and become a part of the DOM. So, for instance, in the example above the `` tag contains some spaces before ``, and that text becomes a `#text` node (it contains a newline and some spaces only).
+Spaces and newlines are totally valid characters, like letters and digits. They form text nodes and become a part of the DOM. So, for instance, in the example above the `` tag contains some spaces before ``, and that text becomes a `#text` node (it contains a newline and some spaces only).
There are only two top-level exclusions:
-1. Spaces and newlines before `` are ignored for historical reasons,
-2. If we put something after ``, then that is automatically moved inside the `body`, at the end, as the HTML spec requires that all content must be inside ``. So there may be no spaces after ``.
+1. Spaces and newlines before `` are ignored for historical reasons.
+2. If we put something after ``, then that is automatically moved inside the `body`, at the end, as the HTML spec requires that all content must be inside ``. So there can't be any spaces after ``.
-In other cases everything's straightforward -- if there are spaces (just like any character) in the document, then they become text nodes in DOM, and if we remove them, then there won't be any.
+In other cases everything's straightforward -- if there are spaces (just like any character) in the document, then they become text nodes in the DOM, and if we remove them, then there won't be any.
Here are no space-only text nodes:
```html no-beautify
-About elksThe truth about elks.
+About elkThe truth about elk.
```
@@ -100,11 +106,11 @@ On further DOM pictures we'll sometimes omit them when they are irrelevant. Such
## Autocorrection
-If the browser encounters malformed HTML, it automatically corrects it when making DOM.
+If the browser encounters malformed HTML, it automatically corrects it when making the DOM.
-For instance, the top tag is always ``. Even if it doesn't exist in the document -- it will exist in the DOM, the browser will create it. The same goes for ``.
+For instance, the top tag is always ``. Even if it doesn't exist in the document, it will exist in the DOM, because the browser will create it. The same goes for ``.
-As an example, if the HTML file is a single word `"Hello"`, the browser will wrap it into `` and ``, add the required ``, and the DOM will be:
+As an example, if the HTML file is the single word `"Hello"`, the browser will wrap it into `` and ``, and add the required ``, and the DOM will be:
@@ -117,7 +123,7 @@ drawHtmlTree(node3, 'div.domtree', 690, 150);
While generating the DOM, browsers automatically process errors in the document, close tags and so on.
-Such document with unclosed tags:
+A document with unclosed tags:
```html no-beautify
Hello
@@ -126,7 +132,7 @@ Such document with unclosed tags:
Dad
```
-...Will become a normal DOM, as the browser reads tags and restores the missing parts:
+...will become a normal DOM as the browser reads tags and restores the missing parts:
@@ -137,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 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:
@@ -154,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
@@ -167,7 +173,7 @@ For example, comments:
- The truth about elks.
+ The truth about elk.
An elk is a smart
*!*
@@ -182,7 +188,7 @@ For example, comments:
@@ -193,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.
@@ -202,29 +208,29 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu
1. `document` -- the "entry point" into DOM.
2. element nodes -- HTML-tags, the tree building blocks.
3. text nodes -- contain text.
-4. comments -- sometimes we can put the information there, it won't be shown, but JS can read it from the DOM.
+4. comments -- sometimes we can put information there, it won't be shown, but JS can read it from the DOM.
## See it for yourself
-To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up DOM at an instant.
+To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant.
Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing.
-To do so, open the web-page [elks.html](elks.html), turn on the browser developer tools and switch to the Elements tab.
+To do so, open the web page [elk.html](elk.html), turn on the browser developer tools and switch to the Elements tab.
It should look like this:
-
+
You can see the DOM, click on elements, see their details and so on.
Please note that the DOM structure in developer tools is simplified. Text nodes are shown just as text. And there are no "blank" (space only) text nodes at all. That's fine, because most of the time we are interested in element nodes.
-Clicking the button in the left-upper corner allows to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it.
+Clicking the button in the left-upper corner allows us to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it.
Another way to do it would be just right-clicking on a webpage and selecting "Inspect" in the context menu.
-
+
At the right part of the tools there are the following subtabs:
- **Styles** -- we can see CSS applied to the current element rule by rule, including built-in rules (gray). Almost everything can be edited in-place, including the dimensions/margins/paddings of the box below.
@@ -247,15 +253,15 @@ Now the last selected element is available as `$0`, the previously selected is `
We can run commands on them. For instance, `$0.style.background = 'red'` makes the selected list item red, like this:
-
+
That's how to get a node from Elements in Console.
There's also a road back. If there's a variable referencing a DOM node, then we can use the command `inspect(node)` in Console to see it in the Elements pane.
-Or we can just output DOM-node in the console and explore "at-place", like `document.body` below:
+Or we can just output the DOM node in the console and explore "in-place", like `document.body` below:
-
+
That's for debugging purposes of course. From the next chapter on we'll access and modify DOM using JavaScript.
@@ -273,4 +279,4 @@ We can use developer tools to inspect DOM and modify it manually.
Here we covered the basics, the most used and important actions to start with. There's an extensive documentation about Chrome Developer Tools at . The best way to learn the tools is to click here and there, read menus: most options are obvious. Later, when you know them in general, read the docs and pick up the rest.
-DOM nodes have properties and methods that allow to travel between them, modify, move around the page and more. We'll get down to them in the next chapters.
+DOM nodes have properties and methods that allow us to travel between them, modify them, move around the page, and more. We'll get down to them in the next chapters.
diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.png b/2-ui/1-document/02-dom-nodes/domconsole0.png
deleted file mode 100644
index 121c11d75..000000000
Binary files a/2-ui/1-document/02-dom-nodes/domconsole0.png and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg
new file mode 100644
index 000000000..eb99f193f
--- /dev/null
+++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/2-ui/1-document/02-dom-nodes/[email protected] b/2-ui/1-document/02-dom-nodes/[email protected]
deleted file mode 100644
index a8953395c..000000000
Binary files a/2-ui/1-document/02-dom-nodes/[email protected] and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.png b/2-ui/1-document/02-dom-nodes/domconsole1.png
deleted file mode 100644
index c04f015cf..000000000
Binary files a/2-ui/1-document/02-dom-nodes/domconsole1.png and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg
new file mode 100644
index 000000000..02ef5f0a6
--- /dev/null
+++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/2-ui/1-document/02-dom-nodes/[email protected] b/2-ui/1-document/02-dom-nodes/[email protected]
deleted file mode 100644
index ce0fa0fff..000000000
Binary files a/2-ui/1-document/02-dom-nodes/[email protected] and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/elks.html b/2-ui/1-document/02-dom-nodes/elk.html
similarity index 86%
rename from 2-ui/1-document/02-dom-nodes/elks.html
rename to 2-ui/1-document/02-dom-nodes/elk.html
index 7d29f3d4e..dc5d65f54 100644
--- a/2-ui/1-document/02-dom-nodes/elks.html
+++ b/2-ui/1-document/02-dom-nodes/elk.html
@@ -1,7 +1,7 @@
- The truth about elks.
+ The truth about elk.
An elk is a smart
diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg
new file mode 100644
index 000000000..448eea9d1
--- /dev/null
+++ b/2-ui/1-document/02-dom-nodes/elk.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/2-ui/1-document/02-dom-nodes/elks.png b/2-ui/1-document/02-dom-nodes/elks.png
deleted file mode 100644
index 03177c40e..000000000
Binary files a/2-ui/1-document/02-dom-nodes/elks.png and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/[email protected] b/2-ui/1-document/02-dom-nodes/[email protected]
deleted file mode 100644
index e8a15bd5b..000000000
Binary files a/2-ui/1-document/02-dom-nodes/[email protected] and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/inspect.png b/2-ui/1-document/02-dom-nodes/inspect.png
deleted file mode 100644
index 075cf9308..000000000
Binary files a/2-ui/1-document/02-dom-nodes/inspect.png and /dev/null differ
diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg
new file mode 100644
index 000000000..60696ec0d
--- /dev/null
+++ b/2-ui/1-document/02-dom-nodes/inspect.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/2-ui/1-document/02-dom-nodes/[email protected] b/2-ui/1-document/02-dom-nodes/[email protected]
deleted file mode 100644
index 8743dd297..000000000
Binary files a/2-ui/1-document/02-dom-nodes/[email protected] and /dev/null differ
diff --git a/2-ui/1-document/03-dom-navigation/1-dom-children/task.md b/2-ui/1-document/03-dom-navigation/1-dom-children/task.md
index 4a9e741a9..d97f2748a 100644
--- a/2-ui/1-document/03-dom-navigation/1-dom-children/task.md
+++ b/2-ui/1-document/03-dom-navigation/1-dom-children/task.md
@@ -4,7 +4,7 @@ importance: 5
# DOM children
-For the page:
+Look at this page:
```html
@@ -18,7 +18,7 @@ For the page:
```
-How to access:
+For each of the following, give at least one way of how to access them:
- The `
` DOM node?
- The `
` DOM node?
- The second `
` (with Pete)?
diff --git a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md
index 266d26b1b..d76936320 100644
--- a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md
+++ b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md
@@ -1,5 +1,5 @@
1. Yes, true. The element `elem.lastChild` is always the last one, it has no `nextSibling`.
-2. No, wrong, because `elem.children[0]` is the first child *among elements*. But there may exist non-element nodes before it. So `previousSibling` may be a text node. Also, if there are no children, then trying to access `elem.children[0]`
+2. No, wrong, because `elem.children[0]` is the first child *among elements*. But there may exist non-element nodes before it. So `previousSibling` may be a text node.
Please note: for both cases if there are no children, then there will be an error.
diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md
index 80cefd1cc..b5f03098c 100644
--- a/2-ui/1-document/03-dom-navigation/article.md
+++ b/2-ui/1-document/03-dom-navigation/article.md
@@ -22,7 +22,7 @@ Let's discuss them in more detail.
The topmost tree nodes are available directly as `document` properties:
`` = `document.documentElement`
-: The topmost document node is `document.documentElement`. That's DOM node of `` tag.
+: The topmost document node is `document.documentElement`. That's the DOM node of the `` tag.
`` = `document.body`
: Another widely used DOM node is the `` element -- `document.body`.
@@ -149,7 +149,7 @@ There are two important consequences:
The first thing is nice. The second is tolerable, because we can use `Array.from` to create a "real" array from the collection, if we want array methods:
```js run
- alert( Array.from(document.body.childNodes).filter ); // now it's there
+ alert( Array.from(document.body.childNodes).filter ); // function
```
```warn header="DOM collections are read-only"
@@ -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.
@@ -226,7 +226,7 @@ The links are similar to those given above, just with `Element` word inside:
- `children` -- only those children that are element nodes.
- `firstElementChild`, `lastElementChild` -- first and last element children.
-- `previousElementSibling`, `nextElementSibling` -- neighbour elements.
+- `previousElementSibling`, `nextElementSibling` -- neighbor elements.
- `parentElement` -- parent element.
````smart header="Why `parentElement`? Can the parent be *not* an element?"
@@ -239,7 +239,7 @@ alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null
```
-The reason is that root node `document.documentElement` (``) has `document` as its parent. But `document` is not an element node, so `parentNode` returns it and `parentElement` does not.
+The reason is that the root node `document.documentElement` (``) has `document` as its parent. But `document` is not an element node, so `parentNode` returns it and `parentElement` does not.
This detail may be useful when we want to travel up from an arbitrary element `elem` to ``, but not to the `document`:
```js
@@ -280,12 +280,12 @@ Till now we described the basic navigation properties.
Certain types of DOM elements may provide additional properties, specific to their type, for convenience.
-Tables are a great example and important particular case of that.
+Tables are a great example of that, and represent a particularly important case:
**The `
`** element supports (in addition to the given above) these properties:
- `table.rows` -- the collection of `
` elements of the table.
- `table.caption/tHead/tFoot` -- references to elements `
`, `
`, `
`.
-- `table.tBodies` -- the collection of `` elements (can be many according to the standard).
+- `table.tBodies` -- the collection of `` elements (can be many according to the standard, but there will always be at least one -- even if it is not in the source HTML, the browser will put it in the DOM).
**``, ``, ``** elements provide the `rows` property:
- `tbody.rows` -- the collection of `
` inside.
@@ -311,8 +311,9 @@ An example of usage:
```
@@ -320,9 +321,9 @@ The specification: [tabular data](https://html.spec.whatwg.org/multipage/tables.
There are also additional navigation properties for HTML forms. We'll look at them later when we start working with forms.
-# Summary
+## Summary
-Given a DOM node, we can go to its immediate neighbours using navigation properties.
+Given a DOM node, we can go to its immediate neighbors using navigation properties.
There are two main sets of them:
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 cd988f739..405129694 100644
--- a/2-ui/1-document/04-searching-elements-dom/article.md
+++ b/2-ui/1-document/04-searching-elements-dom/article.md
@@ -6,9 +6,27 @@ There are additional searching methods for that.
## document.getElementById or just id
-If an element has the `id` attribute, then there's a global variable by the name from that `id`.
+If an element has the `id` attribute, we can get the element using the method `document.getElementById(id)`, no matter where it is.
-We can use it to immediately access the element no matter where it is:
+For instance:
+
+```html run
+
+
Element
+
+
+
+```
+
+Also, there's a global variable named by `id` that references the element:
```html run
@@ -16,57 +34,44 @@ We can use it to immediately access the element no matter where it is:
```
-The behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. Good for very simple scripts, but there may be name conflicts. Also, when we look in JS and don't have HTML in view, it's not obvious where the variable comes from.
-
-If we declare a variable with the same name, it takes precedence:
+...That's unless we declare a JavaScript variable with the same name, then it takes precedence:
```html run untrusted height=0
```
-The better alternative is to use a special method `document.getElementById(id)`.
-
-For instance:
+```warn header="Please don't use id-named global variables to access elements"
+This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility.
-```html run
-
-
Element
-
+The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from.
-
+In real life `document.getElementById` is the preferred method.
```
-Here in the tutorial we'll often use `id` to directly reference an element, but that's only to keep things short. In real life `document.getElementById` is the preferred method.
-
-```smart header="There can be only one"
+```smart header="The `id` must be unique"
The `id` must be unique. There can be only one element in the document with the given `id`.
-If there are multiple elements with the same `id`, then the behavior of corresponding methods is unpredictable. The browser may return any of them at random. So please stick to the rule and keep `id` unique.
+If there are multiple elements with the same `id`, then the behavior of methods that use it is unpredictable, e.g. `document.getElementById` may return any of such elements at random. So please stick to the rule and keep `id` unique.
```
-```warn header="Only `document.getElementById`, not `anyNode.getElementById`"
-The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document.
+```warn header="Only `document.getElementById`, not `anyElem.getElementById`"
+The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document.
```
## querySelectorAll [#querySelectorAll]
@@ -98,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]
@@ -111,9 +116,9 @@ In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but t
Previous methods were searching the DOM.
-The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`.
+The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`.
-The method comes in handy when we are iterating over elements (like in array or something) and trying to filter those that interest us.
+The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us.
For instance:
@@ -137,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.
@@ -149,7 +154,7 @@ For instance:
Chapter 1
-
Chapter 1
+
Chapter 2
@@ -173,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
@@ -358,7 +363,7 @@ There are 6 main methods to search for nodes in DOM:
-By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts.
+By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts.
Besides that:
diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md
index 6c0053d0b..99dde5bcd 100644
--- a/2-ui/1-document/05-basic-dom-node-properties/article.md
+++ b/2-ui/1-document/05-basic-dom-node-properties/article.md
@@ -10,7 +10,7 @@ Different DOM nodes may have different properties. For instance, an element node
Each DOM node belongs to the corresponding built-in class.
-The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it.
+The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it.
Here's the picture, explanations to follow:
@@ -18,16 +18,39 @@ Here's the picture, explanations to follow:
The classes are:
-- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later.
-- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes.
-- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`.
-- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by concrete HTML elements:
+- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything.
+
+ Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later.
+
+- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes.
+
+ It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality).
+
+- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole.
+
+ The `document` global object belongs exactly to this class. It serves as an entry point to the DOM.
+
+- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by:
+ - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `
```
-Why the attribute may be preferable to classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`?
+Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`?
-That's because an attribute is more convenient to manage. The state can be changed as easy as:
+Because an attribute is more convenient to manage. The state can be changed as easy as:
```js
// a bit simpler than removing old/adding a new class
div.setAttribute('order-state', 'canceled');
```
-But there may be a possible problem with custom attributes. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected effects in such case.
+But there may be a possible problem with custom attributes. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, and more attributes appear to suit the needs of developers. There may be unexpected effects in such case.
To avoid conflicts, there exist [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes) attributes.
diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md
index e127bc0ef..40c75dff3 100644
--- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md
+++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md
@@ -6,7 +6,7 @@ importance: 5
We have an empty DOM element `elem` and a string `text`.
-Which of these 3 commands do exactly the same?
+Which of these 3 commands will do exactly the same?
1. `elem.append(document.createTextNode(text))`
2. `elem.innerHTML = text`
diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md
index 15238fcf4..1414e90c1 100644
--- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md
+++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md
@@ -39,15 +39,19 @@ The clock-managing functions:
```js
let timerId;
-function clockStart() { // run the clock
- timerId = setInterval(update, 1000);
+function clockStart() { // run the clock
+ if (!timerId) { // only set a new interval if the clock is not running
+ timerId = setInterval(update, 1000);
+ }
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
- timerId = null;
+ timerId = null; // (**)
}
```
Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then.
+
+Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`.
diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html
index 1bf642b10..84ee26f19 100644
--- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html
+++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html
@@ -43,15 +43,19 @@
}
function clockStart() {
- timerId = setInterval(update, 1000);
+ // set a new interval only if the clock is stopped
+ // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again
+ if (!timerId) {
+ timerId = setInterval(update, 1000);
+ }
update(); // <-- start right now, don't wait 1 second till the first setInterval works
}
function clockStop() {
clearInterval(timerId);
+ timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart()
}
- clockStart();
diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md
index 25dd58b0c..49243e8e3 100644
--- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md
+++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md
@@ -1,19 +1,18 @@
The solution is short, yet may look a bit tricky, so here I provide it with extensive comments:
-
```js
-let sortedRows = Array.from(table.rows)
- .slice(1)
- .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);
+let sortedRows = Array.from(table.tBodies[0].rows) // 1
+ .sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML));
-table.tBodies[0].append(...sortedRows);
+table.tBodies[0].append(...sortedRows); // (3)
```
-1. Get all `
`, like `table.querySelectorAll('tr')`, then make an array from them, cause we need array methods.
-2. The first TR (`table.rows[0]`) is actually a table header, so we take the rest by `.slice(1)`.
-3. Then sort them comparing by the content of the first `
` (the name field).
-4. Now insert nodes in the right order by `.append(...sortedRows)`.
+The step-by-step algorthm:
+
+1. Get all `
`, from `
`.
+2. Then sort them comparing by the content of the first `
` (the name field).
+3. Now insert nodes in the right order by `.append(...sortedRows)`.
- Tables always have an implicit
element, so we need to take it and insert into it: a simple `table.append(...)` would fail.
+We don't have to remove row elements, just "re-insert", they leave the old place automatically.
- Please note: we don't have to remove them, just "re-insert", they leave the old place automatically.
+P.S. In our case, there's an explicit `` in the table, but even if HTML table doesn't have ``, the DOM structure always has it.
diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html
index 81e985748..40692031a 100644
--- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html
+++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html
@@ -1,37 +1,30 @@
-
-
-
-
-
-
-
+
diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/task.md b/2-ui/1-document/07-modifying-document/12-sort-table/task.md
index 41d6fca29..7cdba35bc 100644
--- a/2-ui/1-document/07-modifying-document/12-sort-table/task.md
+++ b/2-ui/1-document/07-modifying-document/12-sort-table/task.md
@@ -6,33 +6,29 @@ importance: 5
There's a table:
+```html run
-
-
Name
-
Surname
-
Age
-
-
-
John
-
Smith
-
10
-
-
-
Pete
-
Brown
-
15
-
-
-
Ann
-
Lee
-
5
-
-
-
...
-
...
-
...
-
+
+
+
Name
Surname
Age
+
+
+
+
+
John
Smith
10
+
+
+
Pete
Brown
15
+
+
+
Ann
Lee
5
+
+
+
...
...
...
+
+
+```
There may be more rows in it.
diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md
index 6b85168b9..3d1f6698f 100644
--- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md
+++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md
@@ -1,9 +1,9 @@
The HTML in the task is incorrect. That's the reason of the odd thing.
-The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
`.
+The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`.
Now it's obvious that when we remove the table, it remains.
-The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
`.
+The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`.
The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct.
diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md
index 03064ed2c..861f70503 100644
--- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md
+++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md
@@ -4,7 +4,11 @@ importance: 1
# Why does "aaa" remain?
-Run the example. Why does `table.remove()` not delete the text `"aaa"`?
+In the example below, the call `table.remove()` removes the table from the document.
+
+But if you run it, you can see that the text `"aaa"` is still visible.
+
+Why does that happen?
```html height=100 run
@@ -18,6 +22,6 @@ Run the example. Why does `table.remove()` not delete the text `"aaa"`?
alert(table); // the table, as it should be
table.remove();
- // why there's still aaa in the document?
+ // why there's still "aaa" in the document?
```
diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md
index 43b0a34a7..a57e7e2d9 100644
--- a/2-ui/1-document/07-modifying-document/6-create-list/task.md
+++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md
@@ -10,7 +10,7 @@ For every list item:
1. Ask a user about its content using `prompt`.
2. Create the `
` with it and add it to `
`.
-3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt).
+3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry).
All elements should be created dynamically.
diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md
index 67bb5e13d..de8be56e9 100644
--- a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md
+++ b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md
@@ -3,7 +3,7 @@ We'll create the table as a string: `"
...
"`, and then assign it t
The algorithm:
1. Create the table header with `
` and weekday names.
-1. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`).
-2. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with `
`.
-3. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `
` to the calendar. If that's a Sunday, then add a newline "</tr><tr>".
-4. If the month has finished, but the table row is not yet full, add empty `
` into it, to make it square.
+2. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`).
+3. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with `
`.
+4. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `
` to the calendar. If that's a Sunday, then add a newline "</tr><tr>".
+5. If the month has finished, but the table row is not yet full, add empty `
` into it, to make it square.
diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md
index 9cdb79ddd..75ce1fbb0 100644
--- a/2-ui/1-document/07-modifying-document/article.md
+++ b/2-ui/1-document/07-modifying-document/article.md
@@ -1,12 +1,12 @@
# Modifying the document
-DOM modifications is the key to create "live" pages.
+DOM modification is the key to creating "live" pages.
Here we'll see how to create new elements "on the fly" and modify the existing page content.
## Example: show a message
-Let's see the methods on example. We'll add a message on the page that looks nicer than `alert`.
+Let's demonstrate using an example. We'll add a message on the page that looks nicer than `alert`.
Here's how it will look:
@@ -28,7 +28,7 @@ Here's how it will look:
*/!*
```
-That was an HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML or an external CSS file).
+That was the HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML/CSS already).
## Creating an element
@@ -48,21 +48,28 @@ To create DOM nodes, there are two methods:
let textNode = document.createTextNode('Here I am');
```
+Most of the time we need to create element nodes, such as the `div` for the message.
+
### Creating the message
-In our case the message is a `div` with `alert` class and the HTML in it:
+Creating the message div takes 3 steps:
```js
+// 1. Create
element
let div = document.createElement('div');
+
+// 2. Set its class to "alert"
div.className = "alert";
+
+// 3. Fill it with the content
div.innerHTML = "Hi there! You've read an important message.";
```
-We created the element, but as of now it's only in a variable. We can't see the element on the page, as it's not yet a part of the document.
+We've created the element. But as of now it's only in a variable named `div`, not in the page yet. So we can't see it.
## Insertion methods
-To make the `div` show up, we need to insert it somewhere into `document`. For instance, in `document.body`.
+To make the `div` show up, we need to insert it somewhere into `document`. For instance, into `` element, referenced by `document.body`.
There's a special method `append` for that: `document.body.append(div)`.
@@ -90,14 +97,20 @@ Here's the full code:
```
-This set of methods provides more ways to insert:
+Here we called `append` on `document.body`, but we can call `append` method on any other element, to put another element into it. For instance, we can append something to `
` by calling `div.append(anotherElement)`.
-- `node.append(...nodes or strings)` -- append nodes or strings at the end of `node`,
-- `node.prepend(...nodes or strings)` -- insert nodes or strings into the beginning of `node`,
-- `node.before(...nodes or strings)` â- insert nodes or strings before the `node`,
-- `node.after(...nodes or strings)` â- insert nodes or strings after the `node`,
+Here are more insertion methods, they specify different places where to insert:
+
+- `node.append(...nodes or strings)` -- append nodes or strings *at the end* of `node`,
+- `node.prepend(...nodes or strings)` -- insert nodes or strings *at the beginning* of `node`,
+- `node.before(...nodes or strings)` â- insert nodes or strings *before* `node`,
+- `node.after(...nodes or strings)` â- insert nodes or strings *after* `node`,
- `node.replaceWith(...nodes or strings)` â- replaces `node` with the given nodes or strings.
+Arguments of these methods are an arbitrary list of DOM nodes to insert, or text strings (that become text nodes automatically).
+
+Let's see them in action.
+
Here's an example of using these methods to add items to a list and the text before/after it:
```html autorun
@@ -121,7 +134,7 @@ Here's an example of using these methods to add items to a list and the text bef
```
-Here's a visual picture what methods do:
+Here's a visual picture of what the methods do:

@@ -139,7 +152,7 @@ before
after
```
-These methods can insert multiple lists of nodes and text pieces in a single call.
+As said, these methods can insert multiple nodes and text pieces in a single call.
For instance, here a string and an element are inserted:
@@ -150,7 +163,7 @@ For instance, here a string and an element are inserted:
```
-All text is inserted *as text*.
+Please note: the text is inserted "as text", not "as HTML", with proper escaping of characters such as `<`, `>`.
So the final HTML is:
@@ -166,7 +179,7 @@ In other words, strings are inserted in a safe way, like `elem.textContent` does
So, these methods can only be used to insert DOM nodes or text pieces.
-But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML`?
+But what if we'd like to insert an HTML string "as html", with all tags and stuff working, in the same manner as `elem.innerHTML` does it?
## insertAdjacentHTML/Text/Element
@@ -199,7 +212,7 @@ For instance:
Bye
```
-That's how we can append an arbitrary HTML to the page.
+That's how we can append arbitrary HTML to the page.
Here's the picture of insertion variants:
@@ -534,13 +547,13 @@ So if we need to add a lot of text into HTML dynamically, and we're at page load
All these methods return `node`.
-- Given a piece of HTML: `elem.insertAdjacentHTML(where, html)`, inserts depending on `where`:
+- Given some HTML in `html`, `elem.insertAdjacentHTML(where, html)` inserts it depending on the value of `where`:
- `"beforebegin"` -- insert `html` right before `elem`,
- `"afterbegin"` -- insert `html` into `elem`, at the beginning,
- `"beforeend"` -- insert `html` into `elem`, at the end,
- `"afterend"` -- insert `html` right after `elem`.
- Also there are similar methods `elem.insertAdjacentText` and `elem.insertAdjacentElement`, they insert text strings and elements, but they are rarely used.
+ Also there are similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used.
- To append HTML to the page before it has finished loading:
- `document.write(html)`
diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md
index e6357c8e0..46aaa3b00 100644
--- a/2-ui/1-document/08-styles-and-classes/article.md
+++ b/2-ui/1-document/08-styles-and-classes/article.md
@@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // back to normal
If we set `style.display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style.display` property at all.
+Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this:
+
+```js run
+document.body.style.background = 'red'; //set background to red
+
+setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second
+```
+
````smart header="Full rewrite with `style.cssText`"
Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only.
@@ -249,7 +257,7 @@ For instance:
```smart header="Computed and resolved values"
There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values):
-1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`.
+1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`.
2. A *resolved* style value is the one finally applied to the element. Values like `1em` or `125%` are relative. The browser takes the computed value and makes all units fixed and absolute, for instance: `height:20px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`.
A long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed.
@@ -261,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper
We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed.
For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here.
-
-There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not:
-
-```html run
-
-
-```
````
```smart header="Styles applied to `:visited` links are hidden!"
@@ -282,7 +276,7 @@ Visited links may be colored using `:visited` CSS pseudoclass.
But `getComputedStyle` does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles.
-JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no sideway for an evil page to test if a link was visited and hence to break the privacy.
+JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids applying geometry-changing styles in `:visited`. That's to guarantee that there's no side way for an evil page to test if a link was visited and hence to break the privacy.
```
## Summary
diff --git a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md
index be47cfd30..796039c2a 100644
--- a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md
+++ b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md
@@ -4,7 +4,7 @@ importance: 5
# What's the scroll from the bottom?
-The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get the size from the bottom scroll (let's call it `scrollBottom`)?
+The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get the size of the bottom scroll (let's call it `scrollBottom`)?
Write the code that works for an arbitrary `elem`.
diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html
index ca9c4d579..8f855ecfa 100755
--- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html
+++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html
@@ -9,7 +9,7 @@
background-color: #00FF00;
position: relative;
}
-
+
#ball {
position: absolute;
}
@@ -20,7 +20,7 @@
@@ -38,4 +38,4 @@
-
\ No newline at end of file
+
diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md
index c6fe6c3bb..afa1d8f50 100644
--- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md
+++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md
@@ -24,17 +24,22 @@ ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';
```
-**Attention: the pitfall!**
+Now the ball is finally centered.
+
+````warn header="Attention: the pitfall!"
The code won't work reliably while `` has no width/height:
```html
```
+````
When the browser does not know the width/height of an image (from tag attributes or CSS), then it assumes them to equal `0` until the image finishes loading.
-After the first load browser usually caches the image, and on next loads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`. That leads to wrong coordinates.
+So the value of `ball.offsetWidth` will be `0` until the image loads. That leads to wrong coordinates in the code above.
+
+After the first load, the browser usually caches the image, and on reloads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`.
We should fix that by adding `width/height` to ``:
diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html
index 9ebe6001e..9f21e5421 100755
--- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html
+++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html
@@ -26,7 +26,7 @@
```
-Setting `scrollTop` to `0` or `Infinity` will make the element scroll to the very top/bottom respectively.
+Setting `scrollTop` to `0` or a big value, such as `1e9` will make the element scroll to the very top/bottom respectively.
````
## Don't take width/height from CSS
@@ -243,9 +243,9 @@ Why should we use geometry properties instead? There are two reasons:
```
- From the CSS standpoint, `width:auto` is perfectly normal, but in JavaScript we need an exact size in `px` that we can use in calculations. So here CSS width is useless at all.
+ From the CSS standpoint, `width:auto` is perfectly normal, but in JavaScript we need an exact size in `px` that we can use in calculations. So here CSS width is useless.
-And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar starts to bug with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is *less* than CSS width. And `clientWidth/clientHeight` take that into account.
+And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar becomes buggy with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is *less* than CSS width. And `clientWidth/clientHeight` take that into account.
...But with `getComputedStyle(elem).width` the situation is different. Some browsers (e.g. Chrome) return the real inner width, minus the scrollbar, and some of them (e.g. Firefox) -- CSS width (ignore the scrollbar). Such cross-browser differences is the reason not to use `getComputedStyle`, but rather rely on geometry properties.
@@ -268,7 +268,7 @@ Elements have the following geometry properties:
- `offsetParent` -- is the nearest positioned ancestor or `td`, `th`, `table`, `body`.
- `offsetLeft/offsetTop` -- coordinates relative to the upper-left edge of `offsetParent`.
- `offsetWidth/offsetHeight` -- "outer" width/height of an element including borders.
-- `clientLeft/clientTop` -- the distance from the upper-left outer corner the inner corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too.
+- `clientLeft/clientTop` -- the distances from the upper-left outer corner to the upper-left inner (content + padding) corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too.
- `clientWidth/clientHeight` -- the width/height of the content including paddings, but without the scrollbar.
- `scrollWidth/scrollHeight` -- the width/height of the content, just like `clientWidth/clientHeight`, but also include scrolled-out, invisible part of the element.
- `scrollLeft/scrollTop` -- width/height of the scrolled out upper part of the element, starting from its upper-left corner.
diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md
index edfb8bcd5..08a2f6576 100644
--- a/2-ui/1-document/10-size-and-scroll-window/article.md
+++ b/2-ui/1-document/10-size-and-scroll-window/article.md
@@ -1,12 +1,12 @@
# Window sizes and scrolling
-How to find out the width and height of the browser window? How to get the full width and height of the document, including the scrolled out part? How to scroll the page using JavaScript?
+How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript?
-For most such requests, we can use the root document element `document.documentElement`, that corresponds to `` tag. But there are additional methods and peculiarities important enough to consider.
+For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider.
## Width/height of the window
-To get window width and height we can use `clientWidth/clientHeight` of `document.documentElement`:
+To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`:

@@ -16,12 +16,12 @@ For instance, this button shows the height of your window:
```
-````warn header="Not `window.innerWidth/Height`"
-Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So why not to use them instead?
+````warn header="Not `window.innerWidth/innerHeight`"
+Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead?
-If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return width/height of the visible part of the document, available for the content.
+If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content.
-...And `window.innerWidth/innerHeight` include the scrollbar.
+`window.innerWidth/innerHeight` includes the scrollbar.
If there's a scrollbar, and it occupies some space, then these two lines show different values:
```js run
@@ -29,7 +29,7 @@ alert( window.innerWidth ); // full window width
alert( document.documentElement.clientWidth ); // window width minus the scrollbar
```
-In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`.
+In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`.
````
```warn header="`DOCTYPE` is important"
@@ -40,9 +40,9 @@ In modern HTML we should always write `DOCTYPE`.
## Width/height of the document
-Theoretically, as the root document element is `documentElement.clientWidth/Height`, and it encloses all the content, we could measure document full size as `documentElement.scrollWidth/scrollHeight`.
+Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`.
-But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right?
+But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Weird, right?
To reliably obtain the full document height, we should take the maximum of these properties:
@@ -60,11 +60,11 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a "
## Get the current scroll [#page-scroll]
-DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`.
+DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties.
-For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except oldler WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`.
+For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`.
-Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`:
+Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`:
```js run
alert('Current scroll from the top: ' + window.pageYOffset);
@@ -73,19 +73,25 @@ alert('Current scroll from the left: ' + window.pageXOffset);
These properties are read-only.
+```smart header="Also available as `window` properties `scrollX` and `scrollY`"
+For historical reasons, both properties exist, but they are the same:
+- `window.pageXOffset` is an alias of `window.scrollX`.
+- `window.pageYOffset` is an alias of `window.scrollY`.
+```
+
## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll]
```warn
-To scroll the page from JavaScript, its DOM must be fully built.
+To scroll the page with JavaScript, its DOM must be fully built.
-For instance, if we try to scroll the page from the script in ``, it won't work.
+For instance, if we try to scroll the page with a script in ``, it won't work.
```
Regular elements can be scrolled by changing `scrollTop/scrollLeft`.
-We can do the same for the page using `document.documentElement.scrollTop/Left` (except Safari, where `document.body.scrollTop/Left` should be used instead).
+We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead).
-Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
+Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
- The method `scrollBy(x,y)` scrolls the page *relative to its current position*. For instance, `scrollBy(0,10)` scrolls the page `10px` down.
@@ -106,50 +112,50 @@ These methods work for all browsers the same way.
## scrollIntoView
-For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
+For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument:
-- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top.
-- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom.
+- If `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element will be aligned with the window top.
+- If `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element will be aligned with the window bottom.
```online
-The button below scrolls the page to make itself show at the window top:
+The button below scrolls the page to position itself at the window top:
-And this button scrolls the page to show it at the bottom:
+And this button scrolls the page to position itself at the bottom:
```
## Forbid the scrolling
-Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document.
+Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document.
-To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll.
+To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position.
```online
Try it:
-
+
-
+
-The first button freezes the scroll, the second one resumes it.
+The first button freezes the scroll, while the second one releases it.
```
-We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`.
+We can use the same technique to freeze the scroll for other elements, not just for `document.body`.
-The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it.
+The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it.
-That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same.
+That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same.
## Summary
Geometry:
-- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height`
+- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight`
- Width/height of the whole document, with the scrolled out part:
```js
diff --git a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md
index f3547096f..4101d4915 100644
--- a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md
+++ b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md
@@ -1,6 +1,6 @@
# Outer corners
-Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect).
+Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect).
Coordinates of the upper-left corner `answer1` and the bottom-right corner `answer2`:
diff --git a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html
index d627bee84..56c95d5ec 100644
--- a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html
+++ b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html
@@ -28,8 +28,8 @@
let box = elem.getBoundingClientRect();
return {
- top: box.top + pageYOffset,
- left: box.left + pageXOffset
+ top: box.top + window.pageYOffset,
+ left: box.left + window.pageXOffset
};
}
diff --git a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html
index 7e841397b..b89db3790 100644
--- a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html
+++ b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html
@@ -26,8 +26,8 @@
let box = elem.getBoundingClientRect();
return {
- top: box.top + pageYOffset,
- left: box.left + pageXOffset
+ top: box.top + window.pageYOffset,
+ left: box.left + window.pageXOffset
};
}
diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md
index c71fa0fce..fc605c414 100644
--- a/2-ui/1-document/11-coordinates/article.md
+++ b/2-ui/1-document/11-coordinates/article.md
@@ -36,7 +36,7 @@ Additionally, there are derived properties:
```online
For instance click this button to see its window coordinates:
-
+
```
+In the first example, the HTML attribute is used to initialize the `button.onclick`, while in the second example -- the script, that's all the difference.
+
**As there's only one `onclick` property, we can't assign more than one event handler.**
In the example below adding a handler with JavaScript overwrites the existing handler:
@@ -124,16 +124,6 @@ In the example below adding a handler with JavaScript overwrites the existing ha
```
-By the way, we can assign an existing function as a handler directly:
-
-```js
-function sayThanks() {
- alert('Thanks!');
-}
-
-elem.onclick = sayThanks;
-```
-
To remove a handler -- assign `elem.onclick = null`.
## Accessing the element: this
@@ -148,9 +138,19 @@ In the code below `button` shows its contents using `this.innerHTML`:
## Possible mistakes
-If you're starting to work with event -- please note some subtleties.
+If you're starting to work with events -- please note some subtleties.
+
+We can set an existing function as a handler:
+
+```js
+function sayThanks() {
+ alert('Thanks!');
+}
+
+elem.onclick = sayThanks;
+```
-**The function should be assigned as `sayThanks`, not `sayThanks()`.**
+But be careful: the function should be assigned as `sayThanks`, not `sayThanks()`.
```js
// right
@@ -160,7 +160,7 @@ button.onclick = sayThanks;
button.onclick = sayThanks();
```
-If we add parentheses, `sayThanks()` -- is a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work.
+If we add parentheses, then `sayThanks()` becomes a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work.
...On the other hand, in the markup we do need the parentheses:
@@ -168,21 +168,17 @@ If we add parentheses, `sayThanks()` -- is a function call. So the last line ac
```
-The difference is easy to explain. When the browser reads the attribute, it creates a handler function with *body from its content*: `sayThanks()`.
+The difference is easy to explain. When the browser reads the attribute, it creates a handler function with body from the attribute content.
So the markup generates this property:
```js
button.onclick = function() {
*!*
- sayThanks(); // the attribute content
+ sayThanks(); // <-- the attribute content goes here
*/!*
};
```
-**Use functions, not strings.**
-
-The assignment `elem.onclick = "alert(1)"` would work too. It works for compatibility reasons, but strongly not recommended.
-
**Don't use `setAttribute` for handlers.**
Such a call won't work:
@@ -199,9 +195,9 @@ Assign a handler to `elem.onclick`, not `elem.ONCLICK`, because DOM properties a
## addEventListener
-The fundamental problem of the aforementioned ways to assign handlers -- we can't assign multiple handlers to one event.
+The fundamental problem of the aforementioned ways to assign handlers is that we *can't assign multiple handlers to one event*.
-For instance, one part of our code wants to highlight a button on click, and another one wants to show a message.
+Let's say, one part of our code wants to highlight a button on click, and another one wants to show a message on the same click.
We'd like to assign two event handlers for that. But a new DOM property will overwrite the existing one:
@@ -211,12 +207,12 @@ input.onclick = function() { alert(1); }
input.onclick = function() { alert(2); } // replaces the previous handler
```
-Web-standard developers understood that long ago and suggested an alternative way of managing handlers using special methods `addEventListener` and `removeEventListener`. They are free of such a problem.
+Developers of web standards understood that long ago and suggested an alternative way of managing handlers using the special methods `addEventListener` and `removeEventListener` which aren't bound by such constraint.
The syntax to add a handler:
```js
-element.addEventListener(event, handler[, options]);
+element.addEventListener(event, handler, [options]);
```
`event`
@@ -229,19 +225,18 @@ element.addEventListener(event, handler[, options]);
: An additional optional object with properties:
- `once`: if `true`, then the listener is automatically removed after it triggers.
- `capture`: the phase where to handle the event, to be covered later in the chapter . For historical reasons, `options` can also be `false/true`, that's the same as `{capture: false/true}`.
- - `passive`: if `true`, then the handler will not `preventDefault()`, we'll cover that later in .
-
+ - `passive`: if `true`, then the handler will not call `preventDefault()`, we'll explain that later in .
To remove the handler, use `removeEventListener`:
```js
-element.removeEventListener(event, handler[, options]);
+element.removeEventListener(event, handler, [options]);
```
````warn header="Removal requires the same function"
To remove a handler we should pass exactly the same function as was assigned.
-That doesn't work:
+This doesn't work:
```js no-beautify
elem.addEventListener( "click" , () => alert('Thanks!'));
@@ -249,7 +244,7 @@ elem.addEventListener( "click" , () => alert('Thanks!'));
elem.removeEventListener( "click", () => alert('Thanks!'));
```
-The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter.
+The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter, as it's a different function object.
Here's the right way:
@@ -266,7 +261,7 @@ input.removeEventListener("click", handler);
Please note -- if we don't store the function in a variable, then we can't remove it. There's no way to "read back" handlers assigned by `addEventListener`.
````
-Multiple calls to `addEventListener` allow to add multiple handlers, like this:
+Multiple calls to `addEventListener` allow it to add multiple handlers, like this:
```html run no-beautify
@@ -291,47 +286,33 @@ Multiple calls to `addEventListener` allow to add multiple handlers, like this:
As we can see in the example above, we can set handlers *both* using a DOM-property and `addEventListener`. But generally we use only one of these ways.
````warn header="For some events, handlers only work with `addEventListener`"
-There exist events that can't be assigned via a DOM-property. Must use `addEventListener`.
-
-For instance, the event `transitionend` (CSS animation finished) is like that.
-
-Try the code below. In most browsers only the second handler works, not the first one.
+There exist events that can't be assigned via a DOM-property. Only with `addEventListener`.
-```html run
-
-
-
+For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and the DOM has been built.
-
+```js
+// this way it works
+document.addEventListener("DOMContentLoaded", function() {
+ alert("DOM built");
+});
```
+So `addEventListener` is more universal. Although, such events are an exception rather than the rule.
````
## Event object
-To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keypress", but what were the pointer coordinates? Which key was pressed? And so on.
+To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keydown", but what were the pointer coordinates? Which key was pressed? And so on.
When an event happens, the browser creates an *event object*, puts details into it and passes it as an argument to the handler.
-Here's an example of getting mouse coordinates from the event object:
+Here's an example of getting pointer coordinates from the event object:
```html run
@@ -353,12 +334,12 @@ Some properties of `event` object:
`event.currentTarget`
: Element that handled the event. That's exactly the same as `this`, unless the handler is an arrow function, or its `this` is bound to something else, then we can get the element from `event.currentTarget`.
-`event.clientX / event.clientY`
-: Window-relative coordinates of the cursor, for mouse events.
+`event.clientX` / `event.clientY`
+: Window-relative coordinates of the cursor, for pointer events.
-There are more properties. They depend on the event type, so we'll study them later when we come to different events in details.
+There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when as we move on to the details of different events.
-````smart header="The event object is also accessible from HTML"
+````smart header="The event object is also available in HTML handlers"
If we assign a handler in HTML, we can also use the `event` object, like this:
```html autorun height=60
@@ -380,17 +361,19 @@ For instance:
```
-As we can see, when `addEventListener` receives an object as the handler, it calls `object.handleEvent(event)` in case of an event.
+As we can see, when `addEventListener` receives an object as the handler, it calls `obj.handleEvent(event)` in case of an event.
-We could also use a class for that:
+We could also use objects of a custom class, like this:
```html run
@@ -412,6 +395,7 @@ We could also use a class for that:
*!*
let menu = new Menu();
+
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
*/!*
@@ -462,7 +446,7 @@ HTML attributes are used sparingly, because JavaScript in the middle of an HTML
DOM properties are ok to use, but we can't assign more than one handler of the particular event. In many cases that limitation is not pressing.
-The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transtionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event.
+The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transitionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event.
No matter how you assign the handler -- it gets an event object as the first argument. That object contains the details about what's happened.
diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md
index 53c31eb91..2448cfa5b 100644
--- a/2-ui/2-events/02-bubbling-and-capturing/article.md
+++ b/2-ui/2-events/02-bubbling-and-capturing/article.md
@@ -68,7 +68,7 @@ For instance, if we have a single handler `form.onclick`, then it can "catch" al
In `form.onclick` handler:
-- `this` (`=event.currentTarget`) is the `
+
+
+
+
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js
index 27ae27b94..6a3202467 100755
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js
@@ -18,7 +18,7 @@ table.onmouseover = function(event) {
// hooray! we entered a new
currentElem = target;
- target.style.background = 'pink';
+ onEnter(currentElem);
};
@@ -40,6 +40,23 @@ table.onmouseout = function(event) {
}
// we left the
. really.
- currentElem.style.background = '';
+ onLeave(currentElem);
currentElem = null;
};
+
+// any functions to handle entering/leaving an element
+function onEnter(elem) {
+ elem.style.background = 'pink';
+
+ // show that in textarea
+ text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
+ text.scrollTop = 1e6;
+}
+
+function onLeave(elem) {
+ elem.style.background = '';
+
+ // show that in textarea
+ text.value += `out <- ${elem.tagName}.${elem.className}\n`;
+ text.scrollTop = 1e6;
+}
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js
index 4700d686e..ae633ad67 100755
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js
@@ -1,13 +1,15 @@
table.onmouseover = function(event) {
let target = event.target;
target.style.background = 'pink';
- text.value += "mouseover " + target.tagName + "\n";
+
+ text.value += `over -> ${target.tagName}\n`;
text.scrollTop = text.scrollHeight;
};
table.onmouseout = function(event) {
let target = event.target;
target.style.background = '';
- text.value += "mouseout " + target.tagName + "\n";
+
+ text.value += `out <- ${target.tagName}\n`;
text.scrollTop = text.scrollHeight;
-};
\ No newline at end of file
+};
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 ff5041b95..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
@@ -1,8 +1,9 @@
+let parent = document.getElementById('parent');
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 721357b7a..4c928eef1 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,39 +1,36 @@
# 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.
+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.
-Here we'll see how to implement Drag'n'Drop using mouse events.
+So here we'll see how to implement Drag'n'Drop using mouse events.
## Drag'n'Drop algorithm
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 add 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);
+ document.body.append(ball);
// 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:
@@ -93,14 +93,14 @@ So we should listen on `document` to catch it.
## Correct positioning
-In the examples above the ball is always moved so, that it's center is under the pointer:
+In the examples above the ball is always moved so that its center is under the pointer:
```js
ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
```
-Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer.
+Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer.
It would be better if we keep the initial shift of the element relative to the pointer.
@@ -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';
```
@@ -219,7 +219,7 @@ That's why the initial idea to put handlers on potential droppables doesn't work
So, what to do?
-There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window).
+There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned.
We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this:
@@ -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..12fe63201 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 0cc341aa9..7bc87a0f0 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
@@ -52,7 +52,7 @@ let form = document.forms[0];
let ageElems = form.elements.age;
*!*
-alert(ageElems[0].value); // 10, the value of the first input name="age"
+alert(ageElems[0]); // [object HTMLInputElement]
*/!*
```
@@ -61,7 +61,7 @@ These navigation properties do not depend on the tag structure. All control elem
````smart header="Fieldsets as \"subforms\""
-A form may have one or many `