substring() — тот ещё подлец. Иногда.
Обещает простую нарезку строки. Выдирает символы между индексами. Выдаёт свежую копию. Мутаций не жди — строки в JS неизменяемы по умолчанию. Но блин, подвохов хватает. Разрабы налетают на них ежедневно, даже спустя десять лет кодинга на JS.
Вот классика из доков:
Метод substring() вырезает кусок строки между начальным и конечным индексами, возвращая новый кусок. Оригинал не меняется — строки в JavaScript неизменяемы.
Просто? Ага. Пока не скормишь ему кривые входы.
Почему substring() переставляет аргументы?
Представьте: “Hello, World!”.substring(5, 0). Ждёте мусор или ошибку? Не-а. Получаете “Hello”. Сам переставил аргументы. Какая забота, JS. А slice()? Обрезает до пустой строки. substr() (покойся с миром) ругается на отрицательные.
Снисходителен. Слишком. Новички в восторге — крашей нет. Профи в бешенстве — предсказуемость важнее. Вот бардак:
const str = "Hello, World!";
console.log(str.substring(5, 0)); // "Hello" — переставил!
console.log(str.slice(5, 0)); // ""
console.log(str.substring(-3)); // Вся строка — отрицательные = 0
А NaN? Тоже 0. Индекс за длиной? Обрезает до .length. JS как бы говорит: «Ладно, потом спасибо скажешь». Не скажешь.
Эта перестановка? Фишка только substring. Родом из времён Netscape, когда браузеры падали от любой ерунды. Исторический багаж — тянем 1990-е поблажки в 2024-й.
Всё в одном абзаце. Бам.
Индексы строк: с нуля, ясно-понятно. “JavaScript”: J=0, a=1, …, t=9. substring(start, end) берёт [start, end). Конец не включается, как в array.slice или for-циклах.
Визуализируем:
|J|a|v|a|S|c|r|i|p|t| 0 1 2 3 4 5 6 7 8 9 10
substring(0,4): ^----^ “Java”. Длина? 4-0=4. Очевидно.
Залезьте в отрицательные — бац, start=0. Нет нарезки с конца. Хотите? slice(-3). А substring делает вид, что вы имели в виду с начала. Хитрец.
substring() против slice() в JavaScript: вечный спор
Slice — крутой брат substring. Отрицательные понимает. Не переставляет. Строже, адекватнее. С конца? slice(-5) бьёт в точку. substring(-5)? Вся строка.
const lang = "JavaScript";
console.log(lang.slice(0,4)); // "Java"
console.log(lang.slice(-6)); // "Script" — магия с конца
console.log(lang.substring(-6)); // "JavaScript" — провал
Зачем substring? Legacy-код. Или эта снисходительность в парсерах. Но в современном JS? Slice рулит. substring — как innerHTML вместо textContent: работает, но зачем?
Производительность? Разница копеечная. Оба O(n). Бенчмарки показывают лёгкое преимущество slice в циклах, но кто режет миллиарды? Мой эксклюзив: substring живёт, потому что туториалы копипастят MDN 2005 года. Эхо-камера. Ставлю: к ES2030 string views добьют обоих. Копий больше не будет.
Реальные кейсы: хаки с URL. Path.substring(1) сбрасывает ведущий слеш. Парсинг email:
const email = "[email protected]";
const at = email.indexOf('@');
const user = email.substring(0, at); // "dev"
const domain = email.substring(at+1); // "example.com"
Полезно. Но indexOf на кривом? Валит апп. Валидируй сначала, герой.
Обрезка: title.substring(0,50) + ‘…’. Норм — до UTF-8 multibyte. “😂”.length=1? Ха, 4 байта. substring калечит эмодзи. Берите grapheme clusters или либы. Строки JS — code points, не символы. 2015 звонит — апгрейдитесь.
Классические капканы. Off-by-one: substring(0, len) берёт всё. len=string.length? Ок. Опечатался? Часть. Перестановка маскирует баги: substring(10,5) работает как (5,10). Скрытый косяк.
TypeScript? string.substring(start?: number, end?: number): string. Слабо. Добавьте оверлоады для надёжности.
function safeSub(str: string, start: number, end?: number): string {
const s = Math.max(0, start);
const e = end === undefined ? str.length : Math.max(0, end);
return str.substring(Math.min(s,e), Math.max(s,e));
}
Перебор? Ага. Но хаос substring просит этого.
substring() уже устарел?
Не совсем. Тянет regex-замены, DOM-текст. Но slice берёт 90% кейсов. Substr? Депрекачено в 2013 — не трогайте.
Парад ловушек: