works
This commit is contained in:
parent
d813c3dd19
commit
b043ea701d
31 changed files with 600 additions and 408 deletions
15
1-js/2-first-steps/07-types/1-string-quotes/solution.md
Normal file
15
1-js/2-first-steps/07-types/1-string-quotes/solution.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
Backticks embed the expression inside `${...}` into the string.
|
||||
|
||||
```js run
|
||||
let name = "Ilya";
|
||||
|
||||
// the expression is a number 1
|
||||
alert( `hello ${1}` ); // Hello, 1
|
||||
|
||||
// the expression is a variable, embed it
|
||||
alert( `hello ${name}` ); // Hello, Ilya
|
||||
|
||||
// the expression is a string "name"
|
||||
alert( `hello ${"name"}` ); // Hello, name
|
||||
```
|
17
1-js/2-first-steps/07-types/1-string-quotes/task.md
Normal file
17
1-js/2-first-steps/07-types/1-string-quotes/task.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# String quotes
|
||||
|
||||
What is the output of the script?
|
||||
|
||||
```js
|
||||
let name = "Ilya";
|
||||
|
||||
alert( `hello ${1}` ); // ?
|
||||
|
||||
alert( `hello ${"name"}` ); // ?
|
||||
|
||||
alert( `hello ${name}` ); // ?
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
importance: 3
|
||||
importance: 5
|
||||
|
||||
---
|
||||
|
17
1-js/2-first-steps/07-types/3-sum-object/solution.md
Normal file
17
1-js/2-first-steps/07-types/3-sum-object/solution.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
```js run
|
||||
let salaries = {
|
||||
John: 100,
|
||||
Ann: 160,
|
||||
Pete: 130
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
for(let key in salaries) {
|
||||
sum += salaries[key];
|
||||
}
|
||||
|
||||
alert(sum); // 390
|
||||
```
|
||||
|
19
1-js/2-first-steps/07-types/3-sum-object/task.md
Normal file
19
1-js/2-first-steps/07-types/3-sum-object/task.md
Normal 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.
|
||||
|
||||
Use `for..in` loop to iterate over the object.
|
13
1-js/2-first-steps/07-types/4-sum-array/solution.md
Normal file
13
1-js/2-first-steps/07-types/4-sum-array/solution.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
```js run
|
||||
let salaries = [ 100, 160, 130 ];
|
||||
|
||||
let sum = 0;
|
||||
for(let salary of salaries) {
|
||||
sum += salary;
|
||||
}
|
||||
|
||||
alert( sum ); // 390
|
||||
```
|
||||
|
13
1-js/2-first-steps/07-types/4-sum-array/task.md
Normal file
13
1-js/2-first-steps/07-types/4-sum-array/task.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Sum the array
|
||||
|
||||
Write the code to get the sum of the array using `for..of` loop and store in the variable `sum`:
|
||||
|
||||
```js
|
||||
let salaries = [ 100, 160, 130 ];
|
||||
```
|
||||
|
||||
Should be `390` here.
|
|
@ -39,7 +39,7 @@ Besides regular numbers there are so-called "special numeric values" which also
|
|||
`NaN` is sticky. Any further operation on `NaN` would give `NaN`:
|
||||
|
||||
```js run
|
||||
alert( "not a number" * 2 + 5 - 9 ); // still NaN
|
||||
alert( "not a number" * 2 + 5 - 9 ); // NaN
|
||||
```
|
||||
|
||||
So, in a long mathematical expression if we have `NaN` in one place, it propagates to the whole result.
|
||||
|
@ -66,7 +66,7 @@ In JavaScript, there are 3 types of quotes.
|
|||
|
||||
1. Double quotes: `"Hello"`.
|
||||
2. Single quotes: `'Hello'`.
|
||||
3. Backtricks: <code>`Hello`</code>.
|
||||
3. Backticks: <code>`Hello`</code>.
|
||||
|
||||
Double and single quotes are similar, "simple" quotes.
|
||||
|
||||
|
@ -178,7 +178,7 @@ let user = {
|
|||
};
|
||||
```
|
||||
|
||||
The `user` object can be imagined as an information shelf with two items labelled "name" and "age".
|
||||
The `user` object can be imagined as a cabinet with two signed files labelled "name" and "age".
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -48,6 +48,23 @@ alert(typeof a); // string
|
|||
|
||||
The string conversion is obvious. A `false` becomes `"false"`, `null` becomes `"null"` etc.
|
||||
|
||||
For objects it's a little bit trickier. By default, regular objects are converted like this:
|
||||
|
||||
```js run
|
||||
alert( {} ); [object Object]
|
||||
```
|
||||
|
||||
Although, some object subtypes have their own way of formatting, for instance, arrays turn into the comma-delimited list of items:
|
||||
|
||||
```js run
|
||||
let arr = [1,2,3];
|
||||
|
||||
alert( arr ); // 1,2,3
|
||||
alert( String(arr) === '1,2,3' ); // true
|
||||
```
|
||||
|
||||
Later we'll see how to create custom rules for string conversions for our objects.
|
||||
|
||||
## Numeric conversion
|
||||
|
||||
Numeric conversion happens in mathematical functions and expressions automatically.
|
||||
|
@ -123,7 +140,7 @@ alert( Boolean(" ") ); // any non-empty string, even whitespaces are true
|
|||
|
||||
There exist three most widely used type conversions: to string, to number and to boolean.
|
||||
|
||||
The conversion to string is usully obvious.
|
||||
The conversion to string is usully obvious for primitive values and depends on the object type for objects. For instance, arrays turn into a comma-delimited list of elements.
|
||||
|
||||
To number follows the rules:
|
||||
|
||||
|
|
|
@ -378,7 +378,7 @@ for(let key in user) {
|
|||
}
|
||||
```
|
||||
|
||||
Note that all "for" constructs allow to declare the looping variable inside the loop, like `let key` here. The name "key" for the variable is not mandatory, we could use any variable name here, usually "key" or "prop" names are used for such iterations.
|
||||
Note that all "for" constructs allow to declare the looping variable inside the loop, like `key` here. We could use another variable name here instead of `key`, for instance, "prop" is also widely used for iterations.
|
||||
|
||||
|
||||
## The "for..of" loop
|
||||
|
@ -445,3 +445,5 @@ If we don't want to do anything more on this iteration and would like to forward
|
|||
|
||||
`Break/continue` support labels before the loop. A label is the only way for `break/continue` to escape the nesting and go to the outer loop.
|
||||
|
||||
To get an array of object property names, there is a method `Object.keys(obj)`.
|
||||
|
||||
|
|
|
@ -135,12 +135,12 @@ showName("Julius", "Caesar");
|
|||
showName("Ilya");
|
||||
```
|
||||
|
||||
The downside is that `arguments` looks like an array, but it's not. It does not support many useful array features. It only exists for backwards compatibility. The rest operator is better.
|
||||
The downside is that `arguments` looks like an array, but it's not. It does not support many useful array features. It mainly exists for backwards compatibility, usually the rest operator is better.
|
||||
````
|
||||
|
||||
## Destructuring in parameters
|
||||
## Destructuring parameters
|
||||
|
||||
There are times when a function may have many parameters. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on.
|
||||
There are times when a function may have many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on.
|
||||
|
||||
Here's a bad way to write such function:
|
||||
|
||||
|
@ -158,7 +158,7 @@ Like this?
|
|||
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
|
||||
```
|
||||
|
||||
That's ugly. And becomes unreadable if we have not 4 but 10 parameters.
|
||||
That's ugly. And becomes unreadable if we deal with more parameters.
|
||||
|
||||
Destructuring comes to the rescue!
|
||||
|
||||
|
@ -221,7 +221,7 @@ showMenu({});
|
|||
showMenu();
|
||||
```
|
||||
|
||||
We can fix this of course, by making an empty object a value by default for the whole destructuring thing:
|
||||
We can fix this of course, by making `{}` a value by default for the whole destructuring thing:
|
||||
|
||||
|
||||
```js run
|
||||
|
@ -281,8 +281,7 @@ In short:
|
|||
Together they help to travel between a list and an array of parameters with ease.
|
||||
|
||||
|
||||
|
||||
## Итого
|
||||
## Summary TODO
|
||||
|
||||
Основные улучшения в функциях:
|
||||
|
||||
|
|
|
@ -267,6 +267,16 @@ The reason is the same: loss of precision. There are 64 bits for the number, 52
|
|||
JavaScript doesn't trigger an error in such case. It does the best to fit the number into the format. Unfortunately, the format is not big enough.
|
||||
````
|
||||
|
||||
```smart header="Two zeroes"
|
||||
Another funny consequence of the internal representation is there are actually two zeroes: `0` and `-0`.
|
||||
|
||||
That's because a sign is represented by a single bit, so every number can be positive or negative, including the zero.
|
||||
|
||||
In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Tests: isFinite and isNaN
|
||||
|
||||
Remember the two special numeric values?
|
||||
|
@ -310,6 +320,17 @@ alert( isFinite(num) );
|
|||
|
||||
Please note that an empty or a space-only string is treated as `0` in all numeric functions. If it's not what's needed, then additional checks are required.
|
||||
|
||||
```smart header="Compare with `Object.is`"
|
||||
|
||||
There is a special built-in method [Object.is](mdn:js/Object/is) to compare values in "even stricter way" than `===`.
|
||||
|
||||
The call `Object.is(value1, value2)` returns the same result as `value1 === value2` with two exceptions:
|
||||
|
||||
1. It can compare with `NaN`, e.g. `Object.is(NaN, NaN) === true`.
|
||||
2. Values `0` and `-0` are different: `Object.is(0, -0) === false`.
|
||||
```
|
||||
|
||||
|
||||
## parseInt and parseFloat
|
||||
|
||||
The numeric conversion using a plus `+` or `Number()` is strict. If a value is not exactly a number, it fails:
|
||||
|
|
|
@ -49,29 +49,8 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL
|
|||
|
||||
Single and double quotes come from ancient times of language creation, and the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile.
|
||||
|
||||
Backticks also allow to specify a "template function" at the beginning.
|
||||
Backticks also allow to specify a "template function" before the first backtick, the syntax is: <code>func`string`</code>. The function `func` is called automatically, receives the string and embedded expressions and can process them. You can read more in the [docs](mdn:JavaScript/Reference/Template_literals#Tagged_template_literals). That is called "tagged templates". This feature makes it easier to wrap strings into custom templating or other functionality, but is rarely used.
|
||||
|
||||
Its name is put before the first backtick. Then it receives the string and embedded expressions can can process them.
|
||||
|
||||
The syntax is:
|
||||
```js run
|
||||
function love(string, value1, value2) {
|
||||
alert( string[0] ); // Hello
|
||||
alert( string[1] ); // and
|
||||
alert( value1 ); // Ilya
|
||||
alert( value2 ); // Julia
|
||||
return value1 + ' ♥ ' + value2;
|
||||
}
|
||||
|
||||
let mom = "Julia";
|
||||
let dad = "Ilya";
|
||||
|
||||
let str = love`Hello ${mom} and ${dad}`;
|
||||
|
||||
alert(str); // 'Julia ♥ Ilya'
|
||||
```
|
||||
|
||||
In the example above, `love` is the name for the function. It is called with an array
|
||||
|
||||
## Special characters
|
||||
|
||||
|
@ -554,7 +533,7 @@ For instance:
|
|||
alert( 'Österreich'.localeCompare('Zealand') ); // -1
|
||||
```
|
||||
|
||||
The method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), that allow to specify the language (by default taken from the environment) and setup additional rules like case sensivity or should `a` and `á` be treated as the same etc.
|
||||
The method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), that allow to specify the language (by default taken from the environment) and setup additional rules like case sensivity or should `"a"` and `"á"` be treated as the same etc.
|
||||
|
||||
## Internal encoding
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
function sumSalaries(salaries) {
|
||||
|
||||
let sum = 0;
|
||||
for (let name in salaries) {
|
||||
sum += salaries[name];
|
||||
for (let salary of Object.values(salaries)) {
|
||||
sum += salary;
|
||||
}
|
||||
|
||||
return sum;
|
||||
|
|
|
@ -6,7 +6,7 @@ importance: 5
|
|||
|
||||
There is a `salaries` object with arbitrary number of salaries.
|
||||
|
||||
Write the function `sumSalaries(salaries)` that returns the sum of all salaries.
|
||||
Write the function `sumSalaries(salaries)` that returns the sum of all salaries using `Object.values` and the `for..of` loop.
|
||||
|
||||
If `salaries` is empty, then the result must be `0`.
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ function topSalary(salaries) {
|
|||
let max = 0;
|
||||
let maxName = null;
|
||||
|
||||
for (let name in salaries) {
|
||||
if (max < salaries[name]) {
|
||||
max = salaries[name];
|
||||
for(let [name, salary] of Object.entries(salaries)) {
|
||||
if (max < salary) {
|
||||
max = salary;
|
||||
maxName = name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,3 +19,4 @@ Create the function `topSalary(salaries)` that returns the name of the top-paid
|
|||
- If `salaries` is empty, it shoul return `null`.
|
||||
- If there are multiple top-paid persons, return any of them.
|
||||
|
||||
P.S. Use `Object.entries` and destructuring to iterate over key/value pairs.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
function count(obj) {
|
||||
return Object.keys(obj).length;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
describe("count", function() {
|
||||
it("counts the number of properties", function() {
|
||||
assert.equal( count({a: 1, b: 2}), 2 );
|
||||
});
|
||||
|
||||
it("returns 0 for an empty object", function() {
|
||||
assert.equal( count({}), 0 );
|
||||
});
|
||||
|
||||
it("ignores symbolic properties", function() {
|
||||
assert.equal( count({ [Symbol('id')]: 1 }), 0 );
|
||||
});
|
||||
});
|
21
1-js/4-data-structures/4-object/5-count-properties/task.md
Normal file
21
1-js/4-data-structures/4-object/5-count-properties/task.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Count properties
|
||||
|
||||
Write a function `count(obj)` that returns the number of properties in the object:
|
||||
|
||||
```js
|
||||
let user = {
|
||||
name: 'John',
|
||||
age: 30
|
||||
};
|
||||
|
||||
alert( count(user) ); // 2
|
||||
```
|
||||
|
||||
Try to make the code as short as possible.
|
||||
|
||||
P.S. Ignore symbolic properties, count only "regular" ones.
|
||||
|
|
@ -5,9 +5,14 @@ describe("multiplyNumeric", function() {
|
|||
height: 300,
|
||||
title: "My menu"
|
||||
};
|
||||
multiplyNumeric(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({}) );
|
||||
});
|
||||
|
||||
});
|
|
@ -5,7 +5,7 @@ Objects in JavaScript combine two functionalities.
|
|||
1. First -- they are "associative arrays": a structure for storing keyed data.
|
||||
2. Second -- they provide features for object-oriented programming.
|
||||
|
||||
Here we concentrate on the first part: using objects as a data store. That's the required base for studying the second part.
|
||||
Here we concentrate on the first part: using objects as a data store, and we will study it in-depth. That's the required base for studying the second part.
|
||||
|
||||
An [associative array](https://en.wikipedia.org/wiki/Associative_array), also called "a hash" or "a dictionary" -- is a data structure for storing arbitrary data in the key-value format.
|
||||
|
||||
|
@ -15,9 +15,10 @@ We can imagine it as a cabinet with signed files. Every piece of data is stored
|
|||
|
||||

|
||||
|
||||
## Creation
|
||||
|
||||
An empty object ("empty cabinet") can be created using one of two syntaxes:
|
||||
## Object literals
|
||||
|
||||
An empty object ("empty cabinet") can be created using one of to syntaxes:
|
||||
|
||||
```js
|
||||
let user = new Object(); // works the same as below
|
||||
|
@ -34,52 +35,12 @@ We can set properties immediately:
|
|||
let user = {
|
||||
name: "John",
|
||||
age: 30,
|
||||
"likes birds": true
|
||||
"likes birds": true // multiword property name must be quoted
|
||||
};
|
||||
```
|
||||
|
||||

|
||||
|
||||
A property name can be only a string (or a symbol, but we do not consider them here). We can try using boolean/numeric names, but they will be treated as strings automatically. Note that "complex" property names, like multiword ones, need to be quoted, to evade syntax errors.
|
||||
|
||||
|
||||
````smart header="Any word or a number can be a property"
|
||||
If something can be a variable name, then it can be used as a property name without quotes.
|
||||
|
||||
But for variables, there are additional limitations:
|
||||
|
||||
- A variable cannot start with a number.
|
||||
- Language-reserved words like `let`, `return`, `function` etc are disallowed.
|
||||
|
||||
These are lifted from literal objects. See:
|
||||
|
||||
```js run
|
||||
let example = {
|
||||
let: 1, // reserved words can be properties
|
||||
return: 2, // they even don't need quotes!
|
||||
function: 3
|
||||
};
|
||||
|
||||
// working fine:
|
||||
alert(example.let + example.return + example.function); // 6
|
||||
```
|
||||
|
||||
So, actually, any number or a valid variable name (even reserved) can be a property and needs no quotes. Quotes allow to use arbitrary strings.
|
||||
````
|
||||
|
||||
|
||||
## Add/remove properties
|
||||
|
||||
Now we can read/write new properties using the dot notation and remove using the `delete` operator:
|
||||
|
||||
```js
|
||||
user.surname = "Smith";
|
||||
|
||||
delete user.age;
|
||||
```
|
||||
|
||||
## Square brackets
|
||||
|
||||
To access a property, there are two syntaxes:
|
||||
|
||||
- The dot notation: `user.name`
|
||||
|
@ -104,7 +65,9 @@ alert( user[key] ); // John (if enter "name"), 30 for the "age"
|
|||
|
||||
The square brackets literally say: "take the property name from the variable".
|
||||
|
||||
Also it is possible to use square brackets in object definition when the property name is stored in a variable or computed:
|
||||
Also it is handy to use square brackets in an object literal, when the property name is stored in a variable.
|
||||
|
||||
That's called a *computed property*:
|
||||
|
||||
```js run
|
||||
let fruit = prompt("Which fruit to buy?", "apple");
|
||||
|
@ -124,19 +87,68 @@ let bag = {};
|
|||
bag[fruit] = 5;
|
||||
```
|
||||
|
||||
...But one statement instead of two.
|
||||
|
||||
We could have used a more complex expression inside square brackets or a quoted string. Anything that would return a property name:
|
||||
|
||||
```js
|
||||
let fruit = 'apple';
|
||||
let bag = {
|
||||
[ fruit.toLowerCase() ]: 5 // if fruit is "APPLE" then bag.apple = 5
|
||||
[ fruit.toUpperCase() ]: 5 // bag.APPLE = 5
|
||||
};
|
||||
```
|
||||
|
||||
## Check for existance
|
||||
|
||||
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's actually a common way to test whether the property exists -- to get it and compare vs undefined:
|
||||
````smart header="Property name must be either a string or a symbol"
|
||||
We can only use strings or symbols as property names.
|
||||
|
||||
Other values are converted to strings, for instance:
|
||||
|
||||
```js run
|
||||
let obj = {
|
||||
0: "test" // same as "0": "test"
|
||||
}
|
||||
|
||||
alert( obj["0"] ); // test
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
````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. With one exclusion. There's a built-in property named `__proto__` with a special functionality (we'll cover it later), which can't be set to a non-object value:
|
||||
|
||||
```js run
|
||||
let obj = {};
|
||||
obj.__proto__ = 5;
|
||||
alert(obj.__proto__); // [object Object], didn't work as intended
|
||||
```
|
||||
|
||||
If we want to store *arbitrary* (user-provided) keys, then this can be a source of bugs. There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in a few chapters, it can support arbitrary keys.
|
||||
````
|
||||
|
||||
## Removing a property
|
||||
|
||||
There's a `delete` operator for that:
|
||||
|
||||
```js
|
||||
delete user.name;
|
||||
```
|
||||
|
||||
## 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 = {};
|
||||
|
@ -171,8 +183,10 @@ let key = "age";
|
|||
alert( key in user ); // true, takes the value of key and checks for such property
|
||||
```
|
||||
|
||||
The `in` operator works in the certain case when the previous method doesn't. That is: when an object property stores `undefined`.
|
||||
````smart header="The property which equals `undefined`"
|
||||
Thee is a case when `"=== undefined"` check fails, but the `"in"` operator works correctly.
|
||||
|
||||
It's when an object property stores `undefined`:
|
||||
|
||||
```js run
|
||||
let obj = { test: undefined };
|
||||
|
@ -180,156 +194,136 @@ let obj = { test: undefined };
|
|||
alert( obj.test ); // undefined, no such property?
|
||||
|
||||
alert( "test" in obj ); // true, the property does exist!
|
||||
alert( "no-such-property" in obj ); // false, no such property (just for the contrast)
|
||||
```
|
||||
|
||||
In the code above, the property `obj.test` stores `undefined`, so the first check fails. But the `in` operator puts things right.
|
||||
In the code above, the property `obj.test` stores `undefined`, so the first check fails.
|
||||
But the `in` operator puts things right.
|
||||
|
||||
Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for unknown values. So the `in` operator is an exotic guest in the code.
|
||||
|
||||
|
||||
## The "for..in" loop [#for..in]
|
||||
|
||||
To process every object property, there's a special loop: `for..in`.
|
||||
|
||||
This syntax construct is a little bit different from the `for(;;)` that we've covered before:
|
||||
|
||||
```js
|
||||
for (key in obj) {
|
||||
/* ... do something with obj[key] ... */
|
||||
}
|
||||
```
|
||||
|
||||
The loop iterates over properties of `obj`. For each property it's name is writen in the `key` variable and the loop body is called.
|
||||
|
||||
````smart header="Inline variable declaration: `for (let key in obj)`"
|
||||
A variable for property names can be declared right in the loop:
|
||||
|
||||
```js
|
||||
for (*!*let key*/!* in menu) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The variable `key` will be only visible inside the loop body. Also we could use another variable name, like: `for(let prop in menu)`.
|
||||
````
|
||||
|
||||
An example of the iteration:
|
||||
## Property shorthands
|
||||
|
||||
```js run
|
||||
let menu = {
|
||||
width: 300,
|
||||
height: 200,
|
||||
title: "Menu"
|
||||
};
|
||||
There are two more syntax features to write a shorter code.
|
||||
|
||||
for (let key in menu) {
|
||||
// the code will be called for each menu property
|
||||
// ...and show its name and value
|
||||
Property value shorthands
|
||||
: To create a property from a variable:
|
||||
|
||||
*!*
|
||||
alert( `Key:${key}, value:${menu[key]}` );
|
||||
*/!*
|
||||
```js
|
||||
let name = "John";
|
||||
|
||||
// same as { name: name }
|
||||
let user = { name };
|
||||
```
|
||||
|
||||
If we have a variable and want to add a same-named property, that's the way to write it shorter.
|
||||
|
||||
```js
|
||||
// can combine normal properties and shorthands
|
||||
let user = { name, age: 30 };
|
||||
```
|
||||
|
||||
Methods definitions
|
||||
: For properties that are functions, there's also a shorter syntax.
|
||||
|
||||
```js
|
||||
// these two objects are equal
|
||||
|
||||
let user = {
|
||||
sayHi: function() {
|
||||
alert("Hello");
|
||||
}
|
||||
};
|
||||
|
||||
let user = {
|
||||
sayHi() { // same as "sayHi: function()"
|
||||
alert("Hello");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
To say the truth, these notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter.
|
||||
|
||||
## Loops
|
||||
|
||||
We've already seen one of the most popular loops: `for..in`
|
||||
|
||||
```js
|
||||
for(let key in obj) {
|
||||
// key iterates over object keys
|
||||
}
|
||||
```
|
||||
|
||||
Note that we're using the square brackets: `menu[key]`. As we've seen before, if the property name is stored in a variable, then we must use square brackets, not the dot notation.
|
||||
But there are also ways to get keys, values or or key/value pairs as arrays:
|
||||
|
||||
### Counting properties
|
||||
- [Object.keys(obj)](mdn:js/Object/keys) -- returns the array of keys.
|
||||
- [Object.values(obj)](mdn:js/Object/values) -- returns the array of values.
|
||||
- [Object.entries(obj)](mdn:js/Object/entries) -- returns the array of `[key, value]` pairs.
|
||||
|
||||
How to see how many properties are stored in the object? There's no method for that.
|
||||
For instance:
|
||||
|
||||
Although, we can count:
|
||||
|
||||
```js run
|
||||
let menu = {
|
||||
width: 300,
|
||||
height: 200,
|
||||
title: "Menu"
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30
|
||||
};
|
||||
```
|
||||
|
||||
*!*
|
||||
let counter = 0;
|
||||
- `Object.keys(user) = [name, age]`
|
||||
- `Object.values(user) = ["John", 30]`
|
||||
- `Object.entries(user) = [ ["name","John"], ["age",30] ]`
|
||||
|
||||
for (let key in menu) {
|
||||
counter++;
|
||||
|
||||
We can use `Object.values` to iterate over object values:
|
||||
|
||||
```js
|
||||
for(let value of Object.values(obj)) {
|
||||
// value iterates over object values
|
||||
}
|
||||
*/!*
|
||||
|
||||
alert( `Total ${counter} properties` ); // Total 3 properties
|
||||
```
|
||||
|
||||
In the next chapter we'll study arrays and see that there's a shorter way: `Object.keys(menu).length`.
|
||||
Here `Object.values(obj)` returns the array of properties, and `for..of` iterates over the array.
|
||||
|
||||
### Are objects ordered?
|
||||
Also we can combine destructuring with `Object.entries` to iterate over key/value pairs:
|
||||
|
||||
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
|
||||
```js
|
||||
for(let [key, value] of Object.entries(obj)) {
|
||||
// key,value iterate over properties
|
||||
}
|
||||
```
|
||||
|
||||
The object is used to generate HTML `<select>` list. If we're making a site mainly for German audience then we probably want `49` to be the first.
|
||||
|
||||
But if we try to loop over the object, we see a totally different picture: USA (1) goes first, then Switzerland (41) and so on.
|
||||
|
||||
That's because according to the language stantard objects have no order. The loop is officially allowed to list properties randomly.
|
||||
|
||||
But in practice, there's a de-facto agreement among modern JavaScript engines.
|
||||
|
||||
- The numeric properties are sorted.
|
||||
- Non-numeric properties are ordered as they appear in the object.
|
||||
|
||||
That agreement is not enforced by a standard, but stands strong, because a lot of JavaScript code is already based on it.
|
||||
|
||||
Now it's easy to see that the properties were iterated in the ascending order, because they are numeric... Of course, object property names are strings, but the Javascript engine detects that it's a number and applies internal optimizations to it, including sorting. That's why we see `1, 41, 44, 49`.
|
||||
|
||||
On the other hand, if the keys are non-numeric, then they are listed as they appear, for instance:
|
||||
The example of all 3 loops:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
surname: "Smith"
|
||||
age: 30
|
||||
};
|
||||
user.age = 25; // add one more
|
||||
|
||||
*!*
|
||||
// as they appear in the object
|
||||
*/!*
|
||||
for (let prop in user) {
|
||||
alert( prop ); // name, surname, age
|
||||
// over keys
|
||||
for(let key in user) {
|
||||
alert(key); // name, then age
|
||||
// can get the values with user[key]
|
||||
}
|
||||
|
||||
// over values
|
||||
for(let value of Object.values(user)) {
|
||||
alert(value); // John, then 30
|
||||
}
|
||||
|
||||
// over key/value pairs
|
||||
for(let [key, value] of Object.entries(user)) {
|
||||
alert(key + ':' + value); // name:John, then age:30
|
||||
}
|
||||
```
|
||||
|
||||
So, to fix the issue with the phone codes, we can "cheat" by making the codes non-numeric. Adding a plus `"+"` sign before each code is enough.
|
||||
|
||||
Like this:
|
||||
```smart header="The loops ignore symbolic properties"
|
||||
All 3 forms of loops (and the given `Object` methods) ignore properties that use `Symbol(...)` as keys.
|
||||
|
||||
```js run
|
||||
let codes = {
|
||||
"+49": "Germany",
|
||||
"+41": "Switzerland",
|
||||
"+44": "Great Britain",
|
||||
// ..,
|
||||
"+1": "USA"
|
||||
};
|
||||
|
||||
for(let code in codes) {
|
||||
// explicitly convert each code to a number if required
|
||||
alert( +code ); // 49, 41, 44, 1
|
||||
}
|
||||
That's actually a wise thing, because symbols are created to make sure that the property can not be accessed accidentaly. There is a separate method named [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys (if we really know what we're doing and insist on that).
|
||||
```
|
||||
|
||||
Now it works as intended.
|
||||
|
||||
|
||||
## Copying by reference
|
||||
|
||||
|
@ -398,7 +392,7 @@ alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
|
|||
|
||||
Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified.
|
||||
|
||||
### Cloning objects
|
||||
## Cloning objects
|
||||
|
||||
What if we need to duplicate an object? Create an independant copy, a clone?
|
||||
|
||||
|
@ -503,23 +497,106 @@ To fix that, we should examine the value of `user[key]` in the cloning loop and
|
|||
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). We can use a ready implementation from the Javascript library [lodash](https://lodash.com). The method is [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
|
||||
|
||||
|
||||
## Ordering
|
||||
|
||||
Are objects ordered? If we loop over an object, do we get all properties in the same order that they are in the `{...}` definition?
|
||||
|
||||
The answer is "yes" for non-numeric properties, "no" for others.
|
||||
|
||||
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 is used to generate HTML `<select>` list. If we're making a site mainly for German audience then we probably want `49` to be the first.
|
||||
|
||||
But if we try to loop over the object, we see a totally different picture:
|
||||
|
||||
- USA (1) goes first
|
||||
- then Switzerland (41) and so on.
|
||||
|
||||
That's because according to the language stantard objects have no order. The loop is officially allowed to list properties randomly.
|
||||
|
||||
But in practice, there's a de-facto agreement among modern JavaScript engines.
|
||||
|
||||
- The numeric properties are sorted.
|
||||
- Non-numeric properties are ordered as they appear in the object.
|
||||
|
||||
That agreement is not enforced by a standard, but stands strong, because a lot of JavaScript code is already based on it.
|
||||
|
||||
Now it's easy to see that the properties were iterated in the ascending order, because they are numeric... Of course, object property names are strings, but the Javascript engine detects that it's a number and applies internal optimizations to it, including sorting. That's why we see `1, 41, 44, 49`.
|
||||
|
||||
On the other hand, if the keys are non-numeric, then they are listed as they appear, for instance:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
surname: "Smith"
|
||||
};
|
||||
user.age = 25; // add one more
|
||||
|
||||
*!*
|
||||
// as they appear in the object
|
||||
*/!*
|
||||
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-numeric. 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) {
|
||||
// explicitly convert each code to a number if required
|
||||
alert( +code ); // 49, 41, 44, 1
|
||||
}
|
||||
```
|
||||
|
||||
Now it works as intended.
|
||||
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
Objects are associative arrays with several special features.
|
||||
|
||||
- Property names are always strings.
|
||||
- Values can be of any type
|
||||
- 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.
|
||||
- Loop over all properties with the `for..in` loop.
|
||||
- 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.
|
||||
|
||||
Numeric properties are sorted, otherwise they are iterated in the declaration order. To keep the order for numeric properties, we can prepend them with `+` to make them seem non-numeric.
|
||||
|
||||
Copying:
|
||||
- Ordering:
|
||||
- Non-numeric properties keep the order.
|
||||
- Numeric properties are sorted. 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.
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
# Arrays basics
|
||||
# Arrays
|
||||
|
||||
As we've seen before, objects in Javascript store arbitrary keyed values. Any string can be a key.
|
||||
Arrays is the built-in subtype of objects, suited to store ordered collections.
|
||||
|
||||
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's difficult to use an object here, because it provides no methods to manage the order of elements. We can't easily access the n-th element. Also we can't insert a new property "before" the existing ones, and so on. It's just not meant for such use.
|
||||
|
||||
For this purpose, there is a special type of objects in JavaScript, named "an array".
|
||||
In this chapter we'll study them in-detail and learn the methods to manipulate them.
|
||||
|
||||
[cut]
|
||||
|
||||
|
@ -91,7 +87,9 @@ A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is one of mo
|
|||
|
||||
In practice we meet it very often. For example, a queue of messages that need to be shown on-screen.
|
||||
|
||||
There's another closely related data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)). It supports two operations:
|
||||
There's another closely related data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
|
||||
|
||||
It supports two operations:
|
||||
|
||||
- `push` adds an element to the end.
|
||||
- `pop` takes an element to the end.
|
||||
|
@ -102,9 +100,13 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to
|
|||
|
||||

|
||||
|
||||
Arrays in Javascript can work both as a queue and as a stack. They allow to add/remove elements both to/from the beginning or the end. In computer science such a data structure is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
|
||||
Stacks are useful when we need to postpone a thing. Like, if we are busy doing something and get a new task to do, we can put (push) it onto the stack, and do so with all new tasks while we are busy. Later when we're done, we can take (pop) the latest task from the stack, and repeat it every time when we get freed. So, no task gets forgotten and the latest task is always the top to execute.
|
||||
|
||||
**The methods that work with the end of the array:**
|
||||
Arrays in Javascript can work both as a queue and as a stack. They allow to add/remove elements both to/from the beginning or the end.
|
||||
|
||||
In computer science such data structure is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
|
||||
|
||||
**Methods that work with the end of the array:**
|
||||
|
||||
`pop`
|
||||
: Extracts the last element of the array and returns it:
|
||||
|
@ -130,7 +132,7 @@ Arrays in Javascript can work both as a queue and as a stack. They allow to add/
|
|||
|
||||
The call `fruits.push(...)` is equal to `fruits[fruits.length] = ...`.
|
||||
|
||||
**The methods that work with the beginning of the array:**
|
||||
**Methods that work with the beginning of the array:**
|
||||
|
||||
`shift`
|
||||
: Extracts the first element of the array and returns it:
|
||||
|
@ -168,28 +170,31 @@ alert( fruits );
|
|||
|
||||
## Internals
|
||||
|
||||
An array is a special kind of object. Numbers are used as keys. And there are special methods and optimizations to work with ordered collections of data, but at the core it's still an object.
|
||||
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.
|
||||
|
||||
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. For instance, it is passed by reference:
|
||||
The "extras" are 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.
|
||||
|
||||
For instance, it is passed by reference, and modifications are visible from everywhere:
|
||||
|
||||
```js run
|
||||
let fruits = ["Banana"]
|
||||
|
||||
let arr = fruits;
|
||||
let copy = fruits;
|
||||
|
||||
alert( arr === fruits ); // the same object
|
||||
alert( copy === fruits ); // the same object
|
||||
|
||||
arr.push("Pear"); // modify it?
|
||||
copy.push("Pear"); // add to copy?
|
||||
|
||||
alert( fruits ); // Banana, Pear
|
||||
alert( arr === fruits ); // still the same single object
|
||||
alert( fruits ); // Banana, Pear - no, we modified fruits (the same object)
|
||||
```
|
||||
|
||||
But what really makes arrays special is their internal representation. The engine tries to store it's elements in the contiguous memory area, one after another, just as painted on the illustrations in this chapter. There are other optimizations as well.
|
||||
But what really makes arrays special is their internal representation. The engine tries to store it's elements in the contiguous memory area, one after another, just as painted on the illustrations in this chapter. There are other optimizations as well, to make it work really fast.
|
||||
|
||||
But things break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.
|
||||
But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.
|
||||
|
||||
For instance, technically we can go like that:
|
||||
For instance, technically we can do like that:
|
||||
|
||||
```js
|
||||
let fruits = []; // make an array
|
||||
|
@ -199,19 +204,17 @@ fruits[99999] = 5; // assign a property with the index far greater than its leng
|
|||
fruits.age = 25; // create a property with an arbitrary name
|
||||
```
|
||||
|
||||
That's possible, because are objects at base. We can add any properties to them.
|
||||
That's possible, because arrays are objects at base. We can add any properties to them.
|
||||
|
||||
But the engine will see that we're working with the array as with a regular object. Array-specific optimizations will be turned off, their benefits disappear.
|
||||
But the engine will see that we're working with the array as with a regular object. Array-specific optimizations are not suited for such cases and will be turned off, their benefits disappear.
|
||||
|
||||
The ways to misuse an array:
|
||||
|
||||
- Add a non-numeric property like `arr.test = 5`.
|
||||
- Make holes, like add `arr[0]` and then `arr[1000]`.
|
||||
- Fill the array in reverse order, like `arr[1000]`, `arr[999]` and so on.
|
||||
- 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.
|
||||
|
||||
Please think of arrays as about special structures to work with the *ordered data*. They provide special methods for that. And there's the `length` property for that too, which auto-increases and decreases when we add/remove the data.
|
||||
|
||||
Arrays are specially suited and carefully tuned inside Javascript engines to work with ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object `{}`.
|
||||
Please think of arrays as about special structures to work with the *ordered data*. They provide special methods for that. Arrays are carefully tuned inside Javascript engines to work with contiguous ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object `{}`.
|
||||
|
||||
## Performance
|
||||
|
||||
|
@ -253,55 +256,27 @@ fruits.pop(); // take 1 element from the end
|
|||
|
||||
The similar thing with the `push` method.
|
||||
|
||||
## Loops
|
||||
|
||||
## The for..of and other loops
|
||||
|
||||
To process all elements, we can use the regular `for` loop:
|
||||
We've already seen two suitable variants of `for` loop:
|
||||
|
||||
```js run
|
||||
let arr = ["Apple", "Orange", "Pear"];
|
||||
|
||||
*!*
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
*/!*
|
||||
alert( arr[i] );
|
||||
}
|
||||
*/!*
|
||||
```
|
||||
|
||||
That's the most optimized and fastest way to loop over the array.
|
||||
|
||||
There's an alternative kind of loops: `for..of`.
|
||||
|
||||
The syntax:
|
||||
```js
|
||||
for(let item of arr) {
|
||||
// item is an element of arr
|
||||
}
|
||||
```
|
||||
|
||||
Reminds of `for..in`, right? But a totally different beast.
|
||||
|
||||
The `for..of` loop works with *iterable* objects. An *iterable* is an object that has a special method named `object[Symbol.iterator]`. We don't need to go any deeper now, because this topic deserves a special chapter and it's going to get it.
|
||||
|
||||
For now it's enough to know that arrays and many other data structures in modern browsers are iterable. That is, they have proper built-in methods to work with `for..of`.
|
||||
|
||||
So we can loop over an array like this:
|
||||
|
||||
```js run
|
||||
let arr = ["Apple", "Orange", "Pear"];
|
||||
|
||||
*!*
|
||||
for (let fruit of arr) {
|
||||
for(let item of arr) {
|
||||
*/!*
|
||||
alert( fruit ); // Apple, Orange, Pear
|
||||
alert( item );
|
||||
}
|
||||
```
|
||||
|
||||
Looks clean and nice, right?
|
||||
|
||||
|
||||
````warn header="Don't use `for..in` for arrays"
|
||||
Technically, because arrays are objects, we could use `for..in`:
|
||||
Technically, because arrays are objects, it is also possible to use `for..in`:
|
||||
|
||||
```js run
|
||||
let arr = ["Apple", "Orange", "Pear"];
|
||||
|
@ -319,15 +294,17 @@ But that's actually a bad idea. There are potential problems with it:
|
|||
|
||||
In the browser as well as in other environments, there are many collections of elements that *look like arrays*. That is, they have `length` and indexes properties, but they have *other non-numeric properties too*, which we usually don't need. The `for..in` loop will list them. If we need to work with arrays and those array-like structures, then these "extra" properties can become a problem.
|
||||
|
||||
2. The `for (let i=0; i<arr.length; i++)` loop in modern engines runs very fast, 10-100 times faster than `for..in`, because it is specially optimized for arrays.
|
||||
````
|
||||
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower.
|
||||
|
||||
So, as a generic recipe, please use:
|
||||
- `for(let item of arr)` -- as a nice-looking variant, probably most of time,
|
||||
- `for(let i=0; i<arr.length; i++)` -- as a fastest, old-browser-compatible version,
|
||||
- `for(let i in arr)` -- never.
|
||||
So we should never use `for..in` for arrays.
|
||||
|
||||
```smart header="Iterable objects"
|
||||
Javascript has a generic concept of *iterables* or, in other words, "array-like" objects.
|
||||
|
||||
An array-like object can have methods and properties of its own, but also implement special methods to be useable in `for..of` loop. We'll often meet such objects and meanwhile will learn to implement iterables by ourselves.
|
||||
|
||||
TODO ??????????????????HERE ?????????????
|
||||
```
|
||||
|
||||
|
||||
## A word about "length"
|
||||
|
@ -377,14 +354,11 @@ If `new Array` is called with a single argument which is a number, then it creat
|
|||
Let's see how one can shoot himself in the foot:
|
||||
|
||||
```js run
|
||||
let arr = new Array(2, 3);
|
||||
alert( arr[0] ); // 2, created an array of [2, 3], all fine
|
||||
let arr = new Array(2); // will it create an array of [2] ?
|
||||
|
||||
*!*
|
||||
arr = new Array(2); // will it create an array of [2] ?
|
||||
alert( arr[0] ); // undefined! no elements.
|
||||
|
||||
alert( arr.length ); // length 2
|
||||
*/!*
|
||||
```
|
||||
|
||||
In the code above, `new Array(number)` has all elements `undefined`.
|
||||
|
@ -406,37 +380,9 @@ alert( matrix[1][1] ); // the central element
|
|||
```
|
||||
|
||||
|
||||
## Object.keys(obj)
|
||||
|
||||
In the section about objects we talked about iterating over properties in a `for..in` loop.
|
||||
|
||||
The method [Object.keys(obj)](mdn:js/Object/keys) returns an array of properties in exactly the same order as `for..in`.
|
||||
|
||||
It should be called exactly as given, not `obj.keys()`, but `Object.keys(obj)`.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let phoneCodeByCountry = {
|
||||
44: "London",
|
||||
7: "Russia",
|
||||
1: "Usa"
|
||||
};
|
||||
|
||||
let codes = Object.keys(phoneCodeByCountry);
|
||||
|
||||
alert( codes ); // 1, 7, 44
|
||||
alert( typeof codes[0] ); // string
|
||||
```
|
||||
|
||||
|
||||
Note that object keys are always converted to strings, the last `typeof` highlights that.
|
||||
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
An array is a special kind of objects, suited to store and manage ordered data items.
|
||||
Array is a special kind of objects, suited to store and manage ordered data items.
|
||||
|
||||
- The declaration:
|
||||
|
||||
|
@ -461,11 +407,8 @@ We can use an array as a deque with the following operations:
|
|||
- `unshift(...items)` adds items to the beginning.
|
||||
|
||||
To loop over the elements of the array:
|
||||
- `for(let item of arr)` -- looks nice,
|
||||
- `for(let item of arr)` -- the modern syntax,
|
||||
- `for(let i=0; i<arr.length; i++)` -- works fastest, old-browser-compatible.
|
||||
- `for(let i in arr)` -- never use.
|
||||
|
||||
To get an array of object properties:
|
||||
- `Object.keys(obj)`
|
||||
|
||||
That were the "extended basics". There are more methods. In the next chapter we'll study them in detail.
|
||||
|
|
|
@ -6,11 +6,11 @@ Arrays provide a lot of methods. In this chapter we'll study them more in-depth.
|
|||
|
||||
## split and join
|
||||
|
||||
The situation from the real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: `John, Pete, Mary`. But for us an array of nameswould be much more comfortable than a single string.
|
||||
Here's the situation from the real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: `John, Pete, Mary`. But for us an array of names would be much more comfortable than a single string. How to get it?
|
||||
|
||||
The [str.split(s)](mdn:js/String/split) method turns the string into array splitting it by the given delimiter `s`.
|
||||
The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`.
|
||||
|
||||
In the example below, we split by a comma and a space:
|
||||
In the example below, we split by a comma followed by space:
|
||||
|
||||
```js run
|
||||
let names = 'Bilbo, Gandalf, Nazgul';
|
||||
|
@ -56,18 +56,20 @@ alert( str ); // Bilbo;Gandalf;Nazgul
|
|||
|
||||
How to delete an element from the array?
|
||||
|
||||
The arrays are objects, so we can use a generic `delete` call:
|
||||
The arrays are objects, so we can try to use `delete`:
|
||||
|
||||
```js run
|
||||
var arr = ["I", "go", "home"];
|
||||
|
||||
delete arr[1]; // remove "go"
|
||||
|
||||
// now arr = ["I", , "home"];
|
||||
alert( arr[1] ); // undefined
|
||||
|
||||
// now arr = ["I", , "home"];
|
||||
alert( arr.length ); // 3
|
||||
```
|
||||
|
||||
The element was removed, but the array still has `arr.length == 3`.
|
||||
The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`.
|
||||
|
||||
That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.
|
||||
|
||||
|
@ -77,11 +79,11 @@ The [arr.splice(str)](mdn:js/Array/splice) method is a swiss army knife for arra
|
|||
|
||||
The syntax is:
|
||||
|
||||
```
|
||||
```js
|
||||
arr.splice(index[, deleteCount, elem1, ..., elemN])
|
||||
```
|
||||
|
||||
It removes `deleteCount` elements, starting from the index `index`, and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements.
|
||||
It starts from the position `index`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements.
|
||||
|
||||
This method is easy to grasp by examples.
|
||||
|
||||
|
@ -97,6 +99,8 @@ arr.splice(1, 1); // from index 1 remove 1 element
|
|||
alert( arr ); // ["I", "JavaScript"]
|
||||
```
|
||||
|
||||
Easy, right? Starting from the index `1` it removed `1` element.
|
||||
|
||||
In the next example we remove 3 elements and replace them by the other two:
|
||||
|
||||
```js run
|
||||
|
@ -119,7 +123,7 @@ let removed = arr.splice(0, 2);
|
|||
alert( removed ); // "I", "study" <-- array of removed elements
|
||||
```
|
||||
|
||||
The `splice` method is also able to only insert the elements, without any removals. For that we need to set `deleteCount` to `0`:
|
||||
The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`:
|
||||
|
||||
```js run
|
||||
let arr = ["I", "study", "JavaScript"];
|
||||
|
@ -201,15 +205,9 @@ alert(arr); // *!*1, 2, 15*/!*
|
|||
|
||||
Now it works as intended.
|
||||
|
||||
Let's step aside and thing what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or html elements or whatever. We have a set of something. To sort it, we need an *ordering function* that knows how to compare the elements. The default is a string order.
|
||||
Let's step aside and thing what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or html elements or whatever. We have a set of *something*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order.
|
||||
|
||||
The `arr.sort(fn)` method has a built-in implementation of a sorting algorithm. We don't need to care which one (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of time). It will walk the array, compare its elements using the provided function and reorder them.
|
||||
|
||||
So, `arr.sort(fn)` is like a black box for us.
|
||||
|
||||
1. We've got an array of some items to sort.
|
||||
2. We call `sort(fn)` providing the function that knows how to compare.
|
||||
3. It sorts.
|
||||
The `arr.sort(fn)` method has a built-in implementation of sorting algorithm. We don't need to care which how it exactly works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison.
|
||||
|
||||
By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them:
|
||||
|
||||
|
@ -219,7 +217,7 @@ By the way, if we ever want to know which elements are compared -- nothing preve
|
|||
});
|
||||
```
|
||||
|
||||
The algorithm may compare an element with multiple other elements. But it tries to make as few comparisons as possible.
|
||||
The algorithm may compare an element multiple times in the process, but it tries to make as few comparisons as possible.
|
||||
|
||||
|
||||
````smart header="A comparison function may return any number"
|
||||
|
@ -274,7 +272,7 @@ Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy.
|
|||
The syntax is:
|
||||
```js
|
||||
let result = arr.find(function(item, index, array) {
|
||||
// return true if the item is what we look for
|
||||
// should return true if the item is what we are looking for
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# Map, Set, WeakMap and WeakSet
|
||||
|
||||
Now we have the following data structures:
|
||||
Now we know the following complex data structures:
|
||||
|
||||
- Objects for storing keyed collections
|
||||
- Arrays for storing ordered collections
|
||||
|
@ -39,28 +39,9 @@ alert( map.get('1') ); // 'str1'
|
|||
alert( map.size ); // 3
|
||||
```
|
||||
|
||||
Every `map.set` call returns the map itself, so we can "chain" the calls:
|
||||
As we can see, unlike objects, keys are not converted to strings. Any type of key is possible.
|
||||
|
||||
```js
|
||||
map.set('1', 'str1')
|
||||
.set(1, 'num1')
|
||||
.set(true, 'bool1');
|
||||
```
|
||||
|
||||
When a `Map` is created, we can pass an array (or another iterable object) with key-value pairs, like this:
|
||||
|
||||
```js
|
||||
// array of [key, value] pairs
|
||||
let map = new Map([
|
||||
['1', 'str1'],
|
||||
[1, 'num1'],
|
||||
[true, 'bool1']
|
||||
]);
|
||||
```
|
||||
|
||||
**Map can use objects as keys.**
|
||||
|
||||
That can be really helpful.
|
||||
**Map can also use objects as keys.**
|
||||
|
||||
For instance:
|
||||
```js run
|
||||
|
@ -75,55 +56,87 @@ visitsCountMap.set(john, 123);
|
|||
alert( visitsCountMap.get(john) ); // 123
|
||||
```
|
||||
|
||||
Using objects as keys is one of most notable and important `Map` features. It's difficult to replace the `Map` with a regular `Object` here.
|
||||
Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but it would be difficult to replace the `Map` with a regular `Object` in the example above.
|
||||
|
||||
|
||||
```smart header="How `Map` compares keys"
|
||||
To test values for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as the strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well.
|
||||
|
||||
This algorithm can't be changed to our own.
|
||||
```
|
||||
|
||||
````smart header="Using an `Object` instead"
|
||||
In some cases objects have a special "unique identifier" property. Or we can create it for them. That allows to use an object:
|
||||
In the old times, before `Map` existed, people added unique identifiers to objects for that:
|
||||
|
||||
```js run
|
||||
// note the id field
|
||||
// we add the id field
|
||||
let john = { name: "John", *!*id: 1*/!* };
|
||||
|
||||
let visitsCounts = {};
|
||||
|
||||
// now store the value by id
|
||||
visitCounts[john.id] = 123;
|
||||
|
||||
alert( visitsCounts[john.id] ); // 123
|
||||
```
|
||||
|
||||
That was the main way to go in the past when maps did not exist in the language. But now we have maps. They are more versatile. And they do not require such identifiers to exist.
|
||||
````
|
||||
...But `Map` is much more elegant.
|
||||
|
||||
|
||||
````smart header="`Map` allows *any* keys"
|
||||
The important syntax diference from objects is that `Map` values are inserted/removed via methods.
|
||||
```smart header="How `Map` compares keys"
|
||||
To test values for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as the strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well.
|
||||
|
||||
```js
|
||||
// object vs map syntax
|
||||
obj[key] = value;
|
||||
map.set(key, value);
|
||||
This algorithm can't be changed or customized.
|
||||
```
|
||||
|
||||
There's a reason for that. In objects, there is a built-in property named `__proto__`. We'll see the details about it later. Here the important thing is that it should not be overwritten. If it is set to a primitive then the write operation is ignored, so we can say that JavaScript protects it to a certain extent.
|
||||
````smart header="`Map` allows *any* strings as keys, `Object` does not"
|
||||
In objects, there is a built-in property named `__proto__`. We'll see the details about it later. Here the important thing is that it should not be overwritten. If it is set to a primitive then the write operation is ignored, so we can say that JavaScript protects it to a certain extent.
|
||||
|
||||
But if key/value pairs come from the user (like a user naming himself `__proto__`), then we can meet unexpected problems with it. That's actually an endless source of bugs in many well-known JavaScript libraries. `Map`, from the other hand, is totally safe:
|
||||
|
||||
```js run
|
||||
let map = new Map();
|
||||
map.set('__proto__', 123);
|
||||
alert( map.get('__proto__') ); // 123
|
||||
```
|
||||
|
||||
But if key/value pairs come from the user (like a user naming himself `__proto__`), then we can meet unexpected problems with it. That's actually an endless source of bugs in many well-known JavaScript libraries. `Map`, from the other hand, is totally safe.
|
||||
````
|
||||
|
||||
````smart header="Chaining"
|
||||
|
||||
### Iteration
|
||||
Every `map.set` call returns the map itself, so we can "chain" the calls:
|
||||
|
||||
For looping over `map`, there are 3 methods:
|
||||
```js
|
||||
map.set('1', 'str1')
|
||||
.set(1, 'num1')
|
||||
.set(true, 'bool1');
|
||||
```
|
||||
````
|
||||
|
||||
- `map.keys()` -- returns an iterable object for keys,
|
||||
- `map.values()` -- returns an iterable object for values,
|
||||
- `map.entries()` -- returns an iterable object for entries `[key, value]`, it's used by default in `for..of`.
|
||||
## Map from Object
|
||||
|
||||
|
||||
When a `Map` is created, we can pass an array (or another iterable object) with key-value pairs, like this:
|
||||
|
||||
```js
|
||||
// array of [key, value] pairs
|
||||
let map = new Map([
|
||||
['1', 'str1'],
|
||||
[1, 'num1'],
|
||||
[true, 'bool1']
|
||||
]);
|
||||
```
|
||||
|
||||
Note that this is exactly the format of [Object.entries](mdn:js/Object/entries), so we can initialize a map from an object:
|
||||
|
||||
```js
|
||||
let map = new Map(Object.entries({
|
||||
name: "John",
|
||||
age: 30
|
||||
}));
|
||||
```
|
||||
|
||||
Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs.
|
||||
|
||||
## Iteration over Map
|
||||
|
||||
For looping over a `map`, there are 3 methods:
|
||||
|
||||
- `map.keys()` -- returns an array-like object for keys,
|
||||
- `map.values()` -- returns an array-like object for values,
|
||||
- `map.entries()` -- returns an array-like object for entries `[key, value]`, it's used by default in `for..of`.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -134,12 +147,12 @@ let recipeMap = new Map([
|
|||
['onion', 50]
|
||||
]);
|
||||
|
||||
// iterate over vegetables
|
||||
// iterate over keys (vegetables)
|
||||
for(let vegetable of recipeMap.keys()) {
|
||||
alert(vegetable); // cucumber, tomateos, onion
|
||||
}
|
||||
|
||||
// iterate over amounts
|
||||
// iterate over values (amounts)
|
||||
for(let amount of recipeMap.values()) {
|
||||
alert(amount); // 500, 350, 50
|
||||
}
|
||||
|
@ -202,11 +215,9 @@ for(let user of set) {
|
|||
}
|
||||
```
|
||||
|
||||
The alternative to `Set` could be an array of users. We can check for duplicates on every insertion using [arr.find](mdn:js/Array/find).
|
||||
The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks.
|
||||
|
||||
But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks.
|
||||
|
||||
### Iteration
|
||||
## Iteration over Set
|
||||
|
||||
We can loop over a set either with `for..of` or using `forEach`:
|
||||
|
||||
|
@ -227,28 +238,35 @@ That's made for compatibility with `Map` where `forEach` has three arguments.
|
|||
|
||||
## WeakMap and WeakSet
|
||||
|
||||
`WeakSet` -- is a special kind of `Set` that does not prevent JavaScript from memory cleaning. `WeakMap` -- is the same thing for `Map`.
|
||||
`WeakSet` is a special kind of `Set` that does not prevent JavaScript from memory cleaning. `WeakMap` is the same thing for `Map`.
|
||||
|
||||
That is: usually the JavaScript engine stores something in memory while it can potentially be accessed/used.
|
||||
That is: usually the JavaScript engine stores a value in memory while it can potentially be accessed/used.
|
||||
|
||||
For instance:
|
||||
```js
|
||||
// created an object
|
||||
let john = { name: "John" };
|
||||
|
||||
// it will be removed from memory now:
|
||||
// the object can be accessed, john is the reference
|
||||
|
||||
// overwrite the reference
|
||||
john = null;
|
||||
|
||||
// the object will be removed from memory
|
||||
```
|
||||
|
||||
We'll go into more details later, but the gist is somewhat obvious, right? If nothing references the object, it can be safely removed.
|
||||
|
||||
Usually, if an object is in a set or an array or another data structure, and the data structure is in memory, then the object remains in memory too. With the exception of `WeakMap/WeakSet`.
|
||||
|
||||
**If an object only exists in `WeakMap/WeakSet`, then it is removed from the memory.**
|
||||
|
||||
That's handy for situations when we have a main storage for the objects somewhere and need to keep additional data for them that only exists while the object exists.
|
||||
|
||||
For instance, we have users and want to keep a visit count for them. But the visit count is only relevant while the user exists.
|
||||
For instance, we have a code that keeps a visit count for each user. And when the user leaves, we don't need to store his visit count any more.
|
||||
|
||||
So we can put it into a `WeakMap`:
|
||||
One way would be to keep track of leaving users and cleaning up the storage manually.
|
||||
|
||||
Another way would be to use `WeakMap`:
|
||||
|
||||
```js run
|
||||
let john = { name: "John" };
|
||||
|
@ -269,11 +287,11 @@ alert( visitsCountMap.get(john) ); // undefined
|
|||
*/!*
|
||||
```
|
||||
|
||||
Do you see the difference versus a regular `Map`? If `visitorCountMap` were a `new Map()`, then `john` would remain in it. If users keep coming and leaving, then there would be more and more values flooding the map.
|
||||
Did you notice the difference versus a regular `Map`? If `visitorCountMap` were a `new Map()`, then the object `john` and the corresponding value would remain in it. If users keep coming and leaving, then there would be more and more values flooding the map.
|
||||
|
||||
So, with a regular `Map`, the user deletion becomes a more tedious task: we also need to clean up the additional stores. And it can be a cumbersome task in the more complex case when users are managed in one place of the code and the additional structure is at another place and is getting no information about removals. `WeakMap` comes to the rescue.
|
||||
So, with a regular `Map`, the user deletion becomes a more tedious task: we also need to clean up the additional stores. And it can become rather cumbersome in more complex cases when users are managed in one place of the code and the additional structure is at another place and is getting no information about removals.
|
||||
|
||||
`WeakMap` uses only objects as keys. It has the following methods:
|
||||
`WeakMap` uses only objects as keys, not primitive values. It has the following methods:
|
||||
|
||||
- `weakMap.get(key)`
|
||||
- `weakMap.set(key, value)`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue