Откуда у примитивной строки в JavaScript появляются методы, например toUpperCase?
Что проверяют
Понимаете ли вы разницу между примитивом и объектом и знаете ли про временную обёртку. Вопрос из базовой теории, но по нему хорошо видно, читал ли человек, как устроен язык, или просто им пользовался.
Как отвечать
Строка это примитив, своих методов у неё нет. Методы лежат в String.prototype. Когда вы пишете "abc".toUpperCase(), движок по правилам языка оборачивает примитив во временный объект String, ищет свойство в его прототипе, вызывает метод и тут же выбрасывает обёртку, вернув примитивный результат.
Отсюда следует поведение, которое обычно и просят объяснить:
const s = "abc";
s.custom = 1; // обёртка создана, свойство записано и умерло вместе с ней
console.log(s.custom); // undefinedВ строгом режиме такая запись бросит ошибку. Читать свойства можно, сохранять нельзя: объект живёт ровно один доступ.
Так же устроены числа, логические значения, Symbol и BigInt. Исключение это null и undefined: обёрток у них нет, поэтому обращение к свойству падает с ошибкой.
Полезно добавить, что реального объекта движки обычно не создают, это оптимизация, а семантика в языке описана именно через обёртку. И что typeof "abc" даёт string, а typeof new String("abc") даёт object, то есть явная обёртка и примитив далеко не одно и то же.
Где спотыкаются
Делают вывод, что строка это объект, и удивляются, почему сравнение идёт по значению, а дописанное свойство пропадает. Примитив неизменяем, и все методы строк возвращают новую строку, а не правят исходную.
Пишут new String(...) руками или расширяют String.prototype. Первое даёт объект, который ведёт себя иначе в сравнениях и проверках, второе конфликтует с библиотеками и с будущими методами языка.
Забывают, что объект-обёртка всегда истинный. Условие с new Boolean(false) выполнится, и по этой же причине явные обёртки примитивов в обычном коде не используют.