18 грудня 2021 р.

Рядки

У 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 викликається автоматично, отримує рядок і вбудовані в неї вирази і може їх обробити. Це називається “теговим шаблоном”. Ця функція полегшує реалізацію користувацької шаблонізації, але рідко використовується на практиці. Детальніше про це можна прочитати в посібнику.

Спеціальні символи

Ще можна створити багатолінійні рядки з одинарними та подвійними лапками за допомогою так званого “символу нового рядка”, записаного як \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 – зберігаються для зворотної сумісності, зараз не використовуються
\xXX Символ з шістнадцятковим юнікодним кодом '\x7A' – це те ж саме що і 'z'
\uXXXX Символ з шістнадцятковим юнікодним кодом XXXX в кодуванні UTF-16, наприклад \u00A9 – це юнікодний символ для знаку копірайту ©. Шістнадцятковий код обовʼязково має складатись з 4 символів
\u{X…XXXXXX} (від 1 до 6 шістнадцяткових символів) Юнікодний символ в кодуванні UTF-32. Деякі рідкісні символи кодуються двома юнікодними символами, що займають 4 байти. Таким чином ми можемо вставляти довгі коди

Приклади з Юнікодом:

alert( "\u00A9" ); // ©
alert( "\u{20331}" ); // 佫, рідкісний китайський ієрогліф (довгий юнікод)
alert( "\u{1F60D}" ); // 😍, емодзі посмішки з очима в формі сердець (інший довгий юнікод)

Усі спеціальні символи починаються зі зворотного слеша \. Його також називають “символом екранування”.

Ми також можемо його використати, якщо хочемо вставити лапки в рядок.

Наприклад:

alert( 'Ім\'я моє — Морж!' ); // Ім'я моє — Морж!

Як бачите, ми повинні “екранувати” лапку зворотним слешем \', оскільки інакше це означало б кінець рядка.

Звісно, потрібно “екранувати” лише такі лапки, якими обрамлений рядок. Як елегантніше рішення, ми могли б замість цього скористатися подвійними або зворотними лапками:

alert( `Ім'я моє — Морж!` ); // Ім'я моє — Морж!

Зверніть увагу, що зворотний слеш \ в JavaScript служить для правильного зчитування рядка. Рядок в памʼяті не містить \. Ви можете чітко це побачити у alert з наведених вище прикладів.

Але що, якщо нам потрібно показати зворотний слеш \ всередині рядка?

Це можна зробити додаванням ще одного зворотного слеша \\:

alert( `Зворотний слеш: \\` ); // Зворотний слеш: \

Довжина рядка

Властивість length містить в собі довжину рядка:

alert( `Моє\n`.length ); // 4

Зверніть увагу, що \n – це один спеціальний символ, тому довжина рівна 4.

length – це властивість

Люди з досвідом роботи в інших мовах випадково намагаються викликати властивість, додаючи круглі дужки: вони пишуть str.length() замість str.length. Це не спрацює.

Зверніть увагу, що str.length – це числове значення, а не функція, додавати дужки не потрібно.

Доступ до символів

Отримати символ, котрий займає позицію pos, можна за допомогою квадратних дужок: [pos], або викликати метод str.charAt(pos). Перший символ займає нульову позицію.

let str = `Привіт`;

// перший символ
alert( str[0] ); // П
alert( str.charAt(0) ); // П

// останній символ
alert( str[str.length - 1] ); // т

Квадратні дужки – це сучасний спосіб отримати символ, тоді як charAt існує більше завдяки історичним причинам.

Різниця між ними лише в тому, що якщо символ з такою позицією відсутній, тоді [] поверне undefined, а charAt – порожній рядок:

let str = `Привіт`;

alert( str[1000] ); // undefined
alert( str.charAt(1000) ); // '' (порожній рядок)

