This commit is contained in:
Ilya Kantor 2017-03-23 11:06:57 +03:00
parent b6ed18e70d
commit c9401b3104
15 changed files with 231 additions and 233 deletions

View file

@ -1,10 +1,11 @@
# Hello, world!
The tutorial that you're reading is about the core JavaScript, that is platform-independant. So you'll be able to learn how to use Node.JS and other things based on that knowledge.
The tutorial that you're reading is about the core JavaScript, that is platform-independant. Further on you will be able learn Node.JS and other platforms that use it.
But we need a working environment to run our scripts, and, just because this book is online, the browser is probably a good choice. We'll use a few browser-specific commands like `alert`, but will keep their amount to the minimum.
But we need a working environment to run our scripts, and, just because this book is online, the browser a good choice. We'll keep the amount of browser-specific commands (like `alert`) to minimum, so that you don't spend time on them if you plan to concentrate on another environment like Node.JS. From the other hand, browser details are explained in detail in the [next part](/ui) of the tutorial. So
So first let's see how to attach a script to the webpage. For server-side environments you can just execute it with a command like `"node my.js"` for Node.JS.
So here we'll see how to attach a script to the webpage, that's simple enough. For server-side environments you can just execute it with a command like `"node my.js"` for Node.JS.
[cut]

View file

@ -38,7 +38,7 @@ alert( 'World' )
Here JavaScript interprets the line break as an "implicit" semicolon. That's also called an [automatic semicolon insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion).
**In most cases a newline implies a simicolon. But "in most cases" does not mean "always"!**
**In most cases a newline implies a semicolon. But "in most cases" does not mean "always"!**
There are cases when a newline does not mean a semicolon, for example:

View file

