Merge pull request #1 from javascript-tutorial/master
Update from original
This commit is contained in:
commit
89d81d369d
26 changed files with 152 additions and 127 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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 can’t 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>.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue