This commit is contained in:
Ilya Kantor 2016-07-31 00:28:27 +03:00
parent 9064e35f3f
commit 4c531b5ae7
371 changed files with 338 additions and 316 deletions

View file

@ -0,0 +1,10 @@
```js
let user = {};
user.name = "John";
user.surname = "Smith";
user.name = "Pete";
delete user.name;
```

View file

@ -0,0 +1,14 @@
importance: 5
---
# Hello, object
Write the code, each line for an action:
1. Create an empty object `user`.
2. Add the property `name` with the value `John`.
3. Add the property `surname` with the value `Smith`.
4. Change the value of the `name` to `Pete`.
5. Remove the property `name` from the object.

View file

@ -0,0 +1,7 @@
function isEmpty(obj) {
for (let key in obj) {
// if the loop has started, there is a prorty
return false;
}
return true;
}

View file

@ -0,0 +1,11 @@
describe("isEmpty", function() {
it("returns true for an empty object", function() {
assert.isTrue(isEmpty({}));
});
it("returns false if a property exists", function() {
assert.isFalse(isEmpty({
anything: false
}));
});
});

View file

@ -0,0 +1,11 @@
Just loop over the object and `return false` immediately if there's at least one property.
```js
function isEmpty(obj)
for(let key in obj) {
return false;
}
return true;
}
```

View file

@ -0,0 +1,20 @@
importance: 5
---
# Check for emptiness
Write the function `isEmpty(obj)` which returns `true` if the object has no properties, `false` otherwise.
Should work like that:
```js
let schedule = {};
alert( isEmpty(schedule) ); // true
schedule["8:30"] = "get up";
alert( isEmpty(schedule) ); // false
```

View file

@ -0,0 +1,16 @@
```js run
let salaries = {
John: 100,
Ann: 160,
Pete: 130
}
let sum = 0;
for(let key in salaries) {
sum += salaries[key];
}
alert(sum); // 390
```

View file

@ -0,0 +1,19 @@
importance: 5
---
# Sum object properties
We have an object storing salaries of our team:
```js
let salaries = {
John: 100,
Ann: 160,
Pete: 130
}
```
Write the code to sum all salaries and store in the variable `sum`. Should be `390` in the example above.
If `salaries` is empty, then the result must be `0`.

View file

@ -0,0 +1,7 @@
function multiplyNumeric(obj) {
for (let key in obj) {
if (typeof obj[key] == 'number') {
obj[key] *= 2;
}
}
}

View file

@ -0,0 +1,17 @@
let menu = {
width: 200,
height: 300,
title: "My menu"
};
function multiplyNumeric(obj) {
/* your code */
}
multiplyNumeric(menu);
alert( "menu width=" + menu.width + " height=" + menu.height + " title=" + menu.title );

View file

@ -0,0 +1,18 @@
describe("multiplyNumeric", function() {
it("multiplies all numeric properties by 2", function() {
var menu = {
width: 200,
height: 300,
title: "My menu"
};
let result = multiplyNumeric(menu);
assert.equal(menu.width, 400);
assert.equal(menu.height, 600);
assert.equal(menu.title, "My menu");
});
it("returns nothing", function() {
assert.isUndefined( multiplyNumeric({}) );
});
});

View file

