Merge pull request #1 from javascript-tutorial/master

Update from original
This commit is contained in:
gzmin 2019-05-15 16:16:33 +08:00 committed by GitHub
commit 89d81d369d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 152 additions and 127 deletions

View file

@ -3,28 +3,24 @@
What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`?
There are special methods in objects that do the conversion.
In that case objects are auto-converted to primitives, and then the operation is carried out.
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to close it.
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.
For objects, there's no to-boolean conversion, because all objects are `true` in a boolean context. So there are only string and numeric conversions.
The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter <info:date>) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
1. All objects are `true` in a boolean context. There are only numeric and string conversions.
2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter <info:date>) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
## ToPrimitive
When an object is used in the context where a primitive is required, for instance, in an `alert` or mathematical operations, it's converted to a primitive value using the `ToPrimitive` algorithm ([specification](https://tc39.github.io/ecma262/#sec-toprimitive)).
We can fine-tune string and numeric conversion, using special object methods.
That algorithm allows us to customize the conversion using a special object method.
Depending on the context, the conversion has a so-called "hint".
The conversion algorithm is called `ToPrimitive` in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive). It's called with a "hint" that specifies the conversion type.
There are three variants:
`"string"`
: When an operation expects a string, for object-to-string conversions, like `alert`:
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
```js
// output
@ -35,7 +31,7 @@ There are three variants:
```
`"number"`
: When an operation expects a number, for object-to-number conversions, like maths:
: For an object-to-number conversion, like when we're doing maths:
```js
// explicit conversion
@ -52,7 +48,7 @@ There are three variants:
`"default"`
: Occurs in rare cases when the operator is "not sure" what type to expect.
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol.
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done.
```js
// binary plus
@ -159,14 +155,21 @@ alert(user + 500); // toString -> John500
In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions.
## ToPrimitive and ToString/ToNumber
## Return types
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
There is no control whether `toString()` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint "number".
**The only mandatory thing: these methods must return a primitive.**
The only mandatory thing: these methods must return a primitive, not an object.
```smart header="Historical notes"
For historical reasons, if `toString` or `valueOf` return an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.
In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error.
```
## Further operations
An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
@ -208,11 +211,6 @@ For instance:
alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber)
```
```smart header="Historical notes"
For historical reasons, methods `toString` or `valueOf` *should* return a primitive: if any of them returns an object, then there's no error, but that object is ignored (like if the method didn't exist).
In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise, there will be an error.
```
## Summary

View file

