diff --git a/1-js/2-first-steps/07-types/1-string-quotes/solution.md b/1-js/2-first-steps/07-types/1-string-quotes/solution.md new file mode 100644 index 00000000..cca7eecd --- /dev/null +++ b/1-js/2-first-steps/07-types/1-string-quotes/solution.md @@ -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 +``` \ No newline at end of file diff --git a/1-js/2-first-steps/07-types/1-string-quotes/task.md b/1-js/2-first-steps/07-types/1-string-quotes/task.md new file mode 100644 index 00000000..14ea6b4d --- /dev/null +++ b/1-js/2-first-steps/07-types/1-string-quotes/task.md @@ -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}` ); // ? +``` \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/1-hello-object/solution.md b/1-js/2-first-steps/07-types/2-hello-object/solution.md similarity index 100% rename from 1-js/4-data-structures/4-object/1-hello-object/solution.md rename to 1-js/2-first-steps/07-types/2-hello-object/solution.md diff --git a/1-js/4-data-structures/4-object/1-hello-object/task.md b/1-js/2-first-steps/07-types/2-hello-object/task.md similarity index 95% rename from 1-js/4-data-structures/4-object/1-hello-object/task.md rename to 1-js/2-first-steps/07-types/2-hello-object/task.md index 8482d3b4..b898112c 100644 --- a/1-js/4-data-structures/4-object/1-hello-object/task.md +++ b/1-js/2-first-steps/07-types/2-hello-object/task.md @@ -1,4 +1,4 @@ -importance: 3 +importance: 5 --- diff --git a/1-js/2-first-steps/07-types/3-sum-object/solution.md b/1-js/2-first-steps/07-types/3-sum-object/solution.md new file mode 100644 index 00000000..b4607014 --- /dev/null +++ b/1-js/2-first-steps/07-types/3-sum-object/solution.md @@ -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 +``` + diff --git a/1-js/2-first-steps/07-types/3-sum-object/task.md b/1-js/2-first-steps/07-types/3-sum-object/task.md new file mode 100644 index 00000000..0c0e252c --- /dev/null +++ b/1-js/2-first-steps/07-types/3-sum-object/task.md @@ -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. diff --git a/1-js/2-first-steps/07-types/4-sum-array/solution.md b/1-js/2-first-steps/07-types/4-sum-array/solution.md new file mode 100644 index 00000000..00b8cf79 --- /dev/null +++ b/1-js/2-first-steps/07-types/4-sum-array/solution.md @@ -0,0 +1,13 @@ + + +```js run +let salaries = [ 100, 160, 130 ]; + +let sum = 0; +for(let salary of salaries) { + sum += salary; +} + +alert( sum ); // 390 +``` + diff --git a/1-js/2-first-steps/07-types/4-sum-array/task.md b/1-js/2-first-steps/07-types/4-sum-array/task.md new file mode 100644 index 00000000..05e8869a --- /dev/null +++ b/1-js/2-first-steps/07-types/4-sum-array/task.md @@ -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. diff --git a/1-js/2-first-steps/07-types/article.md b/1-js/2-first-steps/07-types/article.md index 177d9510..b7e05eb7 100644 --- a/1-js/2-first-steps/07-types/article.md +++ b/1-js/2-first-steps/07-types/article.md @@ -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: `Hello`. +3. Backticks: `Hello`. 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". ![user object](object-user.png) diff --git a/1-js/2-first-steps/08-type-conversions/article.md b/1-js/2-first-steps/08-type-conversions/article.md index 57525e33..0559a058 100644 --- a/1-js/2-first-steps/08-type-conversions/article.md +++ b/1-js/2-first-steps/08-type-conversions/article.md @@ -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: diff --git a/1-js/2-first-steps/15-while-for/article.md b/1-js/2-first-steps/15-while-for/article.md index e2544c2f..f6ebf2b0 100644 --- a/1-js/2-first-steps/15-while-for/article.md +++ b/1-js/2-first-steps/15-while-for/article.md @@ -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)`. + diff --git a/1-js/2-first-steps/20-function-parameters/article.md b/1-js/2-first-steps/20-function-parameters/article.md index 88bec5ca..3145bf5f 100644 --- a/1-js/2-first-steps/20-function-parameters/article.md +++ b/1-js/2-first-steps/20-function-parameters/article.md @@ -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 Основные улучшения в функциях: diff --git a/1-js/4-data-structures/2-number/article.md b/1-js/4-data-structures/2-number/article.md index 6b9f2c1d..5686544c 100644 --- a/1-js/4-data-structures/2-number/article.md +++ b/1-js/4-data-structures/2-number/article.md @@ -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: diff --git a/1-js/4-data-structures/3-string/article.md b/1-js/4-data-structures/3-string/article.md index d812af1a..0319657e 100644 --- a/1-js/4-data-structures/3-string/article.md +++ b/1-js/4-data-structures/3-string/article.md @@ -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: func`string`. 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 diff --git a/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/solution.js b/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/solution.js index 7e980ba0..509b3589 100644 --- a/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/solution.js +++ b/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/solution.js @@ -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; diff --git a/1-js/4-data-structures/4-object/3-sum-salaries/task.md b/1-js/4-data-structures/4-object/3-sum-salaries/task.md index 2ccdc27e..211357d0 100644 --- a/1-js/4-data-structures/4-object/3-sum-salaries/task.md +++ b/1-js/4-data-structures/4-object/3-sum-salaries/task.md @@ -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`. diff --git a/1-js/4-data-structures/4-object/4-max-salary/_js.view/solution.js b/1-js/4-data-structures/4-object/4-max-salary/_js.view/solution.js index fa32d1a7..d95cf1b1 100644 --- a/1-js/4-data-structures/4-object/4-max-salary/_js.view/solution.js +++ b/1-js/4-data-structures/4-object/4-max-salary/_js.view/solution.js @@ -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; } } diff --git a/1-js/4-data-structures/4-object/4-max-salary/task.md b/1-js/4-data-structures/4-object/4-max-salary/task.md index 89052aea..a1f6e380 100644 --- a/1-js/4-data-structures/4-object/4-max-salary/task.md +++ b/1-js/4-data-structures/4-object/4-max-salary/task.md @@ -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. diff --git a/1-js/4-data-structures/4-object/5-count-properties/_js.view/solution.js b/1-js/4-data-structures/4-object/5-count-properties/_js.view/solution.js new file mode 100644 index 00000000..1853d20e --- /dev/null +++ b/1-js/4-data-structures/4-object/5-count-properties/_js.view/solution.js @@ -0,0 +1,4 @@ +function count(obj) { + return Object.keys(obj).length; +} + diff --git a/1-js/4-data-structures/4-object/5-count-properties/_js.view/test.js b/1-js/4-data-structures/4-object/5-count-properties/_js.view/test.js new file mode 100644 index 00000000..e568c320 --- /dev/null +++ b/1-js/4-data-structures/4-object/5-count-properties/_js.view/test.js @@ -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 ); + }); +}); \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/5-multiply-numeric/solution.md b/1-js/4-data-structures/4-object/5-count-properties/solution.md similarity index 100% rename from 1-js/4-data-structures/4-object/5-multiply-numeric/solution.md rename to 1-js/4-data-structures/4-object/5-count-properties/solution.md diff --git a/1-js/4-data-structures/4-object/5-count-properties/task.md b/1-js/4-data-structures/4-object/5-count-properties/task.md new file mode 100644 index 00000000..d7aebb1f --- /dev/null +++ b/1-js/4-data-structures/4-object/5-count-properties/task.md @@ -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. + diff --git a/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/solution.js b/1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/solution.js similarity index 100% rename from 1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/solution.js rename to 1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/solution.js diff --git a/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/source.js b/1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/source.js similarity index 100% rename from 1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/source.js rename to 1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/source.js diff --git a/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/test.js b/1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/test.js similarity index 69% rename from 1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/test.js rename to 1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/test.js index 1c0d6cf9..7a538ff8 100644 --- a/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/test.js +++ b/1-js/4-data-structures/4-object/6-multiply-numeric/_js.view/test.js @@ -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({}) ); + }); + }); \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/6-multiply-numeric/solution.md b/1-js/4-data-structures/4-object/6-multiply-numeric/solution.md new file mode 100644 index 00000000..e69de29b diff --git a/1-js/4-data-structures/4-object/5-multiply-numeric/task.md b/1-js/4-data-structures/4-object/6-multiply-numeric/task.md similarity index 100% rename from 1-js/4-data-structures/4-object/5-multiply-numeric/task.md rename to 1-js/4-data-structures/4-object/6-multiply-numeric/task.md diff --git a/1-js/4-data-structures/4-object/article.md b/1-js/4-data-structures/4-object/article.md index ef20fbc3..ed3eeae9 100644 --- a/1-js/4-data-structures/4-object/article.md +++ b/1-js/4-data-structures/4-object/article.md @@ -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 ![](object.png) -## 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 }; ``` ![](object-user-props.png) -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,155 +194,135 @@ 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" -}; - -*!* -let counter = 0; - -for (let key in menu) { - counter++; -} -*/!* - -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`. - -### Are objects ordered? - -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 `` 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. diff --git a/1-js/4-data-structures/7-array/article.md b/1-js/4-data-structures/7-array/article.md index 33d6df53..a5568538 100644 --- a/1-js/4-data-structures/7-array/article.md +++ b/1-js/4-data-structures/7-array/article.md @@ -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 ![](stack.png) -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