@ -0,0 +1,33 @@
importance: 3
---
# Multiply numeric properties by 2
Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`.
For instance:
```js
// before the call
let menu = {
width: 200,
height: 300,
title: "My menu"
};
multiplyNumeric(menu);
// after the call
menu = {
width: 400,
height: 600,
title: "My menu"
};
```
Please note that `multiplyNumeric` does not need to return anything. It should modify the object in-place.
P.S. Use `typeof` to check for a number here.

View file

@ -0,0 +1,688 @@
# Objects
As we know, there are 7 language types in Javascript. Six of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever).
In contrast, objects are used to store keyed collections of various data and more complex entities. In Javascript, objects penetrate almost every aspect of the language. So we must understand them first before going in-depth anywhere else.
[cut]
An object can be created with figure brackets `{…}` with an optional list of "key: value" pairs. In programming that's sometimes called an "associative array" or a "hash".
We can imagine an object as a cabinet with signed files. Every piece of data is stored in it's file by the key. It's easy to find a file by it's name or add/remove a file.
![](object.png)
An empty object ("empty cabinet") can be created using one of two syntaxes:
```js
let user = new Object(); // "object constructor" syntax
let user = {}; // "object literal" syntax
```
![](object-user-empty.png)
Usually, the figure brackets `{...}` are used. That declaration is called an *object literal*.
## Literals and properties
We can immediately put some data into `{...}` as "key: value" pairs. Every pair is called *an object property*:
```js
let user = { // an object
name: "John", // by key "name" store value "John"
age: 30 // by key "age" store value 30
};
```
A property has an identifier (also "name" and "key") before the colon `":"` and a value to the right of it.
In the `user` object, there are two properties:
1. The first property has the name `"name"` and the value `"John"`.
2. The second one has the name `"age"` and the value `30`.
The resulting `user` object can be imagined as a cabinet with two signed files labelled "name" and "age".
![user object](object-user.png)
We can add, remove and read files from it any time.
Property values are accessible using the dot notation:
```js
// get fields of the object:
alert( user.name ); // John
alert( user.age ); // 30
```
A value can be of any time, let's add a boolean one:
```js
user.isAdmin = true;
```
![user object 2](object-user-isadmin.png)
...And remove `age` with the help of `delete` operator:
```js
delete user.age;
```
![user object 3](object-user-delete.png)
We can also use multiword property names, but then they must be quoted:
```js
let user = {
name: "John",
age: 30,
"likes birds": true // multiword property name must be quoted
};
```
![](object-user-props.png)
## Square brackets
For multiword properties, the dot access won't work:
```js
// this would give a syntax error
user.likes birds = true
```
That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations.
There's an alternative "square bracket notation" that works with any string:
```js
user["likes birds"] = true;
```
Square brackets are also the way to access a property by the name from the variable:
```js
let key = "likes birds";
user[key] = true; // same as above
```
The square brackets mean: "take the property name from the variable".
That variable can be assigned at run-time, for instance:
```js run
let user = {
name: "John",
age: 30
};
let key = prompt("What do you want to know about the user?", "name");
// access by variable
alert( user[key] ); // John (if enter "name")
```
### Computed properties
*Computed properties* are square brackets used inside an object literal:
```js run
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
*!*
[fruit]: 5, // the name of the property is taken from the variable fruit
*/!*
};
alert( bag.apple ); // 5 if fruit="apple"
```
Here, the value of `fruit` variable is used as the property name. So, if a visitor enters `"apple"`, `bag` will become `{apple: 5}`.
Essentially, that works the same as:
```js run
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};
// take property name from the fruit variable
bag[fruit] = 5;
```
We can use more complex expressions inside square brackets. Anything that results in a property name:
```js
let fruit = 'apple';
let bag = {
['apple' + 'Computers']: 5 // bag.appleComputers = 5
};
```
Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are more cumbersome to write. So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.
````smart header="Trailing comma"
The last property in the list may end with a comma:
```js
let user = {
name: "John",
age: 30*!*,*/!*
}
```
That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike.
````
````smart header="Reserved words are allowed as property names"
A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
But for an object property, there's no such restruction. Any name is fine:
```js run
let obj = {
for: 1,
let: 2,
return: 3
}
alert( obj.for + obj.let + obj.return ); // 6
```
Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value:
```js run
let obj = { __proto__: 5 };
alert(obj.__proto__); // [object Object], didn't work as intended
```
Later we'll learn more about that `__proto__` and see how to work that problem [todo Object.create(null)]. Also we'll learn another data structure [Map](info:map-set-weakmap-weakset) that doesn't have such problems and supports arbitrary keys.
````
## Property name shorthand
In real code we often use existing variables as values for property names.
For instance:
```js run
function makeUser(name, age) {
*!*
// take values for name and age from variables
*/!*
return {
name: name,
age: age
};
}
let user = makeUser("John", 30);
alert(user.name); // John
```
In the example above, properties have same names as variables. There's a special shorthand notation that is shorter.
Instead of `name:name` we can just write `name`, like this:
```js
function makeUser(name, age) {
*!*
return {
name, // same as name: name
age // same as age: age
};
*/!*
}
```
We can use both normal properties and shorthands in the same object:
```js
let user = {
name, // same as name:name
age: 30
};
```
## Existance check
A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined:
```js run
let user = {};
alert( user.noSuchProperty === undefined ); // true means "no such property"
```
There also exists a special operator `"in"` to check for the existance of a property.
The syntax is:
```js
"key" in object
```
For instance:
```js run
let user = { name: "John", age: 30 };
alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist
```
Please note that at the left side of `in` there must be a *property name*. That's usually a quoted string.
If we omit quotes, that would mean a variable containing the actual name to be tested. For instance:
```js run
let user = { age: 30 };
let key = "age";
alert( *!*key*/!* in user ); // true, takes the name from key and checks for such property
```
````smart header="Using \"in\" for properties that store `undefined`"
Usually, the strict comparison `"=== undefined"` check works fine. But there's a special case when it fails, but `"in"` works correctly.
It's when an object property exists, but stores `undefined`:
```js run
let obj = {
test: undefined
};
alert( obj.test ); // it's undefined, so - no such property?
alert( "test" in obj ); // true, the property does exist!
```
In the code above, the property `obj.test` technically exists. So the `in` operator works right.
Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code.
````
## The "for..in" loop
To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before.
The syntax:
```js
for(key in object) {
// executes the body for each key among object properties
}
```
For instance, let's output all properties of `user`:
```js run
let user = {
name: "John",
age: 30,
isAdmin: true
};
for(let key in user) {
// keys
alert( key ); // name, age, 30
// values for the keys
alert( user[key] ); // John, 30, true
}
```
Note that all "for" constructs allow to declare the looping variable inside the loop, like `let key` here.
Also, we could use another variable name here instead of `key`. For instance, `"for(let prop in obj)"` is also widely used.
### Ordered like an object
Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order that they are added in it?
The short answer is: "ordered like an object": integer properties are sorted, others appear in creation order. The details follow.
As an example, let's consider an object with the phone codes:
```js run
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
*!*
for(let code in codes) {
alert(code); // 1, 41, 44, 49
}
*/!*
```
The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first.
But if we run the code, we see a totally different picture:
- USA (1) goes first
- then Switzerland (41) and so on.
The phone codes go in the ascending sorted order, because they are integer. So we see `1, 41, 44, 49`.
````smart header="Integer properties? What's that?"
The "integer property" term here means a string that can be converted to-from integer without a change.
So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:
```js run
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same ⇒ not integer property
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same ⇒ not integer property
```
````
...On the other hand, if the keys are non-integer, then they are listed in the creation order, for instance:
```js run
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // add one more
*!*
// non-integer properties are listed in the creation order
*/!*
for (let prop in user) {
alert( prop ); // name, surname, age
}
```
So, to fix the issue with the phone codes, we can "cheat" by making the codes non-integer. Adding a plus `"+"` sign before each code is enough.
Like this:
```js run
let codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
// ..,
"+1": "USA"
};
for(let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
```
Now it works as intended.
## References
One of fundamental differences of objects vs primitives is that they are stored and copied "by reference".
Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value".
For instance:
```js
let message = "Hello!";
let phrase = message;
```
As a result we have two independant variables, each one is storing the string `"Hello!"`.
![](variable-copy-value.png)
Objects are not like that.
**A variable stores not the object itself, but it's "address in memory", in other words "a reference" to it.**
Here's the picture for the object:
```js
let user = {
name: "John"
};
```
![](variable-contains-reference.png)
Note that the object itself is stored somewhere in memory. The variable `user` has a "reference" to it.
**When an object variable is copied -- the reference is copied, the object is not duplicated.**
If we imagine an object as a cabinet, then a variable is a key to it. Copying a variable duplicates the key, but not the cabinet itself.
For instance:
```js no-beautify
let user = { name: "John" };
let admin = user; // copy the reference
```
Now we have two variables, each one with the reference to the same object:
![](variable-copy-reference.png)
Now can use any variable to access the cabinet and modify its contents:
```js run
let user = { name: 'John' };
let admin = user;
*!*
admin.name = 'Pete'; // changed by the "admin" reference
*/!*
alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
```
The example above demonstrates that there is only one object. Like if we had a cabinet with two keys and used one of them (`admin`) to get into it -- later using the other one (`user`) we will see things modified.
### Comparison by reference
The equality `==` and strict equality `===` operators for objects work exactly the same, simple way.
**Two object variables are equal only when reference the same object.**
```js run
let a = {};
let b = a; // copy the reference
alert( a == b ); // true, both variables reference the same object
alert( a === b ); // true
```
In all other cases objects are non-equal, even if their content is the same.
For instance:
```js run
let a = {};
let b = {}; // two independent objects
alert( a == b ); // false
```
That rule only applies to object vs object equality checks.
For other comparisons like whether an object less/greater than another object (`obj1 > obj2`) or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to say the truth, such comparisons occur very rarely in real code and usually are a result of a coding mistake.
## Cloning and merging, Object.assign
So, copying an object variable creates one more reference to the same object.
But what if we need to duplicate an object? Create an independant copy, a clone?
That's also doable, but a little bit more difficult, because there's no built-in method for that in Javascript. Actually, that's rarely needed, copying by reference is good most of the time.
But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
Like this:
```js run
let user = {
name: "John",
age: 30
};
*!*
let clone = {}; // the new empty object
// let's copy all user properties into it
for (let key in user) {
clone[key] = user[key];
}
*/!*
// now clone is a fully independant clone
clone.name = "Pete"; // changed the data in it
alert( user.name ); // still John
```
Also we can use the method [Object.assign](mdn:js/Object/assign) for that.
The syntax is:
```js
Object.assign(dest[, src1, src2, src3...])
```
- `dest` and other arguments (can be as many as needed) are objects
It copies the properties of all arguments starting from the 2nd (`src1`, `src2` etc) into the `dest`. Then it returns `dest`.
For instance, we can use it to merge several objects into one:
```js
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
*!*
// copies all properties from permissions1 and permissions2 into user
Object.assign(user, permissions1, permissions2);
*/!*
// now user = { name: "John", canView: true, canEdit: true }
```
If the receiving object (`user`) already has the same named property, it will be overwritten:
```js
let user = { name: "John" };
// overwrite name, add isAdmin
Object.assign(user, { name: "Pete", isAdmin: true });
// now user = { name: "Pete", isAdmin: true }
```
We also can use `Object.assign` to replace the loop for simple cloning:
```js
let user = {
name: "John",
age: 30
};
*!*
let clone = Object.assign({}, user);
*/!*
```
It copies all properties of `user` into the empty object and returns it. Actually, the same as the loop, but shorter.
Till now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them?
Like this:
```js run
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
alert( user.sizes.height ); // 182
```
Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes:
Like this:
```js run
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true, same object
user.sizes.width++; // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one
```
To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate it's structure as well. That is called a "deep cloning".
There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](w3c.github.io/html/infrastructure.html#internal-structured-cloning-algorithm). Not to reinvent the wheel, we can use a working implementation of it from the Javascript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
## Summary
[todo rewrite]
Objects are associative arrays with several special features.
- Property keys are either strings or symbols.
- Values can be of any type.
Property access:
- Read/write uses the dot notation: `obj.property` or square brackets `obj["property"]/obj[varWithKey]`.
- The deletion is made via the `delete` operator.
- Existance check is made by the comparison vs `undefined` or via the `in` operator.
- Three forms of looping:
- `for(key in obj)` for the keys.
- `for(value of Object.values(obj))` for the values.
- `for([key,value] of Object.entries(obj))` for both.
- Ordering:
- Integer properties in sorted order first, then strings in creation order, then symbols in creation order.
- To keep the order for numeric properties, we can prepend them with `+` to make them look like non-numeric.
- Objects are assigned and copied by reference.
What we've just seen is called a "plain object", or just `Object`.
There are many other kinds of objects in Javascript:
- `Array` to store ordered data collections,
- `Date` to store the information about the date and time,
- `Error` to store the information about an error.
- ...And so on.
Sometimes people say something like "Array type" or "Date type", but formally they are not types of their own, but belong to a single "object" data type. And they extend it in various ways.
Objects in JavaScript are very powerful. Here we've just scratched the surface of the topic that is really huge. We'll be closely working with objects and learning more about them in further parts of the tutorial.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,210 @@
# Garbage collection
Memory management in Javascript is performed automatically and invisibly to us. We create primitives, objects, functions... All that takes memory.
What happens when something is not needed any more? How Javascript engine discovers that and cleans up?
[cut]
## Reachability
The main concept of memory management in Javascript is *reachability*.
Simply put, "reachable" values are those that are accessible now or in the future. They are guaranteed to be stored in memory.
1. There's a base set of inherently reachable values, that cannot be deleted for obvious reasons.
For instance:
- Local variables and parameters of the current function.
- Variables and parameters for other functions on the current chain of nested calls.
- Global variables.
- (there are some other, internal ones as well)
These values are called *roots*.
2. Any other value is retained in memory only while it's reachable from a root by a reference of by a chain of references.
There's a background process that runs by the engine itself called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that became unreachable.
## A simple example
Here's the simplest example:
```js
// user has a reference to the object
let user = {
name: "John"
};
```
![](memory-user-john.png)
Here the arrow depicts an object reference. The global variable `"user"` references the object `{name: "John"}` (we'll call it John for brevity). The `"name"` property of John stores a primitive, so it's painted inside the object.
If the value of `user` is overwritten, the reference is lost:
```js
user = null;
```
![](memory-user-john-lost.png)
Now John becomes unreachable. There's no way to access it, no references to it. Garbage collector will junk the data and free the memory.
## Two references
Now let's imagine we copied the reference from `user` to `admin`:
```js
// user has a reference to the object
let user = {
name: "John"
};
*!*
let admin = user;
*/!*
```
![](memory-user-john-admin.png)
Now if we do the same:
```js
user = null;
```
...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed.
## Interlinked objects
Now a more complex example. The family:
```js
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
```
Function `marry` "marries" two objects by giving them references to each other and returns a new object that contains them both.
The resulting memory structure:
![](family.png)
As of now, all objects are reachable.
Now let's remove two references:
```js
delete family.father;
delete family.mother.husband;
```
![](family-delete-refs.png)
It's not enough to delete any one of them, because all objects would still be reachable.
But if we delete both, then we can see that John has no incoming references any more:
![](family-no-father.png)
Outgoing references do not matter. Only incoming ones can make an object reachable. So, John is now unreachable and will be removed from the memory with all its data that also became unaccessible.
After garbage collection:
![](family-no-father-2.png)
## Unreachable island
It is possible that the whole island of interlinked objects becomes unreachable and is removed from the memory.
The source object is the same as above. Then:
```js
family = null;
```
The in-memory picture becomes:
![](family-no-family.png)
This example demonstrates how important the concept of reachability is.
It's obvious that John and Ann are still linked, both have incoming references. But that's not enough.
The former `"family"` object has been unlinked from the root, there's no reference to it any more, so the whole island becomes unreachable and will be removed.
## Internal algorithms
The basic garbage collection algorithm is called "mark-and-sweep".
Regularly the following "garbage collection" steps are performed:
- The garbage collector takes roots and "marks" them.
- Then it visits and "marks" all references from them.
- Then it visits marked objects and marks *their* references. All visited objects are remembered, not to visit the same object twice in the future.
- ...And so on until there are unvisited references (reachable from the roots).
- All objects except marked ones are removed.
For instance, if our object structure looks like this:
![](garbage-collection-1.png)
Then the first step marks the roots:
![](garbage-collection-2.png)
Then their references are marked:
![](garbage-collection-3.png)
...And their refences, while possible:
![](garbage-collection-4.png)
Now the objects that could not be visited in the process are considered unreachable and will be removed:
![](garbage-collection-5.png)
That's the concept how garbage collection works.
Javascript engines apply many optimizations to it, to make it run faster and be more hidden behind the scenes.
Some of the optimizations:
- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, so they can be cleaned up more aggressively. Those "new" that survive for long enough, become "old".
- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays. So the engine tries to split the job into pieces. Then pieces are executed one at a time. That requires some extra bookkeeping between them.
- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution.
Detailed learning of these optimization is also possible, but it requires a lot of under-the-hood digging. Javascript engines implement garbage collection differently. And -- what's even more important, things change, so going really deep "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below.
## Summary
The main things to know:
- Garbage collection is performed automatically. We cannot force or prevent it.
- Objects are retained in memory while they are reachable.
- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
Modern engines implement advanced algorithms of garbage collection.
If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection), and [V8 blog](http://v8project.blogspot.com/) also publishes articles about changes in memory management from time to time. Also you'd better prepare yourself by learning about V8 in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects.
In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,43 @@
**Error**!
Try it:
```js run
let user = {
name: "John",
go: function() { alert(this.name) }
}
(user.go)() // error!
```
The error message in most browsers does not give understanding what went wrong.
**The error appears because a semicolon is missing after `user = {...}`.**
Javascript does not assume a semicolon before a bracket `(user.go)()`, so it reads the code like:
```js no-beautify
let user = { go:... }(user.go)()
```
Then we can also see that such a joint expression is syntactically a call of the object `{ go: ... }` as a function with the argument `(user.go)`. And that also happens on the same line with `let user`, so the `user` object has not yet even been defined, hence the error.
If we insert the semicolon, all is fine:
```js run
let user = {
name: "John",
go: function() { alert(this.name) }
}*!*;*/!*
(user.go)() // John
```
Please note that brackets around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters.

View file

@ -0,0 +1,19 @@
importance: 2
---
# Syntax check
What is the resule of this code?
```js no-beautify
let user = {
name: "John",
go: function() { alert(this.name) }
}
(user.go)()
```
P.S. There's a pitfall :)

View file

@ -0,0 +1,22 @@
Here's the explanations.
1. That's a regular object method call.
2. The same, brackets do not change the order of operations here, the dot is first anyway.
3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
```js no-beautify
f = obj.go; // calculate the expression
f(); // call what we have
```
Here `f()` is executed as a function, without `this`.
4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type.
Any operation on it except a method call (like assignment `=` or `||`) turns it into an ordinary value, which does not carry the information allowing to set `this`.

View file

@ -0,0 +1,26 @@
importance: 3
---
# Explain the value of "this"
In the code above we intend to call `user.go()` method 4 times in a row.
But calls `(1)` and `(2)` works differently from `(3)` and `(4)`. Why?
```js run no-beautify
let obj, method;
obj = {
go: function() { alert(this); }
};
obj.go(); // (1) [object Object]
(obj.go)(); // (2) [object Object]
(method = obj.go)(); // (3) undefined
(obj.go || obj.stop)(); // (4) undefined
```

View file

@ -0,0 +1,46 @@
**Answer: an error.**
Try it:
```js run
function makeUser() {
return {
name: "John",
ref: this
};
};
let user = makeUser();
alert( user.ref.name ); // Error: Cannot read property 'name' of undefined
```
That's because rules that set `this` do not look at object literals.
Here the value of `this` inside `makeUser()` is `undefined`, because it is called as a function, not as a method.
And the object literal itself has no effect on `this`. The value of `this` is one for the whole function, code blocks and object literals do not affect it.
So `ref: this` actually takes current `this` of the function.
Here's the opposite case:
```js run
function makeUser() {
return {
name: "John",
*!*
ref() {
return this;
}
*/!*
};
};
let user = makeUser();
alert( user.ref().name ); // John
```
Now it works, because `user.ref()` is a method. And the value of `this` is set to the object before dot `.`.

View file

@ -0,0 +1,23 @@
importance: 5
---
# Using "this" in object literal
Here the function `makeUser` returns an object.
What is the result of accessing its `ref`? Why?
```js
function makeUser() {
return {
name: "John",
ref: this
};
};
let user = makeUser();
alert( user.ref.name ); // What's the result?
```

View file

@ -0,0 +1,14 @@
let calculator = {
sum() {
return this.a + this.b;
},
mul() {
return this.a * this.b;
},
read() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
}
};

View file

@ -0,0 +1,28 @@
describe("calculator", function() {
context("when 2 and 3 entered", function() {
beforeEach(function() {
sinon.stub(window, "prompt");
prompt.onCall(0).returns("2");
prompt.onCall(1).returns("3");
calculator.read();
});
afterEach(function() {
prompt.restore();
});
it("the sum is 5", function() {
assert.equal(calculator.sum(), 5);
});
it("the multiplication product is 6", function() {
assert.equal(calculator.mul(), 6);
});
});
});

View file

@ -0,0 +1,23 @@
```js run demo
let calculator = {
sum() {
return this.a + this.b;
},
mul() {
return this.a * this.b;
},
read() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
}
};
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
```

View file

@ -0,0 +1,24 @@
importance: 5
---
# Create a calculator
Create an object `calculator` with three methods:
- `read()` prompts for two values and saves them as object properties.
- `sum()` returns the sum of saved values.
- `mul()` multiplies saved values and returns the result.
```js
let calculator = {
// ... your code ...
};
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
```
[demo]

View file

@ -0,0 +1,40 @@
The solution is to return the object itself from every call.
```js run
let ladder = {
step: 0,
up() {
this.step++;
*!*
return this;
*/!*
},
down() {
this.step--;
*!*
return this;
*/!*
},
showStep() {
alert( this.step );
*!*
return this;
*/!*
}
}
ladder.up().up().down().up().down().showStep(); // 1
```
We also can write a single call per line. For long chains it's more readable:
```js
ladder
.up()
.up()
.down()
.up()
.down()
.showStep(); // 1
```

View file

@ -0,0 +1,39 @@
importance: 2
---
# Chaining
There's a `ladder` object that allows to go up and down:
```js
let ladder = {
step: 0,
up() {
this.step++;
},
down() {
this.step--;
},
showStep: function() { // shows the current step
alert( this.step );
}
};
```
Now, if we need to make several calls in sequence, can do it like this:
```js
ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1
```
Modify the code of `up` and `down` to make the calls chainable, like this:
```js
ladder.up().up().down().showStep(); // 1
```
Such approach is widely used across Javascript libraries.

View file

@ -0,0 +1,31 @@
Try running it:
```js run
let str = "Hello";
str.test = 5; // (*)
alert(str.test);
```
There may be two kinds of result:
1. `undefined`
2. An error.
Why? Let's replay what's happening at line `(*)`:
1. When a property of `str` is accessed, a "wrapper object" is created.
2. The operation with the property is carried out on it. So, the object gets the `test` property.
3. The operation finishes and the "wrapper object" disappears.
So, on the last line, `str` has no trace of the property. A new wrapper object for every object operation on a string.
Some browsers though may decide to further limit the programmer and disallow to assign properties to primitives at all. That's why in practice we can also see errors at line `(*)`. It's a little bit farther from the specification though.
**This example clearly shows that primitives are not objects.**
They just can not store data.
All property/method operations are performed with the help of temporary objects.

View file

@ -0,0 +1,18 @@
importance: 5
---
# Can I add a string property?
Consider the following code:
```js
let str = "Hello";
str.test = 5;
alert(str.test);
```
How do you think, will it work? What will be shown?

View file

@ -0,0 +1,305 @@
# Object methods, "this"
Objects are usually created to represent entities of the real world, like users, orders and so on:
```js
let user = {
name: "John",
age: 30
};
```
And, in the real world, a user can `act`: to select something from the shopping cart, to login, to logout etc.
Let's implement the same in Javascript using functions in properties.
[cut]
## Method examples
For the start, let's teach the `user` to say hello:
```js run
let user = {
name: "John",
age: 30
};
*!*
user.sayHi = function() {
alert("Hello!");
};
*/!*
user.sayHi(); // Hello!
```
Here we've just used a Function Expression to create the function and assign it to the property `user.sayHi` of the object.
Then we can call it. The user now can speak!
A function that is the property of an object is called its *method*.
So, here we've got a method `sayHi` of the object `user`.
Of course, we could use a Function Declaration to add a method:
```js run
let user = {
// ...
};
*!*
// first, declare
function sayHi() {
alert("Hello!");
};
// then add the method
user.sayHi = sayHi;
*/!*
user.sayHi(); // Hello!
```
That would also work, but is longer. Also we get an "extra" function `sayHi` outside of the `user` object. Usually we don't want that.
```smart header="Object-oriented programming"
When we write our code using objects to represent entities, that's called an [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP".
OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture.
```
### Method shorthand
There exists a shorter syntax for methods in an object literal:
```js
// these objects do the same
let user = {
sayHi: function() {
alert("Hello");
}
};
// method shorthand looks better, right?
let user = {
*!*
sayHi() { // same as "sayHi: function()"
*/!*
alert("Hello");
}
};
```
As demonstrated, we can omit `"function"` and just write `sayHi()`.
To say the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred.
## "this" in methods
It's common that an object method needs to access the information stored in the object to do its job.
For instance, `user.sayHi()` may need to mention the name of the user.
**To access the object, a method can use the `this` keyword.**
The value of `this` is the object "before dot", the one used to call the method.
For instance:
```js run
let user = {
name: "John",
age: 30,
sayHi() {
*!*
alert( this.name ); // "this" means "this object"
*/!*
}
};
user.sayHi(); // John
```
Here during the execution of `user.sayHi()`, the value of `this` will be `user`.
Technically, it's also possible to access the object without `this`:
```js
...
sayHi() {
alert( *!*user.name*/!* );
}
...
```
...But such code is unreliable. If we decide to copy `user` to another variable, e.g. `admin = user` and overwrite `user` with something else, then it will access the wrong object.
That's demonstrated below:
```js run
let user = {
name: "John",
age: 30,
sayHi() {
*!*
alert( user.name ); // leads to an error
*/!*
}
};
let admin = user;
user = null; // overwrite to make things obvious
admin.sayHi(); // wops! inside sayHi(), the old name is used! error!
```
If we used `this.name` instead of `user.name` inside the `alert`, then the code would work.
## "this" is not bound
In Javascript, "this" keyword behaves unlike most other programming languages. First, it can be used in any function.
There's no syntax error in the code like that:
```js
function sayHi() {
alert( *!*this*/!*.name );
}
```
The value of `this` is evaluated during the run-time. And it can be anything.
For instance, the same function may have different "this" when called from different objects:
```js run
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
*!*
// use the same functions in two objects
user.f = sayHi;
admin.f = sayHi;
*/!*
// these calls have different this
// "this" inside the function is the object "before the dot"
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // Admin (dot or square brackets access the method doesn't matter)
```
Actually, we can call the function without an object at all:
```js run
function sayHi() {
alert(this);
}
sayHi();
```
In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error.
In non-strict mode (if you forgot `use strict`) the value of `this` in such case will be the *global object* (`"window"` for browser, we'll study it later). This is just a historical thing that `"use strict"` fixes.
Please note that usually a call of a function using `this` without an object is not normal, but rather a programming mistake. If a function has `this`, then it is usually meant to be called in the context of an object.
```smart header="The consequences of unbound `this`"
If you come from another programming languages, then you are probably used to an idea of a "bound `this`", where methods defined in an object always have `this` referencing that object.
The idea of unbound, run-time evaluated `this` has both pluses and minuses. From one side, a function can be reused for different objects. From the other side, it's possible to occasionally loose `this` by making an improper call.
Here we are not to judge whether this language design decision is good or bad. We will understand how to work with it, how to get benefits and evade problems.
```
## Internals: Reference Type
An intricate method call can loose `this`, for instance:
```js run
let user = {
name: "John",
hi() { alert(this.name); },
bye() { alert("Bye"); }
};
user.hi(); // John (the simple call works)
*!*
// now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // Error!
*/!*
```
On the last line there is an intricate code that evaluates an expression to get the method. In this case the result is `user.hi`.
The method is immediately called with brackets `()`. But that doesn't work right. You can see that the call results in an error, cause the value of `"this"` inside the call becomes `undefined`.
Actually, anything more complex than a simple `obj.method()` (or square brackets here) looses `this`.
If we want to understand why it happens -- let's get under the hood of how `obj.method()` call works.
Looking closely, we may notice two operations in `obj.method()` statement:
- the dot `'.'` retrieves the property `obj.method`.
- brackets `()` execute it (assuming that's a function).
So, you might have already asked yourself, why does it work? That is, if we put these operations on separate lines, then `this` will be lost for sure:
```js run
let user = {
name: "John",
hi() { alert(this.name); }
}
*!*
// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined
*/!*
```
That's because a function is a value of its own. It does not carry the object. So `hi = user.hi` saves it into the variable, and then on the last line it is completely standalone.
**To make `user.hi()` calls work, Javascript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).**
The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language.
The value of Reference Type is a three-value combination `(base, name, strict)`, where:
- `base` is the object.
- `name` is the property.
- `strict` is true if `use strict` is in effect.
The result of a property access `'.'` is a value of Reference Type. For `user.hi` in strict mode it is:
```js
// Reference Type value
(user, "hi", true)
```
When brackets `()` are called on the Reference Type, they receive the full information about the object and it's method, and can set the right `this` (`=user` in this case).
Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "looses" `this`.
So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj[method]()` syntax (they do the same here).
## Summary
[todo]
- Primitives except `null` and `undefined` provide many helpful methods. We plan to study those in the next chapters.
- Formally, these methods work via temporary objects, but JavaScript engines are very well tuned to optimize that internally, so they are not expensive to call.

View file

@ -0,0 +1,31 @@
Try running it:
```js run
let str = "Hello";
str.test = 5; // (*)
alert(str.test);
```
There may be two kinds of result:
1. `undefined`
2. An error.
Why? Let's replay what's happening at line `(*)`:
1. When a property of `str` is accessed, a "wrapper object" is created.
2. The operation with the property is carried out on it. So, the object gets the `test` property.
3. The operation finishes and the "wrapper object" disappears.
So, on the last line, `str` has no trace of the property. A new wrapper object for every object operation on a string.
Some browsers though may decide to further limit the programmer and disallow to assign properties to primitives at all. That's why in practice we can also see errors at line `(*)`. It's a little bit farther from the specification though.
**This example clearly shows that primitives are not objects.**
They just can not store data.
All property/method operations are performed with the help of temporary objects.

View file

@ -0,0 +1,18 @@
importance: 5
---
# Can I add a string property?
Consider the following code:
```js
let str = "Hello";
str.test = 5;
alert(str.test);
```
How do you think, will it work? What will be shown?

View file

@ -0,0 +1,95 @@
# Methods of primitives
JavaScript allows to work with primitives (strings, numbers etc) as if they were objects. They also have methods and such. Of course, primitives are not objects (and here we plan to make it even more clear), but can be used like them.
[cut]
Let's formulate the key distinction between primitives and objects.
A primitive
: Is a value of a primitive type. There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`.
An object
: Is capable of storing multiple values as properties.
Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript, e.g. functions are objects.
One of the best thing about objects is that we can store a function as one of properties:
```js run
let john = {
name: "John",
sayHi: function() {
alert("Hi buddy!");
}
};
john.sayHi(); // Hi buddy!
```
So, here we've made an object `john` with the method `sayHi`.
There exist many built-in objects, including those that work with dates, errors, HTML elements etc. They have different properties and methods.
But features come at a price!
Objects are "heavier" than primitives. They require additional resources to support the internal machinery. But properties and methods are useful in programming, Javascript engines try to optimize them, so the price is usually fair.
## A primitive as an object
Here's the paradox faced by the creator of JavaScript:
- There are many things one would want to do with a primitive like a string or a number. Could be great to access them as methods.
- Primitives must be as fast and lightweight as possible.
The solution looks a little bit awkward, but here it is.
1. Primitives are still primitive. A single value, as desired.
2. The language allows to access methods and properties of strings, numbers, booleans and symbols.
3. When it happens, a special "object wrapper" is created that provides the functionality and then is destroyed.
The "object wrappers" are different for each primitive type and are named specifically: `String`, `Number`, `Boolean` and `Symbol`. Thus they provide different sets of methods.
For instance, there exists a method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns the capitalized string.
Here's how it works:
```js run
let str = "Hello";
alert( str.toUpperCase() ); // HELLO
```
Simple, right? And here's what actually happens in `str.toUpperCase()`:
1. The string `str` is a primitive. So in the moment of accessing its property a special object is created that both knows the value of the string and has useful methods, like `toUpperCase()`.
2. That method runs and returns a new string (shown by `alert`).
3. The special object is destroyed, leaving the primitive `str` alone.
So, primitives can provide methods, but they still remain lightweight.
Of course, a JavaScript engine highly optimizes that process. Internally it may skip the creation of the extra object at all. But it must adhere to the specification and behave as if it creates one.
A number has methods of it's own, for instance, [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to the given precision:
```js run
let n = 1.23456;
alert( n.toFixed(2) ); // 1.23
```
We'll see more specific methods in chapters <info:number> and <info:string>.
````warn header="null/undefined have no methods"
Special primitives `null` and `undefined` are exceptions. They have no corresponding "wrapper objects" and provide no methods. In a sense, they are "the most primitive".
An attempt to access a property of such value would give an error:
```js run
alert(null.test); // error
````
## Summary
- Primitives except `null` and `undefined` provide many helpful methods. We plan to study those in the next chapters.
- Formally, these methods work via temporary objects, but JavaScript engines are very well tuned to optimize that internally, so they are not expensive to call.

View file

@ -0,0 +1,18 @@
Да, возможны.
Они должны возвращать одинаковый объект. При этом если функция возвращает объект, то `this` не используется.
Например, они могут вернуть один и тот же объект `obj`, определённый снаружи:
```js run no-beautify
var obj = {};
function A() { return obj; }
function B() { return obj; }
var a = new A;
var b = new B;
alert( a == b ); // true
```

View file

@ -0,0 +1,19 @@
importance: 2
---
# Две функции один объект
Возможны ли такие функции `A` и `B` в примере ниже, что соответствующие объекты `a,b` равны (см. код ниже)?
```js no-beautify
function A() { ... }
function B() { ... }
var a = new A;
var b = new B;
alert( a == b ); // true
```
Если да -- приведите пример кода с такими функциями.

View file

@ -0,0 +1,15 @@
function Calculator() {
this.read = function() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
};
this.sum = function() {
return this.a + this.b;
};
this.mul = function() {
return this.a * this.b;
};
}

View file

@ -0,0 +1,25 @@
sinon.stub(window, "prompt")
prompt.onCall(0).returns("2");
prompt.onCall(1).returns("3");
describe("calculator", function() {
var calculator;
before(function() {
calculator = new Calculator();
calculator.read();
});
it("при вводе 2 и 3 сумма равна 5", function() {
assert.equal(calculator.sum(), 5);
});
it("при вводе 2 и 3 произведение равно 6", function() {
assert.equal(calculator.mul(), 6);
});
});
after(function() {
prompt.restore();
});

