(V8 developer here.)
General note up front: strings are very common on the web, so JavaScript engines go to great lengths to implement many different optimizations for many different things you can do with strings, so as a result, the string handling system in a modern JS engine tends to be very complicated. With that out of the way, we can focus on "simple" strings here.
a single character would take 2 bytes
There's a bit more to it: V8 internally distinguishes between one-byte and two-byte strings. When all characters in a given string can be represented with just one byte, then (usually) that's what V8 will do.
The shallow size and retained size are both 16 bytes. Why?
Commenters have already posted a link to a description of the difference between "shallow" and "retained" size, so I won't get into that. For simple strings it is indeed always the same value.
All objects on the heap start with a shape descriptor, which takes one pointer size (usually 4 bytes these days, thanks to "pointer compression" on 64-bit platforms).
Strings additionally have two more fields of 4 bytes each in their object header: the string's hash (which is needed a lot, so to avoid having to recompute it all the time, it is cached there), and the string's length.
After that, they store the actual characters. The size of any heap object must be a multiple of the pointer size, i.e. a multiple of 4, so the size of the string is rounded up to that; the last few bytes may be unused.
So, in summary, the size of a simple string with n
ASCII characters is:
12 + 4 * Math.ceil(n/4)
(This may change over time, it'll be different if pointer compression was turned off at build time, it'll be different when there are two-byte characters in the string, it'll be different when the string is a "sliced" or "cons" string, it'll be different when the string is shared with Blink, and I'm probably forgetting some cases where it'll also be different.)
If you extend your experiment just a little, you'll see that:
""
takes 12 bytes
"1"
through "1234"
take 16 bytes
"12345"
through "12345678"
take 20 bytes
"123456789"
takes 24 bytes, and so on.