@ -1,12 +1,12 @@
# Arrays
# Arrays
Objects allow you to store keyed collections of values. That's fine.
But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc.
But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc.
It is not convenient to use an object here, because it provides no methods to manage the order of elements. We cant insert a new property “between” the existing ones. Objects are just not meant for such use.
There exists a special data structure named `Array`, to store ordered collections.
There exists a special data structure named `Array`, to store ordered collections.
## Declaration
@ -81,10 +81,10 @@ arr[3](); // hello
````smart header="Trailing comma"
An array, just like an object, may end with a comma:
```js
```js
let fruits = [
"Apple",
"Orange",
"Apple",
"Orange",
"Plum"*!*,*/!*
];
```
@ -106,7 +106,7 @@ Arrays support both operations.
In practice we need it very often. For example, a queue of messages that need to be shown on-screen.
There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
It supports two operations:
@ -121,7 +121,7 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to
For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).
Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.
Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.
In computer science the data structure that allows it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
@ -189,11 +189,11 @@ alert( fruits );
## Internals
An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. Numbers are used as keys.
An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. That's essentially the same as `obj[key]`, where `arr` is the object, while numbers are used as keys.
They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object.
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
For instance, it is copied by reference:
@ -203,7 +203,7 @@ let fruits = ["Banana"]
let arr = fruits; // copy by reference (two variables reference the same array)
alert( arr === fruits ); // true
arr.push("Pear"); // modify the array by reference
alert( fruits ); // Banana, Pear - 2 items now
@ -229,7 +229,7 @@ But the engine will see that we're working with the array as with a regular obje
The ways to misuse an array:
- Add a non-numeric property like `arr.test = 5`.
- Add a non-numeric property like `arr.test = 5`.
- Make holes, like: add `arr[0]` and then `arr[1000]` (and nothing between them).
- Fill the array in the reverse order, like `arr[1000]`, `arr[999]` and so on.
@ -296,7 +296,7 @@ let fruits = ["Apple", "Orange", "Plum"];
// iterates over array elements
for (let fruit of fruits) {
alert( fruit );
alert( fruit );
}
```
@ -320,7 +320,7 @@ But that's actually a bad idea. There are potential problems with it:
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The `for..in` loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem.
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks or seem irrelevant. But still we should be aware of the difference.
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks. But still we should be aware of the difference.
Generally, we shouldn't use `for..in` for arrays.
@ -338,7 +338,7 @@ fruits[123] = "Apple";
alert( fruits.length ); // 124
```
Note that we usually don't use arrays like that.
Note that we usually don't use arrays like that.
Another interesting thing about the `length` property is that it's writable.
@ -385,7 +385,7 @@ To evade such surprises, we usually use square brackets, unless we really know w
## Multidimensional arrays
Arrays can have items that are also arrays. We can use it for multidimensional arrays, to store matrices:
Arrays can have items that are also arrays. We can use it for multidimensional arrays, for example to store matrices:
```js run
let matrix = [
@ -445,7 +445,7 @@ Array is a special kind of object, suited to storing and managing ordered data i
The call to `new Array(number)` creates an array with the given length, but without elements.
- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods.
- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods.
- If we shorten `length` manually, the array is truncated.
We can use an array as a deque with the following operations:
@ -461,4 +461,3 @@ To loop over the elements of the array:
- `for (let i in arr)` -- never use.
We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter <info:array-methods>.

View file

@ -453,7 +453,7 @@ let wrapper = function() {
}
```
We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to arguments. The alternative is to use rest parameters object that is a real array.
We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array.
There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.

View file

@ -85,7 +85,7 @@ alert(user.surname); // Cooper
Now we have a "virtual" property. It is readable and writable, but in fact does not exist.
```smart header="Accessor properties are only accessible with get/set"
Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data properety any more.
Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data property any more.
- If there's a getter -- we can read `object.prop`, otherwise we can't.
- If there's a setter -- we can set `object.prop=...`, otherwise we can't.

View file

@ -312,7 +312,6 @@ All other key/value-getting methods, such as `Object.keys`, `Object.values` and
They only operate on the object itself. Properties from the prototype are taken into account.
```
## Summary
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.

View file

@ -49,7 +49,7 @@ Right now they are fully independent.
But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`.
To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`.
Here `Rabbit` inherits from `Animal`:
@ -209,7 +209,7 @@ With constructors it gets a little bit tricky.
Till now, `Rabbit` did not have its own `constructor`.
According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following `constructor` is generated:
According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated:
```js
class Rabbit extends Animal {
@ -309,15 +309,15 @@ alert(rabbit.earLength); // 10
Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way.
First to say, from all that we've learned till now, it's impossible for `super` to work.
First to say, from all that we've learned till now, it's impossible for `super` to work at all!
Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, how to retrieve the `method`? Naturally, we need to take the `method` from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object. How JavaScript engine should get the prototype of `this`?
Maybe we can get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that doesn't work.
The task may seem simple, but it isn't. The engine could try to get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`. Unfortunately, that doesn't work.
Let's try to do it. Without classes, using plain objects for the sake of simplicity.
Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.
Here, `rabbit.eat()` should call `animal.eat()` method of the parent object:
In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`:
```js run
let animal = {
@ -418,7 +418,7 @@ To provide the solution, JavaScript adds one more special internal property for
This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. So that's a very important change in the language.
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility.
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility. Regular method calls know nothing about `[[HomeObject]]`, it only matters for `super`.
Let's see how it works for `super` -- again, using plain objects:

View file

@ -86,7 +86,7 @@ That's the correct variant:
[codetabs src="scopes-working" height="140" current="hello.js"]
In the browser, independant top-level scope also exists for each `<script type="module">`:
In the browser, independent top-level scope also exists for each `<script type="module">`:
```html run
<script type="module">
@ -263,7 +263,7 @@ When using modules, we should be aware that HTML-document can show up before the
### Async works on inline scripts
Async attribute `<script async type="module">` is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independantly of other scripts or the HTML document.
Async attribute `<script async type="module">` is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independently of other scripts or the HTML document.
For example, the script below has `async`, so it doesn't wait for anyone.

View file

@ -103,7 +103,7 @@ Well, there are few reasons.
export function becomeSilent() { ... }
```
Now if we in fact need only one of them in our project:
Now if we only use one of `lib.js` functions in our project:
```js
// 📁 main.js
import {sayHi} from './lib.js';
@ -218,8 +218,7 @@ export default function(user) { // no function name
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
```
That's fine, because `export default` is only one per file, so `import` always knows what to import.
Contrary to that, omitting a name for named imports would be an error:
That's fine, because `export default` is only one per file. Contrary to that, omitting a name for named imports would be an error:
```js
export class { // Error! (non-default export needs a name)
@ -229,9 +228,9 @@ export class { // Error! (non-default export needs a name)
### "Default" alias
The "default" word is a kind of "alias" for the default export, for scenarios when we need to reference it somehow.
The "default" keyword is used as an "alias" for the default export, for standalone exports and other scenarios when we need to reference it.
For example, if we already have a function declared, that's how to `export default` it:
For example, if we already have a function declared, that's how to `export default` it (separately from the definition):
```js
function sayHi(user) {
@ -278,7 +277,7 @@ new User('John');
### Should I use default exports?
One should be careful about using default exports, because they are somewhat more different to maintain.
One should be careful about using default exports, because they are more difficult to maintain.
Named exports are explicit. They exactly name what they import, so we have that information from them, that's a good thing.
@ -286,12 +285,15 @@ Also, named exports enforce us to use exactly the right name to import:
```js
import {User} from './user.js';
// import {MyUser} won't work, the name must be {User}
```
For default exports, we need to create a name on our own:
For default exports, we always choose the name when importing:
```js
import MyUser from './user.js'; // could be import Anything..., and it'll work
import User from './user.js'; // works
import MyUser from './user.js'; // works too
// could be import Anything..., and it'll be work
```
So, there's a little bit more freedom that can be abused, so that team members may use different names for the same thing.
@ -409,16 +411,18 @@ Import:
- `import {default as x} from "mod"`
- Everything:
- `import * as obj from "mod"`
- Only fetch/evalute the module, don't import:
- Import the module (it runs), but do not assign it to a variable:
- `import "mod"`
We can put import/export statements below or after other code, that doesn't matter.
We can put import/export statements at the top or at the bottom of a script, that doesn't matter.
So this is technically fine:
```js
sayHi();
import {sayHi} from './say.js'; // import at the end of the file
// ...
import {sayHi} from './say.js'; // import at the end of the script
```
In practice imports are usually at the start of the file, but that's only for better convenience.