View file

@ -0,0 +1,26 @@
```js run demo
function Calculator() {
this.read = function() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
};
this.sum = function() {
return this.a + this.b;
};
this.mul = function() {
return this.a * this.b;
};
}
var calculator = new Calculator();
calculator.read();
alert( "Сумма=" + calculator.sum() );
alert( "Произведение=" + calculator.mul() );
```

View file

@ -0,0 +1,24 @@
importance: 5
---
# Создать Calculator при помощи конструктора
Напишите *функцию-конструктор* `Calculator`, которая создает объект с тремя методами:
- Метод `read()` запрашивает два значения при помощи `prompt` и запоминает их в свойствах объекта.
- Метод `sum()` возвращает сумму запомненных свойств.
- Метод `mul()` возвращает произведение запомненных свойств.
Пример использования:
```js
var calculator = new Calculator();
calculator.read();
alert( "Сумма=" + calculator.sum() );
alert( "Произведение=" + calculator.mul() );
```
[demo]

View file

@ -0,0 +1,8 @@
function Accumulator(startingValue) {
this.value = startingValue;
this.read = function() {
this.value += +prompt('Сколько добавлять будем?', 0);
};
}

View file

@ -0,0 +1,37 @@
describe("Accumulator(1)", function() {
var accumulator;
before(function() {
accumulator = new Accumulator(1);
});
beforeEach(function() {
sinon.stub(window, "prompt")
});
afterEach(function() {
prompt.restore();
});
it("начальное значение 1", function() {
assert.equal(accumulator.value, 1);
});
it("после ввода 0 значение 1", function() {
prompt.returns("0");
accumulator.read();
assert.equal(accumulator.value, 1);
});
it("после ввода 1 значение 2", function() {
prompt.returns("1");
accumulator.read();
assert.equal(accumulator.value, 2);
});
it("после ввода 2 значение 4", function() {
prompt.returns("2");
accumulator.read();
assert.equal(accumulator.value, 4);
});
});