Ми також можемо перебрати рядок посимвольно, використовуючи 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( 'Інтерфейс'[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("Є співпадіння"); // тепер працює!
}

Трюк з побітовим НЕ

Один зі старих прийомів, який тут використовується, це побітовий оператор НЕ~. Він перетворює число в 32-розрядне ціле число (вилучає десяткову частину, якщо вона є), а потім повертає всі біти в його двійковому представленні.

На практиці це означає просту річ: для 32-розрядних чисел значення ~n рівне -(n+1).

Наприклад:

alert( ~2 ); // -3, те саме що -(2+1)
alert( ~1 ); // -2, те саме що -(1+1)
alert( ~0 ); // -1, те саме що -(0+1)
alert( ~-1 ); // 0, те саме що -(-1+1)

Як ми можемо бачити, ~n рівне 0 лише при n == -1 (для будь якого 32-розрядного цілого числа n)

Відповідно, проходження перевірки if ( ~str.indexOf("...") ) означає, що результат indexOf відрізняється від -1. Іншими словами, коли є збіг.

Таку перевірку іноді використовують як компактний indexOf:

let str = "Віджет";

if (~str.indexOf("Віджет")) {
  alert( 'Є співпадіння!' ); // Працює
}

Зазвичай використовувати можливості мови неочевидним способом не рекомендується, але цей трюк широко використовується в старому коді, тому це важливо розуміти.

Просто запамʼятайте: if (~str.indexOf(...)) означає “якщо знайдено”.

Проте, якщо бути точніше, через те, що великі числа обрізаються до 32 бітів оператором ~, існують числа, для яких результат також буде 0, найменше таке число ~4294967295=0. Тому така перевірка буде працювати для рядків невеликої довжини.

Зараз такий трюк ми можемо побачити лише в старому коді, тому що в сучасному JavaScript є метод .includes.

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.

Цей метод майже такий самий що і slice, але він дозволяє задати 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 символа

Давайте підсумуємо ці методи щоб не заплутатись:

Метод вибирає… відʼємні значення
slice(start, end) від start до end (end не включно) дозволяє відʼємні значення
substring(start, end) між start та end відʼємні значення інтерпретуються як 0
substr(start, length) length символів від start дозволяє відʼємні значення start
Який метод вибрати?

Усі вони можуть виконати задачу. Формально substr має незначний недолік: він описаний не в основній специфікації JavaScript, а в Annex B, який охоплює лише функції браузера, які існують переважно з історичних причин. Тому не браузерні середовища, можуть не підтримувати його. Але на практиці це працює всюди.

З двох інших варіантів slice дещо гнучкіший, він допускає негативні аргументи та коротший в записі. Отже, достатньо запамʼятати лише slice з цих трьох методів.

Порівняння рядків

Як ми знаємо з розділу Оператори порівняння, рядки порівнюються символ за символом в алфавітному порядку.

Хоча, є деякі дивацтва.

  1. Літера в малому регістрі завжди більша за літеру у великому:

    alert( 'a' > 'Z' ); // true
  2. Літери з діакритичними знаками “не в порядку”:

    alert( 'Österreich' > 'Zealand' ); // true

    Це може призвести до дивних результатів, якщо ми відсортуємо ці назви країн. Зазвичай люди очікують, що Zealand буде після Österreich.

Щоб зрозуміти, що відбувається, давайте розглянемо внутрішнє представлення рядків у JavaScript.

Усі рядки кодуються за допомогою UTF-16. Тобто: кожен символ має відповідний цифровий код. Існують спеціальні методи, які дозволяють отримати символ для коду і навпаки.

str.codePointAt(pos)

Повертає код символу на позиції pos:

// літери в різному регістрі мають різні коди
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
String.fromCodePoint(code)

Створює символ за його кодом code

alert( String.fromCodePoint(90) ); // Z

Ми також можемо додати юнікодні символи за їхніми кодами, використовуючи \u, за яким слідує шістнадцятковий код:

// 90 – це 5a в шістнадцятковій системі числення
alert( '\u005a' ); // Z

Тепер давайте подивимося на символи з кодами 65..220 (латинський алфавіт і трохи більше), створивши з них рядок:

let str = '';

for (let i = 65; i <= 220; i++) {
  str += String.fromCodePoint(i);
}
alert( str );
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„
// ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ

Бачите? Спочатку вводяться великі символи, потім кілька спеціальних, потім символи нижнього регістру та Ö ближче до кінця виводу.

Тепер стає очевидним, чому a > Z.

Символи порівнюються за їх числовим кодом. Більший код означає, що символ більше. Код для a (97) більший за код для Z (90).

  • Усі малі літери йдуть після великих, оскільки їхні коди більші.
  • Деякі літери, як-от Ö, стоять окремо від основного алфавіту. Тут його код більший за будь-що від a до z.

Правильне порівняння

«Правильний» алгоритм порівняння рядків є складнішим, ніж може здатися, тому що для різних мов – різні алфавіти.

Отже, браузеру потрібно знати, яку мову використовувати для порівняння.

На щастя, усі сучасні браузери (IE10- вимагає додаткової бібліотеки Intl.js) підтримують стандарт інтернаціоналізації ECMA-402.

Він забезпечує спеціальний метод для порівняння рядків різними мовами, дотримуючись їхніх правил.

Виклик str.localeCompare(str2) повертає ціле число, яке вказує, чи є str меншим, рівним чи більшим за str2 відповідно до правил мови:

  • Повертає відʼємне число, якщо str менше, ніж str2.
  • Повертає додатне число, якщо str більше, ніж str2.
  • Повертає 0, якщо вони рівні.

Наприклад:

alert( 'Österreich'.localeCompare('Zealand') ); // -1

Цей метод насправді має два додаткові аргументи, зазначені в документації, що дозволяє йому вказати мову (типово взяту з середовища, порядок букв залежить від мови) і встановити додаткові правила, як-от чутливість до регістру або чи слід розглядати різницю між "a" та "á".

Як усе влаштовано, Юнікод

Поглиблення в тему

Розділ заглиблюється у внутрішню частину рядків. Ці знання знадобляться вам, якщо ви плануєте мати справу з емодзі, рідкісними математичними чи ієрогліфічними символами чи іншими рідкісними символами.

Ви можете пропустити розділ, якщо не плануєте підтримувати їх.

Сурогатні пари

Усі часто використовувані символи мають 2-байтові коди. Літери в більшості європейських мов, цифри і навіть більшість ієрогліфів мають 2-байтове представлення.

Але 2 байти – це лише 65536 комбінацій, і цього недостатньо для кожного можливого символу. Тому рідкісні символи кодуються парою 2-байтових символів, які називаються «сурогатною парою».

Довжина таких символів – 2:

alert( '𝒳'.length ); // 2, математичний символ "x" у верхньому регістрі
alert( '😂'.length ); // 2, емодзі -- обличчя зі сльозами радості
alert( '𩷶'.length ); // 2, рідкісний китайський ієрогліф

Зауважте, що сурогатні пари не існували на момент створення JavaScript, і тому мова не обробляє їх належним чином!

Насправді ми маємо один символ у кожному з наведених вище рядків, але length показує довжину 2.

String.fromCodePoint і str.codePointAt – це кілька рідкісних методів, які правильно працюють із сурогатними парами. Вони зʼявились в мові нещодавно. До них були лише String.fromCharCode і str.charCodeAt. Ці методи насправді такі ж, як fromCodePoint/codePointAt, але не працюють із сурогатними парами.

Отримати символ може бути складно, оскільки сурогатні пари розглядаються як два символи:

alert( '𝒳'[0] ); // дивні символи...
alert( '𝒳'[1] ); // ...частини сурогатної пари

Зверніть увагу, що частини сурогатної пари не мають значення один без одного. Отже, сповіщення в прикладі вище насправді відображають сміття.

Технічно, сурогатні пари також можна виявити за їх кодами: якщо символ має код в інтервалі 0xd800..0xdbff, то це перша частина сурогатної пари. Наступний символ (друга частина) повинен мати код в інтервалі 0xdc00..0xdfff. За стандартом ці інтервали зарезервовані виключно для сурогатних пар.

У наведеному вище випадку:

// charCodeAt не підтримує сурогатні пари, тому дає коди для частин

alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, між 0xd800 та 0xdbff
alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, між 0xdc00 та 0xdfff

Більше способів роботи із сурогатними парами ви знайдете пізніше в розділі Ітеративні об’єкти. Для цього також є спеціальні бібліотеки, але немає достатньо відомої, щоб запропонувати її тут.

Діакритичні знаки та нормалізація

У багатьох мовах є символи, які складаються з основного символу з позначкою над/під ним.

Наприклад, буква a може бути базовим символом для: àáâäãåā. Найбільш поширені “складені” символи мають власний код у таблиці UTF-16. Але не всі, оскільки можливих комбінацій занадто багато.

Для підтримки довільних композицій UTF-16 дозволяє нам використовувати кілька юнікодних символів: основний символ, за яким слідує один або багато символів «позначок», які «прикрашають» його.

Наприклад, якщо у нас є S, за яким слідує спеціальний символ “крапка зверху” (код \u0307), він відображається як Ṡ.

alert( 'S\u0307' ); // Ṡ

Якщо нам потрібна додаткова позначка над літерою (або під нею) – не проблема, просто додайте потрібний символ позначки.

Наприклад, якщо ми додамо символ “крапка внизу” (код \u0323), то матимемо “S з крапками зверху і знизу”: .

Наприклад:

alert( 'S\u0307\u0323' ); // Ṩ

Це забезпечує велику гнучкість, але також є цікава проблема: два символи візуально можуть виглядати однаково, але представлені різними юнікодними композиціями.

Наприклад:

let s1 = 'S\u0307\u0323'; // Ṩ, S + крапка зверху + крапка знизу
let s2 = 'S\u0323\u0307'; // Ṩ, S + крапка знизу + крапка зверху

alert( `s1: ${s1}, s2: ${s2}` );

alert( s1 == s2 ); // false, хоча на вигляд символи однакові (?!)

Щоб вирішити це, існує алгоритм “юнікодної нормалізації”, який приводить кожен рядок до єдиної “нормальної” форми.

Це реалізовано за допомогою str.normalize().

alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true

Цікаво, що в нашій ситуації normalize() насправді об’єднує послідовність з 3 символів в один: \u1e68 (S з двома крапками).

alert( "S\u0307\u0323".normalize().length ); // 1

alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true

Насправді це не завжди так. Причина в тому, що символ є “досить поширеним”, тому розробники UTF-16 включили його в основну таблицю і присвоїли йому код.

Якщо ви хочете дізнатися більше про правила та варіанти нормалізації – вони описані в додатку до стандарту Unicode: Unicode Normalization Forms, але для більшості практичних для цілей інформації з цього розділу достатньо.

Підсумки

  • Є 3 види лапок. Зворотні лапки дозволяють рядку охоплювати кілька ліній і вбудовувати вирази ${…}.
  • Рядки в JavaScript кодуються за допомогою UTF-16.
  • Ми можемо використовувати спеціальні символи, такі як \n, і вставляти літери за допомогою їхнього юнікоду за допомогою \u....
  • Щоб отримати символ, використовуйте: [].
  • Щоб отримати підрядок, використовуйте: slice або substring.
  • Щоб перевести рядок у нижній/верхній регістри, використовуйте: toLowerCase/toUpperCase.
  • Щоб знайти підрядок, використовуйте: indexOf, або includes/startsWith/endsWith для простих перевірок.
  • Щоб порівняти рядки з урахуванням правил мови, використовуйте: localeCompare, інакше вони порівнюються за кодами символів.

Є кілька інших корисних методів у рядках:

  • str.trim() – видаляє (“обрізає”) пробіли з початку та кінця рядка.
  • str.repeat(n) – повторює рядок n разів.
  • …та багато іншого можна знайти в посібнику.

Рядки також мають методи пошуку/заміни регулярними виразами. Але це велика тема, тому пояснюється в окремому розділі Regular expressions.

Завдання

важливість: 5

Напишіть функцію ucFirst(str), яка повертає рядок str з першим символом у верхньому регістрі, наприклад:

ucFirst("василь") == "Василь";

Відкрити пісочницю з тестами.

Ми не можемо “замінити” перший символ, оскільки рядки в JavaScript незмінні.

Але ми можемо створити новий рядок на основі існуючого, з першим символом у верхньому регістрі:

let newStr = str[0].toUpperCase() + str.slice(1);

Але є невелика проблема. Якщо str порожній рядок, то str[0] буде undefined, а оскільки undefined не має методу toUpperCase(), ми отримаємо помилку.

Тут є два варіанти:

  1. Використати str.charAt(0), оскільки він завжди повертає рядок (навіть для порожнього рядка).
  2. Додати перевірку на порожній рядок.

Ось 2-й варіант:

function ucFirst(str) {
  if (!str) return str;

  return str[0].toUpperCase() + str.slice(1);
}

alert( ucFirst("василь") ); // Василь

Відкрити рішення із тестами в пісочниці.

важливість: 5

Напишіть функцію checkSpam(str), яка повертає true, якщо str містить ‘viagra’ or ‘XXX’, інакше false.

Функція має бути нечутливою до регістру:

checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false

Відкрити пісочницю з тестами.

Щоб зробити пошук нечутливим до регістру, давайте переведемо рядок у нижній регістр, а потім здійснимо пошук:

function checkSpam(str) {
  let lowerStr = str.toLowerCase();

  return lowerStr.includes('viagra') || lowerStr.includes('xxx');
}

alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );

Відкрити рішення із тестами в пісочниці.

важливість: 5

Створіть функцію truncate(str, maxlength), яка перевіряє довжину str і, якщо вона перевищує maxlength – замінює кінець str символом трьох крапок "…", щоб його довжина була рівною maxlength.

Результатом функції повинен бути урізаний (якщо потребується) рядок.

Наприклад:

truncate("Що я хотів би розповісти на цю тему:", 20) = "Що я хотів би розпо…"

truncate("Всім привіт!", 20) = "Всім привіт!"

Відкрити пісочницю з тестами.

Максимальна довжина має бути maxlength, тому нам потрібно її трохи обрізати, щоб дати місце для символу трьох крапок.

Зауважте, що насправді існує один юнікодний символ для “трьох крапок”. Це не три послідовні крапки.

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

Відкрити рішення із тестами в пісочниці.

важливість: 4

У нас є вартість у вигляді "$120". Тобто: спочатку йде знак долара, а потім число.

Створіть функцію extractCurrencyValue(str), яка витягне числове значення з такого рядка та поверне його.

Приклад:

alert( extractCurrencyValue('$120') === 120 ); // true

Відкрити пісочницю з тестами.

function extractCurrencyValue(str) {
  return +str.slice(1);
}

Відкрити рішення із тестами в пісочниці.

Навчальна карта

Коментарі

прочитайте це, перш ніж коментувати…
  • Якщо у вас є пропозиції, щодо покращення підручника, будь ласка, створіть обговорення на GitHub або одразу створіть запит на злиття зі змінами.
  • Якщо ви не можете зрозуміти щось у статті, спробуйте покращити її, будь ласка.
  • Щоб вставити код, використовуйте тег <code>, для кількох рядків – обгорніть їх тегом <pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)