Skip to content

Commit

Permalink
Merge pull request #1 from javascript-tutorial/master
Browse files Browse the repository at this point in the history
Update from original
  • Loading branch information
gongzemin authored May 15, 2019
2 parents 4e079a5 + 1072055 commit 89d81d3
Show file tree
Hide file tree
Showing 26 changed files with 152 additions and 127 deletions.
42 changes: 20 additions & 22 deletions 1-js/04-object-basics/05-object-toprimitive/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).

That algorithm allows us to customize the conversion using a special object method.
We can fine-tune string and numeric conversion, using special object methods.

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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
35 changes: 17 additions & 18 deletions 1-js/05-data-types/04-array/article.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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"*!*,*/!*
];
```
Expand All @@ -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:

Expand All @@ -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).

Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -296,7 +296,7 @@ let fruits = ["Apple", "Orange", "Plum"];

// iterates over array elements
for (let fruit of fruits) {
alert( fruit );
alert( fruit );
}
```

Expand All @@ -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.

Expand All @@ -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.

Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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:
Expand All @@ -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>.

Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion 1-js/07-object-properties/02-property-accessors/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion 1-js/08-prototypes/01-prototype-inheritance/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
16 changes: 8 additions & 8 deletions 1-js/09-classes/02-class-inheritance/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions 1-js/13-modules/01-modules-intro/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 89d81d3

Please sign in to comment.