View file

@ -0,0 +1,18 @@
```js run demo
function Accumulator(startingValue) {
this.value = startingValue;
this.read = function() {
this.value += +prompt('Сколько добавлять будем?', 0);
};
}
var accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert( accumulator.value );
```

View file

@ -0,0 +1,27 @@
importance: 5
---
# Создать Accumulator при помощи конструктора
Напишите *функцию-конструктор* `Accumulator(startingValue)`.
Объекты, которые она создает, должны хранить текущую сумму и прибавлять к ней то, что вводит посетитель.
Более формально, объект должен:
- Хранить текущее значение в своём свойстве `value`. Начальное значение свойства `value` ставится конструктором равным `startingValue`.
- Метод `read()` вызывает `prompt`, принимает число и прибавляет его к свойству `value`.
Таким образом, свойство `value` является текущей суммой всего, что ввел посетитель при вызовах метода `read()`, с учетом начального значения `startingValue`.
Ниже вы можете посмотреть работу кода:
```js
var accumulator = new Accumulator(1); // начальное значение 1
accumulator.read(); // прибавит ввод prompt к текущему значению
accumulator.read(); // прибавит ввод prompt к текущему значению
alert( accumulator.value ); // выведет текущее значение
```
[demo]

View file

@ -0,0 +1,29 @@
function Calculator() {
var methods = {
"-": function(a, b) {
return a - b;
},
"+": function(a, b) {
return a + b;
}
};
this.calculate = function(str) {
var split = str.split(' '),
a = +split[0],
op = split[1],
b = +split[2]
if (!methods[op] || isNaN(a) || isNaN(b)) {
return NaN;
}
return methods[op](a, b);
}
this.addMethod = function(name, func) {
methods[name] = func;
};
}

View file

@ -0,0 +1,26 @@
var calculator;
before(function() {
calculator = new Calculator;
});
it("calculate(12 + 34) = 46", function() {
assert.equal(calculator.calculate("12 + 34"), 46);
});
it("calculate(34 - 12) = 22", function() {
assert.equal(calculator.calculate("34 - 12"), 22);
});
it("добавили умножение: calculate(2 * 3) = 6", function() {
calculator.addMethod("*", function(a, b) {
return a * b;
});
assert.equal(calculator.calculate("2 * 3"), 6);
});
it("добавили возведение в степень: calculate(2 ** 3) = 8", function() {
calculator.addMethod("**", function(a, b) {
return Math.pow(a, b);
});
assert.equal(calculator.calculate("2 ** 3"), 8);
});

View file

@ -0,0 +1,52 @@
```js run
function Calculator() {
var methods = {
"-": function(a, b) {
return a - b;
},
"+": function(a, b) {
return a + b;
}
};
this.calculate = function(str) {
var split = str.split(' '),
a = +split[0],
op = split[1],
b = +split[2]
if (!methods[op] || isNaN(a) || isNaN(b)) {
return NaN;
}
return methods[op](+a, +b);
}
this.addMethod = function(name, func) {
methods[name] = func;
};
}
var calc = new Calculator;
calc.addMethod("*", function(a, b) {
return a * b;
});
calc.addMethod("/", function(a, b) {
return a / b;
});
calc.addMethod("**", function(a, b) {
return Math.pow(a, b);
});
var result = calc.calculate("2 ** 3");
alert( result ); // 8
```
- Обратите внимание на хранение методов. Они просто добавляются к внутреннему объекту.
- Все проверки и преобразование к числу производятся в методе `calculate`. В дальнейшем он может быть расширен для поддержки более сложных выражений.

View file

@ -0,0 +1,42 @@
importance: 5
---
# Создайте калькулятор
Напишите конструктор `Calculator`, который создаёт расширяемые объекты-калькуляторы.
Эта задача состоит из двух частей, которые можно решать одна за другой.
1. Первый шаг задачи: вызов `calculate(str)` принимает строку, например "1 + 2", с жёстко заданным форматом "ЧИСЛО операция ЧИСЛО" (по одному пробелу вокруг операции), и возвращает результат. Понимает плюс `+` и минус `-`.
Пример использования:
```js
var calc = new Calculator;
alert( calc.calculate("3 + 7") ); // 10
```
2. Второй шаг -- добавить калькулятору метод `addMethod(name, func)`, который учит калькулятор новой операции. Он получает имя операции `name` и функцию от двух аргументов `func(a,b)`, которая должна её реализовывать.
Например, добавим операции умножить `*`, поделить `/` и возвести в степень `**`:
```js
var powerCalc = new Calculator;
powerCalc.addMethod("*", function(a, b) {
return a * b;
});
powerCalc.addMethod("/", function(a, b) {
return a / b;
});
powerCalc.addMethod("**", function(a, b) {
return Math.pow(a, b);
});
var result = powerCalc.calculate("2 ** 3");
alert( result ); // 8
```
- Поддержка скобок и сложных математических выражений в этой задаче не требуется.
- Числа и операции могут состоять из нескольких символов. Между ними ровно один пробел.
- Предусмотрите обработку ошибок. Какая она должна быть - решите сами.

View file

@ -0,0 +1,183 @@
# Using "new" to create objects
The regular `{...}` syntax allows to create one object. But often we need to create many similar objects.
That can be done using constructor functions and the `"new"` operator.
[cut]
## Constructor function
Constructor functions technically are regular functions. There are two agreements though:
1. They are named with capital letter first.
2. They should be executed only with `"new"` operator.
For instance:
```js run
function User(name) {
this.name = name;
this.isAdmin = false;
}
*!*
let user = new User("Jack");
*/!*
alert(user.name); // Jack
alert(user.isAdmin); // false
```
When a function is executed as `new User(...)`, it does the following steps:
1. A new empty object is created and assigned to `this`.
2. The function executes. Usually it modifies `this`, adds new properties to it.
3. The value of `this` is returned.
In other words, `new User(...)` does something like:
```js
function User(name) {
*!*
// this = {}; (implicitly)
*/!*
// we add properties to this
this.name = name;
this.isAdmin = false;
*!*
// return this; (implicitly)
*/!*
}
```
So the result of `new User("Jack")` is the same object as:
```js
let user = {
name: "Jack",
isAdmin: false
};
```
Now if we want to create other users, we can call `new User("Ann")`, `new User("Alice")` and so on. Much shorter than using literals every time, and also reads well.
That's the main purpose of constructors -- to implement reusable object creation code.
Let's note once again -- technically, any function can be used as a constructor. That is: any function can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`.
````smart header="new function() { ... }"
If we have many lines of code all about creation of a single complex object, we can wrap them in constructor function, like this:
```js
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ...other code for user creation
// maybe complex logic and statements
// local variables etc
};
```
The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code for a single complex object only.
````
## Return from constructors
Usually, constructors do not have a `return` statement. Their task is to write all necessary stuff into `this`, and it automatically becomes the result.
But if there is a `return` statement, then the rule is simple:
- If `return` is called with object, then it is returned instead of `this`.
- If `return` is called with a primitive, it's ignored.
In other words, `return` with an object returns that object, otherwise `this` is returned.
For instance, here `return` overrides `this` by returning an object:
```js run
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- returns an object
}
alert( new BigUser().name ); // Godzilla, got that object
```
And here's an example with an empty `return` (or we could place a primitive after it, doesn't matter):
```js run
function SmallUser() {
this.name = "John";
return; // finishes the execution, returns this
// ...
}
alert( new SmallUser().name ); // John
```
Most of time constructors return nothing. Here we mention the special behavior with returning objects mainly for the sake of completeness.
````smart header="Omitting brackets"
By the way, we can omit brackets after `new`, if it has no arguments:
```js
let user = new User; // <-- no brackets
// same as
let user = new User();
```
Omitting brackets here is not considered a "good style", but the syntax is permitted by specification.
````
## Methods in constructor
Using constuctor functions to create objects gives a great deal of flexibility. The constructor function may have parameters that define how to construct the object, what to put in it.
Of course, we can add to `this` not only properties, but methods as well.
For instance, `new User(name)` below creates an object with the given `name` and the method `sayHi`:
```js run
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
*!*
let john = new User("John");
john.sayHi(); // My name is: John
*/!*
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
```
## Summary
- Constructor functions or, shortly, constructors, are regular functions, but there's a common agreement to name them with capital letter first.
- Constructor functions should only be called using `new`. Such call implies a creation of empty `this` at the start and returning the populated one at the end.
We can use constructor functions to make multiple similar objects. But the topic is much deeper than described here. So we'll return it later and cover more in-depth.
As of now, it's important to understand what `new` is, because Javascript provides constructor functions for many built-in language objects: like `Date` for dates, `Set` for sets and others that we plan to study.

View file

