У JavaScript текстові дані зберігаються у вигляді рядків. Не існує окремого типу для одного символу.
Внутрішній формат для рядків завжди UTF-16, він не привʼязаний до кодування сторінки.
Лапки
Згадаймо види лапок.
Рядки можуть бути включені в одинарні лапки, подвійні лапки або зворотні знаки:
let single = 'одинарні-лапки';
let double = "подвійні-лапки";
let backticks = `зворотні-лапки`;
Одинарні та подвійні лапки по суті однакові. Однак зворотні лапки дозволяють нам вставляти будь-який вираз у рядок, загортаючи його у ${…}
:
function sum(a, b) {
return a + b;
}
alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.
Ще однією перевагою використання зворотних лапок є те, що вони дозволяють рядку охоплювати кілька ліній:
let guestList = `Гості:
* Іван
* Петро
* Марія
`;
alert(guestList); // список гостей в кілька рядків
Виглядає природно, правда? Але одинарні або подвійні лапки так не працюють.
Якщо ми спробуємо їх використати в кілька рядків, буде помилка:
let guestList = "Гості: // Помилка: Unexpected token ILLEGAL
* Іван";
Одинарні та подвійні лапки беруть свій початок з давніх часів створення мови, коли не було потреби у багатолінійних рядках. Зворотні лапки зʼявилися набагато пізніше і тому є більш універсальними.
Зворотні лапки також дозволяють нам задати “шаблонну функцію” перед першими зворотніми лапками. Синтаксис такий: func`string`
. Функція func
викликається автоматично, отримує рядок і вбудовані в неї вирази і може їх обробити. Це називається “теговим шаблоном”, це рідко використовується на практиці, але ви можете прочитати детальніше про це на MDN: Template literals.
Спеціальні символи
Ще можна створити багатолінійні рядки з одинарними та подвійними лапками за допомогою так званого “символу нового рядка”, записаного як \n
, який позначає розрив рядка:
let guestList = "Гості:\n * Іван\n * Петро\n * Марія";
alert(guestList); // список гостей в декілька рядків, як і вище
Наприклад, ці два рядки рівнозначні, просто написані по-різному:
let str1 = "Привіт\nСвіт"; // два рядки з використанням "символу нового рядка"
// два рядки з використанням звичайного нового рядка та зворотних лапок
let str2 = `Привіт
Світ`;
alert(str1 == str2); // true
Є й інші, менш поширені “спеціальні” символи.
Символ | Опис |
---|---|
\n |
Розрив рядка |
\r |
У текстових файлах Windows комбінація двох символів \r\n являє собою розрив рядка, тоді як в інших ОС, це просто \n . Так склалось з історичних причин, більшість ПЗ під Windows також розуміє \n |
\' , \" , \` |
Лапки |
\\ |
Зворотний слеш |
\t |
Знак табуляції |
\b , \f , \v |
Backspace, Form Feed, Vertical Tab – зберігаються для зворотної сумісності, зараз не використовуються |
Усі спеціальні символи починаються зі зворотного слеша \
. Його також називають “символом екранування”.
Оскільки це так особливо, якщо нам потрібно показати зворотний слеш \
у рядку, нам потрібно подвоїти його:
alert( `Зворотний слеш: \\` ); // Зворотний слеш: \
Так звані “екрановані” лапки \'
, \"
, \`
використовуються для вставки цих лапок в рядок, який обмежено таким же типом лапок.
Наприклад:
alert( 'Ім\'я моє — Морж!' ); // Ім'я моє — Морж!
Як бачите, ми повинні “екранувати” лапку зворотним слешем \'
, оскільки інакше це означало б кінець рядка.
Звісно, потрібно “екранувати” лише такі лапки, якими обрамлений рядок. Як елегантніше рішення, ми могли б замість цього скористатися подвійними або зворотними лапками:
alert( `Ім'я моє — Морж!` ); // Ім'я моє — Морж!
Окрім цих спеціальних символів, існує також спеціальна нотація для кодів Unicode \u…
, вона використовується рідко та описана в додатковому розділі про Unicode.
Довжина рядка
Властивість length
містить в собі довжину рядка:
alert( `Моє\n`.length ); // 4
Зверніть увагу, що \n
– це один спеціальний символ, тому довжина рівна 4
.
length
– це властивістьЛюди з досвідом роботи в інших мовах випадково намагаються викликати властивість, додаючи круглі дужки: вони пишуть str.length()
замість str.length
. Це не спрацює.
Зверніть увагу, що str.length
– це числове значення, а не функція, додавати дужки не потрібно. Не .length()
, а .length
.
Доступ до символів
Отримати символ, котрий займає позицію pos
, можна за допомогою квадратних дужок: [pos]
, або викликати метод str.at(pos). Перший символ займає нульову позицію:
let str = `Привіт`;
// перший символ
alert( str[0] ); // П
alert( str.charAt(0) ); // П
// останній символ
alert( str[str.length - 1] ); // т
alert( str.at(-1) );
Як бачите, перевага методу .at(pos)
полягає в тому, що він допускає від’ємну позицію. Якщо pos
від’ємне число, тоді позиція відраховується з кінця рядка.
Отже, .at(-1)
означає останній символ, а .at(-2)
– передостанній, тощо.
Квадратні дужки завжди повертають undefined
для від’ємних індексів, наприклад:
let str = `Привіт`;
alert( str[-2] ); // undefined
alert( str.at(-2) ); // і
Ми також можемо перебрати рядок посимвольно, використовуючи for..of
:
for (let char of "Привіт") {
alert(char); // П,р,и,в,і,т (char — спочатку "П", потім "р", потім "и" і так далі)
}
Рядки незмінні
В JavaScript рядки не можна змінювати. Змінити символ неможливо.
Спробуємо показати на прикладі:
let str = 'Ой';
str[0] = 'о'; // помилка
alert( str[0] ); // не працює
Можна створити новий рядок замість старого, записавши його в ту саму змінну.
Ось так:
let str = 'Ой';
str = 'о' + str[1]; // замінюємо рядок
alert( str ); // ой
В наступних розділах ми побачимо більше прикладів.
Зміна регістру
Методи toLowerCase() та toUpperCase() змінюють регістр символів:
alert( 'Інтерфейс'.toUpperCase() ); // ІНТЕРФЕЙС
alert( 'Інтерфейс'.toLowerCase() ); // інтерфейс
Або якщо ми хочемо перенести в нижній регістр конкретний символ:
alert( 'Interface'[0].toLowerCase() ); // 'і'
Пошук підрядка
Існує декілька способів для пошуку підрядка.
str.indexOf
Перший метод – str.indexOf(substr, pos).
Він шукає підрядок substr
в рядку str
, починаючи з позиції pos
, і повертає позицію, де знаходиться збіг, або -1
якщо збігів не було знайдено.
Наприклад:
let str = 'Віджет з ідентифікатором';
alert( str.indexOf('Віджет') ); // 0, тому що 'Віджет' було знайдено на початку
alert( str.indexOf('віджет') ); // -1, збігів не знайдено, пошук чутливий до регістру
alert( str.indexOf("ід") ); // 1, підрядок "ід" знайдено на позиції 1 (..іджет з ідентифікатором)
Необовʼязковий другий параметр pos
дозволяє нам почати пошук із заданої позиції.
Наприклад, перший збіг "ід"
знаходиться на позиції 1
. Щоб знайти наступний збіг, почнемо пошук з позиції 2
:
let str = 'Віджет з ідентифікатором';
alert( str.indexOf('ід', 2) ) // 9
Щоб знайти усі збіги, нам потрібно запустити indexOf
в циклі. Кожен новий виклик здійснюється з позицією після попереднього збігу:
let str = 'Хитрий, як лисиця, сильний, як Як';
let target = 'як'; // давайте знайдемо це
let pos = 0;
while (true) {
let foundPos = str.indexOf(target, pos);
if (foundPos == -1) break;
alert( `Знайдено тут: ${foundPos}` );
pos = foundPos + 1; // продовжуємо з наступної позиції
}
Той самий алгоритм можна записати коротше:
let str = "Хитрий, як лисиця, сильний, як Як";
let target = "як";
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
alert( pos );
}
str.lastIndexOf(substr, position)
Також є схожий метод str.lastIndexOf(substr, position), що виконує пошук від кінця рядка до його початку.
У ньому будуть перераховані збіги в зворотному порядку.
Існує незручність з indexOf
в умові if
. Ми не можемо помістити його в if
таким чином:
let str = "Віджет з ідентифікатором";
if (str.indexOf("Віджет")) {
alert("Є співпадіння"); // не працює
}
В прикладі вище alert
не відображається, оскільки str.indexOf("Віджет")
повертає 0
(це означає, що він знайшов збіг у початковій позиції). Це правильно, але if
вважає, що 0
– це false
.
Тому нам потрібно робити перевірку на -1
, як тут:
let str = "Віджет з ідентифікатором";
if (str.indexOf("Віджет") != -1) {
alert("Є співпадіння"); // тепер працює!
}
includes, startsWith, endsWith
Сучасніший метод str.includes(substr, pos) повертає true/false
в залежності від того чи є substr
в рядку str
.
Цей метод доцільно використовувати, коли потрібно перевірити чи є збіг, але не потрібна позиція:
alert( "Віджет з ідентифікатором".includes("Віджет") ); // true
alert( "Привіт".includes("Бувай") ); // false
Необовʼязковий другий аргумент pos
– це позиція з якої почнеться пошук:
alert( "Віджет".includes("ід") ); // true
alert( "Віджет".includes("ід", 3) ); // false, починаючи з 3-го символа, підрядка "ід" немає
Відповідно, методи str.startsWith та str.endsWith перевіряють, чи починається і чи закінчується рядок певним підрядком.
alert( "Віджет".startsWith("Від") ); // true, "Віджет" починається з "Від"
alert( "Віджет".endsWith("жет") ); // true, "Віджет" закінчується підрядком "жет"
Отримання підрядка
В JavaScript є 3 метода для отримання підрядка: substring
, substr
та slice
.
str.slice(start [, end])
-
Повертає частину рядка починаючи від
start
до (але не включно)end
.Наприклад:
let str = "stringify"; alert( str.slice(0, 5) ); // 'strin', підрядок від 0 до 5 (5 не включно) alert( str.slice(0, 1) ); // 's', від 0 до 1, але 1 не включно, тому лише символ на позиції 0
Якщо другий аргумент відсутній, тоді
slice
поверне символи до кінця рядка:let str = "stringify"; alert( str.slice(2) ); // 'ringify', з позиції 2 і до кінця
Також для
start/end
можна задати відʼємне значення. Це означає, що позиція буде рахуватися з кінця рядка:let str = "stringify"; // починаємо з 4-го символа справа, і закінчуємо на 1-му символі справа alert( str.slice(-4, -1) ); // 'gif'
str.substring(start [, end])
-
Повертає частину рядка між
start
таend
(не включаючиend
)…Цей метод майже такий самий що і
slice
, але він дозволяє задатиstart
більше ніжend
(у цьому випадку він просто міняє значенняstart
іend
місцями).Наприклад:
let str = "stringify"; // для substring ці два приклади однакові alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" // ...але не для slice: alert( str.slice(2, 6) ); // "ring" (те саме) alert( str.slice(6, 2) ); // "" (порожній рядок)
Відʼємні аргументи (на відміну від slice) не підтримуються, вони інтерпретуються як
0
. str.substr(start [, length])
-
Повертає частину рядка з позиції
start
, із заданою довжиноюlength
.На відміну від попередніх методів, цей дозволяє вказати довжину
length
замість кінцевої позиції:let str = "stringify"; alert( str.substr(2, 4) ); // 'ring', починаючи з позиції 2 отримуємо 4 символа
Перший аргумент може бути відʼємним, щоб рахувати з кінця:
let str = "stringify"; alert( str.substr(-4, 2) ); // 'gi', починаючи з позиції 4 з кінця отримуєму 2 символа
Цей метод міститься в Annex B специфікації мови. Це означає, що лише рушії браузерного Javascript мають його підтримувати, і не рекомендується його використовувати. На практиці це підтримується всюди.
Давайте підсумуємо ці методи щоб не заплутатись:
Метод | вибирає… | відʼємні значення |
---|---|---|
slice(start, end) |
від start до end (не включаючи end ) |
дозволяє відʼємні значення |
substring(start, end) |
між start та end (не включаючи end ) |
відʼємні значення інтерпретуються як 0 |
substr(start, length) |
length символів від start |
дозволяє відʼємні значення start |
Усі вони можуть виконати задачу. Формально substr
має незначний недолік: він описаний не в основній специфікації JavaScript, а в Annex B, який охоплює лише функції браузера, які існують переважно з історичних причин. Тому не браузерні середовища, можуть не підтримувати його. Але на практиці це працює всюди.
З двох інших варіантів slice
дещо гнучкіший, він допускає від’ємні аргументи та коротший в записі.
Отже, достатньо запамʼятати лише slice
з цих трьох методів.
Порівняння рядків
Як ми знаємо з розділу Оператори порівняння, рядки порівнюються символ за символом в алфавітному порядку.
Хоча, є деякі дивацтва.
-
Літера в малому регістрі завжди більша за літеру у великому:
alert( 'a' > 'Z' ); // true
-
Літери з діакритичними знаками “не в порядку”:
alert( 'Österreich' > 'Zealand' ); // true
Це може призвести до дивних результатів, якщо ми відсортуємо ці назви країн. Зазвичай люди очікують, що
Zealand
буде післяÖsterreich
.
Щоб зрозуміти, що відбувається, давайте розглянемо внутрішнє представлення рядків у JavaScript закодованих за допомогою UTF-16. Тобто: кожен символ має відповідний числовий код…
Існують спеціальні методи, які дозволяють отримати символ по коду і навпаки.
str.codePointAt(pos)
-
Повертає десяткове число, що є кодом символу на позиції
pos
:// літери в різному регістрі мають різні коди alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90 alert( "z".codePointAt(0) ); // 122 alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value)
String.fromCodePoint(code)
-
Створює символ за його кодом
code
alert( String.fromCodePoint(90) ); // Z alert( String.fromCodePoint(0x5a) ); // Z (ми також можемо використовувати шістнадцяткове значення як аргумент)
Тепер давайте подивимося на символи з кодами 65..220
(латинський алфавіт і трохи більше), створивши з них рядок:
let str = '';
for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i);
}
alert( str );
// Output:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ
Бачите? Спочатку вводяться великі символи, потім кілька спеціальних, потім символи нижнього регістру та Ö
ближче до кінця виводу.
Тепер стає очевидним, чому a > Z
.
Символи порівнюються за їх числовим кодом. Більший код означає, що символ більше. Код для a
(97) більший за код для Z
(90).
- Усі малі літери йдуть після великих, оскільки їхні коди більші.
- Деякі літери, як-от
Ö
, стоять окремо від основного алфавіту. Тут його код більший за будь-що відa
доz
.
Правильне порівняння
«Правильний» алгоритм порівняння рядків є складнішим, ніж може здатися, тому що для різних мов – різні алфавіти.
Отже, браузеру потрібно знати, яку мову використовувати для порівняння.
На щастя, усі сучасні браузери підтримують стандарт інтернаціоналізації ECMA-402.
Він забезпечує спеціальний метод для порівняння рядків різними мовами, дотримуючись їхніх правил.
Виклик str.localeCompare(str2) повертає ціле число, яке вказує, чи є str
меншим, рівним чи більшим за str2
відповідно до правил мови:
- Повертає відʼємне число, якщо
str
менше, ніжstr2
. - Повертає додатне число, якщо
str
більше, ніжstr2
. - Повертає
0
, якщо вони рівні.
Наприклад:
alert( 'Österreich'.localeCompare('Zealand') ); // -1
Цей метод насправді має два додаткові аргументи, зазначені в документації, що дозволяє йому вказати мову (типово взяту з середовища, порядок букв залежить від мови) і встановити додаткові правила, як-от чутливість до регістру або чи слід розглядати різницю між "a"
та "á"
.
Підсумки
- Є 3 види лапок. Зворотні лапки дозволяють рядку охоплювати кілька ліній і застосовувати вбудовувані вирази
${…}
. - Ми можемо використовувати спеціальні символи, такі як розрив рядка
\n
. - Щоб отримати символ, використовуйте:
[]
або методat
. - Щоб отримати підрядок, використовуйте:
slice
абоsubstring
. - Щоб перевести рядок у нижній/верхній регістри, використовуйте:
toLowerCase/toUpperCase
. - Щоб знайти підрядок, використовуйте:
indexOf
, абоincludes/startsWith/endsWith
для простих перевірок. - Щоб порівняти рядки з урахуванням правил мови, використовуйте:
localeCompare
, інакше вони порівнюються за кодами символів.
Є кілька інших корисних методів у рядках:
str.trim()
– видаляє (“обрізає”) пробіли з початку та кінця рядка.str.repeat(n)
– повторює рядокn
разів.- …та багато іншого можна знайти в посібнику.
Рядки також мають методи пошуку/заміни регулярними виразами. Але це велика тема, тому пояснюється в окремому розділі Регулярні вирази.
Крім того, на даний момент важливо знати, що рядки базуються на кодуванні Unicode, і тому виникають проблеми з порівнянням, які ми описали вище. Більше про Unicode у розділі Юнікод, внутрішня будова рядків.