ok
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(""), "");
|
||||
});
|
||||
});
|
|
@ -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. Возможны и более короткие решения, использующие методы для работы со строками, которые мы пройдём далее.
|
|
@ -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()`.
|
||||
|
|
|
@ -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');
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
});
|
|
@ -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') );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
function truncate(str, maxlength) {
|
||||
return (str.length > maxlength) ?
|
||||
str.slice(0, maxlength - 3) + '...' : str;
|
||||
str.slice(0, maxlength - 1) + '…' : str;
|
||||
}
|
|
@ -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!"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
```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) );
|
||||
```
|
||||
|
||||
Можно было бы написать этот код ещё короче:
|
||||
Note that there is actually a single unicode character for an ellipsis. That's not three dots.
|
||||
|
||||
```js run
|
||||
function truncate(str, maxlength) {
|
||||
return (str.length > maxlength) ?
|
||||
str.slice(0, maxlength - 3) + '...' : str;
|
||||
str.slice(0, maxlength - 1) + '…' : str;
|
||||
}
|
||||
```
|
||||
|
||||
P.S. Кстати, в кодироке Unicode существует специальный символ "троеточие": `…` (HTML: `…`), который можно использовать вместо трёх точек. Если его использовать, то можно отрезать только один символ.
|
||||
|
|
|
@ -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!"
|
||||
```
|
||||
|
||||
Эта функция имеет применение в жизни. Она используется, чтобы усекать слишком длинные темы сообщений.
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Возьмём часть строки после первого символа и приведём к числу: `+str.slice(1)`.
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
<table>
|
||||
<CAPTION>Специальные символы</CAPTION>
|
||||
<thead>
|
||||
<tr><th>Символ</th><th>Описание</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>\b</td><td>Backspace</td></tr>
|
||||
<tr><td>\f</td><td>Form feed</td></tr>
|
||||
<tr><td>\n</td><td>New line</td></tr>
|
||||
<tr><td>\r</td><td>Carriage return</td></tr>
|
||||
<tr><td>\t</td><td>Tab</td></tr>
|
||||
<tr><td>\uNNNN</td><td>Символ в кодировке Юникод с шестнадцатеричным кодом `NNNN`. Например, `\u00A9` -- юникодное представление символа копирайт ©
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
## Методы и свойства
|
||||
|
||||
Здесь мы рассмотрим методы и свойства строк, с некоторыми из которых мы знакомились ранее, в главе <info:properties-and-methods>.
|
||||
|
||||
### Длина 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() ); // 'и'
|
||||
```
|
||||
|
||||
### Поиск подстроки
|
||||
|
||||
Для поиска подстроки есть метод <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/indexOf">indexOf(подстрока[, начальная_позиция])</a>.
|
||||
|
||||
Он возвращает позицию, на которой находится `подстрока` или `-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
|
||||
```
|
||||
|
||||
Также существует аналогичный метод <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/lastIndexOf">lastIndexOf</a>, который ищет не с начала, а с конца строки.
|
||||
|
||||
````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( foundPos ); // нашли на этой позиции
|
||||
pos = foundPos + 1; // продолжить поиск со следующей
|
||||
}
|
||||
alert( "Hello".includes("Bye") ); // false
|
||||
```
|
||||
|
||||
Такой цикл начинает поиск с позиции `0`, затем найдя подстроку на позиции `foundPos`, следующий поиск продолжит с позиции `pos = foundPos+1`, и так далее, пока что-то находит.
|
||||
|
||||
Впрочем, тот же алгоритм можно записать и короче:
|
||||
The methods [str.startsWith](mdn:String/startsWith) and [str.endsWith](mdn:String/endsWith) do exactly what they promise:
|
||||
|
||||
```js run
|
||||
var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
|
||||
var target = "Иа"; // цель поиска
|
||||
|
||||
*!*
|
||||
var pos = -1;
|
||||
while ((pos = str.indexOf(target, pos + 1)) != -1) {
|
||||
alert( pos );
|
||||
}
|
||||
*/!*
|
||||
alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
|
||||
alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
|
||||
```
|
||||
|
||||
### Взятие подстроки: substr, substring, slice.
|
||||
|
||||
В JavaScript существуют целых 3 (!) метода для взятия подстроки, с небольшими отличиями между ними.
|
||||
## Getting a substring
|
||||
|
||||
`substring(start [, end])`
|
||||
: Метод `substring(start, end)` возвращает подстроку с позиции `start` до, но не включая `end`.
|
||||
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
|
||||
var str = "*!*s*/!*tringify";
|
||||
alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1.
|
||||
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
|
||||
```
|
||||
|
||||
Если аргумент `end` отсутствует, то идет до конца строки:
|
||||
If there is no `end` argument, then `slice` goes till the end of the string:
|
||||
|
||||
```js run
|
||||
var str = "st*!*ringify*/!*";
|
||||
alert(str.substring(2)); // ringify, символы с позиции 2 до конца
|
||||
let str = "st*!*ringify*/!*";
|
||||
alert( str.slice(2) ); // ringify, from the 2nd position till the end
|
||||
```
|
||||
|
||||
<dt>`substr(start [, length])`
|
||||
: Первый аргумент имеет такой же смысл, как и в `substring`, а второй содержит не конечную позицию, а количество символов.
|
||||
Negative values for `start/end` are also possible. They mean the position is counted from the string end:
|
||||
|
||||
```js run
|
||||
var str = "st*!*ring*/!*ify";
|
||||
str = str.substr(2,4); // ring, со 2й позиции 4 символа
|
||||
alert(str)
|
||||
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
|
||||
```
|
||||
|
||||
Если второго аргумента нет -- подразумевается "до конца строки".
|
||||
|
||||
`slice(start [, end])`
|
||||
: Возвращает часть строки от позиции `start` до, но не включая, позиции `end`. Смысл параметров -- такой же как в `substring`.
|
||||
`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:
|
||||
|
||||
Различие между `substring` и `slice` -- в том, как они работают с отрицательными и выходящими за границу строки аргументами:
|
||||
|
||||
`substring(start, end)`
|
||||
: Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки:
|
||||
|
||||
```js run
|
||||
alert( "testme".substring(-2) ); // "testme", -2 становится 0
|
||||
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)
|
||||
|
||||
```
|
||||
|
||||
Кроме того, если <code>start > end</code>, то аргументы меняются местами, т.е. возвращается участок строки *между* `start` и `end`:
|
||||
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
|
||||
alert( "testme".substring(4, -1) ); // "test"
|
||||
// -1 становится 0 -> получили substring(4, 0)
|
||||
// 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test"
|
||||
let str = "st*!*ring*/!*ify";
|
||||
alert( str.substr(2, 4) ); // ring, from the 2nd position get 4 characters
|
||||
```
|
||||
|
||||
`slice`
|
||||
: Отрицательные значения отсчитываются от конца строки:
|
||||
The first argument may be negative, to count from the end:
|
||||
|
||||
```js run
|
||||
alert( "testme".slice(-2) ); // "me", от 2 позиции с конца
|
||||
let str = "strin*!*gi*/!*fy";
|
||||
alert( str.substr(-4, 2) ); // gi, from the 4th position get 2 characters
|
||||
```
|
||||
|
||||
```js run
|
||||
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.
|
||||
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.
|
||||
```
|
||||
|
||||
Это гораздо более удобно, чем странная логика `substring`.
|
||||
## Comparing strings
|
||||
|
||||
Отрицательное значение первого параметра поддерживается в `substr` во всех браузерах, кроме IE8-.
|
||||
As we know from the chapter <info:comparison>, strings are compared character-by-character, in the alphabet order.
|
||||
|
||||
Если выбирать из этих трёх методов один, для использования в большинстве ситуаций -- то это будет `slice`: он и отрицательные аргументы поддерживает и работает наиболее очевидно.
|
||||
Although, there are some oddities.
|
||||
|
||||
## Кодировка Юникод
|
||||
|
||||
Как мы знаем, символы сравниваются в алфавитном порядке `'А' < 'Б' < 'В' < ... < 'Я'`.
|
||||
|
||||
Но есть несколько странностей..
|
||||
|
||||
1. Почему буква `'а'` маленькая больше буквы `'Я'` большой?
|
||||
1. A lowercase letter is always greater than the uppercase:
|
||||
|
||||
```js run
|
||||
alert( 'а' > 'Я' ); // true
|
||||
```
|
||||
2. Буква `'ё'` находится в алфавите между `е` и `ж`: <code>абвгде**ё**жз..</code>. Но почему тогда `'ё'` больше `'я'`?
|
||||
|
||||
```js run
|
||||
alert( 'ё' > 'я' ); // true
|
||||
alert( 'a' > 'Z' ); // 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`:
|
||||
2. Letters with diacritical marks are "out of the alphabet":
|
||||
|
||||
```js run
|
||||
alert( String.fromCharCode(1072) ); // 'а'
|
||||
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.
|
||||
|
||||
str.charCodeAt(pos)
|
||||
: Возвращает код символа на позиции `pos`. Отсчет позиции начинается с нуля.
|
||||
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
|
||||
alert( "абрикос".charCodeAt(0) ); // 1072, код 'а'
|
||||
// different case letters have different codes
|
||||
alert( "z".codePointAt(0) ); // 122
|
||||
alert( "Z".codePointAt(0) ); // 90
|
||||
```
|
||||
|
||||
Теперь вернемся к примерам выше. Почему сравнения `'ё' > 'я'` и `'а' > 'Я'` дают такой странный результат?
|
||||
|
||||
Дело в том, что **символы сравниваются не по алфавиту, а по коду**. У кого код больше -- тот и больше. В юникоде есть много разных символов. Кириллическим буквам соответствует только небольшая часть из них, подробнее -- [Кириллица в Юникоде](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`:
|
||||
`String.fromCodePoint(code)`
|
||||
: Creates a character by its numeric `code`
|
||||
|
||||
```js run
|
||||
var str = '';
|
||||
for (var i = 1034; i <= 1113; i++) {
|
||||
str += String.fromCharCode(i);
|
||||
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{|}~
|
||||
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
|
||||
```
|
||||
|
||||
Результат:
|
||||
<div style="overflow: auto">
|
||||
<code>ЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљ</code>
|
||||
</div>
|
||||
Now it becomes obvious why `a > Z`.
|
||||
|
||||
Мы можем увидеть из этого отрезка две важных вещи:
|
||||
The characters are compared by their numeric code. The greater code means that the character is greater.
|
||||
|
||||
1. **Строчные буквы идут после заглавных, поэтому они всегда больше.**
|
||||
And we can easily see that:
|
||||
|
||||
В частности, `'а'(код 1072) > 'Я'(код 1071)`.
|
||||
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`.
|
||||
|
||||
То же самое происходит и в английском алфавите, там `'a' > 'Z'`.
|
||||
2. **Ряд букв, например `ё`, находятся вне основного алфавита.**
|
||||
|
||||
В частности, маленькая буква `ё` имеет код, больший чем `я`, поэтому **`'ё'(код 1105) > 'я'(код 1103)`**.
|
||||
### The correct way
|
||||
|
||||
Кстати, большая буква `Ё` располагается в Unicode до `А`, поэтому **`'Ё'`(код 1025) < `'А'`(код 1040)**. Удивительно: есть буква меньше чем `А` :)
|
||||
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
|
||||
alert( "ö" > "z" ); // true
|
||||
alert( 'Österreich'.localeCompare('Zealand') ); // -1
|
||||
```
|
||||
|
||||
```smart header="Юникод в HTML"
|
||||
Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя "числовую ссылку" (numeric character reference).
|
||||
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.
|
||||
|
||||
Для этого нужно написать сначала `&#`, затем код, и завершить точкой с запятой `';'`. Например, символ `'а'` в виде числовой ссылки: `а`.
|
||||
## Encoding
|
||||
|
||||
Если код хотят дать в 16-ричной системе счисления, то начинают с `&#x`.
|
||||
```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.
|
||||
|
||||
В юникоде есть много забавных и полезных символов, например, символ ножниц: ✂ (`✂`), дроби: ½ (`½`) ¾ (`¾`) и другие. Их можно использовать вместо картинок в дизайне.
|
||||
You can skip the section if all you need is common letters and digits.
|
||||
```
|
||||
|
||||
## Посимвольное сравнение
|
||||
### Surrogate pairs
|
||||
|
||||
Сравнение строк работает *лексикографически*, иначе говоря, посимвольно.
|
||||
Most symbols have a 2-byte code. Letters of most european languages, numbers, even most hieroglyphs have a 2-byte representation.
|
||||
|
||||
Сравнение строк `s1` и `s2` обрабатывается по следующему алгоритму:
|
||||
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".
|
||||
|
||||
1. Сравниваются первые символы: `s1[0]` и `s2[0]`. Если они разные, то сравниваем их и, в зависимости от результата их сравнения, возвратить `true` или `false`. Если же они одинаковые, то...
|
||||
2. Сравниваются вторые символы `s1[1]` и `s2[1]`
|
||||
3. Затем третьи `s1[2]` и `s2[2]` и так далее, пока символы не будут наконец разными, и тогда какой символ больше -- та строка и больше. Если же в какой-либо строке закончились символы, то считаем, что она меньше, а если закончились в обеих -- они равны.
|
||||
|
||||
Спецификация языка определяет этот алгоритм более детально. Если же говорить простыми словами, смысл алгоритма в точности соответствует порядку, по которому имена заносятся в орфографический словарь.
|
||||
|
||||
```js
|
||||
"Вася" > "Ваня" // true, т.к. начальные символы совпадают, а потом 'с' > 'н'
|
||||
"Дома" > "До" // true, т.к. начало совпадает, но в 1й строке больше символов
|
||||
```
|
||||
|
||||
````warn header="Числа в виде строк сравниваются как строки"
|
||||
Бывает, что числа приходят в скрипт в виде строк, например как результат `prompt`. В этом случае результат их сравнения будет неверным:
|
||||
Examples of symbols encoded this way:
|
||||
|
||||
```js run
|
||||
alert( "2" > "14" ); // true, так как это строки, и для первых символов верно "2" > "1"
|
||||
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.
|
||||
|
||||
Getting a symbol can also be tricky, because most functions treat surrogate pairs as two characters:
|
||||
|
||||
```js run
|
||||
alert( 2 > "14" ); // false
|
||||
alert( '𩷶'[0] ); // some strange symbols
|
||||
alert( '𝒳'[0] ); // pieces of the surrogate pair
|
||||
```
|
||||
````
|
||||
|
||||
## Правильное сравнение
|
||||
Note that pieces of the surrogate pair have no meaning without each other. So, the alerts actually display garbage.
|
||||
|
||||
Все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), поддерживающий сравнение строк на разных языках, с учётом их правил.
|
||||
How to solve this problem? First, let's make sure you have it. Not every project deals with surrogate pairs.
|
||||
|
||||
Способ использования:
|
||||
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.
|
||||
|
||||
To generate arbitrary compositions, several unicode characters are used: the base character and one or many "mark" characters.
|
||||
|
||||
For instance, if we have `S` followed by "dot above" character (code `\u0307`), it is shown as Ṡ.
|
||||
|
||||
```js run
|
||||
var str = "Ёлки";
|
||||
|
||||
alert( str.localeCompare("Яблони") ); // -1
|
||||
alert( 'S\u0307' ); // Ṡ
|
||||
```
|
||||
|
||||
Метод `str1.localeCompare(str2)` возвращает `-1`, если `str1 < str2`, `1`, если `str1 > str2` и `0`, если они равны.
|
||||
If we need a one more mark over the letter (or below it) -- no problems, just add the necessary mark character.
|
||||
|
||||
Более подробно про устройство этого метода можно будет узнать в статье <info:intl>, когда это вам понадобится.
|
||||
For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`.
|
||||
|
||||
## Итого
|
||||
The example:
|
||||
|
||||
- Строки в 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-, в которых нужна библиотека <https://github.com/andyearnshaw/Intl.js/>. Такое сравнение работает через вызов `str1.localeCompare(str2)`.
|
||||
```js run
|
||||
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.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
alert( 'S\u0307\u0323' ); // Ṩ, S + dot above + dot below
|
||||
alert( 'S\u0323\u0307' ); // Ṩ, S + dot below + dot above
|
||||
|
||||
alert( 'S\u0307\u0323' == 'S\u0323\u0307' ); // false
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Больше информации о методах для строк можно получить в справочнике: <http://javascript.ru/String>.
|
|
@ -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;
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
11
1-js/4-data-structures/4-object/2-is-empty/_js.view/test.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
describe("isEmpty", function() {
|
||||
it("returns true for an empty object", function() {
|
||||
assert.isTrue(isEmpty({}));
|
||||
});
|
||||
|
||||
it("returns false if a property exists", function() {
|
||||
assert.isFalse(isEmpty({
|
||||
anything: false
|
||||
}));
|
||||
});
|
||||
});
|
1
1-js/4-data-structures/4-object/2-is-empty/solution.md
Normal file
|
@ -0,0 +1 @@
|
|||
|
20
1-js/4-data-structures/4-object/2-is-empty/task.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Check for emptiness
|
||||
|
||||
Write the function `isEmpty(obj)` which returns `true` if the object has no properties, `false` otherwise.
|
||||
|
||||
Should work like that:
|
||||
|
||||
```js
|
||||
let schedule = {};
|
||||
|
||||
alert( isEmpty(schedule) ); // true
|
||||
|
||||
schedule["8:30"] = "get up";
|
||||
|
||||
alert( isEmpty(schedule) ); // false
|
||||
```
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
function sumSalaries(salaries) {
|
||||
|
||||
let sum = 0;
|
||||
for (let name in salaries) {
|
||||
sum += salaries[name];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
24
1-js/4-data-structures/4-object/3-sum-salaries/task.md
Normal file
|
@ -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
|
||||
```
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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({}) );
|
||||
});
|
||||
});
|
0
1-js/4-data-structures/4-object/4-max-salary/solution.md
Normal file
21
1-js/4-data-structures/4-object/4-max-salary/task.md
Normal file
|
@ -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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
function multiplyNumeric(obj) {
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] == 'number') {
|
||||
obj[key] *= 2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
```
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
33
1-js/4-data-structures/4-object/5-multiply-numeric/task.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
importance: 3
|
||||
|
||||
---
|
||||
|
||||
# Multiply numeric properties by 2
|
||||
|
||||
Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`.
|
||||
|
||||
For instance:
|
||||
|
||||
```js
|
||||
// before the call
|
||||
let menu = {
|
||||
width: 200,
|
||||
height: 300,
|
||||
title: "My menu"
|
||||
};
|
||||
|
||||
multiplyNumeric(menu);
|
||||
|
||||
// after the call
|
||||
menu = {
|
||||
width: 400,
|
||||
height: 600,
|
||||
title: "My menu"
|
||||
};
|
||||
```
|
||||
|
||||
Please note that `multiplyNumeric` does not need to return anything. It should modify the object in-place.
|
||||
|
||||
P.S. Use `typeof` to check for a number here.
|
||||
|
||||
|
|
@ -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)`, т.к. он короче.
|
||||
|
||||
## Операции с объектом
|
||||
|
||||
Объект может содержать в себе любые значения, которые называются *свойствами объекта*. Доступ к свойствам осуществляется по *имени свойства* (иногда говорят *"по ключу"*).
|
||||
|
||||
Например, создадим объект `person` для хранения информации о человеке:
|
||||
Usually, the second syntax is prefered, because it's shorter and allows to define properties immediately:
|
||||
|
||||
```js
|
||||
var person = {}; // пока пустой
|
||||
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.
|
||||
|
||||
|
||||
|
||||
## Add/remove properties
|
||||
|
||||
Now we can read/write new properties using the dot notation and remove using the `delete` operator:
|
||||
|
||||
```js
|
||||
// при присвоении свойства в объекте автоматически создаётся "ящик"
|
||||
// с именем "name" и в него записывается содержимое 'Вася'
|
||||
person.name = 'Вася';
|
||||
user.surname = "Smith";
|
||||
|
||||
person.age = 25; // запишем ещё одно свойство: с именем 'age' и значением 25
|
||||
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;
|
||||
```
|
||||
|
||||
Следующая операция:
|
||||
<ol start="4">
|
||||
<li>**Проверка существования свойства с определенным ключом.**</li>
|
||||
</ol>
|
||||
...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 `<select>` list. If we're making a site mainly for German audience then we probably want `49` to be the first.
|
||||
|
||||
But if we try to loop over the object, we see a totally different picture: USA (1) goes first, then Switzerland (41) and so on.
|
||||
|
||||
That's because according to the language stantard objects have no order. The loop is officially allowed to list properties randomly.
|
||||
|
||||
But in practice, there's a de-facto agreement among JavaScript engines.
|
||||
|
||||
- The numeric properties are sorted.
|
||||
- Non-numeric properties are ordered as they appear in the object.
|
||||
|
||||
That agreement is not enforced by a standard, but stands strong, because a lot of JavaScript code is already based on it.
|
||||
|
||||
```smart header="Older JS Engines order everything"
|
||||
Old JavaScript engines, like IE9-, keep all properties sorted. But this behavior is a relict nowadays.
|
||||
```
|
||||
|
||||
Now it's easy to see that the properties were iterated in the ascending order, because they are numeric... Of course, object property names are strings, but the Javascript engine detects that it's a number and applies internal optimizations to it, including sorting. That's why we see `1, 41, 44, 49`.
|
||||
|
||||
On the other hand, if the keys are non-numeric, then they are listed as they appear, for instance:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
surname: "Smith"
|
||||
};
|
||||
user.age = 25; // add one more
|
||||
|
||||
*!*
|
||||
// as they appear in the object
|
||||
*/!*
|
||||
for (let prop in user) {
|
||||
alert( prop ); // name, surname, age
|
||||
}
|
||||
```
|
||||
|
||||
So, to fix the issue with the phone codes, we can "cheat" by making the codes non-numeric. Adding a plus `"+"` sign before each code is enough.
|
||||
|
||||
Like this:
|
||||
|
||||
```js run
|
||||
let codes = {
|
||||
"+49": "Germany",
|
||||
"+41": "Switzerland",
|
||||
"+44": "Great Britain",
|
||||
// ..,
|
||||
"+1": "USA"
|
||||
};
|
||||
|
||||
for(let code in codes) {
|
||||
// explicitly convert each code to a number if required
|
||||
alert( +code ); // 49, 41, 44, 1
|
||||
}
|
||||
```
|
||||
|
||||
Now it works as intended.
|
||||
|
||||
|
||||
## Copying by reference
|
||||
|
||||
One of fundamental differences of objects vs primitives is that they are stored and copied "by reference".
|
||||
|
||||
Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value".
|
||||
|
||||
For instance:
|
||||
|
||||
```js
|
||||
var user = {
|
||||
name: "Vasya",
|
||||
age: 25
|
||||
let message = "hello";
|
||||
let phrase = message;
|
||||
```
|
||||
|
||||
As a result we have two independant variables, each one is storing the string `"hello"`.
|
||||
|
||||

|
||||
|
||||
Objects are not like that.
|
||||
|
||||
**A variable stores not the object itself, but it's "address in memory", in other words "a reference" to it.**
|
||||
|
||||
Here's the picture for the object:
|
||||
|
||||
```js
|
||||
let user = {
|
||||
name: "John"
|
||||
};
|
||||
```
|
||||
|
||||
Здесь содержится информация о свойстве `name` и его строковом значении, а также о свойстве `age` и его численном значении. Представим, что таких объектов много.
|
||||

|
||||
|
||||
Получится, что информация об именах свойств `name` и `age` дублируется в каждом объекте. Чтобы этого избежать, браузер применяет оптимизацию.
|
||||
Note that the object itself is stored somewhere in memory. The variable `user` has a "reference" to it.
|
||||
|
||||
**При создании множества объектов одного и того же вида (с одинаковыми полями) интерпретатор выносит описание полей в отдельную структуру. А сам объект остаётся в виде непрерывной области памяти с данными.**
|
||||
**When an object variable is copied -- the reference is copied, the object is still single.**
|
||||
|
||||
Например, есть много объектов с полями `name` и `age`:
|
||||
We can easily grasp it if imagine an object as a cabinet, and a variable is a key to it. We can copy that key to another variable, the cabinet is still single.
|
||||
|
||||
For instance:
|
||||
|
||||
```js no-beautify
|
||||
{name: "Вася", age: 25}
|
||||
{name: "Петя", age: 22}
|
||||
{name: "Маша", age: 19}
|
||||
...
|
||||
let user = { name: "John" };
|
||||
|
||||
let admin = user; // copy the reference
|
||||
```
|
||||
|
||||
Для их эффективного хранения будет создана структура, которая описывает данный вид объектов. Выглядеть она будет примерно так: `<string name, number age>`. А сами объекты будут представлены в памяти только данными:
|
||||
Now we have two variables, each one with the reference to the same object:
|
||||
|
||||
```js no-beautify
|
||||
<структура: string name, number age>
|
||||
Вася 25
|
||||
Петя 22
|
||||
Маша 19
|
||||

|
||||
|
||||
Compare it with the primitives' picture. There's only one object, it's not copied. The reference is.
|
||||
|
||||
Just as with copied keys, we can use any variable to open the cabinet and modify its contents:
|
||||
|
||||
```js run
|
||||
let user = { name: 'John' };
|
||||
|
||||
let admin = user;
|
||||
|
||||
*!*admin.name*/!* = 'Pete'; // changed by the "admin" reference
|
||||
|
||||
alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
|
||||
```
|
||||
|
||||
При добавлении нового объекта такой структуры достаточно хранить значения полей, но не их имена. Экономия памяти -- налицо.
|
||||
Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified.
|
||||
|
||||
А что происходит, если к объекту добавляется новое свойство? Например, к одному из них добавили свойство `isAdmin`:
|
||||
### Cloning objects
|
||||
|
||||
What if we need to duplicate an object? Create an independant copy, a clone?
|
||||
|
||||
That's also doable, but a little bit more difficult, because there's no such method in Javascript. Frankly, that's very rarely needed.
|
||||
|
||||
But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
|
||||
|
||||
Like this:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30
|
||||
};
|
||||
|
||||
*!*
|
||||
let clone = {}; // the new empty object
|
||||
|
||||
// let's copy all user properties into it
|
||||
for (let key in user) {
|
||||
clone[key] = user[key];
|
||||
}
|
||||
*/!*
|
||||
|
||||
// now clone is a fully independant clone
|
||||
clone.name = "Pete"; // changed the data in it
|
||||
|
||||
alert( user.name ); // still John
|
||||
```
|
||||
|
||||
Also we can use the method [Object.assign](mdn:Object/assign) for that:
|
||||
|
||||
```js
|
||||
user.isAdmin = true;
|
||||
Object.assign(dest[, src1, src2, src3...])
|
||||
```
|
||||
|
||||
В этом случае браузер смотрит, есть ли уже структура, под которую подходит такой объект. Если нет -- она создаётся и объект привязывается к ней.
|
||||
It assumes that all arguments are objects. It copies the properties of all arguments starting from the 2nd (`src1`, `src2` etc) into the `dest`. Then it returns `dest`.
|
||||
|
||||
**Эта оптимизация является примером того, что далеко не всё то, что мы пишем, один-в-один переносится в память.**
|
||||
For instance:
|
||||
```js
|
||||
let user = { name: "John" };
|
||||
|
||||
Современные интерпретаторы очень стараются оптимизировать как код, так и структуры данных. Детали применения и реализации этого способа хранения варьируются от браузера к браузеру. О том, как это сделано в Chrome можно узнать, например, из презентации [Know Your Engines](http://www.slideshare.net/newmovie/know-yourengines-velocity2011). Она была некоторое время назад, но с тех пор мало что изменилось.
|
||||
let permissions1 = { canView: true };
|
||||
let permissions2 = { canEdit: true };
|
||||
|
||||
## Итого
|
||||
Object.assign(user, permissions1, permissions2);
|
||||
|
||||
Объекты -- это ассоциативные массивы с дополнительными возможностями:
|
||||
// now user = { name: "John", canView: true, canEdit: true }
|
||||
```
|
||||
|
||||
- Доступ к элементам осуществляется:
|
||||
<ul>
|
||||
<li>Напрямую по ключу `obj.prop = 5`
|
||||
- Через переменную, в которой хранится ключ:
|
||||
Here we can use it instead of the loop for copying:
|
||||
|
||||
```js
|
||||
var key = "prop";
|
||||
obj[key] = 5
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30
|
||||
};
|
||||
|
||||
*!*
|
||||
let clone = Object.assign({}, user);
|
||||
*/!*
|
||||
```
|
||||
|
||||
<li>Удаление ключей: `delete obj.name`.</li>
|
||||
<li>Существование свойства может проверять оператор `in`: `if ("prop" in obj)`, как правило, работает и просто сравнение `if (obj.prop !== undefined)`.</li>
|
||||
</ul>
|
||||
It copies all properties of `user` into the empty object and returns it. Actually, the same as the loop, but shorter.
|
||||
|
||||
Up to now we assumed that all properties of `user` are primitive. But actually properties can be references to other objects. What to do with them?
|
||||
|
||||
Like this:
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
sizes: {
|
||||
height: 182,
|
||||
width: 50
|
||||
}
|
||||
};
|
||||
|
||||
alert( user.sizes.height ); // 182
|
||||
```
|
||||
|
||||
Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` will be copied by reference. So `clone` and `user` will share the same sizes.
|
||||
|
||||
To fix that, we should examine the value of `user[key]` in the cloning loop and if it's an object, then replicate it's structure as well. That is called a "deep cloning".
|
||||
|
||||
There's a standard algorithm for deep cloning that handles this case and more complex cases, called the [Structured cloning algorithm](w3c.github.io/html/infrastructure.html#internal-structured-cloning-algorithm). We can use a ready implementation from the Javascript library [lodash](https://lodash.com). The method is [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
|
||||
|
||||
## Summary
|
||||
|
||||
Objects are associative arrays with several special features.
|
||||
|
||||
- Property names are always strings.
|
||||
- Values can be of any type
|
||||
|
||||
Property access:
|
||||
|
||||
- Read/write uses the dot notation: `obj.property` or square brackets `obj["property"]/obj[varWithKey]`.
|
||||
- The deletion is made via the `delete` operator.
|
||||
- Existance check is made by the comparison vs `undefined` or via the `in` operator.
|
||||
- Loop over all properties with the `for..in` loop.
|
||||
|
||||
Numeric properties are sorted, otherwise they are iterated in the declaration order. To keep the order for numeric properties, we can prepend them with `+` to make them seem non-numeric.
|
||||
|
||||
Copying:
|
||||
|
||||
- Objects are assigned and copied by reference.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
@ -1,6 +0,0 @@
|
|||
function isEmpty(obj) {
|
||||
for (var key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
describe("isEmpty", function() {
|
||||
it("если объект пустой - возвращает true", function() {
|
||||
assert.isTrue(isEmpty({}));
|
||||
});
|
||||
|
||||
it("если у объекта есть любое свойство, не важно какое - возвращает false", function() {
|
||||
assert.isFalse(isEmpty({
|
||||
anything: false
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
|
||||
```js run
|
||||
function isEmpty(obj) {
|
||||
for (var key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var schedule = {};
|
||||
|
||||
alert( isEmpty(schedule) ); // true
|
||||
|
||||
schedule["8:30"] = "подъём";
|
||||
|
||||
alert( isEmpty(schedule) ); // false
|
||||
```
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Определите, пуст ли объект
|
||||
|
||||
Создайте функцию `isEmpty(obj)`, которая возвращает `true`, если в объекте нет свойств и `false` -- если хоть одно свойство есть.
|
||||
|
||||
Работать должно так:
|
||||
|
||||
```js
|
||||
function isEmpty(obj) {
|
||||
/* ваш код */
|
||||
}
|
||||
|
||||
var schedule = {};
|
||||
|
||||
alert( isEmpty(schedule) ); // true
|
||||
|
||||
schedule["8:30"] = "подъём";
|
||||
|
||||
alert( isEmpty(schedule) ); // false
|
||||
```
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
|
||||
```js run
|
||||
"use strict";
|
||||
|
||||
var salaries = {
|
||||
"Вася": 100,
|
||||
"Петя": 300,
|
||||
"Даша": 250
|
||||
};
|
||||
|
||||
var sum = 0;
|
||||
for (var name in salaries) {
|
||||
sum += salaries[name];
|
||||
}
|
||||
|
||||
alert( sum );
|
||||
```
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Сумма свойств
|
||||
|
||||
Есть объект `salaries` с зарплатами. Напишите код, который выведет сумму всех зарплат.
|
||||
|
||||
Если объект пустой, то результат должен быть `0`.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
"use strict";
|
||||
|
||||
var salaries = {
|
||||
"Вася": 100,
|
||||
"Петя": 300,
|
||||
"Даша": 250
|
||||
};
|
||||
|
||||
//... ваш код выведет 650
|
||||
```
|
||||
|
||||
P.S. Сверху стоит `use strict`, чтобы не забыть объявить переменные.
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
|
||||
```js run
|
||||
"use strict";
|
||||
|
||||
var salaries = {
|
||||
"Вася": 100,
|
||||
"Петя": 300,
|
||||
"Даша": 250
|
||||
};
|
||||
|
||||
var max = 0;
|
||||
var maxName = "";
|
||||
for (var name in salaries) {
|
||||
if (max < salaries[name]) {
|
||||
max = salaries[name];
|
||||
maxName = name;
|
||||
}
|
||||
}
|
||||
|
||||
alert( maxName || "нет сотрудников" );
|
||||
```
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Свойство с наибольшим значением
|
||||
|
||||
Есть объект `salaries` с зарплатами. Напишите код, который выведет имя сотрудника, у которого самая большая зарплата.
|
||||
|
||||
Если объект пустой, то пусть он выводит "нет сотрудников".
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
"use strict";
|
||||
|
||||
var salaries = {
|
||||
"Вася": 100,
|
||||
"Петя": 300,
|
||||
"Даша": 250
|
||||
};
|
||||
|
||||
// ... ваш код выведет "Петя"
|
||||
```
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
||||
|
||||
function multiplyNumeric(obj) {
|
||||
for (var key in obj) {
|
||||
if (isNumeric(obj[key])) {
|
||||
obj[key] *= 2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
||||
|
||||
// ... ваш код ...
|
|
@ -1,34 +0,0 @@
|
|||
importance: 3
|
||||
|
||||
---
|
||||
|
||||
# Умножьте численные свойства на 2
|
||||
|
||||
Создайте функцию `multiplyNumeric`, которая получает объект и умножает все численные свойства на 2. Например:
|
||||
|
||||
```js
|
||||
// до вызова
|
||||
var menu = {
|
||||
width: 200,
|
||||
height: 300,
|
||||
title: "My menu"
|
||||
};
|
||||
|
||||
multiplyNumeric(menu);
|
||||
|
||||
// после вызова
|
||||
menu = {
|
||||
width: 400,
|
||||
height: 600,
|
||||
title: "My menu"
|
||||
};
|
||||
```
|
||||
|
||||
P.S. Для проверки на число используйте функцию:
|
||||
|
||||
```js
|
||||
function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
||||
```
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
# Объекты: перебор свойств
|
||||
|
||||
Для перебора всех свойств из объекта используется цикл по свойствам `for..in`. Эта синтаксическая конструкция отличается от рассмотренного ранее цикла `for(;;)`.
|
||||
|
||||
[cut]
|
||||
|
||||
## for..in [#for..in]
|
||||
|
||||
Синтаксис:
|
||||
|
||||
```js
|
||||
for (key in obj) {
|
||||
/* ... делать что-то с obj[key] ... */
|
||||
}
|
||||
```
|
||||
|
||||
При этом `for..in` последовательно переберёт свойства объекта `obj`, имя каждого свойства будет записано в `key` и вызвано тело цикла.
|
||||
|
||||
````smart header="Объявление переменной в цикле `for (var key in obj)`"
|
||||
Вспомогательную переменную `key` можно объявить прямо в цикле:
|
||||
|
||||
```js
|
||||
for (*!*var key*/!* in menu) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Так иногда пишут для краткости кода. Можно использовать и любое другое название, кроме `key`, например `for(var propName in menu)`.
|
||||
````
|
||||
|
||||
Пример итерации по свойствам:
|
||||
|
||||
```js run
|
||||
var menu = {
|
||||
width: 300,
|
||||
height: 200,
|
||||
title: "Menu"
|
||||
};
|
||||
|
||||
for (var key in menu) {
|
||||
// этот код будет вызван для каждого свойства объекта
|
||||
// ..и выведет имя свойства и его значение
|
||||
|
||||
*!*
|
||||
alert( "Ключ: " + key + " значение:" + menu[key] );
|
||||
*/!*
|
||||
}
|
||||
```
|
||||
|
||||
Обратите внимание, мы использовали квадратные скобки `menu[key]`. Как уже говорилось, если имя свойства хранится в переменной, то обратиться к нему можно только так, не через точку.
|
||||
|
||||
## Количество свойств в объекте
|
||||
|
||||
Как узнать, сколько свойств хранит объект?
|
||||
|
||||
Готового метода для этого нет.
|
||||
|
||||
Самый кросс-браузерный способ -- это сделать цикл по свойствам и посчитать, вот так:
|
||||
|
||||
```js run
|
||||
var menu = {
|
||||
width: 300,
|
||||
height: 200,
|
||||
title: "Menu"
|
||||
};
|
||||
|
||||
*!*
|
||||
var counter = 0;
|
||||
|
||||
for (var key in menu) {
|
||||
counter++;
|
||||
}
|
||||
*/!*
|
||||
|
||||
alert( "Всего свойств: " + counter );
|
||||
```
|
||||
|
||||
В следующих главах мы пройдём массивы и познакомимся с другим, более коротким, вызовом: `Object.keys(menu).length`.
|
||||
|
||||
## В каком порядке перебираются свойства?
|
||||
|
||||
Для примера, рассмотрим объект, который задаёт список опций для выбора страны:
|
||||
|
||||
```js
|
||||
var codes = {
|
||||
// телефонные коды в формате "код страны": "название"
|
||||
"7": "Россия",
|
||||
"38": "Украина",
|
||||
// ..,
|
||||
"1": "США"
|
||||
};
|
||||
```
|
||||
|
||||
Здесь мы предполагаем, что большинство посетителей из России, и поэтому начинаем с `7`, это зависит от проекта.
|
||||
|
||||
При выборе телефонного кода мы хотели бы предлагать варианты, начиная с первого. Обычно на основе списка генерируется `select`, но здесь нам важно не это, а важно другое.
|
||||
|
||||
**Правда ли, что при переборе `for(key in codes)` ключи `key` будут перечислены именно в том порядке, в котором заданы?**
|
||||
|
||||
**По стандарту -- нет. Но некоторое соглашение об этом, всё же, есть.**
|
||||
|
||||
Соглашение говорит, что если имя свойства -- нечисловая строка, то такие ключи всегда перебираются в том же порядке. Так получилось по историческим причинам и изменить это сложно: поломается много готового кода.
|
||||
|
||||
С другой стороны, если имя свойства -- число, то все современные браузеры сортируют такие свойства в целях внутренней оптимизации.
|
||||
|
||||
К примеру, рассмотрим объект с заведомо нечисловыми свойствами:
|
||||
|
||||
```js run
|
||||
var user = {
|
||||
name: "Вася",
|
||||
surname: "Петров"
|
||||
};
|
||||
user.age = 25;
|
||||
|
||||
*!*
|
||||
// порядок перебора соответствует порядку присвоения свойства
|
||||
*/!*
|
||||
for (var prop in user) {
|
||||
alert( prop ); // name, surname, age
|
||||
}
|
||||
```
|
||||
|
||||
А теперь -- что будет, если перебрать объект с кодами?
|
||||
|
||||
```js run
|
||||
var codes = {
|
||||
// телефонные коды в формате "код страны": "название"
|
||||
"7": "Россия",
|
||||
"38": "Украина",
|
||||
"1": "США"
|
||||
};
|
||||
|
||||
for (var code in codes) alert( code ); // 1, 7, 38
|
||||
```
|
||||
|
||||
При запуске этого кода в современном браузере мы увидим, что на первое место попал код США!
|
||||
|
||||
Нарушение порядка возникло, потому что ключи численные. Интерпретатор JavaScript видит, что строка на самом деле является числом и преобразует ключ в немного другой внутренний формат. Дополнительным эффектом внутренних оптимизаций является сортировка.
|
||||
|
||||
**А что, если мы хотим, чтобы порядок был именно таким, какой мы задали?**
|
||||
|
||||
Это возможно. Можно применить небольшой хак, который заключается в том, чтобы сделать все ключи нечисловыми, например, добавим в начало дополнительный символ `'+'`:
|
||||
|
||||
```js run
|
||||
var codes = {
|
||||
"+7": "Россия",
|
||||
"+38": "Украина",
|
||||
"+1": "США"
|
||||
};
|
||||
|
||||
for (var code in codes) {
|
||||
var value = codes[code];
|
||||
code = +code; // ..если нам нужно именно число, преобразуем: "+7" -> 7
|
||||
|
||||
alert( code + ": " + value ); // 7, 38, 1 во всех браузерах
|
||||
}
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
- Цикл по ключам: `for (key in obj)`.
|
||||
- Порядок перебора соответствует порядку объявления для нечисловых ключей, а числовые -- сортируются (в современных браузерах).
|
||||
- Если нужно, чтобы порядок перебора числовых ключей соответствовал их объявлению в объекте, то используют трюк: числовые ключи заменяют на похожие, но содержащие не только цифры. Например, добавляют в начало `+`, как описано в примере выше, а потом, в процессе обработки, преобразуют такие ключи в числа.
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
# Объекты: передача по ссылке
|
||||
|
||||
Фундаментальным отличием объектов от примитивов, является их хранение и копирование "по ссылке".
|
||||
|
||||
[cut]
|
||||
|
||||
## Копирование по значению
|
||||
|
||||
Обычные значения: строки, числа, булевы значения, `null/undefined` при присваивании переменных копируются целиком или, как говорят, *"по значению"*.
|
||||
|
||||
```js
|
||||
var message = "Привет";
|
||||
var phrase = message;
|
||||
```
|
||||
|
||||
В результате такого копирования получились две полностью независимые переменные, в каждой из которых хранится значение `"Привет"`.
|
||||
|
||||

|
||||
|
||||
## Копирование по ссылке
|
||||
|
||||
С объектами -- всё не так.
|
||||
|
||||
**В переменной, которой присвоен объект, хранится не сам объект, а "адрес его места в памяти", иными словами -- "ссылка" на него.**
|
||||
|
||||
Вот как выглядит переменная, которой присвоен объект:
|
||||
|
||||
```js
|
||||
var user = {
|
||||
name: "Вася"
|
||||
};
|
||||
```
|
||||
|
||||

|
||||
|
||||
Внимание: объект -- вне переменной. В переменной -- лишь "адрес" (ссылка) для него.
|
||||
|
||||
**При копировании переменной с объектом -- копируется эта ссылка, а объект по-прежнему остается в единственном экземпляре.**
|
||||
|
||||
Например:
|
||||
|
||||
```js no-beautify
|
||||
var user = { name: "Вася" }; // в переменной - ссылка
|
||||
|
||||
var admin = user; // скопировали ссылку
|
||||
```
|
||||
|
||||
Получили две переменные, в которых находятся ссылки на один и тот же объект:
|
||||
|
||||

|
||||
|
||||
**Так как объект всего один, то изменения через любую переменную видны в других переменных:**
|
||||
|
||||
```js run
|
||||
var user = { name: 'Вася' };
|
||||
|
||||
var admin = user;
|
||||
|
||||
*!*admin.name*/!* = 'Петя'; // поменяли данные через admin
|
||||
|
||||
alert(*!*user.name*/!*); // 'Петя', изменения видны в user
|
||||
```
|
||||
|
||||
```smart header="Переменная с объектом как \"ключ\" к сейфу с данными"
|
||||
Ещё одна аналогия: переменная, в которую присвоен объект, на самом деле хранит не сами данные, а ключ к сейфу, где они хранятся.
|
||||
|
||||
При копировании её, получается что мы сделали копию ключа, но сейф по-прежнему один.
|
||||
```
|
||||
|
||||
## Клонирование объектов
|
||||
|
||||
Иногда, на практике -- очень редко, нужно скопировать объект целиком, создать именно полную независимую копию, "клон" объекта.
|
||||
|
||||
Что ж, можно сделать и это. Для этого нужно пройти по объекту, достать данные и скопировать на уровне примитивов.
|
||||
|
||||
Примерно так:
|
||||
|
||||
```js run
|
||||
var user = {
|
||||
name: "Вася",
|
||||
age: 30
|
||||
};
|
||||
|
||||
*!*
|
||||
var clone = {}; // новый пустой объект
|
||||
|
||||
// скопируем в него все свойства user
|
||||
for (var key in user) {
|
||||
clone[key] = user[key];
|
||||
}
|
||||
*/!*
|
||||
|
||||
// теперь clone - полностью независимая копия
|
||||
clone.name = "Петя"; // поменяли данные в clone
|
||||
|
||||
alert( user.name ); // по-прежнем "Вася"
|
||||
```
|
||||
|
||||
В этом коде каждое свойство объекта `user` копируется в `clone`. Если предположить, что они примитивны, то каждое скопируется по значению и мы как раз получим полный клон.
|
||||
|
||||
Если же свойства объектов, в свою очередь, могут хранить ссылки на другие объекты, то нужно обойти такие подобъекты и тоже склонировать их. Это называют "глубоким" клонированием.
|
||||
|
||||
## Вывод в консоли
|
||||
|
||||
Откройте консоль браузера (обычно `key:F12`) и запустите следующий код:
|
||||
|
||||
```js run
|
||||
var time = {
|
||||
year: 2345,
|
||||
month: 11,
|
||||
day: 10,
|
||||
hour: 11,
|
||||
minute: 12,
|
||||
second: 13,
|
||||
microsecond: 123456
|
||||
}
|
||||
|
||||
console.log(time); // (*)
|
||||
time.microsecond++; // (**)
|
||||
|
||||
console.log(time);
|
||||
time.microsecond++;
|
||||
|
||||
console.log(time);
|
||||
time.microsecond++;
|
||||
```
|
||||
|
||||
Как видно, в нём некий объект выводится строкой `(*)`, затем он меняется в строке `(**)` и снова выводится, и так несколько раз. Пока ничего необычного, типичная ситуация -- скрипт делает какую-то работу с объектом и выводит в консоли то, как она продвигается.
|
||||
|
||||
Необычное -- в другом!
|
||||
|
||||
При раскрытии каждый объект будет выглядеть примерно так (скриншот из Chrome):
|
||||
|
||||

|
||||
|
||||
**Судя по выводу, свойство `microsecond` всегда было равно `123459`... Или нет?**
|
||||
|
||||
Если посмотреть на код выше то, очевидно, нет! Это свойство меняется, а консоль нас просто дурит.
|
||||
|
||||
**При "раскрытии" свойств объекта в консоли -- браузер всегда выводит их текущие (на момент раскрытия) значения.**
|
||||
|
||||
Так происходит именно потому, что вывод не делает "копию" текущего содержимого, а сохраняет лишь ссылку на объект. Запомните эту особенность консоли, в будущем, при отладке скриптов у вас не раз возникнет подобная ситуация.
|
||||
|
||||
## Итого
|
||||
|
||||
- Объект присваивается и копируется "по ссылке". То есть, в переменной хранится не сам объект а, условно говоря, адрес в памяти, где он находится.
|
||||
- Если переменная-объект скопирована или передана в функцию, то копируется именно эта ссылка, а объект остаётся один в памяти.
|
||||
|
||||
Это -- одно из ключевых отличий объекта от примитива (числа, строки...), который при присвоении как раз копируется "по значению", то есть полностью.
|
||||
|
|
@ -1,79 +1,76 @@
|
|||
# Массивы c числовыми индексами
|
||||
# Arrays with numeric indexes
|
||||
|
||||
*Массив* -- разновидность объекта, которая предназначена для хранения пронумерованных значений и предлагает дополнительные методы для удобного манипулирования такой коллекцией.
|
||||
*Array* -- is a special kind of objects, suited to store ordered, numbered collections of values. It provides additional methods to manipulate the collection.
|
||||
|
||||
Они обычно используются для хранения упорядоченных коллекций данных, например -- списка товаров на странице, студентов в группе и т.п.
|
||||
For instance, we can use arrays to keep a list of students in the group, a list of goods in the catalog etc.
|
||||
|
||||
[cut]
|
||||
|
||||
## Объявление
|
||||
## Definition
|
||||
|
||||
Синтаксис для создания нового массива -- квадратные скобки со списком элементов внутри.
|
||||
|
||||
Пустой массив:
|
||||
There are two syntaxes for creating an empty array:
|
||||
|
||||
```js
|
||||
var arr = [];
|
||||
let arr = new Array();
|
||||
let arr = [];
|
||||
```
|
||||
|
||||
Массив `fruits` с тремя элементами:
|
||||
Almost all the time, the second syntax is used. We can also list elements in the brackets:
|
||||
|
||||
```js
|
||||
var fruits = ["Яблоко", "Апельсин", "Слива"];
|
||||
let fruits = ["Apple", "Orange", "Plum"];
|
||||
```
|
||||
|
||||
**Элементы нумеруются, начиная с нуля.**
|
||||
Array elements are numbered, starting with zero.
|
||||
|
||||
Чтобы получить нужный элемент из массива -- указывается его номер в квадратных скобках:
|
||||
We can get an element by its number in square brackets:
|
||||
|
||||
```js run
|
||||
var fruits = ["Яблоко", "Апельсин", "Слива"];
|
||||
let fruits = ["Apple", "Orange", "Plum"];
|
||||
|
||||
alert( fruits[0] ); // Яблоко
|
||||
alert( fruits[1] ); // Апельсин
|
||||
alert( fruits[2] ); // Слива
|
||||
alert( fruits[0] ); // Apple
|
||||
alert( fruits[1] ); // Orange
|
||||
alert( fruits[2] ); // Plum
|
||||
```
|
||||
|
||||
Элемент можно всегда заменить:
|
||||
We can replace an element:
|
||||
|
||||
```js
|
||||
fruits[2] = 'Груша'; // теперь ["Яблоко", "Апельсин", "Груша"]
|
||||
fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
|
||||
```
|
||||
|
||||
...Или добавить:
|
||||
...Or add to the array:
|
||||
|
||||
```js
|
||||
fruits[3] = 'Лимон'; // теперь ["Яблоко", "Апельсин", "Груша", "Лимон"]
|
||||
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Plum", "Lemon"]
|
||||
```
|
||||
|
||||
Общее число элементов, хранимых в массиве, содержится в его свойстве `length`:
|
||||
The total count of the elements in the array is its `length`:
|
||||
|
||||
```js run
|
||||
var fruits = ["Яблоко", "Апельсин", "Груша"];
|
||||
let fruits = ["Apple", "Orange", "Plum"];
|
||||
|
||||
alert( fruits.length ); // 3
|
||||
```
|
||||
|
||||
**Через `alert` можно вывести и массив целиком.**
|
||||
|
||||
При этом его элементы будут перечислены через запятую:
|
||||
**We can also use `alert` to show the whole array.**
|
||||
|
||||
```js run
|
||||
var fruits = ["Яблоко", "Апельсин", "Груша"];
|
||||
let fruits = ["Apple", "Orange", "Plum"];
|
||||
|
||||
alert( fruits ); // Яблоко,Апельсин,Груша
|
||||
alert( fruits ); // Apple,Orange,Plum
|
||||
```
|
||||
|
||||
**В массиве может храниться любое число элементов любого типа.**
|
||||
**An array can store elements of any type.**
|
||||
|
||||
В том числе, строки, числа, объекты, вот например:
|
||||
For instance:
|
||||
|
||||
```js run no-beautify
|
||||
// микс значений
|
||||
var arr = [ 1, 'Имя', { name: 'Петя' }, true ];
|
||||
// mix of values
|
||||
let arr = [ 1, 'Apple', { name: 'John' }, true, function() {} ];
|
||||
|
||||
// получить объект из массива и тут же -- его свойство
|
||||
alert( arr[2].name ); // Петя
|
||||
// get the object at index 2 and then its name
|
||||
alert( arr[2].name ); // John
|
||||
```
|
||||
|
||||
## Методы pop/push, shift/unshift
|
||||
|
|