DEV Community

Parth
Parth

Posted on

How does a string have methods if it's not an object?

This is the fifth post in a series I'm writing as I learn JavaScript.

By now this series has a running theme: I learn a rule, then I find something that seems to break it, and chasing down why teaches me more than the rule did.

Here's this one.

I'd just learned that strings are primitives. Not objects — primitives. A string is a single value, like a number or a boolean. Fine.

Then, on the same day, I wrote this:

let name = "parth";
console.log(name.toUpperCase()); // "PARTH"
console.log(name.length);        // 5
Enter fullscreen mode Exit fullscreen mode

And stopped.

.toUpperCase() is a method. .length is a property. Those belong to objects. But I was just told a string is a primitive, not an object. So how does a primitive have methods hanging off it?

Either strings are secretly objects, or something sneaky is happening. It turns out to be the second one, and it has a name: autoboxing.

First, strings really are primitives

Just to be sure I hadn't misunderstood:

console.log(typeof "parth"); // "string", not "object"
Enter fullscreen mode Exit fullscreen mode

A normal string is a primitive. It's not an object. That part was true.

So the methods aren't on the string. The string is borrowing them.

What actually happens: autoboxing

When you write name.toUpperCase(), JavaScript does something behind your back, in this order:

  1. It sees you trying to use a method on a primitive.
  2. It temporarily wraps the primitive in its matching object — here, a String object. (Numbers get a Number object, booleans a Boolean, and so on.)
  3. The object does have all the methods, so .toUpperCase() runs.
  4. You get your result back.
  5. The temporary wrapper object is thrown away. That wrap-use-discard move is autoboxing. The primitive stays a primitive. For a split second, JavaScript loans it an object body so it can use the methods, then takes the body back the instant it's done.

You never see the wrapper. It's created and destroyed inside that one expression.

So the string didn't have methods. JavaScript briefly lent it some.

The object version actually exists

The wrapper JavaScript creates temporarily is a real thing you can make yourself, on purpose, with new String():

let a = "parth";              // primitive string
let b = new String("parth");  // String OBJECT

console.log(typeof a); // "string"
console.log(typeof b); // "object"  ← different!
Enter fullscreen mode Exit fullscreen mode

b is the object version — the same kind of object autoboxing creates for a moment, except this one sticks around because you asked for it.

You can confirm it's a real object instance with instanceof:

console.log(b instanceof String); // true  — b was built from the String constructor
console.log(a instanceof String); // false — a is a primitive, not an instance of anything
Enter fullscreen mode Exit fullscreen mode

(An instance is just an object created from a constructor or class — new String("parth") creates an instance of String, the same way new Date() creates an instance of Date.)

You almost never want the object version

Here's the catch, and the reason I'm glad I learned this rather than just using new String():

let a = "parth";
let b = new String("parth");

console.log(a === b); // false!
Enter fullscreen mode Exit fullscreen mode

They look identical, but one's a primitive and one's an object, so strict equality says no. Object wrappers also behave strangely in conditionals and comparisons. The rule that falls out of this:

Always write string primitives — "parth", not new String("parth"). Let autoboxing hand you the methods when you need them. You get all the convenience of the object with none of the weirdness.

The object wrapper isn't something you're meant to create. It's machinery JavaScript uses internally, and it just happens to be reachable.

What I actually took away

For a while I thought JavaScript was being contradictory: strings are primitives, but also they have methods like objects do. Both can't be true.

They are both true — because of timing. The string is a primitive at rest. It only becomes object-shaped for the instant you call a method, then snaps back. Autoboxing is the bridge between "simple value" and "has useful methods," and it's invisible precisely because it cleans up after itself.

It's the same lesson as the rest of this series, really: the weird behavior isn't JavaScript being careless. It's a mechanism doing something deliberate, just quietly. Once you can see it, it stops being a contradiction and starts being a feature.


That's where this little series lands for now — five "wait, what?" moments from my first notebook of JavaScript, each one explained instead of memorized. If you've hit one I haven't covered, drop it in the comments. It might be the next post.

Top comments (1)

Collapse
 
gautamtalksdev profile image
Gautam K •

The "thrown away" part is easy to see for yourself. Try let s = "parth"; s.nickname = "p"; console.log(s.nickname); and you get undefined: the property landed on the temporary wrapper, which was discarded. Add "use strict" at the top and the same line throws a TypeError instead.

For the "strange in conditionals" point: if (new String("")) runs, because every object is truthy, even one wrapping an empty string. And one edge case: null and undefined have no wrapper at all, which is why null.length is a TypeError rather than undefined. Nice series, by the way; chasing the exception is how this stuff sticks.