diff --git a/1-js/2-first-steps/19-function-expression/article.md b/1-js/2-first-steps/19-function-expression/article.md index 9599cc08..38ebf093 100644 --- a/1-js/2-first-steps/19-function-expression/article.md +++ b/1-js/2-first-steps/19-function-expression/article.md @@ -68,17 +68,40 @@ In more detail: Please note again: there are no brackets after `sayHi`. If they were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. 3. Now the function can be called both as `sayHi()` and `func()`. - Note, that we could also have used a Function Expression to declare `sayHi`, in the first line: `let sayHi = function() { ... }`. Everything would work the same. +Every value in Javascript has the type. What type of value is a function? + +**In Javascript, a function is an object.** + +We can abuse this by adding properties to it and using them in the code: + +```js run +function sayHi() { + alert("Hi"); + sayHi.counter++; +} +sayHi.counter = 0; + +sayHi(); // Hi +sayHi(); // Hi + +alert( `Called ${sayHi.counter} times` ); // Called 2 times +``` + +There are many well-known Javascript libraries that make use of this. They create a function and attach many other functions to it as its properties. For instance, the [jquery](https://jquery.com) library creates a function named `$`, the library [lodash](https://lodash.com) creates a function `_`. So, we have something that can do the job by itself and also carries a bunch of other functionality. + + ```smart header="A function is a value representing an \"action\"" Regular values like strings or numbers represent the *data*. A function can be perceived as an *action*. -We can copy it between variables and run when we want. +We can copy it between variables and run when we want. We can even add properties to it if we wish. ``` + + ## Function Expression as a method Now let's go back: we have two ways of declaring a function. Do we really need both? What's about Function Expressions that makes it a good addition? diff --git a/1-js/4-data-structures/2-number/article.md b/1-js/4-data-structures/2-number/article.md index e3b18ba5..d0c04929 100644 --- a/1-js/4-data-structures/2-number/article.md +++ b/1-js/4-data-structures/2-number/article.md @@ -327,18 +327,11 @@ Sometimes `isFinite` is used to validate the string value for being a regular nu ```js run let num = +prompt("Enter a number", ''); +// isFinite will be true only for regular numbers alert(`num:${num}, isFinite:${isFinite(num)}`); ``` -Here `num` is always of a number type, because of the unary plus `+`, but... - -- It may be `NaN` if the string is non-numeric. -- It may be `Infinity` if the user typed in `"Infinity"`. - -The `isFinite` test checks that and returns `true` only if it's a regular number. That's just what we usually need. - -Please note that an empty or a space-only string would give `num=0` in the described case. - +Please note that an empty or a space-only string is treated as `0` in the described case. If it's not what's needed, then additional checks are required. ## parseInt and parseFloat diff --git a/1-js/4-data-structures/3-string/1-ucfirst/_js.view/test.js b/1-js/4-data-structures/3-string/1-ucfirst/_js.view/test.js index b935f642..d5c50ff5 100644 --- a/1-js/4-data-structures/3-string/1-ucfirst/_js.view/test.js +++ b/1-js/4-data-structures/3-string/1-ucfirst/_js.view/test.js @@ -1,9 +1,9 @@ describe("ucFirst", function() { - it('делает первый символ заглавным', function() { - assert.strictEqual(ucFirst("вася"), "Вася"); + it('Uppercases the first symbol', function() { + assert.strictEqual(ucFirst("john"), "John"); }); - it('для пустой строки возвращает пустую строку', function() { + it("Doesn't die on an empty string", function() { assert.strictEqual(ucFirst(""), ""); }); }); \ No newline at end of file diff --git a/1-js/4-data-structures/3-string/1-ucfirst/solution.md b/1-js/4-data-structures/3-string/1-ucfirst/solution.md index 7f6420c5..4809cf12 100644 --- a/1-js/4-data-structures/3-string/1-ucfirst/solution.md +++ b/1-js/4-data-structures/3-string/1-ucfirst/solution.md @@ -1,26 +1,27 @@ -Мы не можем просто заменить первый символ, т.к. строки в JavaScript неизменяемы. +We can't "replace" the first character, because strings in JavaScript are immutable. -Но можно пересоздать строку на основе существующей, с заглавным первым символом: +But we can make a new string based on the existing one, with the uppercased first character: ```js -var newStr = str[0].toUpperCase() + str.slice(1); +let newStr = str[0].toUpperCase() + str.slice(1); ``` -Однако, есть небольшая проблемка -- в случае, когда строка пуста, будет ошибка. +There's a small problem though. If `str` is empty, then `str[0]` is undefined, so we'll get an error. -Ведь `str[0] == undefined`, а у `undefined` нет метода `toUpperCase()`. +There are two variants here: -Выхода два. Первый -- использовать `str.charAt(0)`, он всегда возвращает строку, для пустой строки -- пустую, но не `undefined`. Второй -- отдельно проверить на пустую строку, вот так: +1. Use `str.charAt(0)`, as it always returns a string (maybe empty). +2. Add a test for an empty string. + +Here's the 2nd variant: ```js run function ucFirst(str) { - // только пустая строка в логическом контексте даст false if (!str) return str; return str[0].toUpperCase() + str.slice(1); } -alert( ucFirst("вася") ); +alert( ucFirst("john") ); // John ``` -P.S. Возможны и более короткие решения, использующие методы для работы со строками, которые мы пройдём далее. \ No newline at end of file diff --git a/1-js/4-data-structures/3-string/1-ucfirst/task.md b/1-js/4-data-structures/3-string/1-ucfirst/task.md index ba67601c..c0e6ecac 100644 --- a/1-js/4-data-structures/3-string/1-ucfirst/task.md +++ b/1-js/4-data-structures/3-string/1-ucfirst/task.md @@ -2,13 +2,11 @@ importance: 5 --- -# Сделать первый символ заглавным +# Uppercast the first character -Напишите функцию `ucFirst(str)`, которая возвращает строку `str` с заглавным первым символом, например: +Write a function `ucFirst(str)` that returns the string `str` with the uppercased first character, for instance: ```js -ucFirst("вася") == "Вася"; -ucFirst("") == ""; // нет ошибок при пустой строке +ucFirst("john") == "John"; ``` -P.S. В JavaScript нет встроенного метода для этого. Создайте функцию, используя `toUpperCase()` и `charAt()`. diff --git a/1-js/4-data-structures/3-string/2-check-spam/_js.view/solution.js b/1-js/4-data-structures/3-string/2-check-spam/_js.view/solution.js index abe6e87c..105d70ea 100644 --- a/1-js/4-data-structures/3-string/2-check-spam/_js.view/solution.js +++ b/1-js/4-data-structures/3-string/2-check-spam/_js.view/solution.js @@ -1,5 +1,5 @@ function checkSpam(str) { - var lowerStr = str.toLowerCase(); + let lowerStr = str.toLowerCase(); - return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx')); + return lowerStr.includes('viagra') || lowerStr.includes('xxx'); } \ No newline at end of file diff --git a/1-js/4-data-structures/3-string/2-check-spam/_js.view/test.js b/1-js/4-data-structures/3-string/2-check-spam/_js.view/test.js index 1564a6b3..85eb24fc 100644 --- a/1-js/4-data-structures/3-string/2-check-spam/_js.view/test.js +++ b/1-js/4-data-structures/3-string/2-check-spam/_js.view/test.js @@ -1,13 +1,13 @@ describe("checkSpam", function() { - it('считает спамом "buy ViAgRA now"', function() { + it('finds spam in "buy ViAgRA now"', function() { assert.isTrue(checkSpam('buy ViAgRA now')); }); - it('считает спамом "free xxxxx"', function() { + it('finds spam in "free xxxxx"', function() { assert.isTrue(checkSpam('free xxxxx')); }); - it('не считает спамом "innocent rabbit"', function() { + it('no spam in "innocent rabbit"', function() { assert.isFalse(checkSpam('innocent rabbit')); }); }); \ No newline at end of file diff --git a/1-js/4-data-structures/3-string/2-check-spam/solution.md b/1-js/4-data-structures/3-string/2-check-spam/solution.md index eaf9dcdb..2468916f 100644 --- a/1-js/4-data-structures/3-string/2-check-spam/solution.md +++ b/1-js/4-data-structures/3-string/2-check-spam/solution.md @@ -1,12 +1,10 @@ -Метод `indexOf` ищет совпадение с учетом регистра. То есть, в строке `'xXx'` он не найдет `'XXX'`. - -Для проверки приведем к нижнему регистру и строку `str` а затем уже будем искать. +To make the search case-insensitive, let's bring the stirng to lower case and then search: ```js run function checkSpam(str) { - var lowerStr = str.toLowerCase(); + let lowerStr = str.toLowerCase(); - return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx')); + return lowerStr.includes('viagra') || lowerStr.includes('xxx'); } alert( checkSpam('buy ViAgRA now') ); diff --git a/1-js/4-data-structures/3-string/2-check-spam/task.md b/1-js/4-data-structures/3-string/2-check-spam/task.md index 3d9978e8..d073adc0 100644 --- a/1-js/4-data-structures/3-string/2-check-spam/task.md +++ b/1-js/4-data-structures/3-string/2-check-spam/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Проверьте спам +# Check for spam -Напишите функцию `checkSpam(str)`, которая возвращает `true`, если строка `str` содержит 'viagra' or 'XXX', а иначе `false`. +Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false. -Функция должна быть нечувствительна к регистру: +The function must be case-insensitive: ```js checkSpam('buy ViAgRA now') == true diff --git a/1-js/4-data-structures/3-string/3-truncate/_js.view/solution.js b/1-js/4-data-structures/3-string/3-truncate/_js.view/solution.js index e37f1a3b..f587df1f 100644 --- a/1-js/4-data-structures/3-string/3-truncate/_js.view/solution.js +++ b/1-js/4-data-structures/3-string/3-truncate/_js.view/solution.js @@ -1,4 +1,4 @@ function truncate(str, maxlength) { - return (str.length > maxlength) ? - str.slice(0, maxlength - 3) + '...' : str; + return (str.length > maxlength) ? + str.slice(0, maxlength - 1) + '…' : str; } \ No newline at end of file diff --git a/1-js/4-data-structures/3-string/3-truncate/_js.view/test.js b/1-js/4-data-structures/3-string/3-truncate/_js.view/test.js index c14666d6..c252f16b 100644 --- a/1-js/4-data-structures/3-string/3-truncate/_js.view/test.js +++ b/1-js/4-data-structures/3-string/3-truncate/_js.view/test.js @@ -1,15 +1,15 @@ describe("truncate", function() { - it("обрезает строку до указанной длины (включая троеточие)", function() { + it("truncate the long string to the given lenth (including the ellipsis)", function() { assert.equal( - truncate("Вот, что мне хотелось бы сказать на эту тему:", 20), - "Вот, что мне хоте..." + truncate("What I'd like to tell on this topic is:", 20), + "What I'd like to te…" ); }); - it("не меняет короткие строки", function() { + it("doesn't change short strings", function() { assert.equal( - truncate("Всем привет!", 20), - "Всем привет!" + truncate("Hi everyone!", 20), + "Hi everyone!" ); }); diff --git a/1-js/4-data-structures/3-string/3-truncate/solution.md b/1-js/4-data-structures/3-string/3-truncate/solution.md index 7af76011..a49b7090 100644 --- a/1-js/4-data-structures/3-string/3-truncate/solution.md +++ b/1-js/4-data-structures/3-string/3-truncate/solution.md @@ -1,26 +1,11 @@ -Так как окончательная длина строки должна быть `maxlength`, то нужно её обрезать немного короче, чтобы дать место для троеточия. +The maximal length must be `maxlength`, so we need to cut it a little shorter, to give space for the ellipsis. + +Note that there is actually a single unicode character for an ellipsis. That's not three dots. ```js run function truncate(str, maxlength) { - if (str.length > maxlength) { - return str.slice(0, maxlength - 3) + '...'; - // итоговая длина равна maxlength - } - - return str; -} - -alert( truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) ); -alert( truncate("Всем привет!", 20) ); -``` - -Можно было бы написать этот код ещё короче: - -```js run -function truncate(str, maxlength) { - return (str.length > maxlength) ? - str.slice(0, maxlength - 3) + '...' : str; + return (str.length > maxlength) ? + str.slice(0, maxlength - 1) + '…' : str; } ``` -P.S. Кстати, в кодироке Unicode существует специальный символ "троеточие": `…` (HTML: `…`), который можно использовать вместо трёх точек. Если его использовать, то можно отрезать только один символ. diff --git a/1-js/4-data-structures/3-string/3-truncate/task.md b/1-js/4-data-structures/3-string/3-truncate/task.md index b93c357d..af37f0dc 100644 --- a/1-js/4-data-structures/3-string/3-truncate/task.md +++ b/1-js/4-data-structures/3-string/3-truncate/task.md @@ -2,18 +2,16 @@ importance: 5 --- -# Усечение строки +# Truncate the text -Создайте функцию `truncate(str, maxlength)`, которая проверяет длину строки `str`, и если она превосходит `maxlength` -- заменяет конец `str` на `"..."`, так чтобы ее длина стала равна `maxlength`. +Create a function `truncate(str, maxlength)` that checks the length of the `str` and, if it exceeds `maxlength` -- replaces the end of `str` with the ellipsis character `"…"`, to make its length equal to `maxlength`. -Результатом функции должна быть (при необходимости) усечённая строка. +The result of the function should be the truncated (if needed) string. -Например: +For instance: ```js -truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хоте..." +truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" -truncate("Всем привет!", 20) = "Всем привет!" +truncate("Hi everyone!", 20) = "Hi everyone!" ``` - -Эта функция имеет применение в жизни. Она используется, чтобы усекать слишком длинные темы сообщений. diff --git a/1-js/4-data-structures/3-string/4-extract-currency/_js.view/test.js b/1-js/4-data-structures/3-string/4-extract-currency/_js.view/test.js index 45e0eb8c..1c3f0bbc 100644 --- a/1-js/4-data-structures/3-string/4-extract-currency/_js.view/test.js +++ b/1-js/4-data-structures/3-string/4-extract-currency/_js.view/test.js @@ -1,6 +1,6 @@ describe("extractCurrencyValue", function() { - it("выделяет из строки $120 число 120", function() { + it("for the string $120 returns the number 120", function() { assert.strictEqual(extractCurrencyValue('$120'), 120); }); diff --git a/1-js/4-data-structures/3-string/4-extract-currency/solution.md b/1-js/4-data-structures/3-string/4-extract-currency/solution.md index d2c8c1a0..8b137891 100644 --- a/1-js/4-data-structures/3-string/4-extract-currency/solution.md +++ b/1-js/4-data-structures/3-string/4-extract-currency/solution.md @@ -1 +1 @@ -Возьмём часть строки после первого символа и приведём к числу: `+str.slice(1)`. \ No newline at end of file + diff --git a/1-js/4-data-structures/3-string/4-extract-currency/task.md b/1-js/4-data-structures/3-string/4-extract-currency/task.md index 11ecc119..feb16e64 100644 --- a/1-js/4-data-structures/3-string/4-extract-currency/task.md +++ b/1-js/4-data-structures/3-string/4-extract-currency/task.md @@ -2,9 +2,15 @@ importance: 4 --- -# Выделить число +# Extract the money -Есть стоимость в виде строки: `"$120"`. То есть, первым идёт знак валюты, а затем -- число. +We have a cost in the form `"$120"`. That is: the dollar sign goes first, and then the number. -Создайте функцию `extractCurrencyValue(str)`, которая будет из такой строки выделять число-значение, в данном случае 120. +Create a function `extractCurrencyValue(str)` that would extract the numeric value from such string and return it. + +The example: + +```js +alert( extractCurrencyValue('$120') === 120 ); // true +``` diff --git a/1-js/4-data-structures/3-string/article.md b/1-js/4-data-structures/3-string/article.md index f0c80f92..f0f9856e 100644 --- a/1-js/4-data-structures/3-string/article.md +++ b/1-js/4-data-structures/3-string/article.md @@ -1,188 +1,314 @@ -# Строки +# Strings -В JavaScript любые текстовые данные являются строками. Не существует отдельного типа "символ", который есть в ряде других языков. +In JavaScript, the textual data is stored as strings. There is no separate type for a single character. -Внутренним форматом строк, вне зависимости от кодировки страницы, является [Юникод (Unicode)](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4). +The internal format for strings is always [UTF-16](https://en.wikipedia.org/wiki/UTF-16), it is not tied to the page encoding. [cut] -## Создание строк +## Quotes -Строки создаются при помощи двойных или одинарных кавычек: +Let's remember the kinds of quotes. + +Strings can be enclosed either with the single, double quotes or in backticks: ```js -var text = "моя строка"; +let single = 'single-quoted'; +let double = "double-quoted"; -var anotherText = 'еще строка'; - -var str = "012345"; +let backticks = `backticks`; ``` -В JavaScript нет разницы между двойными и одинарными кавычками. - -### Специальные символы - -Строки могут содержать специальные символы. Самый часто используемый из таких символов -- это "перевод строки". - -Он обозначается как `\n`, например: +Single and double quotes are essentially the same. Backticks allow to embed any expression into the string, including function calls: ```js run -alert( 'Привет\nМир' ); // выведет "Мир" на новой строке +function sum(a, b) { + return a + b; +} + +alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3. ``` -Есть и более редкие символы, вот их список: +Another advantage of using backticks is that they allow a string to span multiple lines: - - - - - - - - - - - - - -
Специальные символы
СимволОписание
\bBackspace
\fForm feed
\nNew line
\rCarriage return
\tTab
\uNNNNСимвол в кодировке Юникод с шестнадцатеричным кодом `NNNN`. Например, `\u00A9` -- юникодное представление символа копирайт © -
+```js run +let guestList = `Guests: + * John + * Pete + * Mary +`; -### Экранирование специальных символов +alert(guestList); // a list of guests, multiple lines +``` -Если строка в одинарных кавычках, то внутренние одинарные кавычки внутри должны быть *экранированы*, то есть снабжены обратным слешем `\'`, вот так: +If we try to use single or double quotes the same way, there will be an error: +```js run +let guestList = "Guests: // Error: Unexpected token ILLEGAL + * John"; +``` + +That's because they come from ancient times of language creation, and the need for multiline strings was not taken into account. Backticks appeared much later. + +````smart header="Template function" +The advanced feature of backticks is the ability to specify a "template function" at the beginning that would get the string and it's `${…}` components and can convert them. + +The syntax is: +```js +function f(...) { /* the function to postprocess he string */ } + +let str = f`my string``; +``` +We'll get back to this advanced stuff later, because it's rarely used and we won't need it any time soon. +```` + +## Special characters + +It is still possible to create multiline strings with single quotes, using a so-called "newline character" written as `\n`, that denotes a line break: + +```js run +let guestList = "Guests:\n * John\n * Pete\n * Mary"; + +alert(guestList); // a list of guests, multiple lines, same as with backticks above +``` + +So to speak, these two lines describe the same: + +```js run +alert( "Hello\nWorld" ); // two lines, just like below + +alert( `Hello +World` ); +``` + +There are other, less common "special" characters as well, here's the list: + +| Character | Description | +|-----------|-------------| +|`\b`|Backspace| +|`\f`|Form feed| +|`\n`|New line| +|`\r`|Carriage return| +|`\t`|Tab| +|`\uNNNN`|A unicode symbol with the hex code `NNNN`, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. Must be exactly 4 hex digits. | +|`\u{NNNNNNNN}`|Some rare characters are encoded with two unicode symbols, taking up to 4 bytes. The long unicode requires braces around.| + +For example: + +```js run +alert( "\u00A9" ); // © +alert( "\u{20331}" ); // 𠌱, a rare chinese hieroglyph +``` + +As we can see, all special characters start with a backslash character `\`. It is also called an "escaping character". + +Another use of it is an insertion of the enclosing quote into the string. + +For instance: + +```js run +alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! +``` + +See, we have to prepend the inner quote by the backslash `\'`, because otherwise it would mean the string end. + +As a more elegant solution, we could wrap the string in double quotes or backticks instead: + +```js run +alert( `I'm the Walrus!` ); // I'm the Walrus! +``` + +Most of time when we know we're going to use this or that kind of quotes inside of the string, we can choose non-conflicting quotes to enclose it. + +Note that the backslash `\` serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above. + +But what if we need exactly a backslash `\` in the string? + +That's possible, but we need to double it like `\\`: + +```js run +alert( `The backslash: \\` ); // The backslash: \ +``` + +## The length and characters + +- The `length` property has the string length: + + ```js run + alert( `My\n`.length ); // 3 + ``` + + Note that `\n` is a single "special" character, so the length is indeed `3`. + +- To get a character, use square brackets `[position]` or the method [str.charAt(position)](mdn:String/charAt). The first character starts from the zero position: + + ```js run + let str = `Hello`; + + // the first character + alert( str[0] ); // H + alert( str.charAt(0) ); // H + + // the last character + alert( str[str.length - 1] ); // o + ``` + + The square brackets is a modern way of getting a character, while `charAt` exists mostly for historical reasons. + + The only difference between them is that if no character found, `[]` returns `undefined`, and `charAt` returns an empty string: + + ```js run + let str = `Hello`; + + alert( str[1000] ); // undefined + alert( str.charAt(1000) ); // '' (an empty string) + ``` + +```warn header="`length` is a property" +Please note that `str.length` is a numeric property, not a function. + +There is no need to add brackets after it. The call `str.length()` won't work, must use bare `str.length`. +``` + +## Strings are immutable + +Strings can't be changed in JavaScript. It is impossible to change a character. + +Let's try to see that it doesn't work: + +```js run +let str = 'Hi'; + +str[0] = 'h'; // error +alert( str[0] ); // doesn't work +``` + +The usual workaround is to create a whole new string and assign it to `str` instead of the old one. + +For instance: + +```js run +let str = 'Hi'; + +str = 'h' + str[1]; // replace the string + +alert( str ); // hi +``` + +In the following sections we'll see more examples of that. + +## Changing the case + +Methods [toLowerCase()](mdn:String/toLowerCase) and [toUpperCase()](mdn:String/toUpperCase) change the case: + +```js run +alert( 'Interface'.toUpperCase() ); // INTERFACE +alert( 'Interface'.toLowerCase() ); // interface +``` + +Or, if we want a single character lowercased: ```js -var str = '*!*I\'m*/!* a JavaScript programmer'; +alert( 'Interface'[0].toLowerCase() ); // 'i' ``` -В двойных кавычках -- экранируются внутренние двойные: +## Finding substrings + +There are multiple ways to look for a substring in a string. + +### str.indexOf + +The first method is [str.indexOf(substr, pos)](mdn:String/indexOf). + +It looks for the `substr` in `str`, starting from the given position `pos`, and returns the position where the match was found or `-1` if nothing found. + +For instance: ```js run -var str = "I'm a JavaScript \"programmer\" "; -alert( str ); // I'm a JavaScript "programmer" +let str = 'Widget with id'; + +alert( str.indexOf('Widget') ); // 0, because 'Widget' is found at the beginning +alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive + +alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id) ``` -Экранирование служит исключительно для правильного восприятия строки JavaScript. В памяти строка будет содержать сам символ без `'\'`. Вы можете увидеть это, запустив пример выше. +The optional second parameter allows to search starting from the given position. -Сам символ обратного слэша `'\'` является служебным, поэтому всегда экранируется, т.е пишется как `\\`: +For instance, the first occurence of `"id"` is at the position `1`. To look for the next occurence, let's start the search from the position `2`: ```js run -var str = ' символ \\ '; +let str = 'Widget with id'; -alert( str ); // символ \ +alert( str.indexOf('id', 2) ) // 12 ``` -Заэкранировать можно любой символ. Если он не специальный, то ничего не произойдёт: + +If we're interested in all occurences, we can run `indexOf` in a loop. Every new call is made with the position after the previous match: + ```js run -alert( "\a" ); // a -// идентично alert( "a" ); +let str = 'As sly as a fox, as strong as an ox'; + +let target = 'as'; // let's look for it + +let pos = 0; +while (true) { + let foundPos = str.indexOf(target, pos); + if (foundPos == -1) break; + + alert( `Found at ${foundPos}` ); + pos = foundPos + 1; // continue the search from the next position +} ``` -## Методы и свойства - -Здесь мы рассмотрим методы и свойства строк, с некоторыми из которых мы знакомились ранее, в главе . - -### Длина length - -Одно из самых частых действий со строкой -- это получение ее длины: +The same algorithm can be layed out shorter: ```js run -var str = "My\n"; // 3 символа. Третий - перевод строки +let str = "As sly as a fox, as strong as an ox"; +let target = "as"; -alert( str.length ); // 3 +*!* +let pos = -1; +while ((pos = str.indexOf(target, pos + 1)) != -1) { + alert( pos ); +} +*/!* ``` -### Доступ к символам +```smart header="`str.lastIndexOf(pos)`" +There is also a similar method [str.lastIndexOf(pos)](mdn:String/lastIndexOf) that searches from the end of the string to its beginning. -Чтобы получить символ, используйте вызов `charAt(позиция)`. Первый символ имеет позицию `0`: +It would list the occurences in the reverse way. +``` + +The inconvenience with `indexOf` is that we can't put it "as is" into an `if` check: ```js run -var str = "jQuery"; -alert( str.charAt(0) ); // "j" +let str = "Widget with id"; + +if (str.indexOf("Widget")) { + alert("We found it"); // won't work +} ``` -В JavaScript **нет отдельного типа "символ"**, так что `charAt` возвращает строку, состоящую из выбранного символа. +That's because `str.indexOf("Widget")` returns `0` (found at the starting position). Right, but `if` considers that `false`. -Также для доступа к символу можно также использовать квадратные скобки: +So, we should actualy check for `-1`, like that: ```js run -var str = "Я - современный браузер!"; -alert( str[0] ); // "Я" +let str = "Widget with id"; + +*!* +if (str.indexOf("Widget") != -1) { +*/!* + alert("We found it"); // works now! +} ``` -Разница между этим способом и `charAt` заключается в том, что если символа нет -- `charAt` выдает пустую строку, а скобки -- `undefined`: +````smart header="The bitwise NOT trick" +One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. For 32-bit integers the call `~n` is the same as `-(n+1)`. -```js run -alert( "".charAt(0) ); // пустая строка -alert( "" [0] ); // undefined -``` - -Вообще же метод `charAt` существует по историческим причинам, ведь квадратные скобки -- проще и короче. - -```warn header="Вызов метода -- всегда со скобками" -Обратите внимание, `str.length` -- это *свойство* строки, а `str.charAt(pos)` -- *метод*, т.е. функция. - -Обращение к методу всегда идет со скобками, а к свойству -- без скобок. -``` - -### Изменения строк - -Содержимое строки в JavaScript нельзя изменять. Нельзя взять символ посередине и заменить его. Как только строка создана -- она такая навсегда. - -Можно лишь создать целиком новую строку и присвоить в переменную вместо старой, например: - -```js run -var str = "строка"; - -str = str[3] + str[4] + str[5]; - -alert( str ); // ока -``` - -### Смена регистра - -Методы `toLowerCase()` и `toUpperCase()` меняют регистр строки на нижний/верхний: - -```js run -alert( "Интерфейс".toUpperCase() ); // ИНТЕРФЕЙС -``` - -Пример ниже получает первый символ и приводит его к нижнему регистру: - -```js -alert( "Интерфейс" [0].toLowerCase() ); // 'и' -``` - -### Поиск подстроки - -Для поиска подстроки есть метод indexOf(подстрока[, начальная_позиция]). - -Он возвращает позицию, на которой находится `подстрока` или `-1`, если ничего не найдено. Например: - -```js run -var str = "Widget with id"; - -alert( str.indexOf("Widget") ); // 0, т.к. "Widget" найден прямо в начале str -alert( str.indexOf("id") ); // 1, т.к. "id" найден, начиная с позиции 1 -alert( str.indexOf("widget") ); // -1, не найдено, так как поиск учитывает регистр -``` - -Необязательный второй аргумент позволяет искать, начиная с указанной позиции. Например, первый раз `"id"` появляется на позиции `1`. Чтобы найти его следующее появление -- запустим поиск с позиции `2`: - -```js run -var str = "Widget with id"; - -alert(str.indexOf("id", 2)) // 12, поиск начат с позиции 2 -``` - -Также существует аналогичный метод lastIndexOf, который ищет не с начала, а с конца строки. - -````smart -Для красивого вызова `indexOf` применяется побитовый оператор НЕ `'~'`. - -Дело в том, что вызов `~n` эквивалентен выражению `-(n+1)`, например: +For instance: ```js run alert( ~2 ); // -(2+1) = -3 @@ -192,275 +318,327 @@ alert( ~0 ); // -(0+1) = -1 alert( ~-1 ); // -(-1+1) = 0 */!* ``` +As we can see, `~n` is zero only if `n == -1`. -Как видно, `~n` -- ноль только в случае, когда `n == -1`. +So, `if ( ~str.indexOf("...") )` means that the `indexOf` result is different from `-1`. -То есть, проверка `if ( ~str.indexOf(...) )` означает, что результат `indexOf` отличен от `-1`, т.е. совпадение есть. - -Вот так: +People use it to shorten `indexOf` checks: ```js run -var str = "Widget"; +let str = "Widget"; -if (~str.indexOf("get")) { - alert( 'совпадение есть!' ); +if (~str.indexOf("Widget")) { + alert( 'Found it!' ); // works } ``` -Вообще, использовать возможности языка неочевидным образом не рекомендуется, поскольку ухудшает читаемость кода. +It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used, generally JavaScript programmers understand it. -Однако, в данном случае, все в порядке. Просто запомните: `'~'` читается как "не минус один", а `"if ~str.indexOf"` читается как `"если найдено"`. +Just remember: `if (~str.indexOf(...))` reads as "if found". ```` -### Поиск всех вхождений +### includes, startsWith, endsWith -Чтобы найти все вхождения подстроки, нужно запустить `indexOf` в цикле. Как только получаем очередную позицию -- начинаем следующий поиск со следующей. +The more modern method [str.includes(substr)](mdn:String/includes) returns `true/false` depending on whether `str` has `substr` as its part. -Пример такого цикла: +That's usually a simpler way to go if we don't need the exact position: ```js run -var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке -var target = "Иа"; // цель поиска +alert( "Widget with id".includes("Widget") ); // true -var pos = 0; -while (true) { - var foundPos = str.indexOf(target, pos); - if (foundPos == -1) break; +alert( "Hello".includes("Bye") ); // false +``` - alert( foundPos ); // нашли на этой позиции - pos = foundPos + 1; // продолжить поиск со следующей +The methods [str.startsWith](mdn:String/startsWith) and [str.endsWith](mdn:String/endsWith) do exactly what they promise: + +```js run +alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid" +alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get" +``` + + +## Getting a substring + +There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`. + +`str.slice(start [, end])` +: Returns the part of the string from `start` to, but not including, `end`. + + For instance: + + ```js run + let str = "stringify"; + alert( str.slice(0,5) ); // 'string', the substring from 0, but not including 5 + alert( str.slice(0,1) ); // 's', the substring from 0, but not including 1 + ``` + + If there is no `end` argument, then `slice` goes till the end of the string: + + ```js run + let str = "st*!*ringify*/!*"; + alert( str.slice(2) ); // ringify, from the 2nd position till the end + ``` + + Negative values for `start/end` are also possible. They mean the position is counted from the string end: + + ```js run + let str = "strin*!*gif*/!*y"; + + // start at the 4th position from the right, end at the 1st from the right + alert( str.slice(-4, -1) ); // gif + ``` + + +`str.substring(start [, end])` +: Returns the part of the string *between* `start` and `end`. + + Almost the same as `slice`, but allows `start` greater than `end`. For instance: + + + ```js run + let str = "st*!*ring*/!*ify"; + + alert( str.substring(2, 6) ); // "ring" + alert( str.substring(6, 2) ); // "ring" + + // compare with slice: + alert( str.slice(2, 6) ); // "ring" (the same) + alert( str.slice(6, 2) ); // "" (an empty string) + + ``` + + Negative arguments are treated as `0`. + + +`str.substr(start [, length])` +: Returns the part of the string from `start`, with the given `length`. + + In contrast with the previous methods, this one allows to specify the `length` instead of the ending position: + + ```js run + let str = "st*!*ring*/!*ify"; + alert( str.substr(2, 4) ); // ring, from the 2nd position get 4 characters + ``` + + The first argument may be negative, to count from the end: + + ```js run + let str = "strin*!*gi*/!*fy"; + alert( str.substr(-4, 2) ); // gi, from the 4th position get 2 characters + ``` + +Let's recap the methods to avoid any confusion: + +| method | selects... | negatives | +|--------|-----------|-----------| +| `slice(start, end)` | from `start` to `end` | allows negatives | +| `substring(start, end)` | between `start` and `end` | negative values mean `0` | +| `substr(start, length)` | from `start` get `length` characters | allows negative `start` | + + +```smart header="Which one to choose?" +All of them can do the job. The author of this chapter finds himself using `slice` almost all the time. +``` + +## Comparing strings + +As we know from the chapter , strings are compared character-by-character, in the alphabet order. + +Although, there are some oddities. + +1. A lowercase letter is always greater than the uppercase: + + ```js run + alert( 'a' > 'Z' ); // true + ``` + +2. Letters with diacritical marks are "out of the alphabet": + + ```js run + alert( 'Österreich' > 'Zealand' ); // true + ``` + + That may give strange results if we sort country names. Usually people would await for `Zealand` to be after `Österreich` in the list. + +To understand the reasoning behind that, let's review the internal representaion of strings in JavaScript. + +All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back. + +`str.codePointAt(pos)` +: Returns the code for the character at position `pos`: + + ```js run + // different case letters have different codes + alert( "z".codePointAt(0) ); // 122 + alert( "Z".codePointAt(0) ); // 90 + ``` + +`String.fromCodePoint(code)` +: Creates a character by its numeric `code` + + ```js run + alert( String.fromCodePoint(90) ); // Z + ``` + + We can also add unicode charactes by their codes using `\u` followed by the hex code: + + ```js run + // 90 is 5a in hexadecimal system + alert( '\u005a' ); // Z + ``` + +Now let's make the string from the characters with codes `65..220` (the latin alphabet and a little bit extra): + +```js run +let str = ''; + +for (let i = 65; i <= 220; i++) { + str += String.fromCodePoint(i); } +alert( str ); +// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ +// ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` -Такой цикл начинает поиск с позиции `0`, затем найдя подстроку на позиции `foundPos`, следующий поиск продолжит с позиции `pos = foundPos+1`, и так далее, пока что-то находит. +Now it becomes obvious why `a > Z`. -Впрочем, тот же алгоритм можно записать и короче: +The characters are compared by their numeric code. The greater code means that the character is greater. + +And we can easily see that: + +1. Lowercase letters go after uppercase letters, their codes are greater. +2. Some letters like `Ö` stand apart from the main alphabet. Here, it's code is greater than anything from `a` to `z`. + + +### The correct way + +The "right" comparisons are more complex than it may seem. Because the alphabets are different for different languages. The same letter may be located differently in different alphabets. + +Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). + +It provides a special method to compare strings in different languages, following their rules. + +[str.localeCompare(str2)](mdn:String/localeCompare): + +- Returns `1` if `str` is greater than `str2` according to the language rules. +- Returns `-1` if `str` is less than `str2`. +- Returns `0` if they are equal. + +For instance: ```js run -var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке -var target = "Иа"; // цель поиска - -*!* -var pos = -1; -while ((pos = str.indexOf(target, pos + 1)) != -1) { - alert( pos ); -} -*/!* +alert( 'Österreich'.localeCompare('Zealand') ); // -1 ``` -### Взятие подстроки: substr, substring, slice. +The method actually has two additional arguments, allowing 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. See the manual for details when you need them. -В JavaScript существуют целых 3 (!) метода для взятия подстроки, с небольшими отличиями между ними. +## Encoding -`substring(start [, end])` -: Метод `substring(start, end)` возвращает подстроку с позиции `start` до, но не включая `end`. +```warn header="Advanced knowledge" +The section goes deeper into string internals. The knowledge will be useful for you if you plan to deal with emoji, rare math of hieroglyphs characters and such. - ```js run - var str = "*!*s*/!*tringify"; - alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1. - ``` +You can skip the section if all you need is common letters and digits. +``` - Если аргумент `end` отсутствует, то идет до конца строки: +### Surrogate pairs - ```js run - var str = "st*!*ringify*/!*"; - alert(str.substring(2)); // ringify, символы с позиции 2 до конца - ``` +Most symbols have a 2-byte code. Letters of most european languages, numbers, even most hieroglyphs have a 2-byte representation. -
`substr(start [, length])` -: Первый аргумент имеет такой же смысл, как и в `substring`, а второй содержит не конечную позицию, а количество символов. +But 2 bytes only allow 65536 combinations that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair". - ```js run - var str = "st*!*ring*/!*ify"; - str = str.substr(2,4); // ring, со 2й позиции 4 символа - alert(str) - ``` - - Если второго аргумента нет -- подразумевается "до конца строки". - -`slice(start [, end])` -: Возвращает часть строки от позиции `start` до, но не включая, позиции `end`. Смысл параметров -- такой же как в `substring`. - -### Отрицательные аргументы - -Различие между `substring` и `slice` -- в том, как они работают с отрицательными и выходящими за границу строки аргументами: - -`substring(start, end)` -: Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки: - - ```js run - alert( "testme".substring(-2) ); // "testme", -2 становится 0 - ``` - - Кроме того, если start > end, то аргументы меняются местами, т.е. возвращается участок строки *между* `start` и `end`: - - ```js run - alert( "testme".substring(4, -1) ); // "test" - // -1 становится 0 -> получили substring(4, 0) - // 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test" - ``` - -`slice` -: Отрицательные значения отсчитываются от конца строки: - - ```js run - alert( "testme".slice(-2) ); // "me", от 2 позиции с конца - ``` - - ```js run - alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца. - ``` - - Это гораздо более удобно, чем странная логика `substring`. - -Отрицательное значение первого параметра поддерживается в `substr` во всех браузерах, кроме IE8-. - -Если выбирать из этих трёх методов один, для использования в большинстве ситуаций -- то это будет `slice`: он и отрицательные аргументы поддерживает и работает наиболее очевидно. - -## Кодировка Юникод - -Как мы знаем, символы сравниваются в алфавитном порядке `'А' < 'Б' < 'В' < ... < 'Я'`. - -Но есть несколько странностей.. - -1. Почему буква `'а'` маленькая больше буквы `'Я'` большой? - - ```js run - alert( 'а' > 'Я' ); // true - ``` -2. Буква `'ё'` находится в алфавите между `е` и `ж`: абвгде**ё**жз... Но почему тогда `'ё'` больше `'я'`? - - ```js run - alert( 'ё' > 'я' ); // true - ``` - -Чтобы разобраться с этим, обратимся к внутреннему представлению строк в JavaScript. - -**Все строки имеют внутреннюю кодировку [Юникод](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).** - -Неважно, на каком языке написана страница, находится ли она в windows-1251 или utf-8. Внутри JavaScript-интерпретатора все строки приводятся к единому "юникодному" виду. Каждому символу соответствует свой код. - -Есть метод для получения символа по его коду: - -String.fromCharCode(code) -: Возвращает символ по коду `code`: - - ```js run - alert( String.fromCharCode(1072) ); // 'а' - ``` - -...И метод для получения цифрового кода из символа: - -str.charCodeAt(pos) -: Возвращает код символа на позиции `pos`. Отсчет позиции начинается с нуля. - - ```js run - alert( "абрикос".charCodeAt(0) ); // 1072, код 'а' - ``` - -Теперь вернемся к примерам выше. Почему сравнения `'ё' > 'я'` и `'а' > 'Я'` дают такой странный результат? - -Дело в том, что **символы сравниваются не по алфавиту, а по коду**. У кого код больше -- тот и больше. В юникоде есть много разных символов. Кириллическим буквам соответствует только небольшая часть из них, подробнее -- [Кириллица в Юникоде](http://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D1%86%D0%B0_%D0%B2_%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4%D0%B5). - -Выведем отрезок символов юникода с кодами от `1034` до `1113`: +Examples of symbols encoded this way: ```js run -var str = ''; -for (var i = 1034; i <= 1113; i++) { - str += String.fromCharCode(i); -} -alert( str ); +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare chinese hieroglyph ``` -Результат: -
-ЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљ -
+Note that surrogate pairs are incorrectly processed by the language most of the time. We actually have a single symbol in each of the strings above, but the `length` shows the length of `2`. -Мы можем увидеть из этого отрезка две важных вещи: +`String.fromCodePoint` and `str.codePointAt` are notable exceptions that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:String/fromCharCode) and [str.charCodeAt](mdn:String/charCodeAt) that do the same, but don't work with surrogate pairs. -1. **Строчные буквы идут после заглавных, поэтому они всегда больше.** - - В частности, `'а'(код 1072) > 'Я'(код 1071)`. - - То же самое происходит и в английском алфавите, там `'a' > 'Z'`. -2. **Ряд букв, например `ё`, находятся вне основного алфавита.** - - В частности, маленькая буква `ё` имеет код, больший чем `я`, поэтому **`'ё'(код 1105) > 'я'(код 1103)`**. - - Кстати, большая буква `Ё` располагается в Unicode до `А`, поэтому **`'Ё'`(код 1025) < `'А'`(код 1040)**. Удивительно: есть буква меньше чем `А` :) - -**Буква `ё` не уникальна, точки над буквой используются и в других языках, приводя к тому же результату.** - -Например, при работе с немецкими названиями: +Getting a symbol can also be tricky, because most functions treat surrogate pairs as two characters: ```js run -alert( "ö" > "z" ); // true +alert( '𩷶'[0] ); // some strange symbols +alert( '𝒳'[0] ); // pieces of the surrogate pair ``` -```smart header="Юникод в HTML" -Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя "числовую ссылку" (numeric character reference). +Note that pieces of the surrogate pair have no meaning without each other. So, the alerts actually display garbage. -Для этого нужно написать сначала `&#`, затем код, и завершить точкой с запятой `';'`. Например, символ `'а'` в виде числовой ссылки: `а`. +How to solve this problem? First, let's make sure you have it. Not every project deals with surrogate pairs. -Если код хотят дать в 16-ричной системе счисления, то начинают с `&#x`. +But if you do, then there are libraries in the net which implement surrogate-aware versions of `slice`, `indexOf` and other functions. Surrogate pairs are detectable by their codes: the first character has the code in the interval of `0xD800..0xDBFF`, while the second is in `0xDC00..0xDFFF`. So if we see a character with the code, say, `0xD801`, then the next one must be the second part of the surrogate pair. -В юникоде есть много забавных и полезных символов, например, символ ножниц: ✂ (`✂`), дроби: ½ (`½`) ¾ (`¾`) и другие. Их можно использовать вместо картинок в дизайне. -``` +### Diacritical marks -## Посимвольное сравнение +In many languages there are symbols that are composed of the base character and a mark above/under it. -Сравнение строк работает *лексикографически*, иначе говоря, посимвольно. +For instance, letter `a` can be the base character for: `àáâäãåā`. Most common "composite" character have their own code in the UTF-16 table. But not all of them. -Сравнение строк `s1` и `s2` обрабатывается по следующему алгоритму: +To generate arbitrary compositions, several unicode characters are used: the base character and one or many "mark" characters. -1. Сравниваются первые символы: `s1[0]` и `s2[0]`. Если они разные, то сравниваем их и, в зависимости от результата их сравнения, возвратить `true` или `false`. Если же они одинаковые, то... -2. Сравниваются вторые символы `s1[1]` и `s2[1]` -3. Затем третьи `s1[2]` и `s2[2]` и так далее, пока символы не будут наконец разными, и тогда какой символ больше -- та строка и больше. Если же в какой-либо строке закончились символы, то считаем, что она меньше, а если закончились в обеих -- они равны. - -Спецификация языка определяет этот алгоритм более детально. Если же говорить простыми словами, смысл алгоритма в точности соответствует порядку, по которому имена заносятся в орфографический словарь. - -```js -"Вася" > "Ваня" // true, т.к. начальные символы совпадают, а потом 'с' > 'н' -"Дома" > "До" // true, т.к. начало совпадает, но в 1й строке больше символов -``` - -````warn header="Числа в виде строк сравниваются как строки" -Бывает, что числа приходят в скрипт в виде строк, например как результат `prompt`. В этом случае результат их сравнения будет неверным: +For instance, if we have `S` followed by "dot above" character (code `\u0307`), it is shown as Ṡ. ```js run -alert( "2" > "14" ); // true, так как это строки, и для первых символов верно "2" > "1" +alert( 'S\u0307' ); // Ṡ ``` -Если хотя бы один аргумент -- не строка, то другой будет преобразован к числу: +If we need a one more mark over the letter (or below it) -- no problems, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +The example: ```js run -alert( 2 > "14" ); // false +alert( 'S\u0307\u0323' ); // Ṩ ``` -```` -## Правильное сравнение +This leads to great flexibility, but also an interesting problem: the same symbol visually can be represented with different unicode compositions. -Все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), поддерживающий сравнение строк на разных языках, с учётом их правил. - -Способ использования: +For instance: ```js run -var str = "Ёлки"; +alert( 'S\u0307\u0323' ); // Ṩ, S + dot above + dot below +alert( 'S\u0323\u0307' ); // Ṩ, S + dot below + dot above -alert( str.localeCompare("Яблони") ); // -1 +alert( 'S\u0307\u0323' == 'S\u0323\u0307' ); // false ``` -Метод `str1.localeCompare(str2)` возвращает `-1`, если `str1 < str2`, `1`, если `str1 > str2` и `0`, если они равны. +To solve it, there exists a "unicode normalization" algorithm that brings each string to the single "normal" form. -Более подробно про устройство этого метода можно будет узнать в статье , когда это вам понадобится. +It is implemented by [str.normalize()](mdn:String/normalize). -## Итого +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` -- Строки в JavaScript имеют внутреннюю кодировку Юникод. При написании строки можно использовать специальные символы, например `\n` и вставлять юникодные символы по коду. -- Мы познакомились со свойством `length` и методами `charAt`, `toLowerCase/toUpperCase`, `substring/substr/slice` (предпочтителен `slice`). Есть и другие методы, например [trim](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) обрезает пробелы с начала и конца строки. -- Строки сравниваются побуквенно. Поэтому если число получено в виде строки, то такие числа могут сравниваться некорректно, нужно преобразовать его к типу *number*. -- При сравнении строк следует иметь в виду, что буквы сравниваются по их кодам. Поэтому большая буква меньше маленькой, а буква `ё` вообще вне основного алфавита. -- Для правильного сравнения существует целый стандарт ECMA 402. Это не такое простое дело, много языков и много правил. Он поддерживается во всех современных браузерах, кроме IE10-, в которых нужна библиотека . Такое сравнение работает через вызов `str1.localeCompare(str2)`. +It's rather funny that in that exactly situation `normalize()` brings a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In real, that is not always so, but the symbol `Ṩ` was considered "common enough" by UTF-16 creators to include it into the main table. + +For most practical tasks that information is enough, but if you want to learn more about normalization rules and variants -- they are described in the appendix to the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/). + + +## Summary + +- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions. +- Strings in JavaScript are encoded using UTF-16. +- We can use special characters like `\n` and insert letters by their unicode using `\u...`. +- To get a character: use `[]`. +- To get a substring: use `slice` or `substr/substring`. +- To lowercase/uppercase a string: use `toLowerCase/toUpperCase`. +- To look for a substring: use `indexOf`, or `includes/startsWith/endsWith` for simple checks. +- To compare strings according to the language, use `localeCompare`, otherwise they are compared by character codes. + +There are several other helpful methods in strings, like `str.trim()` that removes ("trims") spaces from the beginning and end of the string, see the [manual](mdn:String) for them. + +Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later. -Больше информации о методах для строк можно получить в справочнике: . \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/1-hello-object/solution.md b/1-js/4-data-structures/4-object/1-hello-object/solution.md index 465bf1ad..60083b96 100644 --- a/1-js/4-data-structures/4-object/1-hello-object/solution.md +++ b/1-js/4-data-structures/4-object/1-hello-object/solution.md @@ -1,10 +1,10 @@ ```js -var user = {}; -user.name = "Вася"; -user.surname = "Петров"; -user.name = "Сергей"; +let user = {}; +user.name = "John"; +user.surname = "Smith"; +user.name = "Pete"; delete user.name; ``` diff --git a/1-js/4-data-structures/4-object/1-hello-object/task.md b/1-js/4-data-structures/4-object/1-hello-object/task.md index 50b3f246..8482d3b4 100644 --- a/1-js/4-data-structures/4-object/1-hello-object/task.md +++ b/1-js/4-data-structures/4-object/1-hello-object/task.md @@ -2,13 +2,13 @@ importance: 3 --- -# Первый объект +# Hello, object -Мини-задача на синтаксис объектов. Напишите код, по строке на каждое действие. +Write the code, each line for an action: -1. Создайте пустой объект `user`. -2. Добавьте свойство `name` со значением `Вася`. -3. Добавьте свойство `surname` со значением `Петров`. -4. Поменяйте значение `name` на `Сергей`. -5. Удалите свойство `name` из объекта. +1. Create an empty object `user`. +2. Add the property `name` with the value `John`. +3. Add the property `surname` with the value `Smith`. +4. Change the value of the `name` to `Pete`. +5. Remove the property `name` from the object. diff --git a/1-js/4-data-structures/4-object/2-is-empty/_js.view/solution.js b/1-js/4-data-structures/4-object/2-is-empty/_js.view/solution.js new file mode 100644 index 00000000..e7f63284 --- /dev/null +++ b/1-js/4-data-structures/4-object/2-is-empty/_js.view/solution.js @@ -0,0 +1,7 @@ +function isEmpty(obj) { + for (let key in obj) { + // if the loop has started, there is a prorty + return false; + } + return true; +} \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/2-is-empty/_js.view/test.js b/1-js/4-data-structures/4-object/2-is-empty/_js.view/test.js new file mode 100644 index 00000000..4db5efab --- /dev/null +++ b/1-js/4-data-structures/4-object/2-is-empty/_js.view/test.js @@ -0,0 +1,11 @@ +describe("isEmpty", function() { + it("returns true for an empty object", function() { + assert.isTrue(isEmpty({})); + }); + + it("returns false if a property exists", function() { + assert.isFalse(isEmpty({ + anything: false + })); + }); +}); \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/2-is-empty/solution.md b/1-js/4-data-structures/4-object/2-is-empty/solution.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/1-js/4-data-structures/4-object/2-is-empty/solution.md @@ -0,0 +1 @@ + diff --git a/1-js/4-data-structures/4-object/2-is-empty/task.md b/1-js/4-data-structures/4-object/2-is-empty/task.md new file mode 100644 index 00000000..c438d36a --- /dev/null +++ b/1-js/4-data-structures/4-object/2-is-empty/task.md @@ -0,0 +1,20 @@ +importance: 5 + +--- + +# Check for emptiness + +Write the function `isEmpty(obj)` which returns `true` if the object has no properties, `false` otherwise. + +Should work like that: + +```js +let schedule = {}; + +alert( isEmpty(schedule) ); // true + +schedule["8:30"] = "get up"; + +alert( isEmpty(schedule) ); // false +``` + 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 new file mode 100644 index 00000000..7e980ba0 --- /dev/null +++ b/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/solution.js @@ -0,0 +1,10 @@ +function sumSalaries(salaries) { + + let sum = 0; + for (let name in salaries) { + sum += salaries[name]; + } + + return sum; +} + diff --git a/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/test.js b/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/test.js new file mode 100644 index 00000000..684b0894 --- /dev/null +++ b/1-js/4-data-structures/4-object/3-sum-salaries/_js.view/test.js @@ -0,0 +1,15 @@ +describe("sumSalaries", function() { + it("returns sum of salaries", function() { + let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 + }; + + assert.equal( sumSalaries(salaries), 650 ); + }); + + it("returns 0 for the empty object", function() { + assert.strictEqual( sumSalaries({}), 0); + }); +}); \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/3-sum-salaries/solution.md b/1-js/4-data-structures/4-object/3-sum-salaries/solution.md new file mode 100644 index 00000000..e69de29b 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 new file mode 100644 index 00000000..2ccdc27e --- /dev/null +++ b/1-js/4-data-structures/4-object/3-sum-salaries/task.md @@ -0,0 +1,24 @@ +importance: 5 + +--- + +# Sum the properties + +There is a `salaries` object with arbitrary number of salaries. + +Write the function `sumSalaries(salaries)` that returns the sum of all salaries. + +If `salaries` is empty, then the result must be `0`. + +For instance: + +```js +let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 +}; + +alert( sumSalaries(salaries) ); // 650 +``` + 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 new file mode 100644 index 00000000..fa32d1a7 --- /dev/null +++ b/1-js/4-data-structures/4-object/4-max-salary/_js.view/solution.js @@ -0,0 +1,16 @@ +function topSalary(salaries) { + + let max = 0; + let maxName = null; + + for (let name in salaries) { + if (max < salaries[name]) { + max = salaries[name]; + maxName = name; + } + } + + return maxName; +} + + diff --git a/1-js/4-data-structures/4-object/4-max-salary/_js.view/test.js b/1-js/4-data-structures/4-object/4-max-salary/_js.view/test.js new file mode 100644 index 00000000..e1da754b --- /dev/null +++ b/1-js/4-data-structures/4-object/4-max-salary/_js.view/test.js @@ -0,0 +1,15 @@ +describe("topSalary", function() { + it("returns top-paid person", function() { + let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 + }; + + assert.equal( topSalary(salaries), "Pete" ); + }); + + it("returns null for the empty object", function() { + assert.isNull( topSalary({}) ); + }); +}); \ No newline at end of file diff --git a/1-js/4-data-structures/4-object/4-max-salary/solution.md b/1-js/4-data-structures/4-object/4-max-salary/solution.md new file mode 100644 index 00000000..e69de29b 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 new file mode 100644 index 00000000..89052aea --- /dev/null +++ b/1-js/4-data-structures/4-object/4-max-salary/task.md @@ -0,0 +1,21 @@ +importance: 5 + +--- + +# The maximal salary + +There is a `salaries` object: + +```js +let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 +}; +``` + +Create the function `topSalary(salaries)` that returns the name of the top-paid person. + +- If `salaries` is empty, it shoul return `null`. +- If there are multiple top-paid persons, return any of them. + 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/5-multiply-numeric/_js.view/solution.js new file mode 100644 index 00000000..4668c1dc --- /dev/null +++ b/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/solution.js @@ -0,0 +1,7 @@ +function multiplyNumeric(obj) { + for (let key in obj) { + if (typeof obj[key] == 'number') { + obj[key] *= 2; + } + } +} \ No newline at end of file diff --git a/1-js/4-data-structures/5-object-for-in/4-multiply-numeric/solution.md b/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/source.js similarity index 51% rename from 1-js/4-data-structures/5-object-for-in/4-multiply-numeric/solution.md rename to 1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/source.js index a2e7e7dd..a02b1e1c 100644 --- a/1-js/4-data-structures/5-object-for-in/4-multiply-numeric/solution.md +++ b/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/source.js @@ -1,26 +1,17 @@ - - -```js run -var menu = { +let menu = { width: 200, height: 300, title: "My menu" }; -function isNumeric(n) { - return !isNaN(parseFloat(n)) && isFinite(n); -} function multiplyNumeric(obj) { - for (var key in obj) { - if (isNumeric(obj[key])) { - obj[key] *= 2; - } - } + + /* your code */ + } multiplyNumeric(menu); alert( "menu width=" + menu.width + " height=" + menu.height + " title=" + menu.title ); -``` diff --git a/1-js/4-data-structures/5-object-for-in/4-multiply-numeric/_js.view/test.js b/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/test.js similarity index 51% rename from 1-js/4-data-structures/5-object-for-in/4-multiply-numeric/_js.view/test.js rename to 1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/test.js index 73a1c465..1c0d6cf9 100644 --- a/1-js/4-data-structures/5-object-for-in/4-multiply-numeric/_js.view/test.js +++ b/1-js/4-data-structures/4-object/5-multiply-numeric/_js.view/test.js @@ -1,13 +1,13 @@ describe("multiplyNumeric", function() { - it("умножает численные свойства на 2", function() { + it("multiplies all numeric properties by 2", function() { var menu = { width: 200, - height: "300", - title: "Моё меню" + height: 300, + title: "My menu" }; multiplyNumeric(menu); assert.equal(menu.width, 400); assert.equal(menu.height, 600); - assert.equal(menu.title, "Моё меню"); + assert.equal(menu.title, "My menu"); }); }); \ 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-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/5-multiply-numeric/task.md new file mode 100644 index 00000000..33eb8922 --- /dev/null +++ b/1-js/4-data-structures/4-object/5-multiply-numeric/task.md @@ -0,0 +1,33 @@ +importance: 3 + +--- + +# Multiply numeric properties by 2 + +Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`. + +For instance: + +```js +// before the call +let menu = { + width: 200, + height: 300, + title: "My menu" +}; + +multiplyNumeric(menu); + +// after the call +menu = { + width: 400, + height: 600, + title: "My menu" +}; +``` + +Please note that `multiplyNumeric` does not need to return anything. It should modify the object in-place. + +P.S. Use `typeof` to check for a number here. + + diff --git a/1-js/4-data-structures/4-object/article.md b/1-js/4-data-structures/4-object/article.md index 9bbd3d5a..4f8b2f8b 100644 --- a/1-js/4-data-structures/4-object/article.md +++ b/1-js/4-data-structures/4-object/article.md @@ -1,321 +1,483 @@ -# Объекты как ассоциативные массивы +# Objects as dictionaries -Объекты в JavaScript сочетают в себе два важных функционала. +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. + +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. [cut] -## Ассоциативные массивы - -[Ассоциативный массив](http://ru.wikipedia.org/wiki/%D0%90%D1%81%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2) -- структура данных, в которой можно хранить любые данные в формате ключ-значение. - -Её можно легко представить как шкаф с подписанными ящиками. Все данные хранятся в ящичках. По имени можно легко найти ящик и взять то значение, которое в нём лежит. +We can imagine it as a cabinet with signed files. Every piece of data is stored in it's file. It's easy to find a file by it's name or add/remove a file. ![](object.png) -В отличие от реальных шкафов, в ассоциативный массив можно в любой момент добавить новые именованные "ящики" или удалить существующие. Далее мы увидим примеры, как это делается. +## Creation -Кстати, в других языках программирования такую структуру данных также называют *"словарь"* и *"хэш"*. - -## Создание объектов - -Пустой объект ("пустой шкаф") может быть создан одним из двух синтаксисов: +An empty object ("empty cabinet") can be created using one of two syntaxes: ```js -1. o = new Object(); -2. o = {}; // пустые фигурные скобки +let obj = new Object(); // works same as below +let obj = {}; ``` -Обычно все пользуются синтаксисом `(2)`, т.к. он короче. +Usually, the second syntax is prefered, because it's shorter and allows to define properties immediately: -## Операции с объектом - -Объект может содержать в себе любые значения, которые называются *свойствами объекта*. Доступ к свойствам осуществляется по *имени свойства* (иногда говорят *"по ключу"*). - -Например, создадим объект `person` для хранения информации о человеке: - -```js -var person = {}; // пока пустой +```js +let user = { + name: "John", + age: 30, + "day of birth": "1 Jan 1990" +}; ``` ![](object-person-empty.png) -Основные операции с объектами -- это создание, получение и удаление свойств. +A property name can be only a string. No number/boolean or any other type can serve that purpose. -Для обращения к свойствам используется запись "через точку", вида `объект.свойство`, например: +Note that complex property names need to be quoted, to evade syntax errors. But normally that's not required. -```js -// при присвоении свойства в объекте автоматически создаётся "ящик" -// с именем "name" и в него записывается содержимое 'Вася' -person.name = 'Вася'; -person.age = 25; // запишем ещё одно свойство: с именем 'age' и значением 25 + +## 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; ``` -![](object-person-1.png) +## Square brackets -Значения хранятся "внутри" ящиков. Обратим внимание -- любые значения, любых типов: число, строка -- не важно. +To access a property, there are two syntaxes: -Чтобы прочитать их -- также обратимся через точку: -```js -alert( person.name + ': ' + person.age ); // "Вася: 25" +- The dot notation: `user.name` +- Square brackets: `user["name"]` + +Square brackets are more powerful, because they allow to specify arbitrary string as a property name. In contrast, the dot notation requires the nae to be a valid variable identifier, that is: no spaces, special chracters etc. + +But more than that, square brackets is the only choice when the name of the property is in a variable. + +For instance: + +```js run +let user = { + name: "John", + age: 30 +}; + +let key = prompt("What do you want to know about the user?", "name"); + +alert( user[key] ); // John (if enter "name"), 30 for the "age" ``` -Удаление осуществляется оператором `delete`: +The square brackets literally say: "take the property name from the variable". -```js -delete person.age; +Also it is possible to use square brackets in object definition when the property name is stored in a variable or computed: + +```js run +let fruit = prompt("Which fruit to buy?", "apple"); + +let bag = { + [fruit]: 5, +}; + +alert( bag.apple ); // 5 if fruit="apple" ``` -Осталось только свойство `name`: +Here, the object `bag` is created with a property with the name from `fruit` variable and the value `5`. -![](object-person-2.png) +Essentially, that works the same as: +```js +let bag = {}; +bag[fruit] = 5; +``` -Следующая операция: -
    -
  1. **Проверка существования свойства с определенным ключом.**
  2. -