@ -0,0 +1,239 @@
# Symbol type
By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types.
Till now we only saw strings. Now let's see the advantages that symbols can give us.
[cut]
## Symbols
"Symbol" value represents an unique identifier with a given name.
A value of this type can be created using `Symbol(name)`:
```js
// id is a symbol with the name "id"
let id = Symbol("id");
```
Symbols are guaranteed to be unique. Even if we create many symbols with the same name, they are different values.
For instance, here are two symbols with the same name -- they are not equal:
```js run
let id1 = Symbol("id");
let id2 = Symbol("id");
*!*
alert(id1 == id2); // false
*/!*
```
If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. Javascript symbols are different.
## "Private" properties
Symbols allow to create concealed, "private" properties of an object, that no other part of code can occasionally access or overwrite.
For instance, if we want to store an "identifier" for the object `user`, we can create a symbol with the name `id` for it:
```js run
let user = { name: "John" };
let id = Symbol("id");
user[id] = "ID Value";
alert( user[id] ); // we can access the data using the symbol as the key
```
Now let's imagine that another script wants to have his own "id" property inside `user`, for his own purposes. That may be another Javascript library, so the scripts are completely unaware for each other.
No problem. It can create its own `Symbol("id")`.
Their script:
```js
// ...
let id = Symbol("id");
user[id] = "Their id value";
```
There will be no conflict, because symbols are always different, even if they have the same name.
Please note that if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict:
```js run
let user = { name: "John" };
// our script uses "id" property
user.id = "ID Value";
// ...if later another script the uses "id" for its purposes...
user.id = "Their id value"
// boom! overwritten! it did not mean to harm the colleague, but did it!
```
### Symbols in literal
If we want to use a symbol in an object literal, we need square brackets.
Like this:
```js
let id = Symbol("id");
let user = {
name: "John",
*!*
[id]: 123 // not just "id: 123"
*/!*
};
```
That's because we use the value from the variable `id`, not the string "id".
### Symbols are not in loop
Symbolic properties do not participate in `for..in` loop.
For instance:
```js run
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
*!*
for(let key in user) alert(key); // name, age (no symbols)
*/!*
// the direct access by the global symbol works
alert( "Direct: " + user[Symbol.for("id")] );
```
That's a part of the general "hiding" concept. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.
In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties:
```js run
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
```
There's no paradox here. That's the expected behavior, because when we clone an object, we expect symbolic properties (like `id`) to be copied as well.
````smart header="Property keys of other types are coerced to strings"
We can only use strings or symbols as keys in objects. Other types are coerced to strings.
For instance:
```js run
let obj = {
0: "test" // same as "0": "test"
}
// bot alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
```
````
## Global symbols
Normally, all symbols are different. But sometimes we want same-named symbols to be the same.
For instance, different parts of our application want to access symbol `"id"` meaning the exactly the same property.
To achieve that, there exists a *global symbol registry*. We can create symbols in it and and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.
To can create or read a symbol in the registry, use `Symbol.for(name)`.
For instance:
```js run
// read from the global registry
let name = Symbol.for("name"); // if the symbol did not exist, it is created
// read it again
let nameAgain = Symbol.for("name");
// the same symbol
alert( name === nameAgain ); // true
```
Symbols inside the registry are called *global symbols*. If we want an application-wide symbol, accessible everywhere in the code -- that's what they are for.
```smart header="That sounds like Ruby"
In some programming languages, like Ruby, there's a single symbol per name.
In Javascript, as we can see, that's right for global symbols.
```
### Symbol.keyFor
For global symbols, not only `Symbol.for(name)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol.
For instance:
```js run
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// get name from symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
```
The `Symbol.keyFor` internally uses the global symbol registry to look up the name for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`.
For instance:
```js run
alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, non-global symbol
```
For non-global symbols, the name is only used for debugging purposes.
## System symbols
There exist many "system" symbols that Javascript uses internally, and we can use them to fine-tune various aspects of our objects.
They are listed in the specification in the [Well-known symbols](https://tc39.github.io/ecma262/#sec-well-known-symbols) table:
- `Symbol.hasInstance`
- `Symbol.isConcatSpreadable`
- `Symbol.iterator`
- `Symbol.toPrimitive`
- ...and so on.
For instance, `Symbol.toPrimitive` allows to describe object to primitive conversion. We'll see its use very soon.
Other symbols will also become familiar when we study the corresponding language features.
## Summary
- Symbol is a primitive type for unique identifiers.
- Symbols are created with `Symbol(name)` call.
- Symbols are useful if we want to create a field that only those who know the symbol can access.
- Symbols don't appear in `for..in` loops.
- Symbols created with `Symbol(name)` are always different, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(name)` returns (creates if needed) a global symbol with the given name. Multiple calls return the same symbol.
- There are system symbols used by Javascript and accessible as `Symbol.*`. We can use them to alter some built-in behaviors.
Technically, symbols are not 100% hidden. There is a build-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not completely hidden and private.
But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. And the one who explicitly calls the aforementioned methods probably understands well what he's doing.

View file

@ -0,0 +1,221 @@
# Object to primitive conversion
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 let's fill it.
[cut]
## Where and why?
The process of object to primitive conversion can be customized, here we'll see how to implement our own methods for it. But first, let's see when it happens.
The conversion of an object to primitive value (a number or a string) is a rare thing in practice.
Just think about cases when such conversion may be necessary. For instance, numeric conversion happens when we compare an object against a primitive: `user > 18`. But what such comparison actually means? Are we going to compare `18` against user's age? Then it would be more obvious to write `user.age > 18`. And it's easier to read and understand it too.
Or, for a string conversion... Where does it happen? Usually, when we output an object. But simple ways of object-as-string output like `alert(user)` are only used for debugging and logging purposes. For real stuff, the output is more complicated, we may need to provide it additional parameters. That's why we usually implement it using object methods like `user.format()` or even in more advanced ways.
So, most of the time, it's more flexible and gives more readable code to explicitly write an object property or call a method than rely on the conversion.
That said, there are still valid reasons why we should know how to-primitive conversion works.
- Simple object-as-string output may be useable sometimes. Without a customized conversion it will show `[object Object]`.
- Many built-in objects implement their own to-primitive conversion, we plan to cover that.
- Sometimes it just happens (on mistake?), and we should understand what's going on.
- Okay, the final one. There are quizzes and questions on interviews that rely on that knowledge. Looks like people think it's a good sigh that person understands Javascript if he knows type conversions well.
## ToPrimitive
The algorithm of object-to-primitive conversion is called `ToPrimitive` in [the specification](https://tc39.github.io/ecma262/#sec-toprimitive).
There are 3 types (also called "hints") of object-to-primitive conversion:
`"string"`
: For object-to-string conversions, like:
```js
// output
alert(obj);
// using object as a property key
anotherObj[obj] = value;
```
`"number"`
: For object-to-number conversions, like:
```js
// explicit conversion
let num = Number(obj);
// maths (except binary plus)
let n = +obj; // unary plus
let delta = date1 - date2;
// less/greater comparison
let greater = user1 > user2;
```
`"default"`
: Occurs in rare cases where it's not clear what is desired.
For instance:
```js
// binary plus can work both with strings (concatenates) and numbers (adds)
let total = car1 + car2;
// obj == string, number or symbol also uses default
if (user == 1) { ... };
```
There's some inconsistency here. The greater/less operator `<>` can work with both strings and numbers, it compares them differently. Still, it uses "number" hint. That's for historical reasons.
In practice, all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And probably we should do the same.
Please note -- there are only three conversions. That simple. There is no "boolean" (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions.
To do the conversion, Javascript tries to find and call these three object methods:
1. `Symbol.toPrimitive(hint)` if exists,
2. Otherwise if hint is `"string"`, try `toString()` and `valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`, try `valueOf()` and `toString()`, whatever exists.
### Symbol.toPrimitive
For instance, here `user` object implements the 1st method:
```js run
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
// conversions demo:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
```
As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion.
### toString/valueOf
Methods `toString` and `valueOf` come from the ancient times. That's why they are not symbols. They provide an alternative "old-style" way to implement the conversion.
If there's no `Symbol.toPrimitive` then Javascript tries to find them and try in the order:
- `toString -> valueOf` for "string" hint.
- `valueOf -> toString` otherwise.
For instance, here `user` does the same as above using a combination of `toString` and `valueOf`:
```js run
let user = {
name: "John",
money: 1000,
// for hint="string"
toString() {
return `{name: "${this.name}"}`;
},
// for hint="number" or "default"
valueOf() {
return this.money;
}
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
```
Often we want a single "catch-all" place to handle all primitive conversions. In this case we can implement `toString` only, like this:
```js run
let user = {
name: "John",
toString() {
return this.name;
}
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
```
In the absense of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions.
## ToPrimitive and ToString/ToNumber
The important thing to know about all primitive-conversion methods is that they 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.**
An operation that was the reason for the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
For instance:
- All mathematical operations except binary plus apply `ToNumber`:
```js run
let obj = {
toString() { // toString used for numeric conversion in the absense of valueOf
return "2";
}
};
alert(obj * 2); // 4
```
- Binary plus first checks if the primitive is a string, and then does concatenation, otherwise performs `ToNumber` and works with numbers.
String example:
```js run
let obj = {
toString() {
return "2";
}
};
alert(obj + 2); // 22 (ToPrimitive returned string => concatenation)
```
Number example:
```js run
let obj = {
toString() {
return true;
}
};
alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber)
```
## Summary
[todo describe article]
Minor notes:
- If `Symbol.toPrimitive` returns an object, that's an error.
- If `toString/valueOf` return an object, they are ignored (historical behavior).
- By default, all objects have both `toString` and `valueOf`, but `valueOf` returns the object itself, and hence is ignored.

View file

@ -0,0 +1,2 @@
# Object basics