Аналог VB Mid на JavaScript: вырезаем подстроку правильно

РазделJavaScript и CSSУровеньНовичок Чтение7 минОпубликовано ТегиJavaScriptСтрокиVB6

В VB подстроку вырезают функцией Mid, а в JavaScript для этого есть сразу три метода с похожими именами и разным поведением. Разбираем, какой из них брать и как не ошибиться на единицу.

Главное. Для подстрок в JavaScript берите slice: он понимает отрицательные индексы и не переставляет аргументы. Нумерацию VB с единицы держите в одной обёртке Mid, а не размазывайте по коду.

Чем Mid в VB отличается от строк JavaScript

Функция Mid(строка, начало, длина) знакома каждому, кто писал на VB6, VBA или VBScript в классическом ASP. Позиции в ней считаются с единицы, третий аргумент задаёт количество символов, а если его опустить, функция вернёт всё до конца строки. Рядом живут Left и Right, которые берут символы с начала и с конца.

В JavaScript всё устроено иначе. Строка ведёт себя как массив символов, индексы начинаются с нуля, а вместо одной функции есть три метода объекта String: substring, substr и slice. Они появились в разное время, принимают похожие аргументы и при этом по-разному реагируют на отрицательные числа и на перепутанные границы. Именно здесь чаще всего рождаются ошибки «на единицу», когда код переносят из серверного VBScript в клиентский скрипт.

Три метода: substring, substr и slice

Метод substring(начало, конец) принимает две позиции, причём символ с индексом «конец» в результат не входит. Отрицательные значения он превращает в ноль, а если начало больше конца, молча меняет их местами. Это удобно, но иногда скрывает ошибку в расчётах.

Метод substr(начало, длина) ближе всех к Mid по смыслу: второй аргумент означает количество символов. Отрицательное начало отсчитывается от конца строки. Однако substr никогда не входил в основную часть стандарта ECMAScript, а старый Internet Explorer до восьмой версии неверно обрабатывал отрицательное начало.

Метод slice(начало, конец) похож на substring, но не переставляет аргументы и понимает отрицательные индексы с обеих сторон: slice(-3) вернёт последние три символа. Поведение предсказуемо во всех браузерах, поэтому именно его стоит брать по умолчанию.

  • «abcdef».substring(4, 1) вернёт «bcd», потому что аргументы поменялись местами;
  • «abcdef».slice(4, 1) вернёт пустую строку, перестановки нет;
  • «abcdef».substr(1, 3) вернёт «bcd», второй аргумент здесь длина;
  • «abcdef».slice(-2) вернёт «ef», а substring(-2) вернёт всю строку.

Функция Mid с поведением как в VB

Чтобы перенесённый код читался без пересчёта индексов в голове, удобно один раз написать обёртку с VB-сигнатурой. Внутри переводим позицию из единичной нумерации в нулевую и превращаем длину в правую границу для slice.

Обратите внимание на проверку аргументов. VB выдаёт ошибку времени выполнения, если начало меньше единицы, а в браузере такая ситуация обычно означает баг в вызывающем коде, поэтому честно бросаем исключение. Если строка равна null или undefined, возвращаем пустую строку: так ведёт себя Mid с пустым Variant.

vbstrings.jsJavaScript
// Аналог VB Mid(str, start[, length]), позиции с единицы
function Mid(str, start, length) {
  if (str == null) return '';
  str = String(str);
  if (start < 1) throw new Error('Mid: start должен быть >= 1');
  var from = start - 1; // переводим в нулевую нумерацию
  if (length === undefined) return str.slice(from);
  if (length < 0) throw new Error('Mid: length не может быть < 0');
  return str.slice(from, from + length);
}

// Mid('Visual Basic', 8)    -> 'Basic'
// Mid('Visual Basic', 1, 6) -> 'Visual'

Left, Right и поиск позиции

