跪拜 Guibai
← All articles
JavaScript · Backend

Why `str.length` Works on a Primitive: V8's Prototype Wrapper Trick

By sarasuki ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

For Western developers working in JavaScript, this explains a fundamental and often confusing behavior: why primitives have methods. Understanding V8's temporary wrapper objects and prototype chain resolution is essential for debugging property lookups, writing performant code, and avoiding subtle bugs when working with strings, numbers, and booleans.

Summary

JavaScript developers know that primitives like strings can't hold custom properties. Yet `str.length` works. The answer lies in V8's internal handling: every primitive literal is temporarily promoted to an object via its corresponding constructor — `new String('abc')` — then demoted back to a primitive before output. During that brief object life, the prototype chain is linked, so properties like `.length` are inherited from `String.prototype`.

This article walks through the three ways to create objects in JavaScript — object literals, native constructors, and custom constructors with `new` — and then dissects what `new` actually does: create an empty `this` object, execute the constructor's code, and link `this.__proto__` to the constructor's `prototype`. For primitives, V8 performs an extra cleanup step, deleting any custom properties added to the wrapper object, but the prototype link remains, enabling method access.

The practical takeaway is that JavaScript's "everything is an object" mantra is more than philosophy — it's a concrete engine behavior. Understanding the prototype chain and the temporary wrapper object explains why primitives have methods, why `const` objects can be mutated, and how to override prototype properties if you really want to.

Takeaways
Primitive types (String, Number, Boolean, etc.) are stored in stack memory as simple values and cannot hold custom properties or methods.
Reference types (Object, Array, Function, Date) are stored in heap memory and can have properties and methods freely added.
Object literals (`{}`) are syntactic sugar for `new Object()` in V8's eyes.
Calling a function with `new` creates a new empty object (`this`), executes the function body, and links `this.__proto__` to the function's `prototype`.
V8 temporarily wraps primitive literals in their corresponding constructor objects (e.g., `new String('abc')`), then strips them back to primitives, deleting any custom properties added.
Properties not found on an instance are looked up on its prototype chain — this is why `str.length` works even though the primitive itself has no `length` property.
You can override prototype properties (e.g., `String.prototype.length`) to change behavior for all instances.
`const` objects can have their properties modified because the reference (memory address) doesn't change; reassigning the variable to a new object is what's forbidden.
Conclusions

The temporary wrapper object is a clever optimization: it gives primitives access to methods without permanently converting them to objects, preserving memory efficiency.

The fact that V8 deletes custom properties from the wrapper before returning the primitive shows that JavaScript's type system is more rigid than it appears — primitives are truly immutable in terms of property storage.

Understanding the prototype chain is essential for debugging: when a property is missing, the lookup doesn't stop at the instance — it walks up the entire chain.

The dual nature of functions (normal call vs. `new` call) is a design choice that enables constructor reuse but also introduces potential confusion if `new` is accidentally omitted.

The article's approach of explaining V8 internals to clarify everyday behavior is a valuable pedagogical technique for intermediate developers.

Concepts & terms
Prototype Chain
In JavaScript, every object has a hidden `__proto__` property that points to its constructor's `prototype` object. When a property is accessed on an object, the engine first looks on the object itself, then walks up the chain of prototypes until it finds the property or reaches `null`.
Wrapper Object / Primitive Wrapper
A temporary object created by JavaScript's built-in constructors (like `String`, `Number`, `Boolean`) when a primitive value is used in a context that requires an object. The wrapper provides access to methods on the prototype, then is discarded.
`[[PrimitiveValue]]`
An internal slot on wrapper objects created by constructors like `String` that stores the original primitive value. When the object is coerced back to a primitive, this value is used.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