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:
-
`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-ричной системе счисления, то начинают с ``.
+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.

-В отличие от реальных шкафов, в ассоциативный массив можно в любой момент добавить новые именованные "ящики" или удалить существующие. Далее мы увидим примеры, как это делается.
+## 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"
+};
```

-Основные операции с объектами -- это создание, получение и удаление свойств.
+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;
```
-
+## 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`.
-
+Essentially, that works the same as:
+```js
+let bag = {};
+bag[fruit] = 5;
+```
-Следующая операция:
-
-
**Проверка существования свойства с определенным ключом.**
-
+...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 `