String.prototype.toString()

Worth a page not for what it does — which is nothing — but for what it reveals: strings come in two forms, and only one of them behaves sensibly.

String methodES1 (1997)
Common call
String(value)
Returns
the primitive string
Replaces
new String(x).toString(), which should not exist
Watch out
new String("a") is an OBJECT — typeof "object", and never === a string
string.toString()
→ string

Examples

1. A no-op on a primitive
'abc'.toString()
Returns
'abc'
2. valueOf is identical
'abc'.valueOf()
Returns
'abc'
3. The wrapper is an object
typeof new String('a')
Returns
'object'
4. And never equal
new String('a') === 'a'
Returns
false
5. Unwrapped it is
new String('a').valueOf() === 'a'
Returns
true
6. String() does not wrap
typeof String('a')
Returns
'string'

Pitfalls

1. new String() creates an object, not a string
typeof reports "object", strict equality against any primitive is false, and a JSON round trip or a Map key behaves differently. It looks like a string in the console and fails every identity check, which makes it one of the more baffling bugs to track down.
Not equal
new String('a') === 'a'
false
No new
String('a') === 'a'
true
2. A String object is always truthy
Even wrapping an empty string. Code that tests a value for emptiness gets the wrong answer, because the object itself is truthy regardless of what is inside it.
Truthy when empty
Boolean(new String(''))
true
Primitive is falsy
Boolean('')
false
3. String(x) and x.toString() differ on null
String() handles null and undefined, producing "null" and "undefined". Calling the method on them throws, because there is no object to call it on. This is the practical reason to prefer the String function.
Throws
null.toString()
TypeError: Cannot read properties of null (reading 'toString')
String() copes
String(null)
'null'
4. Autoboxing makes the wrapper mostly invisible
Calling any method on a primitive temporarily wraps it, so "a".toUpperCase() works without you ever creating an object. That is why explicitly constructing one is both unnecessary and confusing — the language already does it, and then throws the wrapper away.
Property is lost
const s = 'a';
s.custom = 1;
s.custom
undefined
Use a real object
const s = {value: "a", custom: 1};
1

When to use

Use it
  • Rarely — the conversion happens implicitly almost everywhere
  • Converting a value where you know it is not null or undefined
Reach for something else
  • Converting anything that might be null → String(x)
  • Building a string from parts → a template literal
  • Creating strings → never use new String
  • You want a number as text → String(n) or `${n}`

Notes

Complexity
O(1) — it returns the existing value
Return
The primitive string; nothing is copied
CPython impl
V8: Builtins-string-tostring
Memory
No allocation for a primitive receiver
Thread-safe
Single-threaded

FAQ

On a string, none — both return the primitive value. They differ on other types, where valueOf returns a primitive of the natural type and toString returns text. For strings the natural type IS text, so the two collapse into the same thing.

'a'.toString() === 'a'.valueOf();   // true

History

ES1
toString and valueOf present from the first version, along with the String constructor and its wrapper objects.