+...But one statement instead of two. -Для проверки существования свойства в объекте есть оператор `in`. - -Его синтаксис: `"prop" in obj`, причем имя свойства -- в виде строки, например: +We could have used a more complex expression inside square brackets or a quoted string. Anything that would return a property name: ```js -if ("name" in person) { - alert( "Свойство name существует!" ); +let bag = { + [ fruit.toLowerCase() ]: 5 // if fruit is "APPLE" then 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: + +```js run +let user = {}; + +alert( user.noSuchProperty === undefined ); // true means "no such property" +``` + +There also exists a special operator `"in"` to check for the existance of a property. + +The syntax is: +```js +"key" in object +``` + +For instance: + +```js run +let user = { name: "John", age: 30 }; + +alert( "age" in user ); // true, user.age exists +alert( "blabla" in user ); // false, user.blabla doesn't exist +``` + +Please note that at the left side of `in` there must be a *string*. The property name must quoted, like `"age"`. + +Without quotes, that would mean a variable containing the actual name to be tested. For instance: + +```js run +let user = { age: 30 }; + +let key = "age"; +alert( key in user ); // true, takes the name "age" from the variable and tests it +``` + +The `in` operator works in the certain case when the previous method doesn't. That is: when an object property stores `undefined`. + + +```js run +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. + +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] ... */ } ``` -Впрочем, чаще используется другой способ -- сравнение значения с `undefined`. +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. -Дело в том, что **в JavaScript можно обратиться к любому свойству объекта, даже если его нет**. Ошибки не будет. - -Но если свойство не существует, то вернется специальное значение `undefined`: - -```js run -var person = {}; - -alert( person.lalala ); // undefined, нет свойства с ключом lalala -``` - -Таким образом **мы можем легко проверить существование свойства -- получив его и сравнив с `undefined`**: - -```js run -var person = { - name: "Василий" -}; - -alert( person.lalala === undefined ); // true, свойства нет -alert( person.name === undefined ); // false, свойство есть. -``` - -````smart header="Разница между проверками `in` и `=== undefined`" -Есть два средства для проверки наличия свойства в объекте: первое -- оператор `in`, второе -- получить его и сравнить его с `undefined`. - -Они почти идентичны, но есть одна небольшая разница. - -Дело в том, что технически возможно, что *свойство есть и равно `undefined`*: - -```js untrusted refresh run -var obj = {}; -obj.test = undefined; // добавили свойство со значением undefined - -*!* -// проверим наличие свойств test и заведомо отсутствующего blabla -alert( obj.test === undefined ); // true -alert( obj.blabla === undefined ); // true -*/!* -``` - -...При этом, как видно из кода, при простом сравнении наличие такого свойства будет неотличимо от его отсутствия. - -Но оператор `in` гарантирует правильный результат: - -```js untrusted refresh run -var obj = {}; -obj.test = undefined; - -*!* -alert( "test" in obj ); // true -alert( "blabla" in obj ); // false -*/!* -``` - -Как правило, в коде мы не будем присваивать `undefined`, чтобы корректно работали обе проверки. А в качестве значения, обозначающего неизвестность и неопределенность, будем использовать `null`. -```` - -### Доступ через квадратные скобки - -Существует альтернативный синтаксис работы со свойствами, использующий квадратные скобки `объект['свойство']`: - -```js run -var person = {}; - -person['name'] = 'Вася'; // то же что и person.name = 'Вася' -``` - -Записи `person['name']` и `person.name` идентичны, но квадратные скобки позволяют использовать в качестве имени свойства любую строку: - -```js run -var person = {}; - -person['любимый стиль музыки'] = 'Джаз'; // то же что и person.name = 'Вася' -``` - -Такое присвоение было бы невозможно "через точку", так интерпретатор после первого пробела подумает, что свойство закончилось, и далее выдаст ошибку: - -```js run -person.любимый стиль музыки = 'Джаз'; // ??? ошибка -``` - -В обоих случаях, **имя свойства обязано быть строкой**. Если использовано значение другого типа -- JavaScript приведет его к строке автоматически. - -### Доступ к свойству через переменную - -Квадратные скобки также позволяют обратиться к свойству, имя которого хранится в переменной: - -```js run -var person = { - age: 25 -}; -var key = 'age'; - -alert( person[key] ); // выведет person['age'] -``` - -Вообще, если имя свойства хранится в переменной (`var key = "age"`), то единственный способ к нему обратиться -- это квадратные скобки `person[key]`. - -Доступ через точку используется, если мы на этапе написания программы уже знаем название свойства. А если оно будет определено по ходу выполнения, например, введено посетителем и записано в переменную, то единственный выбор -- квадратные скобки. - -### Объявление со свойствами - -Объект можно заполнить значениями при создании, указав их в фигурных скобках: `{ ключ1: значение1, ключ2: значение2, ... }`. - -Такой синтаксис называется *литеральным* (англ. literal). - -Следующие два фрагмента кода создают одинаковый объект: +````smart header="Inline variable declaration: `for (let key in obj)`" +A variable for property names can be declared right in the loop: ```js -var menuSetup = { +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: + +```js run +let menu = { width: 300, height: 200, title: "Menu" }; -// то же самое, что: +for (let key in menu) { + // the code will be called for each menu property + // ...and show its name and value -var menuSetup = {}; -menuSetup.width = 300; -menuSetup.height = 200; -menuSetup.title = 'Menu'; -``` - -Названия свойств можно перечислять как в кавычках, так и без, если они удовлетворяют ограничениям для имён переменных. - -Например: - -```js -var menuSetup = { - width: 300, - 'height': 200, - "мама мыла раму": true -}; -``` - -В качестве значения можно тут же указать и другой объект: - -```js -var user = { - name: "Таня", - age: 25, *!* - size: { - top: 90, - middle: 60, - bottom: 90 - } + alert( `Key:${key}, value:${menu[key]}` ); */!* } - -alert(user.name) // "Таня" - -alert(user.size.top) // 90 ``` -Здесь значением свойства `size` является объект `{top: 90, middle: 60, bottom: 90 }`. -## Компактное представление объектов +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. -```warn header="Hardcore coders only" -Эта секция относится ко внутреннему устройству структуры данных. Она не обязательна к прочтению. +### Counting properties + +How to see how many properties are stored in the object? There's no method for that. + +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 `