This commit is contained in:
Ilya Kantor 2014-11-16 01:40:20 +03:00
parent 962caebbb7
commit 87bf53d076
1825 changed files with 94929 additions and 0 deletions

View file

@ -0,0 +1,9 @@
function ucFirst(str) {
var newStr = str.charAt(0).toUpperCase();
for(var i=1; i<str.length; i++) {
newStr += str.charAt(i);
}
return newStr;
}

View file

@ -0,0 +1,9 @@
describe("ucFirst", function() {
it('делает первый символ заглавным', function() {
assert.strictEqual( ucFirst("вася"), "Вася" );
});
it('для пустой строки возвращает пустую строку', function() {
assert.strictEqual( ucFirst(""), "" );
});
});

View file

@ -0,0 +1,20 @@
Мы не можем просто заменить первый символ, т.к. строки в JavaScript неизменяемы.
Но можно пересоздать строку на основе существующей, с заглавным первым символом:
```js
//+ run
function ucFirst(str) {
var newStr = str.charAt(0).toUpperCase();
for(var i=1; i<str.length; i++) {
newStr += str.charAt(i);
}
return newStr;
}
alert( ucFirst("вася") );
```
P.S. Возможны и более короткие решения, использующие методы для работы со строками, которые мы пройдём далее.

View file

@ -0,0 +1,12 @@
# Сделать первый символ заглавным
[importance 5]
Напишите функцию `ucFirst(str)`, которая возвращает строку `str` с заглавным первым символом, например:
```js
ucFirst("вася") == "Вася";
ucFirst("") == ""; // нет ошибок при пустой строке
```
P.S. В JavaScript нет встроенного метода для этого. Создайте функцию, используя `toUpperCase()` и `charAt()`.

View file

@ -0,0 +1,5 @@
function checkSpam(str) {
var lowerStr = str.toLowerCase();
return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx'));
}

View file

@ -0,0 +1,13 @@
describe("checkSpam", function() {
it('считает спамом "buy ViAgRA now"', function() {
assert.isTrue( checkSpam('buy ViAgRA now') );
});
it('считает спамом "free xxxxx"', function() {
assert.isTrue( checkSpam('free xxxxx') );
});
it('не считает спамом "innocent rabbit"', function() {
assert.isFalse( checkSpam('innocent rabbit') );
});
});

View file

@ -0,0 +1,17 @@
Метод `indexOf` ищет совпадение с учетом регистра. То есть, в строке `'xXx'` он не найдет `'XXX'`.
Для проверки приведем к нижнему регистру и строку `str` а затем уже будем искать.
```js
//+ run
function checkSpam(str) {
var lowerStr = str.toLowerCase();
return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx'));
}
alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );
```

View file

@ -0,0 +1,14 @@
# Проверьте спам
[importance 5]
Напишите функцию `checkSpam(str)`, которая возвращает `true`, если строка `str` содержит 'viagra' or 'XXX', а иначе `false`.
Функция должна быть нечувствительна к регистру:
```js
checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false
```

View file

@ -0,0 +1,4 @@
function truncate(str, maxlength) {
return (str.length > maxlength) ?
str.slice(0, maxlength - 3) + '...' : str;
}

View file

@ -0,0 +1,16 @@
describe("truncate", function() {
it("обрезает строку до указанной длины (включая троеточие)", function() {
assert.equal(
truncate("Вот, что мне хотелось бы сказать на эту тему:", 20),
"Вот, что мне хоте..."
);
});
it("не меняет короткие строки", function() {
assert.equal(
truncate("Всем привет!", 20),
"Всем привет!"
);
});
});

View file

@ -0,0 +1,28 @@
Так как окончательная длина строки должна быть `maxlength`, то нужно её обрезать немного короче, чтобы дать место для троеточия.
```js
//+ run
function truncate(str, maxlength) {
if (str.length > maxlength) {
return str.slice(0, maxlength - 3) + '...';
// итоговая длина равна maxlength
}
return str;
}
alert(truncate("Вот, что мне хотелось бы сказать на эту тему:", 20));
alert(truncate("Всем привет!", 20));
```
Можно было бы написать этот код ещё короче:
```js
//+ run
function truncate(str, maxlength) {
return (str.length > maxlength) ?
str.slice(0, maxlength - 3) + '...' : str;
}
```
P.S. Кстати, в кодироке Unicode существует специальный символ "троеточие": `…` (HTML: `&hellip;`), который можно использовать вместо трёх точек. Если его использовать, то можно отрезать только один символ.

