Работа со строками в современном JavaScript

0
110
views
javascript logo

Хочешь проверить свои знания по JS?

Подпишись на наш канал с тестами по JS в Telegram!

×

Сайт tproger.ru опубликовал перевод статьи «Working With Strings in Modern JavaScript». Это руководство, охватывающее всё, что вам нужно знать о работе со строками в JavaScript. Представляем его вашему вниманию.

Создание строк

По сути, в JavaScript есть две категории строк: строковые примитивы и объекты String.

Примитивы

Строковые примитивы создаются следующими способами:

let example1 = "BaseClass"
// или 
let example2 = String("BaseClass")

Почти во всех случаях вы должны использовать один из этих методов для создания новой строки.

При определении строкового литерала можно использовать одинарные кавычки ('') или двойные кавычки ("").

Объекты

Вы можете создать объект String, используя ключевое слово new.

Единственное реальное преимущество объекта перед строковым примитивом состоит в том, что вы можете назначить ему дополнительные свойства:

let website = new String("BaseClass")
website.rating = "great!"

Однако очень мало случаев, когда это полезно. Практически во всех случаях следует создавать строковый примитив.

Все знакомые вам методы строк являются частью объекта String, а не примитива.

Когда вы вызываете метод для строкового примитива, JavaScript оборачивает примитив в String-объект и вызывает метод этого объекта.

Шаблонные строки

Базовые шаблонные строки

Шаблонные строки позволяют объединять переменные и текст в новую строку с использованием более удобочитаемого синтаксиса.

Вместо двойных или одинарных кавычек заключите строку в обратные кавычки и вставьте переменные, используя синтаксис ${variableName}

До

let opinion = "Love"
let tweet = "I " + opinion + " JavaScript"

// "I Love JavaScript"

После

let opinion = "Love"
let tweet = `I ${opinion} JavaScript`

// "I Love JavaScript"

Вы также можете включать выражения в шаблонные строки:

let age = 37
let result = 
`You ${age > 30 ? "do" : "don't"} remember VHS tapes!`

"You do remember VHS tapes!"

Сейчас браузеры очень хорошо поддерживают работу с шаблонными строками в JavaScript.

  • Chrome: 41+
  • Edge: 13+
  • Firefox: 34+
  • Safari: 9.1+
  • Opera: 29+

Вы также можете вкладывать шаблоны друг в друга, как показано в этом примере из MDN:

const classes = `header ${
  isLargeScreen() ? '' : 
  `icon-${item.isCollapsed ? 'expander' : 'collapser'}`
}`;

Теговые шаблоны

Теговые шаблоны позволяют создать функцию, которая парсит шаблонную строку.

Это может быть действительно мощным инструментом и наиболее наглядно демонстрируется на примере.

Представьте, что у нас есть функция censor(), которая удаляет любые оскорбительные слова в строке, введенной пользователем.

Когда мы хотим подвергнуть строку цензуре, мы можем вручную вызвать censor() для каждого введенного пользователем значения:

let response = `Hi ${censor(name)},
 you entered ${censor(jobTitle)}.`

Или мы могли бы использовать теговые шаблоны.

Это позволяет нам написать функцию, которая принимает строковые значения из шаблонной строки и все выражения, используемые в шаблоне:

let censorStrings = (strings, name, jobTitle) => {
    // strings: ["Hi ", ", you entered ", "."]
    // name: "Dave"
    // jobTitle: "Developer"
}

let name = "Dave"
let jobTitle = "Developer"

let response = censorStrings`Hi ${name},
 you entered ${jobTitle}.`

Обратите внимание, что в последней строке мы «тегаем» строку нашей функцией, добавляя ее перед шаблонной строкой, а не явно вызывая функцию censorStrings().

Это означает, что теперь мы можем управлять шаблонной строкой и значениями внутри неё.

Первым аргументом теговой функции всегда является массив строк. Остальные аргументы представляют каждую переменную / выражение, используемые в шаблонной строке.

Это означает, что вы не обязательно будете знать, сколько аргументов ожидать в вашей функции «тегирования».

В этих случаях полезно поместить каждый из оставшихся аргументов в массив (используя синтаксис «rest»), чтобы вы могли их перебирать:

let censorStrings = (strings, ...inputs) => {
    // strings: ["Hi ", ", you entered ", "."]
    // inputs: ["Dave", "Developer"]
}

Теперь у нас есть доступ к шаблонной строке и отдельным аргументам. Мы можем отслеживать каждую переменную, используемую в строке:

let censorStrings = (strings, ...inputs) => {
    // Подвергнуть цензуре каждую переменную, 
    // используемую в шаблонной строке
    let censored = inputs.map(i => censor(i))
}

let name = "Dave"
let jobTitle = "Developer"

let response = censorStrings`Hi ${name},
 you entered ${jobTitle}.`

Наконец, наша теговая функция должна вернуть обработанную строку.

Для этого мы просто объединяем исходный массив строк и массив (измененных) входных данных в новый массив.

// (strings)            (inputs)
// "Hi "            ->
//                      <- "Dave"
// ", you entered " ->
//                      <- "Developer"
// "."              ->

// "Hi Dave, you entered Developer."

Здесь мы делаем это с помощью .reduce():

return strings.reduce((result, currentString, i) => (
    `${result}${currentString}${censored[i] || ''}`
), '')

Наша теговая функция теперь готова, и ее можно использовать везде, где нам нужно цензурировать вводимые пользователем данные:

let censorStrings = (strings, ...inputs) => {
    // Подвергнуть цензуре каждую переменную, 
    // используемую в шаблонной строке
    let censored = inputs.map(i => censor(i))

    // Чередование строки и (цензурированных) 
    // введенных данных для создания конечной строки
    return strings.reduce((result, currentString, i) => (
        `${result}${currentString}${censored[i] || ''}`
    ), '')
}

let name = "Dave"
let jobTitle = "Developer"

let response = censorStrings`Hi ${name},
 you entered ${jobTitle}.`

// "Hi Dave, you entered Developer."

Теговая функция не обязательно должна возвращать строку.

Например, есть библиотеки для React, которые принимают шаблонную строку и возвращают компонент React.

Raw-строки в JavaScript

String.raw — это предопределенная теговая функция.

Она позволяет вам получить доступ к строке без обработки каких-либо значений после обратного слэша.

Например, при использовании строки, содержащей \n со String.raw, вместо получения новой строки вы получите фактические символы \ и n:

// БЕЗ использования String.raw
`Hello\nWorld`
// Hello
// World

// С String.raw
String.raw`Hello\nWorld`
// Hello\nWorld

Это может быть полезно (помимо прочего) для написания строк, в которых вам обычно приходится избегать большого количества символов обратного слэша, таких как пути к файлам:

// Без string.raw, мы должны избегать 
// \ разделителей каталогов
var path = `C:\\Program Files\\file.json`

// С string.raw, нет необходимости избегать разделителей
var path = String.raw`C:\Program Files\file.json`

При использовании string.raw символ \ экранирует последнюю обратную кавычку.

Это означает, что вы не можете заканчивать raw-строку символом \ следующим образом:

// Последняя обратная кавычка (`) будет исключена, это
// значит, что строка не завершена правильно
String.raw`c:\Program Files\`

Объединение строк

Конкатенация строк

Вы можете объединить (или «конкатенировать») несколько строк, чтобы создать новую, используя символ +:

let name = "Base" + "Class"

// Вывод: "BaseClass"

Этот подход также можно использовать для разделения создания строки на несколько строк для удобства чтения:

let name = "This really is " +
    "a very " +
    "long string"

// "This really is a very long string"

Вы также можете объединять строки с переменными (нестроковые переменные будут преобразованы в строки):

let precinct = 99
let name = "Brooklyn " + precinct

// Вывод: "Brooklyn 99"

Чтобы создать новую строку, добавив ее в конец существующей, используйте +=:

let name = "Three "
name += "Blind "
name += "Mice"

// "Three Blind Mice"

Вы также можете объединить строки и переменные с помощью метода string.concat(), но это не рекомендуется по соображениям производительности.

Вместо этого используйте операторы + или +=, как показано выше.

Повторение строки

Метод repeat() JavaScript возвращает новую строку, содержащую исходную строку, повторяющуюся несколько раз.

let warning = "Londons Burning. "
let result = warning.repeat(2)

// "London's Burning. London's Burning. "

Вы можете использовать string.repeat() в следующих браузерах:

  • Chrome: 41+
  • Edge: 12+
  • Firefox: 24+
  • Safari: 9+
  • Opera: 28+

Объединение строк

Вы можете объединить массив строк в одну, используя метод .join() для массива.

По умолчанию элементы разделяются запятой:

let heros = ["Superman", "Wonder Woman", "Hulk"]
heros.join()

// "Superman,Wonder Woman,Hulk"

Вы также можете указать строку, используемую для разделения элементов:

let heroes = ["Superman", "Wonder Woman", "Hulk"]
heroes.join(" or ")

// "Superman or Wonder Woman or Hulk"

Передача пустой строки в string.join объединит элементы, между которыми ничего нет:

let heroes = ["Superman", "Wonder Woman", "Hulk"]
heroes.join("")

// "SupermanWonder WomanHulk"

Когда toString() используется в массиве, он также возвращает список строк, разделенных запятыми.

let heroes = ["Superman", "Wonder Woman", "Hulk"]
heroes.toString()

// Вывод: Superman,Wonder Woman,Hulk"

Разделение строки

Вы можете разделить строку на массив с помощью метода split().

Типичные варианты использования:

Превращаем предложение в массив слов, разбивая его по пробелам:

"Welcome to Paradise".split(" ")
// [ "Welcome", "to", "Paradise" ]

…или разделение многострочной строки на отдельные строки:

let song = `Hello,
Is it me you're looking for?`