Left и Right получаются из slice в одну строку. Единственная тонкость в Right: при длине ноль выражение slice(-0) вернёт всю строку, потому что минус ноль равен нулю. Поэтому отрицательный индекс считаем явно через длину строки.

Функция InStr в VB тоже считает с единицы и возвращает ноль, если подстрока не найдена. Её аналог в JavaScript, indexOf, возвращает -1 и считает с нуля. Если оставить привычную проверку «If InStr(...) > 0» в виде «indexOf(...) > 0», совпадение в самом начале строки потеряется. Это самая частая ошибка при переносе логики.

vbstrings.jsJavaScript
function Left(str, length) {
  return String(str).slice(0, Math.max(0, length));
}

function Right(str, length) {
  str = String(str);
  // явный расчёт: slice(-0) вернул бы всю строку
  return str.slice(Math.max(0, str.length - length));
}

// InStr с нумерацией VB: 0, если не найдено
function InStr(str, search) {
  return String(str).indexOf(search) + 1;
}
При переносе условий меняйте «InStr(s, x) > 0» на «s.indexOf(x) !== -1», а не на «> 0».

Пример: разбор номера заказа в поле формы

Допустим, пользователь вводит код вида «ORD-2008-001542», а форма должна сразу показать год и порядковый номер. Разделитель ищем через indexOf и lastIndexOf, а дальше режем строку методом slice. Такой разбор не зависит от длины префикса и переживёт смену формата номера.

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

order.jsJavaScript
function parseOrder(code) {
  code = code.replace(/^\s+|\s+$/g, ''); // обрезаем пробелы
  if (!/^[A-Z]+-\d{4}-\d+$/.test(code)) return null;
  var first = code.indexOf('-');
  var last = code.lastIndexOf('-');
  return {
    prefix: code.slice(0, first),
    year: parseInt(code.slice(first + 1, last), 10),
    number: parseInt(code.slice(last + 1), 10)
  };
}

// parseOrder('ORD-2008-001542') -> {prefix:'ORD', year:2008, number:1542}

Подводные камни

Методы строк в JavaScript работают с кодовыми единицами UTF-16, а не с видимыми символами. Кириллица и латиница занимают одну единицу, а эмодзи и редкие иероглифы занимают две. Если обрезать строку посередине такой пары, на экране появится знак вопроса или пустой квадрат. Для полей с именами и адресами это редкость, но в комментариях пользователей встречается постоянно.

Вторая ловушка связана с типами. Значение поля формы всегда строка, а вот данные из JSON могут прийти числом. У числа нет метода slice, поэтому перед вызовом стоит привести значение через String(). В наших обёртках это уже сделано.

  • не используйте substr в новом коде: он помечен в стандарте как устаревший;
  • для обрезки текста под карточку проверяйте длину до обрезки, а многоточие добавляйте только при реальном сокращении;
  • parseInt всегда вызывайте с основанием 10, иначе строка «08» в старых браузерах разберётся как восьмеричная.

Как это делают сегодня

Сегодня substr официально считается наследием, и линтеры предупреждают о нём. Для последнего символа есть метод at(-1), для дополнения нулями padStart, а корректно делить строку на видимые символы с учётом эмодзи умеет Intl.Segmenter. На сервере в .NET аналогом остаётся String.Substring, а в C# появились диапазоны вида s[1..^1].

Вопросы и ответы

Чем substring отличается от slice?

Оба принимают начало и конец, но substring превращает отрицательные значения в ноль и меняет аргументы местами, если начало больше конца. Метод slice отсчитывает отрицательные индексы от конца строки и ничего не переставляет.

Можно ли продолжать использовать substr?

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

Почему Mid в JavaScript съедает первый символ?

Скорее всего, позиция из VB передана без поправки: в VB первый символ имеет номер 1, а в JavaScript индекс 0. Вычтите единицу из начальной позиции или используйте обёртку с VB-сигнатурой.

Читайте также