View file

@ -0,0 +1,17 @@
# Усечение строки
[importance 5]
Создайте функцию `truncate(str, maxlength)`, которая проверяет длину строки `str`, и если она превосходит `maxlength` -- заменяет конец `str` на `"..."`, так чтобы ее длина стала равна `maxlength`.
Результатом функции должна быть (при необходимости) усечённая строка.
Например:
```js
truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хотело..."
truncate("Всем привет!", 20) = "Всем привет!"
```
Эта функция имеет применение в жизни. Она используется, чтобы усекать слишком длинные темы сообщений.

View file

@ -0,0 +1,538 @@
# Строки
В JavaScript любые текстовые данные являются строками. Не существует отдельного типа "символ", который есть в ряде других языков.
Внутренним форматом строк, вне зависимости от кодировки страницы, является [Юникод (Unicode)](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).
[cut]
## Создание строк
Строки создаются при помощи двойных или одинарных кавычек:
```js
var text = "моя строка";
var anotherText = 'еще строка';
var str = "012345";
```
В JavaScript **нет разницы между двойными и одинарными кавычками**.
### Специальные символы
Строки могут содержать специальные символы. Самый часто используемый из таких символов -- это *перевод строки*.
Он обозначается как `\n`, например:
```js
//+ run
alert('Привет\nМир'); // выведет "Мир" на новой строке
```
Есть и более редкие символы, вот их список:
<table>
<CAPTION>Специальные символы</CAPTION>
<tr><th>Символ</th><th>Описание</th>
<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` -- юникодное представление символа копирайт &#xA9;
</td></tr>
</table>
### Экранирование специальных символов
Если строка в одинарных кавычках, то внутренние одинарные кавычки внутри должны быть *экранированы*, то есть снабжены обратным слешем `\'`, вот так:
```js
var str = '*!*I\'m*/!* a JavaScript programmer';
```
В двойных кавычках -- экранируются внутренние двойные:
```js
//+ run
var str = "I'm a JavaScript \"programmer\" ";
alert(str);
```
Экранирование служит исключительно для правильного восприятия строки JavaScript. **В памяти строка будет содержать сам символ без `'\'`**. Вы можете увидеть это, запустив пример выше.
Сам символ обратного слэша `'\'` является служебным, поэтому всегда экранируется, т.е пишется как `\\`:
```js
//+ run
var str = ' символ \\ ';
alert(str); // символ \
```
Заэкранировать можно любой символ. Если он не специальный, то ничего не произойдёт:
```js
//+ run
alert( "\a" ); // a
// идентично alert( "a" );
```
## Методы и свойства
Здесь мы рассмотрим методы и свойства строк, с некоторыми из которых мы знакомились ранее, в главе [](/properties-and-methods).
### Длина length
Одно из самых частых действий со строкой -- это получение ее длины:
```js
//+ run
var str = "My\n"; // 3 символа. Третий - перевод строки
alert(str.length); // 3
```
### Доступ к символам
Чтобы получить символ, используйте вызов `charAt(позиция)`. Первый символ имеет позицию `0`:
```js
//+ run
var str = "jQuery";
alert( str.charAt(0) ); // "j"
```
В JavaScript **нет отдельного типа "символ"**, так что `charAt` возвращает строку, состоящую из выбранного символа.
[smart]
В современных браузерах (не IE7-) для доступа к символу можно также использовать квадратные скобки:
```js
//+ run
var str = "Я - современный браузер!";
alert(str[0]); // "Я", IE8+
```
Разница между этим способом и `charAt` заключается в том, что если символа нет -- `charAt` выдает пустую строку, а скобки -- `undefined`:
```js
//+ run
alert( "".charAt(0) ); // пустая строка
alert( ""[0] ); // undefined, IE8+
```
[/smart]
[warn header="Вызов метода -- всегда со скобками"]
Обратите внимание, `str.length` -- это *свойство* строки, а `str.charAt(pos)` -- *метод*, т.е. функция.
Обращение к методу всегда идет со скобками, а к свойству -- без скобок.
[/warn]
### Изменения строк
Строки в JavaScript нельзя изменять. Можно прочитать символ, но нельзя заменить его. Как только строка создана -- она такая навсегда.
Чтобы это обойти, создаётся новая строка и присваивается в переменную вместо старой:
```js
//+ run
var str = "строка";
str = str.charAt(3) + str.charAt(4) + str.charAt(5);
alert(str); // ока
```
### Смена регистра
Методы `toLowerCase()` и `toUpperCase()` меняют регистр строки на нижний/верхний:
```js
//+ run
alert( "Интерфейс".toUpperCase() ); // ИНТЕРФЕЙС
```
Пример ниже получает первый символ и приводит его к нижнему регистру:
```js
alert( "Интерфейс".charAt(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("Lalala") ); // -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)`, например:
```js
//+ run
alert( ~2 ); // -(2+1) = -3
alert( ~1 ); // -(1+1) = -2
alert( ~0 ); // -(0+1) = -1
*!*
alert( ~-1 ); // -(-1+1) = 0
*/!*
```
Как видно, `~n` -- ноль только в случае, когда `n == -1`.
То есть, проверка `if ( ~str.indexOf(...) )` означает, что результат `indexOf` отличен от `-1, т.е. совпадение есть.
Вот так:
```js
//+ run
var str = "Widget";
if( ~str.indexOf("get") ) {
alert('совпадение есть!');
}
```
Вообще, использовать возможности языка неочевидным образом не рекомендуется, поскольку ухудшает читаемость кода.
Однако, в данном случае, все в порядке. Просто запомните: `'~'` читается как "не минус один", а `"if ~str.indexOf"` читается как `"если найдено"`.
[/smart]
### Поиск всех вхождений
Чтобы найти все вхождения подстроки, нужно запустить `indexOf` в цикле. Как только получаем очередную позицию -- начинаем следующий поиск со следующей.
Пример такого цикла:
```js
//+ run
var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска
var pos = 0;
while(true) {
var foundPos = str.indexOf(target, pos);
if (foundPos == -1) break;
alert(foundPos); // нашли на этой позиции
pos = foundPos + 1; // продолжить поиск со следующей
}
```
Такой цикл начинает поиск с позиции `0`, затем найдя подстроку на позиции `foundPos`, следующий поиск продолжит с позиции `pos = foundPos+1`, и так далее, пока что-то находит.
Впрочем, тот же алгоритм можно записать и короче:
```js
//+ run
var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска
*!*
var pos = -1;
while ( (pos = str.indexOf(target, pos+1)) != -1) {
alert(pos);
}
*/!*
```
### Взятие подстроки: substr, substring, slice.
В JavaScript существуют целых 3 (!) метода для взятия подстроки, с небольшими отличиями между ними.
<dl>
<dt>`substring(start [, end])`
<dd>
Метод `substring(start, end)` возвращает подстроку с позиции `start` до, но не включая `end`.
```js
//+ run
var str = "*!*s*/!*tringify";
alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1.
```
Если аргумент `end` отсутствует, то идет до конца строки:
```js
//+ run
var str = "st*!*ringify*/!*";
alert(str.substring(2)); // ringify, символы с позиции 2 до конца
```
</dd>
<dt>`substr(start [, length])`</dt>
<dd>Первый аргумент имеет такой же смысл, как и в `substring`, а второй содержит не конечную позицию, а количество символов.
```js
//+ run
var str = "st*!*ring*/!*ify";
str = str.substr(2,4); // ring, со 2й позиции 4 символа
alert(str)
```
Если второго аргумента нет -- подразумевается "до конца строки".</dd>
<dt>`slice(start [, end])`</dt>
<dd>Возвращает часть строки от позиции `start` до, но не включая, позиции `end`. Смысл параметров -- такой же как в `substring`.</dd>
</dl>
### Отрицательные аргументы
Различие между `substring` и `slice` -- в том, как они работают с отрицательными и выходящими за границу строки аргументами:
<dl>
<dt>`substring(start, end)`</dt>
<dd>Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки:
```js
//+ run
alert( "testme".substring(-2) ); // "testme", -2 становится 0
```
Кроме того, если <code>start &gt; end</code>, то аргументы меняются местами, т.е. возвращается участок строки *между* `start` и `end`:
```js
//+ run
alert( "testme".substring(4, -1) ); // "test"
// -1 становится 0 -> получили substring(4, 0)
// 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test"
```
</dd>
<dt>`slice`</dt>
<dd>Отрицательные значения отсчитываются от конца строки:
```js
//+ run
alert( "testme".slice(-2) ); // "me", от 2 позиции с конца
```
```js
//+ run
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.
```
Это гораздо более удобно, чем странная логика `substring`.
</dd>
</dl>
Отрицательное значение первого параметра поддерживается в `substr` во всех браузерах, кроме IE8-.
[summary]
Самый удобный метод из списка -- это `slice(start, end)`. Очень забавно, что не `substr`, не `substring`, а именно метод `slice`, название которого наименее очевидно.
Впрочем, `substr(start, length)` тоже можно использовать, но с оглядкой на IE8-, который не поддерживает отрицательный `start`.
[/summary]
## Кодировка Юникод
Как мы знаем, символы сравниваются в алфавитном порядке `'А' < 'Б' < 'В' < ... < 'Я'`.
Но есть несколько странностей..
<ol>
<li>Почему буква `'а'` маленькая больше буквы `'Я'` большой?
```js
//+ run
alert( 'а' > 'Я' ); // true
```
</li><li>
Буква `'ё'` находится в алфавите между `е` и `ж`: <code>абвгде**ё**жз..</code>. Но почему тогда `'ё'` больше `'я'`?
```js
//+ run
alert( 'ё' > 'я' ); // true
```
</li>
</ol>
Чтобы разобраться с этим, обратимся к внутреннему представлению строк в JavaScript.
**Все строки имеют внутреннюю кодировку [Юникод](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).**
Неважно, на каком языке написана страница, находится ли она в windows-1251 или utf-8. Внутри JavaScript-интерпретатора все строки приводятся к единому "юникодному" виду. Каждому символу соответствует свой код.
Есть метод для получения символа по его коду:
<dl>
<dt>String.fromCharCode(code)</dt>
<dd>Возвращает символ по коду `code`:
```js
//+ run
alert( String.fromCharCode(1072) ); // 'а'
```
</dd>
</dl>
...И метод для получения цифрового кода из символа:
<dl>
<dt>str.charCodeAt(pos)</dt>
<dd>Возвращает код символа на позиции `pos`. Отсчет позиции начинается с нуля.
```js
//+ run
alert( "абрикос".charCodeAt(0) ); // 1072, код 'а'
```
</dd>
</dl>
Теперь вернемся к примерам выше. Почему сравнения `'ё' > 'я'` и `'а' > 'Я'` дают такой странный результат?
Дело в том, что **символы сравниваются не по алфавиту, а по коду**. У кого код больше -- тот и больше. В юникоде есть много разных символов. Кириллическим буквам соответствует только небольшая часть из них, подробнее -- [Кириллица в Юникоде](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`:
```js
//+ run
var str = '';
for (var i=1034; i<=1113; i++) {
str += String.fromCharCode(i);
}
alert(str);
```
Результат:
<div style="overflow: auto">
<code>ЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљ</code>
</div>
Мы можем увидеть из этого отрезка две важных вещи:
<ol>
<li>**Строчные буквы идут после заглавных, поэтому они всегда больше.**
В частности, `'а'(код 1072) > 'Я'(код 1071)`.
То же самое происходит и в английском алфавите, там `'a' > 'Z'`.
</li>
<li>**Ряд букв, например `ё`, находятся вне основного алфавита.**
В частности, маленькая буква `ё` имеет код, больший чем `я`, поэтому **`'ё'(код 1105) > 'я'(код 1103)`**.
Кстати, большая буква `Ё` располагается в Unicode до `А`, поэтому **`'Ё'`(код 1025) < `'А'`(код 1040)**. Удивительно: есть буква меньше чем `А` :)
</li>
</ol>
**Буква `ё` не уникальна, точки над буквой используются и в других языках, приводя к тому же результату.**
Например, при работе с немецкими названиями:
```js
//+ run
alert( "ö" > "z" ); // true
```
[smart header="Юникод в HTML"]
Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя "числовую ссылку" (numeric character reference).
Для этого нужно написать сначала `&#`, затем код, и завершить точкой с запятой `';'`. Например, символ `'а'` в виде числовой ссылки: `&#1072;`.
Если код хотят дать в 16-ричной системе счисления, то начинают с `&#x`.
В юникоде есть много забавных и полезных символов, например, символ ножниц: &#x2702; (`&#x2702;`), дроби: &#xBD; (`&#xBD;`) &#xBE; (`&#xBE;`) и другие. Их можно использовать вместо картинок в дизайне.
[/smart]
## Посимвольное сравнение
Сравнение строк работает *лексикографически*, иначе говоря, посимвольно.
Сравнение строк `s1` и `s2` обрабатывается по следующему алгоритму:
<ol><li>Сравниваются первые символы: `a = s1.charAt(0)` и `b = s2.charAt(0)`. Если они разные, то сравниваем их и, в зависимости от результата их сравнения, возвратить `true` или `false`. Если же они одинаковые, то...</li>
<li>Сравниваются вторые символы `a = s1.charAt(1)` и `b = s2.charAt(1)`</li>
<li>Затем третьи `a = s1.charAt(2)` и `b = s2.charAt(2)` и так далее, пока символы не будут наконец разными, и тогда какой символ больше -- та строка и больше. Если же в какой-либо строке закончились символы, то считаем, что она меньше, а если закончились в обеих -- они равны.</li>
</ol>
Спецификация языка определяет этот алгоритм более детально. Если же говорить простыми словами, смысл алгоритма в точности соответствует порядку, по которому имена заносятся в орфографический словарь.
```js
"Вася" > "Ваня" // true, т.к. начальные символы совпадают, а потом 'с' > 'н'
"Дома" > "До" // true, т.к. начало совпадает, но в 1й строке больше символов
```
[warn header="Числа в виде строк сравниваются как строки"]
Бывает, что числа приходят в скрипт в виде строк, например как результат `prompt`. В этом случае результат их сравнения будет неверным:
```js
//+ run
alert("2" > "14"); // true, так как это строки, и для первых символов верно "2" > "1"
```
Если хотя бы один аргумент -- не строка, то другой будет преобразован к числу:
```js
//+ run
alert(2 > "14"); // false
```
[/warn]
## Правильное сравнение
Все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), поддерживающий сравнение строк на разных языках, с учётом их правил.
Способ использования:
```js
//+ run
var str = "Ёлки";
alert( str.localeCompare("Яблони") ); // -1
```
Метод `str1.localeCompare(str2)` возвращает `-1`, если `str1 < str2`, `1`, если `str1 > str2` и `0`, если они равны.
Более подробно про устройство этого метода можно будет узнать в статье [](/intl), когда это вам понадобится.
## Итого
<ul>
<li>Строки в JavaScript имеют внутреннюю кодировку Юникод. При написании строки можно использовать специальные символы, например `\n` и вставлять юникодные символы по коду.</li>
<li>Мы познакомились со свойством `length` и методами `charAt`, `toLowerCase/toUpperCase`, `substring/substr/slice` (предпочтителен `slice`)</li>
<li>Строки сравниваются побуквенно. Поэтому если число получено в виде строки, то такие числа могут сравниваться некорректно, нужно преобразовать его к типу *number*.</li>
<li>При сравнении строк следует иметь в виду, что буквы сравниваются по их кодам. Поэтому большая буква меньше маленькой, а буква `ё` вообще вне основного алфавита.</li>
<li>Для правильного сравнения существует целый стандарт ECMA 402. Это не такое простое дело, много языков и много правил. Он поддерживается во всех современных браузерах, кроме IE10-, в которых нужна библиотека [](https://github.com/andyearnshaw/Intl.js/). Такое сравнение работает через вызов `str1.localeCompare(str2)`.</li>
</ul>
Больше информации о методах для строк можно получить в справочнике: [http://javascript.ru/String]().