song.split("\n")

// [ "Hello,", "Is it me you're looking for?" ]

Вы также можете ограничить количество элементов, которые вы хотите вернуть из split(), передав необязательный второй параметр.

Если вам нужно преобразовать строку в JavaScript в массив символов учитывайте, что метод split() не работает для символов Unicode, которые представлены «суррогатными парами»:

"👋!".split("") // ["�", "�", "!"]

В современных браузерах вместо этого можно использовать spread-оператор:

[..."👋!"] // ["👋", "!"]

Сравнение строк

Равенство

Как вы знаете, что сравнивая два строковых примитива, вы можете использовать операторы == или ===:

"abba" === "abba" // true
"abba" == "abba" // true

Если вы сравниваете строковый примитив с чем-то, что не является строкой, == и === ведут себя по-разному.

При использовании оператора == не-строка будет преобразована в строку. Это означает, что JavaScript попытается преобразовать его в строку перед сравнением значений.

9 == "9"
// становится 
"9" == "9" //true

Для строгого сравнения, когда не-строки не приводятся к строкам, используйте ===:

9 === "9" // false

То же самое верно и для операторов неравенства != и !==:

9 != "9" // false
9 !== "9" // true

Если вы не знаете, что использовать, отдавайте предпочтение строгому равенству ===.

При использовании объектов String два объекта с одинаковым значением не считаются равными строками в JavaScript:

new String("js") == new String("js") // false
new String("js") === new String("js") // false

Чувствительность к регистру

Когда требуется сравнение без учета регистра, обычно преобразуют обе строки в верхний или нижний регистры и сравнивают результат.

"Hello".toUpperCase() === "HeLLo".toUpperCase() // true

Однако иногда вам нужно больше контроля над сравнением. Об этом в следующем разделе …

Работа с диакритическими знаками в строках JavaScript

Диакритические знаки — это модификации буквы, например é или ž.

Возможно вы захотите указать, как они обрабатываются при сравнении двух строк.

Например, в некоторых языках принято исключать акценты при написании прописных букв.

Если вам нужно сравнение без учета регистра, простое преобразование двух строк в один и тот же регистр с помощью toUpperCase() или toLowerCase() не будет учитывать добавление / удаление акцентов и может не дать ожидаемого результата.

Если вам нужен более точный контроль над сравнением, используйте вместо него localeCompare:

let a = 'Résumé';
let b = 'RESUME';
   
// Возвращает 'false' - строки не совпадают
a.toLowerCase() === b.toLowerCase()
   
// Возвращает '0' - строки совпадают
a.localeCompare(b, undefined, { sensitivity: 'base' })

Метод localeCompare позволяет указать «sensitivity» сравнения.

Здесь мы использовали base «sensitivity» для сравнения строк с использованием их «базовых» символов (что означает, что регистр и акценты игнорируются).

Поддержка localeCompare() браузерами:

  • Chrome: 24+
  • Edge: 12+
  • Firefox: 29+
  • Safari: 10+
  • Opera: 15+

Больше / меньше

При сравнении строк с использованием операторов < и > JavaScript будет сравнивать каждый символ в «лексикографическом порядке».

Это означает, что они сравниваются по буквам в том порядке, в котором они появляются в словаре:

"aardvark" < "animal" // true
"gamma" > "zulu" // false

При сравнении строк с использованием < или > строчные буквы считаются большими, чем прописные.

"aardvark" > "Animal" // true

Это происходит потому, что JavaScript использует значение каждого символа в Unicode, где строчные буквы идут после прописных.

True или false строки

Пустые строки в JavaScript считаются равными false при сравнении с использованием оператора == (но не при использовании ===).

("" == false) // true
("" === false) // false

Строки со значением являются «истинными», поэтому вы можете делать нечто подобное:

if (someString) {    
// у строки есть значение
} else {    
// пустая строка или undefined
}

Сортировка строк

Image by Iván Dequito from Pixabay

Простой Array.sort()

Самый простой способ отсортировать массив строк — использовать метод Array.sort():

["zebra", "aligator", "mouse"].sort()
// [ "aligator", "mouse", "zebra" ]

При сортировке массива строк они сравниваются с использованием «кода UTF-16» каждого символа.

В Unicode заглавные буквы находятся перед строчными.

Это означает, что строки, начинающиеся с заглавной буквы, всегда находятся перед строками, начинающимися со строчных букв:

["zebra", "aligator", "Mouse"].sort()
// [ "Mouse", "aligator", "zebra" ]

Вы можете избежать такого поведения, сначала преобразовав все строки в один и тот же регистр, или используя localeCompare (см. ниже), что обычно более эффективно.

localeCompare

Использование localeCompare в качестве функции сортировки позволяет сравнивать строки без учета регистра:

let animals = ["zebra", "aligator", "Mouse"]
animals.sort((a, b) => a.localeCompare(b));

/// [ "aligator", "Mouse", "zebra" ]

Вы также можете использовать localeCompare, чтобы игнорировать диакритические знаки (например, акцент) при сортировке строк. См. дополнительную информацию в разделе «Работа с диакритическими знаками».

Многострочные строки

Вы можете добавлять новые строки, используя \n:

let result = "The winner is:\nBaseClass!"

// The winner is:
// BaseClass!

В шаблонной строке новые строки учитываются внутри обратных кавычек:

let result = `The winner is:
BaseClass!`

// The winner is:
// BaseClass!

В шаблонных строках вы можете избежать разрывов строки, добавив \ в конце строки.

let result = `All on \
    one line`

// "All on one line"

Отступы в строках

Вы можете добавить пробел в начало или конец строки, пока она не достигнет указанной длины, используя padStart() или padEnd():

// Дополнить строку "hello" пробелами, чтобы
// она получила длину в 8 символов:

"hello".padStart(8) // "   hello"
"hello".padEnd(8) // "hello   "

Вместо пробела вы можете дополнить целевую строку другой строкой, передав ее в качестве второго параметра.

Эта строка будет повторяться до тех пор, пока не будет достигнута целевая длина (строка будет обрезана, если она не помещается):

// Увеличить "Train" до 13 символов,
// используя строку "Choo".
"Train".padStart(13, "Choo")

// Вывод: "ChooChooTrain"

Поддержка padStart() и padEnd() браузерами::

  • Chrome: 57+
  • Edge: 15+
  • Firefox: 48+
  • Safari: 10+
  • Opera: 44+

Извлечение части строки

Подстроки

Эти методы принимают индекс первого символа, который вы хотите извлечь из строки.

Они возвращают все от этого символа до конца строки:

"I am Groot!".slice(5) // "Groot!"
"I am Groot!".substring(5) // Groot!

Второй (необязательный) аргумент — это символ, на котором вы хотите остановиться.

Этот последний символ не включается в вывод:

// Извлечь новую строку с 5-го символа,
// до (но не включая!) 10-го символа

"I am Groot!".slice(5, 10) // "Groot"
"I am Groot!".substring(5, 10) // "Groot"

Итак, какой из них вы должны использовать?

Они очень похожи, но с небольшими отличиями:

  • Если конечное значение выше начального, substring() «исправит» их, заменив их местами, но slice() просто вернет пустую строку.
  • substring() обрабатывает отрицательный индекс как 0. Со slice() вы можете использовать отрицательное число для обратного отсчета от конца строки. Например, .slice(-3) вернет последние 3 символа строки.

Также существует метод substr(), похожий на slice() и substring().

Это устаревший API. Хотя вряд ли он будет использоваться в ближайшее время, для работы со строками в JavaScript вам следует использовать один из двух вышеупомянутых методов, где это возможно.

Одиночные символы

Метод charAt() возвращает определенный символ из строки (помните, что индексы начинаются с 0):

"Hello".charAt(1) // "e"

Вы также можете рассматривать строку как массив и обращаться к ней напрямую следующим образом:

"Hello" [1] // e

Доступ к строке как к массиву может привести к путанице, когда строка хранится в переменной.

Использование charAt() более явное:

// Что такое 'someVariable'?
let result = someVariable[1]

// 'someVariable' - точно строка
let result = someVariable.charAt(1)

Изменение регистра строки в JavaScript

Вы можете сделать строку с заглавными буквами следующим образом:

Image by Iwona Olczyk from Pixabay
"hello".toUpperCase() // "HELLO

Или все в нижнем регистре, например:

"Hello".toLowerCase() // "hello

Эти методы обычно используются для преобразования двух строк в верхний / нижний регистр, чтобы выполнить их сравнение без учета регистра.

В зависимости от сравниваемых строк вам может потребоваться больший контроль над сравнением. Вместо этого рассмотрите возможность использования localeCompare.

Удаление пробелов

Следующие методы удаляют все пробелы, табуляции, неразрывные пробелы и символы окончания строки (например, \n) из соответствующей части строки:

"  Trim Me  ".trim() // "Trim Me"
"  Trim Me  ".trimStart() // "Trim Me  "
"  Trim Me  ".trimEnd() // "  Trim Me"

"With Newline\n".trimEnd() // "With NewLine"

trimStart() и trimEnd() были введены в ES10 и теперь являются «предпочтительными» методами для использования в соответствии с этой спецификацией.

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

Для совместимости во всех современных браузерах используйте trimLeft() и trimRight():

"  Trim Me  ".trimLeft() // "Trim Me  "
"  Trim Me  ".trimRight() // "  Trim Me"

Поиск текста в строке

Найти позицию подстроки

Вы можете искать строку внутри другой строки в JavaScript с помощью indexOf().

Этот метод вернет позицию первого упоминания искомой подстроки в строке или -1, если подстрока не найдена:

"Superman".indexOf("man") // '5'
"Superman".indexOf("foo") // '-1'

Вы также можете использовать метод регулярных выражений search(), чтобы сделать то же самое:

"Superman".search("man") // '5'

Чтобы найти последнее вхождение поискового запроса, используйте lastIndexOf():

"Yabba Dabba Doo".indexOf("bb") // '2'
"Yabba Dabba Doo".lastIndexOf("bb") // '8'

Все эти методы вернут -1, если подстрока не найдена в целевой строке.

Начинается с / заканчивается на

Вы можете использовать методы indexOf(), указанные выше, чтобы проверить, начинается ли строка с поискового запроса или заканчивается им.

Однако ES6 добавил для этого специальные методы:

"Wonder Woman".startsWith("Wonder") // true
"Wonder Woman".endsWith("Woman") // true

Поддержка startsWith() и endsWith() браузерами:

  • Chrome: 41+
  • Edge: 12+
  • Firefox: 17+
  • Safari: 9+
  • Opera: 28+

Includes

Если вам не важна конкретная позиция подстроки и важно только, находится ли она вообще в целевой строке, вы можете использовать includes():

"War Games".includes("Game") // true

Поддержка includes() браузерами:

  • Chrome: 41+
  • Edge: 12+
  • Firefox: 40+
  • Safari: 9+
  • Opera: 28+

Регулярные выражения

Чтобы найти первое совпадение регулярного выражения, используйте .search().

// Найдите позицию первого строчного символа
"Ironman 3".search(/[a-z]/) // '1

Чтобы вернуть массив, содержащий все совпадения регулярного выражения, используйте match() с модификатором /g (global):

// Вернуть все прописные буквы
"This is England".match(/[A-Z]/g)

// Вывод: [ "T", "E" ]  

(Использование match() без модификатора /g вернет только первое совпадение и некоторые дополнительные свойства, такие как индекс результата в исходной строке и любые именованные группы захвата).

Если вам нужна дополнительная информация о каждом совпадении, включая их индекс в исходной строке, вы можете использовать matchAll.

Этот метод возвращает итератор, поэтому вы можете использовать цикл for … of для результатов. Вы должны использовать регулярное выражение с модификатором /g/ в matchAll():

// Находим все заглавные буквы в поле ввода
let input = "This is England"
let matches = input.matchAll(/[A-Z]/g)

// Превращаем результат в массив, используя
// spread-оператор
[...matches]

// Возвращает:
// [
//   ["T", index: 0, groups: undefined]
//   ["E", index: 8, groups: undefined]
// ]

Подробнее о регулярных выражениях.

Замена символов в строке

Вы можете использовать replace() для замены определенного текста в строке.

Первый аргумент replace() — это текст, который нужно найти и заменить, второй — текст, которым его нужно заменить.

Передача строки в качестве первого аргумента заменяет только первое совпадение:

"no no no!".replace("no", "yes")
// "yes no no!"

Если вы хотите заменить все совпадения, вы можете передать регулярное выражение с модификатором ‘greedy’ (/g) в качестве первого аргумента:

"no no no!".replace(/no/g, "yes")
// "yes yes yes!"

ES2021 добавил replaceAll(), чтобы упростить замену всех совпадений:

"no no no!".replaceAll("no", "yes")
// "yes yes yes!"

Поддержка replaceAll() браузерами:

  • Chrome: 85+
  • Edge: 85+
  • Firefox: 77+
  • Safari: 13.1+
  • Opera: 71+

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here