@ -1,6 +1,6 @@
# Date and time
Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date and provides methods for date/time management.
Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date, time and provides methods for date/time management.
For instance, we can use it to store creation/modification times, or to measure time, or just to print out the current date.
@ -33,10 +33,10 @@ To create a new `Date` object call `new Date()` with one of the following argume
The number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*.
It is a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp, there's a `date.getTime()` method (see below).
It's a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp using the `date.getTime()` method (see below).
`new Date(datestring)`
: If there is a single argument and it's a string, then it is parsed with the `Date.parse` algorithm (see below).
: If there is a single argument, and it's a string, then it is parsed with the `Date.parse` algorithm (see below).
```js run
@ -52,7 +52,7 @@ To create a new `Date` object call `new Date()` with one of the following argume
- The `year` must have 4 digits: `2013` is okay, `98` is not.
- The `month` count starts with `0` (Jan), up to `11` (Dec).
- The `date` parameter is actually the day of month, if absent then `1` is assumed.
- If `hours/minutes/seconds/ms` is absent, then it is equal `0`.
- If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`.
For instance:
@ -61,7 +61,7 @@ To create a new `Date` object call `new Date()` with one of the following argume
new Date(2011, 0, 1); // the same, hours etc are 0 by default
```
The precision is 1 ms (1/1000 sec):
The minimal precision is 1 ms (1/1000 sec):
```js run
let date = new Date(2011, 0, 1, 2, 3, 4, 567);
@ -70,7 +70,7 @@ To create a new `Date` object call `new Date()` with one of the following argume
## Access date components
The are many methods to access the year, month etc from the `Date` object. But they can be easily remembered when categorized.
The are many methods to access the year, month and so on from the `Date` object. But they can be easily remembered when categorized.
`getFullYear()`
: Get the year (4 digits)
@ -85,7 +85,7 @@ The are many methods to access the year, month etc from the `Date` object. But t
: Get the corresponding time components.
```warn header="Not `getYear()`, but `getFullYear()`"
Many JavaScript engines implement a non-standard method `getYear()`. This method is non-standard. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for that.
Many JavaScript engines implement a non-standard method `getYear()`. This method is deprecated. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for the year.
```
Additionally, we can get a day of week:
@ -95,7 +95,7 @@ Additionally, we can get a day of week:
**All the methods above return the components relative to the local time zone.**
There are also their UTC-counterparts, that return day, month, year etc for the time zone UTC+0: `getUTCFullYear()`, `getUTCMonth()`, `getUTCDay()`. Just insert the `"UTC"` right after `"get"`.
There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0: `getUTCFullYear()`, `getUTCMonth()`, `getUTCDay()`. Just insert the `"UTC"` right after `"get"`.
If your local time zone is shifted relative to UTC, then the code below shows different hours:
@ -106,8 +106,7 @@ let date = new Date();
// the hour in your current time zone
alert( date.getHours() );
// what time is it now in London winter time (UTC+0)?
// the hour in UTC+0 time zone
// the hour in UTC+0 time zone (London time without daylight savings)
alert( date.getUTCHours() );
```
@ -120,7 +119,10 @@ Besides the given methods, there are two special ones, that do not have a UTC-va
: Returns the difference between the local time zene and UTC, in minutes:
```js run
alert( new Date().getTimezoneOffset() ); // For UTC-1 outputs 60
// if you are in timezone UTC-1, outputs 60
// if you are in timezone UTC+3, outputs -180
alert( new Date().getTimezoneOffset() );
```
## Setting date components
@ -138,7 +140,7 @@ The following methods allow to set date/time components:
Every one of them except `setTime()` has a UTC-variant, for instance: `setUTCHours()`.
As we can see, some methods can set multiple components at once, for example `setHours`. Those components that are not mentioned -- are not modified.
As we can see, some methods can set multiple components at once, for example `setHours`. The components that are not mentioned are not modified.
For instance:
@ -146,10 +148,10 @@ For instance:
let today = new Date();
today.setHours(0);
alert( today ); // today, but the hour is changed to 0
alert(today); // still today, but the hour is changed to 0
today.setHours(0, 0, 0, 0);
alert( today ); // today, 00:00:00 sharp.
alert(today); // still today, now 00:00:00 sharp.
```
## Autocorrection
@ -163,7 +165,7 @@ let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!?
alert(date); // ...is 1st Feb 2013!
```
**Out-of-range date components are distributed automatically.**
Out-of-range date components are distributed automatically.
Let's say we need to increase the date "28 Feb 2016" by 2 days. It may be "2 Mar" or "1 Mar" in case of a leap-year. We don't need to think about it. Just add 2 days. The `Date` object will do the rest:
@ -185,7 +187,7 @@ date.setSeconds(date.getSeconds() + 70);
alert( date ); // shows the correct date
```
We can also set zero or even negative componens. For example:
We can also set zero or even negative values. For example:
```js run
let date = new Date(2016, 0, 2); // 2 Jan 2016
@ -199,16 +201,16 @@ alert( date ); // 31 Dec 2015
## Date to number, date diff
When a `Date` object is converted to number, it becomes its number of milliseconds:
When a `Date` object is converted to number, it becomes the timestamp same as `date.getTime()`:
```js run
let date = new Date();
alert( +date ); // will the milliseconds, same as date.getTime()
alert(+date); // the number of milliseconds, same as date.getTime()
```
**The important side effect: dates can be substracted, the result is their difference in ms.**
The important side effect: dates can be substracted, the result is their difference in ms.
That's some times used for time measurements:
That can be used for time measurements:
```js run
let start = new Date(); // start counting
@ -229,7 +231,7 @@ If we only want to measure the difference, we don't need `Date` object.
There's a special method `Date.now()` that returns the current timestamp.
It is semantically equivalent to `new Date().getTime()`, but it does not create an intermediate `Date` object. So it's faster and does not put pressure on garbage collection.
It is semantically equivalent to `new Date().getTime()`, but it doesn't create an intermediate `Date` object. So it's faster and doesn't put pressure on garbage collection.
It is used mostly for convenience or when performance matters, like in games in JavaScript or other specialized applications.
@ -256,21 +258,25 @@ alert( `The loop took ${end - start} ms` ); // substract numbers, not dates
If we want a reliable benchmark of CPU-hungry function, we should be careful.
For instance, let's measure two function that get a delta between two dates, which one is faster?
For instance, let's measure two functions that calculate the difference between two dates: which one is faster?
```js
// we have date1 and date2, which function faster returns their difference in ms?
function diffSubstract(date1, date2) {
return date2 - date1;
}
// or
function diffGetTime(date1, date2) {
return date2.getTime() - date1.getTime();
}
```
These two do exactly the same, because when dates are substracted, they are coerced into numbers of milliseconds, exactly the same thing as `date.getTime()` returns.
These two do exactly the same thing, but one of them uses an explicit `date.getTime()` to get the date in ms, and the other one relies on a date-to-number transform. Their result is always the same.
The first idea is to run them many times in a row and measure the difference. For our case, functions are very simple, so we have to do it around 100000 times.
So, which one is faster?
The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it around 100000 times.
Let's measure:
@ -300,11 +306,11 @@ Wow! Using `getTime()` is so much faster! That's because there's no type convers
Okay, we have something. But that's not a good benchmark yet.
Imagine that in the time of running `bench(diffSubstract)` CPU was doing something in parallel, and it was taking resources. And at the time of the second benchmark the work was finished.
Imagine that at the time of running `bench(diffSubstract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` the work has finished.
Is that real? Of course it is, especially for the modern multi-process OS.
A pretty real scenario for a modern multi-process OS.
As a result, the first benchmark will have less CPU resources than the second. That's a reason for wrong results.
As a result, the first benchmark will have less CPU resources than the second. That may lead to wrong results.
**For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.**
@ -346,7 +352,7 @@ alert( 'Total time for diffGetTime: ' + time2 );
Modern JavaScript engines start applying advanced optimizations only to "hot code" that executes many times (no need to optimize rarely executed things). So, in the example above, first executions are not well-optimized. We may want to add a heat-up run:
```js
// heat up
// added for "heating up" prior to the main loop
bench(diffSubstract);
bench(diffGetTime);
@ -357,24 +363,22 @@ for (let i = 0; i < 10; i++) {
}
```
```warn header="Be careful doing micro-benchmarking"
Modern JavaScript engines perform many optimizations. Some of them may tweak artificial tests results compared to normal usage.
So if you seriously want to understand performance, then please first study how the JavaScript engine works. And then you probably won't need microbenchmarks at all, or apply them rarely.
```warn header="Be careful doing microbenchmarking"
Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all.
The great pack of articles about V8 can be found at <http://mrale.ph>.
```
## Date.parse from a string
The method [Date.parse](mdn:js/Date/parse) can read the date from a string.
The method [Date.parse(str)](mdn:js/Date/parse) can read a date from a string.
The format is: `YYYY-MM-DDTHH:mm:ss.sssZ`, where:
The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where:
- `YYYY-MM-DD` -- is the date: year-month-day.
- The ordinary character `T` is used as the delimiter.
- The character `"T"` is used as the delimiter.
- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds.
- The optional `'Z'` part denotes the time zone in the format `+-hh:mm` or a single `Z` that would mean UTC+0.
- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` that would mean UTC+0.
Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`.
@ -385,7 +389,7 @@ For instance:
```js run
let ms = Date.parse('2012-01-26T13:51:50.417-07:00');
alert( ms ); // 1327611110417 (timestam)
alert(ms); // 1327611110417 (timestamp)
```
We can instantly create a `new Date` object from the timestamp:
@ -396,26 +400,24 @@ let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
alert(date);
```
## Summary
- Date and time in JavaScript are represented with the [Date](mdn:js/Date) object. We can't create "only date" or "only time".
- Date and time in JavaScript are represented with the [Date](mdn:js/Date) object. We can't create "only date" or "only time": `Date` objects always carry both.
- Months are counted from the zero (yes, January is a zero month).
- Days of week (for `getDay()`) are also counted from the zero (Sunday).
- Days of week in `getDay()` are also counted from the zero (that's Sunday).
- `Date` auto-corrects itself when out-of-range components are set. Good for adding/substracting days/months/hours.
- Dates can be substructed, giving their difference in milliseconds. That's because a `Date` becomes the timestamp if converted to a number.
- Use `Date.now()` to get the current timestamp fast.
Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds.
Also, sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it.
For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading, but adds 3 digits after the point to it. So totally it becomes microsecond percision:
Also, sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point):
```js run
alert(`Loading started ${performance.now()}ms ago`);
// Something like: "Loading started 34731.26000000001ms ago"
// it may show more than 3 digits after the decimal point, but only 3 first are correct
// .26 is microseconds (260 microseconds)
// more than 3 digits after the decimal point are precision errors, but only 3 first are correct
```
Node.JS has `microtime` module and other ways. Technically, any device and environment allows to get that, it's just not in `Date`.
Node.JS has `microtime` module and other ways. Technically, any device and environment allows to get more precision, it's just not in `Date`.

View file

@ -4,8 +4,8 @@ We may decide to execute a function not right now, but at a certain time later.
There are two methods for it:
- `setTimeout` allows to run a function once after the given interval of time.
- `setInterval` allows to run a function regularly with the given interval between the runs.
- `setTimeout` allows to run a function once after the interval of time.
- `setInterval` allows to run a function regularly with the interval between the runs.
These methods are not a part of JavaScript specification. But most environments have the internal scheduler and provide these methods. In particular, they are supported in all browsers and Node.JS.
@ -168,6 +168,7 @@ let timerId = setTimeout(function request() {
...send request...
if (request failed due to server overload) {
// increase the interval to the next run
delay *= 2;
}
@ -179,7 +180,7 @@ let timerId = setTimeout(function request() {
And if we regulary have CPU-hungry tasks, then we can measure the time taken by the execition and plan the next call sooner or later.
**Recursive `setTimeout` guarantees a delay before the executions, `setInterval` -- does not.**
**Recursive `setTimeout` guarantees a delay between the executions, `setInterval` -- does not.**
Let's compare two code fragments. The first one uses `setInterval`:

View file

@ -4,9 +4,9 @@ importance: 5
# Working with prototype
Here's the code that creates a pair of object, then alters them.
Here's the code that creates a pair of objects, then modifies them.
Which values will be shown in the process?
Which values are shown in the process?
```js
let animal = {

View file

@ -4,7 +4,9 @@ importance: 5
# Searching algorithm
We have object:
The task has two parts.
We have an object:
```js
let head = {
@ -25,9 +27,5 @@ let pockets = {
};
```
The task has two parts:
1. Use `__proto__` to assign prototypes in a way that any property lookup will follow the path: `pockets -> bed -> table -> head`.
For instance, `pockets.pen` should be `3` (found in `table`), and `bed.glasses` should be `1` (found in `head`).
1. Use `__proto__` to assign prototypes in a way that any property lookup will follow the path: `pockets` -> `bed` -> `table` -> `head`. For instance, `pockets.pen` should be `3` (found in `table`), and `bed.glasses` should be `1` (found in `head`).
2. Answer the question: is it faster to get `glasses` as `pocket.glasses` or `head.glasses`? Benchmark if needed.

View file

@ -1,5 +1,6 @@
**The answer: `rabbit`.**
That's because `this` is an object before the dot, so `rabbit.eat()` naturally means `rabbit`.
That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`.
Property lookup and execution are two successive things. The method is found in the prototype, but then is run in the context of `rabbit`.
Property lookup and execution are two different things.
The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`

View file

@ -6,7 +6,7 @@ importance: 5
We have `rabbit` inheriting from `animal`.
If we call `rabbit.eat()`, which object receives `full`: `animal` or `rabbit`?
If we call `rabbit.eat()`, which object receives the `full` property: `animal` or `rabbit`?
```js
let animal = {
@ -21,4 +21,3 @@ let rabbit = {
rabbit.eat();
```

View file

@ -1,6 +1,6 @@
Let's look carefully at what's going on in the call `speedy.eat("apple")`.
1. The method `speedy.eat` is found in the prototype (`hamster`), but called in the context of `speedy`. So the value of `this` is correct.
1. The method `speedy.eat` is found in the prototype (`=hamster`), then executed with `this=speedy` (the object before the dot).
2. Then `this.stomach.push()` needs to find `stomach` property and call `push` on it. It looks for `stomach` in `this` (`=speedy`), but nothing found.
@ -8,9 +8,11 @@ Let's look carefully at what's going on in the call `speedy.eat("apple")`.
4. Then it calls `push` on it, adding the food into *the stomach of the prototype*.
It turns out that all hamsters share a single stomach!
So all hamsters share a single stomach!
Please note that it would not happen in case of a simple assignment `this.stomach=`:
Every time the `stomach` is taken from the prototype, then `stomach.push` modifies it "at place".
Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`:
```js run
let hamster = {
@ -24,9 +26,13 @@ let hamster = {
}
};
let speedy = { __proto__: hamster };
let speedy = {
__proto__: hamster
};
let lazy = { __proto__: hamster };
let lazy = {
__proto__: hamster
};
// Speedy one found the food
speedy.eat("apple");
@ -36,9 +42,9 @@ alert( speedy.stomach ); // apple
alert( lazy.stomach ); // <nothing>
```
Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object. And for a method call `this.stomach.push`, the object is to be found first (in the prototype), then called, that's the difference.
Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object.
But more often, we can totally evade the problem by making sure that each hamster has his own stomach, explicitly:
Also we can totally evade the problem by making sure that each hamster has his own stomach:
```js run
let hamster = {
@ -71,5 +77,4 @@ alert( speedy.stomach ); // apple
alert( lazy.stomach ); // <nothing>
```
As a common solution, all object properties, like `stomach` above, are usually written into each object. That prevents such problems. From the other hand, methods and primives can safely stay in prototypes.
As a common solution, all properties that describe the state of a particular object, like `stomach` above, are usually written into that object. That prevents such problems.

View file

@ -6,7 +6,6 @@ For instance, we have a `user` object with its properties and methods, and want
*Prototypal inheritance* is a language feature that helps in that.
[cut]
## [[Prototype]]
@ -15,7 +14,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named
![prototype](object-prototype-empty.png)
That `[[Prototype]]` has a "magical" meaning. When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called a "prototypal inheritance". Many cool language features and approaches are based on it.
That `[[Prototype]]` has a "magical" meaning. When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it.
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
@ -34,9 +33,9 @@ rabbit.__proto__ = animal;
*/!*
```
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. We'll talk about other ways of setting it later, as for now `__proto__` will do just fine.
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. We'll talk about other ways of setting it later, but for now `__proto__` will do just fine.
So now if we look for something in `rabbit` and it's missing, JavaScript automatically takes it from `animal`.
If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`.
For instance:
@ -54,14 +53,14 @@ rabbit.__proto__ = animal; // (*)
// we can find both properties in rabbit now:
*!*
alert( rabbit.eats ); // true
alert( rabbit.eats ); // true (**)
*/!*
alert( rabbit.jumps ); // true
```
Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
Then, when `alert` tries to read property `rabbit.eats`, it can find it `rabbit`, so it follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
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):
![](proto-animal-rabbit.png)
@ -126,21 +125,25 @@ alert(longEar.jumps); // true (from rabbit)
There are actually only two limitations:
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in circle.
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
2. The value of `__proto__` can be either an object or `null`. All other values (like primitives) are ignored.
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
## Read/write rules
The prototype is only used for reading properties, write/delete for data properties works directly with the object.
The prototype is only used for reading properties.
For data properties (not getters/setters) write/delete operations work directly with the object.
In the example below, we assign its own `walk` method to `rabbit`:
```js run
let animal = {
eats: true,
walk() { /* unused by rabbit, because (see below) it has its own */ }
walk() {
/* this method won't be used by rabbit */
}
};
let rabbit = {
@ -160,18 +163,22 @@ Since now, `rabbit.walk()` call finds the method immediately in the object and e
![](proto-animal-rabbit-walk-2.png)
The assignment handling is different for accessor properties, because these properties behave more like functions. For instance, check out `admin.fullName` property in the code below:
For getters/setters -- if we read/write a property, they are looked up in the prototype and invoked.
For instance, check out `admin.fullName` property in the code below:
```js run
let user = {
name: "John",
surname: "Smith",
*!*
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
*/!*
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
@ -179,21 +186,13 @@ let admin = {
isAdmin: true
};
// setter triggers!
*!*
admin.fullName = "Alice Cooper";
*/!*
alert(admin.fullName); // John Smith (*)
alert(admin.name); // Alice
alert(admin.surname); // Cooper
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
```
Here in the line `(*)` the property `admin.fullName` has a setter in the prototype `user`. So it is not written into `admin`. Instead, the inherited setter is called.
So, the general rule would be:
1. For accessor properties use a setter (from the prototype chain).
2. Otherwise assign directly to the object.
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 is has a setter in the prototype, so it is called.
## The value of "this"
@ -201,7 +200,7 @@ An interesting question may arise in the example above: what's the value of `thi
The answer is simple: `this` is not affected by prototypes at all.
**No matter where a method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.**
**No matter where the method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.**
So, the setter actually uses `admin` as `this`, not `user`.
@ -240,14 +239,14 @@ The resulting picture:
![](proto-animal-rabbit-walk-3.png)
If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to its methods. But `this` in each method would be the corresponding object, not `animal`.
If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects.
In other words, methods are shared, but the state will be not.
As a result, methods are shared, but the object state is not.
## Summary
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
- We can use `obj.__proto__` to access it (there are other ways too, to be covered soon).
- The object references by `[[Prototype]]` is called a "prototype".
- The object referenced by `[[Prototype]]` is called a "prototype".
- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. Write/delete operations work directly on the object, they don't use the prototype (unless the property is actually a setter).
- If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current objects even if they are inherited.
- 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.

View file

@ -8,7 +8,7 @@ A character class is a special notation that matches any symbol from the set.
For instance, there's a "digit" class. It's written as `\d`. We put it in the pattern, and during the search any digit matches it.
That is: a regexp `pattern:/\d/` looks for a digit:
For instance, the regexp `pattern:/\d/` looks for a single digit:
```js run
let str = "+7(903)-123-45-67";

View file

@ -61,12 +61,12 @@ A handler on a parent element can always get the details about where it actually
Note the differences from `this` (=`event.currentTarget`):
- `event.target` -- is the "source" element that initiated the event, it doesn't change through the bubbling process.
- `event.target` -- is the "target" element that initiated the event, it doesn't change through the bubbling process.
- `this` -- is the "current" element, the one that has a currently running handler on it.
For instance, if we have a single handler `form.onclick`, then it can "catch" all clicks inside the form. No matter where the click happened, it bubbles up to `<form>` and runs the handler.
In that handler:
In `form.onclick` handler:
- `this` (`=event.currentTarget`) is the `<form>` element, because the handler runs on it.
- `event.target` is the concrete element inside the form that actually was clicked.
@ -75,11 +75,11 @@ Check it out:
[codetabs height=220 src="bubble-target"]
It's surely possible that `event.target` equals `this` -- when the click was directly on `<form>`.
It's possible that `event.target` equals `this` -- when the click is made directly on the `<form>` element.
## Stopping bubbling
A bubbling event goes from the source element straight up. Normally it goes till `<html>`, and then to `document` object, and some events even reach `window`, calling all handlers on its path.
A bubbling event goes from the target element straight up. Normally it goes upwards till `<html>`, and then to `document` object, and some events even reach `window`, calling all handlers on the path.
But any handler may decide that the event has been fully processed and stop the bubbling.
@ -96,9 +96,9 @@ For instance, here `body.onclick` doesn't work if you click on `<button>`:
```smart header="event.stopImmediatePropagation()"
If an element has multiple event handlers on a single event, then even if one of them stops the bubbling, the other ones still execute.
In other words, `event.stopPropagation()` stops the move upwards, but on the current element all handlers will run.
In other words, `event.stopPropagation()` stops the move upwards, but on the current element all other handlers will run.
To completely stop all bubbling and prevent handlers on the current element from running, there's a method `event.stopImmediatePropagation()`. After its call no other handlers execute.
To stop the bubbling and prevent handlers on the current element from running, there's a method `event.stopImmediatePropagation()`. After it no other handlers execute.
```
```warn header="Don't stop bubbling without a need!"
@ -108,55 +108,60 @@ Sometimes `event.stopPropagation()` creates hidden pitfalls that later may becom
For instance:
1. We create a nested menu. Each submenu handles clicks on its elements and calls `stopPropagation` so that outer parts don't trigger. Everything works.
2. Later we decide to catch clicks inside the whole window, to track users' behavior (where people click). Some counters do that. Usually a counter code does that by `document.addEventListener('click', ...)`.
3. Our statistics won't work over the area where clicks are stopped by `stopPropagation`. We've got a "dead zone".
1. We create a nested menu. Each submenu handles clicks on its elements and calls `stopPropagation` so that outer parts don't trigger.
2. Later we decide to catch clicks inside the whole window, to track users' behavior (where people click). Some counters do that. Usually a counter code does that by `document.addEventListener('click')`.
3. Our counter won't work over the area where clicks are stopped by `stopPropagation`. We've got a "dead zone".
There are usually many ways to do things and keep bubbling. One of them is to use custom events, we'll cover them later. Also we can write our data into the `event` object in one handler and read it in another one.
There's usually no real need to prevent the bubbling. One of them is to use custom events, we'll cover them later. Also we can write our data into the `event` object in one handler and read it in another one, so we can pass to handlers on parents information about the processing below.
```
## Capturing
There's another "stage" of event processing called "capturing". It is rarely used in the code, but sometimes can be useful.
There's another phase of event processing called "capturing". It is rarely used in real code, but sometimes can be useful.
The standard [DOM Events 3](http://www.w3.org/TR/DOM-Level-3-Events/) describes 3 stages of event processing:
The standard [DOM Events](http://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation:
1. Capturing stage -- an event goes down to the element.
2. Target stage -- an event reached the source element.
3. Bubbling stage -- the event bubbles up from the element.
1. Capturing phase -- the event goes down to the element.
2. Target phase -- the event reached the target element.
3. Bubbling phase -- the event bubbles up from the element.
Here's the picture of a click on `<td>` inside a table:
Here's the picture of a click on `<td>` inside a table, taken from the specification:
![](eventflow.png)
That is: for a click on `<td>` the event first goes through the ancestors chain down to the element (capturing), then it reaches the target, and then it goes up (bubbles), calling handlers on its way.
**Before we only talked about bubbling, because the capturing stage is rarely used. Normally it is invisible to us.**
**Before we only talked about bubbling, because the capturing phase is rarely used. Normally it is invisible to us.**
Handlers added using `on...`-property or using HTML attributes don't know anything about capturing, they only run on the 2nd and 3rd stages.
Handlers added using `on<event>`-property or using HTML attributes or using `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases.
To catch an event on the capturing stage, we need to use the 3rd argument of `addEventListener`:
To catch an event on the capturing phase, we need to set the 3rd argument of `addEventListener` to `true`.
- If it's `true`, then the handler is set on the capturing stage.
- If it's `false` (default), then on the bubbling stage.
Actually, there are two possible values for that optional last argument:
Note that while formally there are 3 stages, the 2nd stage (reached the element) is not handled separately: handlers on both capturing and bubbling stages do that.
- If it's `false` (default), then the handler is set on the bubbling phrase.
- If it's `true`, then the handler is set on the capturing phrase.
Note that while formally there are 3 phrases, the 2nd phrase ("target phase": the event reached the element) is not handled separately: handlers on both capturing and bubbling phrases trigger at that phase.
Handlers on the target element trigger last on the capturing state, and then trigger first on the bubbling stage.
Let's see it in action:
```html run autorun height=100 edit
<table border="1">
<tr>
<td>Shady Grove</td>
<td>Aeolian</td>
</tr>
<tr>
<td>Over the River, Charlie</td>
<td>Dorian</td>
</tr>
</table>
```html run autorun height=140 edit
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
<script>
for(let elem of document.querySelectorAll('*')) {
@ -168,19 +173,22 @@ Let's see it in action:
The code sets click handlers on *every* element in the document to see which ones are working.
If you click on `<td>`, then the sequence is: `HTML` -> `BODY` -> `TABLE` -> `TBODY` -> `TR` -> `TD` (capturing stage, the first listener), and then `TD` -> `TR` -> `TBODY` -> `TABLE` -> `BODY` -> `HTML` (bubbling stage, the second listener).
If you click on `<td>`, then the sequence is:
Please note that `TD` shows up two times: at the end of capturing and at the start of bubbling.
1. `HTML` -> `BODY` -> `FORM` -> `DIV` -> `P` (capturing phrase, the first listener), and then:
2. `P` -> `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phrase, the second listener).
There's a property `event.eventPhrase` that tells us the number of the stage on which the event was caught. But it's rarely used, because we usually know it in the handler.
Please note that `P` shows up two times: at the end of capturing and at the start of bubbling.
There's a property `event.eventPhrase` that tells us the number of the phrase on which the event was caught. But it's rarely used, because we usually know it in the handler.
## Summary
The event handling process:
- When an event happens -- the most nested element where it happens gets labeled as the "source element" (`event.target`).
- When an event happens -- the most nested element where it happens gets labeled as the "target element" (`event.target`).
- Then the event first moves from the document root down the `event.target`, calling handlers assigned with `addEventListener(...., true)` on the way.
- Then the event moves from `event.target` up to the root, calling handlers assigned using `on*` and `addEventListener(...., false)`.
- Then the event moves from `event.target` up to the root, calling handlers assigned using `on<event>` and `addEventListener` without the 3rd argument or with the 3rd argument `false`.
Each handler can access `event` object properties:
@ -188,11 +196,11 @@ Each handler can access `event` object properties:
- `event.currentTarget` (=`this`) -- the current element the handles the event (the one that has the handler on it)
- `event.eventPhase` -- the current phase (capturing=1, bubbling=3).
Any event handler can stop the event by calling `event.stopPropagation()`, but that's not recommended, because we can't really be sure we won't need it, maybe for completely different things.
Any event handler can stop the event by calling `event.stopPropagation()`, but that's not recommended, because we can't really be sure we won't need it above, maybe for completely different things.
The capturing stage is used very rarely, usually we handle events on bubbling. And there's a logic behind that.
The capturing phrase is used very rarely, usually we handle events on bubbling. And there's a logic behind that.
In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then (if needed) higher-level powers, and so on.
In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed.
The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `<td>` may be suited for that exactly `<td>`, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last.

View file

@ -1,6 +1,6 @@
# Mouse events basics
In this chapter we'll get into more details of mouse events and their properties.
In this chapter we'll get into more details about mouse events and their properties.
[cut]
@ -16,12 +16,12 @@ The most used simple events are:
: Mouse button is clicked/released over an element.
`mouseover/mouseout`
: Mouse pointer comes over/out an element.
: Mouse pointer comes over/out from an element.
`mousemove`
: Every mouse move over an element triggers that event.
There are several other event types too, we'll cover them later.
...There are several other event types too, we'll cover them later.
### Complex events
@ -36,7 +36,7 @@ There are several other event types too, we'll cover them later.
Complex events are made of simple ones, so in theory we could live without them. But they exist, and that's good, because they are convenient.
For touchscreen devices mouse events also happen, because they are emulated.
For touchscreen and touchpad devices mouse events also happen, they are emulated.
### Events order
@ -44,25 +44,25 @@ An action may trigger multiple events.
For instance, a click first triggers `mousedown`, when the button is pressed, then `mouseup` and `click` when it's released.
In cases when a single action initiated multiple events, their order is fixed. That is, the handlers are be called in the order `mousedown` -> `mouseup` -> `click`. Events are handled in sequence: `onmouseup` finishes before `onclick` runs.
In cases when a single action initiates multiple events, their order is fixed. That is, the handlers are called in the order `mousedown` -> `mouseup` -> `click`. Events are handled in the same sequence: `onmouseup` finishes before `onclick` runs.
```online
Click the button below and you'll see which events happen. Try double-click too.
Click the button below and you'll see the events. Try double-click too.
On the teststand below all mouse events are logged, and if there are more than 1 second delay between them, then they are separated by a horizontal ruler.
Also we can see the `which` property that allows to detect the mouse button. We'll cover it a bit later.
Also we can see the `which` property that allows to detect the mouse button.
<input onmousedown="return logMouse(event)" onmouseup="return logMouse(event)" onclick="return logMouse(event)" oncontextmenu="return logMouse(event)" ondblclick="return logMouse(event)" value="Click me with the right or the left mouse button" type="button"> <input onclick="logClear('test')" value="Clear" type="button"> <form id="testform" name="testform"> <textarea style="font-size:12px;height:150px;width:360px;"></textarea></form>
```
## Getting the button: which
Click-related events always have the `which` property that allows to see the button.
Click-related events always have the `which` property that allows to get the button.
It is not used for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click.
But if we track `mousedown` and `mouseup`, then we need it, because they trigger on any button.
But if we track `mousedown` and `mouseup`, then we need it, because these events trigger on any button, so `which` allows to distinguish between "right-mousedown" and "left-mousedown".
There are the three possible values:
@ -70,7 +70,7 @@ There are the three possible values:
- `event.which == 2` - the middle button
- `event.which == 3` - the right button
The middle button is somewhat exotic right now.
The middle button is somewhat exotic right now and is very rarely used.
## Modifiers: shift, alt, ctrl and meta
@ -81,7 +81,7 @@ The properties are:
- `shiftKey`
- `altKey`
- `ctrlKey`
- `metaKey` (for Mac)
- `metaKey` (`key:Cmd` for Mac)
For instance, the button below only works on `key:Alt+Shift`+click:
@ -100,13 +100,13 @@ For instance, the button below only works on `key:Alt+Shift`+click:
```
```warn header="Attention: on Mac it's usually `Cmd` instead of `Ctrl`"
On Windows and Linux there are modifier keys `key:Alt`, `key:Shift` and `key:Ctrl`. On Mac there's one more: `key:Cmd`, that corresponds to the property `metaKey`.
On Windows and Linux there are modifier keys `key:Alt`, `key:Shift` and `key:Ctrl`. On Mac there's one more: `key:Cmd`, it corresponds to the property `metaKey`.
In most cases when Windows/Linux uses `key:Ctrl`, on Mac people use `key:Cmd`. So where a Windows user presses `key:Ctrl+Enter` or `key:Ctrl+A`, a Mac user would press `key:Cmd+Enter` or `key:Cmd+A`, and so on, most apps use `key:Cmd` instead of `key:Ctrl`.
So if we want to support combinations like `key:Ctrl`+click, then for Mac it makes sense to use `key:Cmd`+click. That's more comfortable for Mac users.
Even if we'd like to force Mac users to `key:Ctrl`+click -- that's kind of difficult. The problem is: a regular click with `key:Ctrl` is interpreted as a *right click* on Mac, and it generates the `contextmenu` event, not `click` like Windows/Linux.
Even if we'd like to force Mac users to `key:Ctrl`+click -- that's kind of difficult. The problem is: a left-click with `key:Ctrl` is interpreted as a *right-click* on Mac, and it generates the `contextmenu` event, not `click` like Windows/Linux.
So if we want users of all operational systems to feel comfortable, then together with `ctrlKey` we should use `metaKey`.
@ -114,7 +114,8 @@ For JS-code it means that we should check `if (event.ctrlKey || event.metaKey)`.
```
```warn header="There are also mobile devices"
Keyboard combinations are good as an addition to the workflow. So that if you have keyboard -- it works. And if your device doesn't have it -- then there's another way to do the same.
Keyboard combinations are good as an addition to the workflow. So that if the visitor has a
keyboard -- it works. And if your device doesn't have it -- then there's another way to do the same.
```
## Coordinates: clientX/Y, pageX/Y
@ -124,37 +125,24 @@ All mouse events have coordinates in two flavours:
1. Window-relative: `clientX` and `clientY`.
2. Document-relative: `pageX` and `pageY`.
See more about coordinates the chapter <info:coordinates>.
For instance, if we have a window of the size 500x500, and the mouse is in the center, then `clientX` and `clientY` are `250`.
If we scroll the page, but the mouse is still in the center, then `clientX/Y` don't change, because they are window-relative.
For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`. And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is. They are similar to `position:fixed`.
````online
Move the mouse over the input field to see `clientX/clientY`:
Move the mouse over the input field to see `clientX/clientY` (it's in the `iframe`, so coordinates are relative to that `iframe`):
```html autorun height=50
<input onmousemove="this.value = event.clientX+':'+event.clientY">
<input onmousemove="this.value=event.clientX+':'+event.clientY" value="Mouse over me">
```
````
That's like `elem.getBoundingClientRect()` and `position:fixed`.
Document-relative coordinates are counted from the left-upper corner of the document, not the window.
Coordinates `pageX`, `pageY` are similar to `position:absolute` on the document level.
Document-relative coordinates are counted from the left-upper corner of the document, not the window. In case of a scrolled page, they also include the scrolled out left-upper part.
These coordinates are connected by the formulas:
```js
// for an arbitrary mouse event
event.pageX = pageXOffset + event.clientX
event.pageY = pageYOffset + event.clientY
```
So technically we don't need `pageX/Y`, because we can always calculate them using the formulas. But it's good that we have them, as a matter of convenience.
You can read more about coordinates in the chapter <info:coordinates>.
## No selection on mousedown
Mouse clicks have a side-effect that may be disturbing. A double click or an occasional cursor move with a pressed button select the text.
Mouse clicks have a side-effect that may be disturbing. A double click selects the text.
If we want to handle click events ourselves, then the "extra" selection doesn't look good.
@ -166,7 +154,7 @@ For instance, a double-click on the text below selects it in addition to our han
There's a CSS way to stop the selection: the `user-select` property from [CSS UI Draft](https://www.w3.org/TR/css-ui-4/).
It's yet in the draft, so browser support it with prefixes:
Most browsers support it with prefixes:
```html autorun height=50
<style>
@ -185,15 +173,15 @@ Before...
...After
```
Now if you double-click on `"Unselectable"`, it doesn't get selected. Seems to work.
Now if you double-click on "Unselectable", it doesn't get selected. Seems to work.
...But there is a side-effect! The text became truly unselectable. Even if a user starts the selection from `"Before"` and ends with `"After"`, the selection skips `"Unselectable"` part. Do we really want to make our text unselectable?
...But there is a potential problem! The text became truly unselectable. Even if a user starts the selection from "Before" and ends with "After", the selection skips "Unselectable" part. Do we really want to make our text unselectable?
Most of time, not really. A user may want to select it, for copying or other needs. That may be disturbing if we don't allow him to do it. So the solution is not that good.
Most of time, we don't. A user may have valid reasons to select the text, for copying or other needs. That may be disturbing if we don't allow him to do it. So the solution is not that good.
What we want is to "fix" our interface. We don't want the selection to occur on double-click, that's it.
What we want is to prevent the selection on double-click, that's it.
An alternative solution would be to handle `mousedown`, like this:
A text selection is the default browser action on `mousedown` event. So the alternative solution would be to handle `mousedown` and prevent it, like this:
```html autorun height=50
Before...
@ -203,9 +191,9 @@ Before...
...After
```
The selection is started on `mousedown` as a default browser action. So if we prevent it, then the bold element is not selected any more on clicks. That's as intended.
Now the bold element is not selected on double clicks.
From the other hand, the text inside it is still selectable. The only limitation: the selection should start not on the text itself, but from "before" or "after" it. Usually that's not a problem.
From the other hand, the text inside it is still selectable. The selection should start not on the text itself, but before or after it. Usually that's fine.
````smart header="Canceling the selection"
Instead of *preventing* the selection, we can cancel it "post-factum" in the event handler.
@ -220,7 +208,7 @@ Before...
...After
```
If you double-click on the bold element, then the selection appears and then is immediately removed. That doesn't look nice, and is not fully reliable though.
If you double-click on the bold element, then the selection appears and then is immediately removed. That doesn't look nice though.
````
````smart header="Preventing copying"
@ -230,31 +218,28 @@ If we want to disable selection to protect our content from copy-pasting, then w
<div *!*oncopy="alert('Copying forbidden!');return false"*/!*>
Dear user,
The copying is forbidden for you.
If you know JS or HTML, then that's not a problem of course,
otherwise we're sorry.
If you know JS or HTML, then you can get everything from the page source though.
</div>
```
If you try to copy a piece of text in the `<div>`, that won't work, because the default action `oncopy` is prevented.
Surely that doesn't stop from opening HTML-source and doing things manually, but not everyone knows how to do it.
Surely that can't stop the user from opening HTML-source, but not everyone knows how to do it.
````
## Summary
Mouse events have following properties:
- Button: `which`
- Button: `which`.
- Modifier keys (`true` if pressed): `altKey`, `ctrlKey`, `shiftKey` and `metaKey` (Mac).
- If you want to handle `key:Ctrl`, then don't forget Mac users, they use `key:Cmd`, so it's better to check `if (e.metaKey || e.ctrlKey)`.
- Window-relative coordinates: `clientX/clientY`
- Document-relative coordinates: `pageX/clientX`
- Window-relative coordinates: `clientX/clientY`.
- Document-relative coordinates: `pageX/clientX`.
In the tasks below it's also important to deal with the selection as an unwanted side-effect of clicks.
There are several ways, for instance:
1. CSS-property `user-select:none` (with browser prefixes) completely disables it.
2. Cancel the selection post-factum using `getSelection().removeAllRanges()`.
3. Handle `mousedown` and prevent the default action.
The third way is preferred most of the time.
3. Handle `mousedown` and prevent the default action (usually the best).

View file

@ -1,12 +1,10 @@
# Keyboard: keydown and keyup
Let's study keyboard events.
Before we get to keyboard, please note that on modern devices there are other ways to "input something". For instance, people use speech recognition (especially on mobile devices) or copy/paste with the mouse.
Before we start, please note that on modern devices there are other ways to "input something" then just a keyboard. For instance, people use speech recognition (tap microphone, say something, see it entered) or copy/paste with a mouse.
So if we want to track any input into an `<input>` field, then keyboard events are not enough. There's another event named `input` to handle changes of an `<input>` field, by any means. And it may be a better choice for such task. We'll cover it later in the chapter <info:events-change-input>.
So if we want to track any input into an `<input>` field, then keyboard events is not enough. There's another event named `input` to handle changes of an `<input>` field, by any means. And it may be a better choice for such task. We'll cover it later in the chapter <info:events-change-input>.
Keyboard events should be used when we want to handle keyboard actions (virtual keyboard usually also counts). For instance, to react on arrow keys `key:Up` and `key:Down` or hotkeys (including combinations of keys).
Keyboard events should be used when we want to handle keyboard actions (virtual keyboard also counts). For instance, to react on arrow keys `key:Up` and `key:Down` or hotkeys (including combinations of keys).
[cut]
@ -25,8 +23,6 @@ Try different key combinations in the text field.
[codetabs src="keyboard-dump" height=480]
```
As you read on, if you want to try things out -- return to the stand and press keys.
## Keydown and keyup
@ -36,7 +32,7 @@ The `keydown` events happens when a key is pressed down, and then `keyup` -- whe
The `key` property of the event object allows to get the character, while the `code` property of the event object allows to get the "physical key code".
For instance, the same key `key:Z` can be pressed with or without `Shift`. That gives us two different characters: a lowercase and uppercase `z`, right?
For instance, the same key `key:Z` can be pressed with or without `Shift`. That gives us two different characters: lowercase `z` and uppercase `Z`.
The `event.key` is exactly the character, and it will be different. But `event.code` is the same:
@ -45,15 +41,20 @@ The `event.key` is exactly the character, and it will be different. But `event.c
| `key:Z` |`z` (lowercase) |`KeyZ` |
| `key:Shift+Z`|`Z` (uppercase) |`KeyZ` |
If a user works with different languages, then switching to another language would make a totally different character instead of `"Z"`. That will become the value of `event.key`, while `event.code` is always the same: `"KeyZ"`.
```smart header="\"KeyZ\" and other key codes"
Key codes like `"KeyZ"` in the example above are described in the [UI Events code specification](https://www.w3.org/TR/uievents-code/).
Every key has the code that depends on its location on the keyboard. Key codes described in the [UI Events code specification](https://www.w3.org/TR/uievents-code/).
For instance:
- Letter keys have codes `"Key<letter>"`: `"KeyA"`, `"KeyB"` etc.
- Digit keys have codes: `"Digit<number>"`: `"Digit0"`, `"Digit1"` etc.
- Special keys are coded by their names: `"Enter"`, `"Backspace"`, `"Tab"` etc.
See [alphanumeric section](https://www.w3.org/TR/uievents-code/#key-alphanumeric-section) for more examples, or just try the [teststand](#keyboard-test-stand) above.
There are several widespread keyboard layouts, and the specification gives key codes for each of them.
See [alphanumeric section of the spec](https://www.w3.org/TR/uievents-code/#key-alphanumeric-section) for more codes, or just try the [teststand](#keyboard-test-stand) above.
```
```warn header="Case matters: `\"KeyZ\"`, not `\"keyZ\"`"
@ -62,29 +63,27 @@ Seems obvious, but people still make mistakes.
Please evade mistypes: it's `KeyZ`, not `keyZ`. The check like `event.code=="keyZ"` won't work: the first letter of `"Key"` must be uppercase.
```
If a user works with different languages, then switching to another language would make a totally different character instead of `"Z"`. That will become the value of `event.key`, while `event.code` is always the same: `"KeyZ"`.
What is a key does not give any character? For instance, `key:Shift` or `key:Tab` or others. For those keys `event.key` is approximately the same as `event.code`:
What if a key does not give any character? For instance, `key:Shift` or `key:F1` or others. For those keys `event.key` is approximately the same as `event.code`:
| Key | `event.key` | `event.code` |
|--------------|-------------|--------------|
| `key:Tab` |`Tab` |`Tab` |
| `key:F1` |`F1` |`F1` |
| `key:Backspace` |`Backspace` |`Backspace` |
| `key:Shift`|`Shift` |`ShiftRight` or `ShiftLeft` |
Please note that `event.code` specifies exactly which key is pressed. For instance, most keyboards have two `key:Shift` keys: on the left and on the right side. The `event.code` tells us exactly which one was pressed, and `event.key` is responsible for the "meaning" of the key: what it is (a "Shift").
Let's say, we want to handle a hotkey: `key:Ctrl+Z` (or `key:Cmd+Z` for Mac). That's an "undo" action in most text editors. We can set a listener on `keydown` and check which key is pressed -- to detect when we have the hotkey.
Let's say, we want to handle a hotkey: `key:Ctrl+Z` (or `key:Cmd+Z` for Mac). Most text editors hook the "Undo" action on it. We can set a listener on `keydown` and check which key is pressed -- to detect when we have the hotkey.
How do you think, should we check the value of `event.key` or `event.code`?
Please answer the question -- in such a listener, should we check the value of `event.key` or `event.code`?
Please, pause and answer.
Made up your mind?
If you've got an understanding, then the answer is, of course, `event.code`, and we don't want `event.key` here. The value of `event.key` can change depending on the language or `CapsLock` enabled. The value of `event.code` is strictly bound to the key, so here we go:
If you've got an understanding, then the answer is, of course, `event.code`, as we don't want `event.key` there. The value of `event.key` can change depending on the language or `CapsLock` enabled. The value of `event.code` is strictly bound to the key, so here we go:
```js run
document.addEventListener('keydown', function(event) {
@ -98,12 +97,12 @@ document.addEventListener('keydown', function(event) {
If a key is being pressed for a long enough time, it starts to repeat: the `keydown` triggers again and again, and then when it's released we finally get `keyup`. So it's kind of normal to have many `keydown` and a single `keyup`.
For all repeating keys the event object has `event.return=true`.
For all repeating keys the event object has `event.return` property set to `true`.
## Default actions
Default actions vary, as there are many possible things that may be initiated by keyboard.
Default actions vary, as there are many possible things that may be initiated by the keyboard.
For instance:
@ -117,7 +116,7 @@ Preventing the default action on `keydown` can cancel most of them, with the exc
For instance, the `<input>` below expects a phone number, so it does not accept keys except digits, `+`, `()` or `-`:
```html run
```html autorun height=60 run
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-';
@ -126,41 +125,41 @@ function checkPhoneKey(key) {
<input *!*onkeydown="return checkPhoneKey(event.key)"*/!* placeholder="Phone, please" type="tel">
```
Please note that special keys like `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V` do not work. That's a side-effect effect of the strict filter `checkPhoneKey`.
Please note that special keys like `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V` do not work in the input. That's a side-effect effect of the strict filter `checkPhoneKey`.
Let's relax it a little bit:
```html run
```html autorun height=60 run
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' ||
key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
}
</script>
<input *!*onkeydown="return checkPhoneKey(event.key)"*/!* placeholder="Phone, please" type="tel">
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">
```
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 that usually 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.
...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.
## Legacy
In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `which` properties of the event object.
There were so many browser incompatibilities that standard developers decided to deprecate all of them and remove from the specification. The old code still works, as the browser keep supporting them, but there's totally no need to use those any more.
There were so many browser incompatibilities that developers of the specification decided to deprecate all of them. The old code still works, as the browser keep supporting them, but there's totally no need to use those any more.
There was time when this chapter included their detailed description. But as of now we can forget about those.
## 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 is sometimes present on laptop keyboards. There's no keyboard event for it, because it's often implemented on lower level than OS.
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.
Keyboard events:
- `keydown` -- on press the key (auto-repeats if the key is pressed for long),
- `keydown` -- on pressing the key (auto-repeats if the key is pressed for long),
- `keyup` -- on releasing the key.
Main keyboard event properties:
@ -168,6 +167,6 @@ Main keyboard event properties:
- `code` -- the "key code" (`"KeyA"`, `"ArrowLeft"` and so on), specific to the physical location of the key on keyboard.
- `key` -- the character (`"A"`, `"a"` and so on), for non-character keys usually has the same value as `code`.
In the past, keyboard events were sometimes used to track user input in form fields. That's not the case any more, because we have `input` and `change` events for that (to be covered later). They are better, because they work for all ways of input, including mouse or speech recognition.
In the past, keyboard events were sometimes used to track user input in form fields. That's not reliable, because the input can come from various sources. We have `input` and `change` events to handle any input (covered later in the chapter <info:events-change-input>). They trigger after any input, including mouse or speech recognition.
We should use keyboard events when we really want keyboard. For example, to react on hotkeys or special keys.

View file

@ -1,7 +1,7 @@
# The JavaScript Tutorial
This repository hosts the content of the JavaScript Tutorial, published at [https://learn.javascript.info](https://learn.javascript.info).
This repository hosts the content of the JavaScript Tutorial, published at [https://javascript.info](https://javascript.info).
Please use this repository to file issues and suggest PRs for the text.