- Item 1: Know Which JavaScript You Are Using
- Item 2: Understand JavaScript's Floating-Point Numbers
- Item 3: Beware of Implicit Coercions
- Item 4: Prefer Primitives to Object Wrappers
- Item 5: Avoid using == with Mixed Types
- Item 6: Learn the Limits of Semicolon Insertion
- Item 7: Think of Strings As Sequences of 16-Bit Code Units
Item 4: Prefer Primitives to Object Wrappers
In addition to objects, JavaScript has five types of primitive values: booleans, numbers, strings, null, and undefined. (Confusingly, the typeof operator reports the type of null as "object", but the ECMA-Script standard describes it as a distinct type.) At the same time, the standard library provides constructors for wrapping booleans, numbers, and strings as objects. You can create a String object that wraps a string value:
var s = newString
("hello"
);
In some ways, a String object behaves similarly to the string value it wraps. You can concatenate it with other values to create strings:
s + " world"
; // "hello world"
You can extract its indexed substrings:
s[4
]; // "o"
But unlike primitive strings, a String object is a true object:
typeof "hello"
; // "string"
typeof s; // "object"
This is an important difference, because it means that you can’t compare the contents of two distinct String objects using built-in operators:
var s1 = newString
("hello"
); var s2 = newString
("hello"
); s1 === s2; // false
Since each String object is a separate object, it is only ever equal to itself. The same is true for the nonstrict equality operator:
s1 == s2; // false
Since these wrappers don’t behave quite right, they don’t serve much of a purpose. The main justification for their existence is their utility methods. JavaScript makes these convenient to use with another implicit coercion: You can extract properties and call methods of a primitive value, and it acts as though you had wrapped the value with its corresponding object type. For example, the String prototype object has a toUpperCase method, which converts a string to uppercase. You can use this method on a primitive string value:
"hello"
.toUpperCase
(); // "HELLO"
A strange consequence of this implicit wrapping is that you can set properties on primitive values with essentially no effect:
"hello"
.someProperty =17
;"hello"
.someProperty; // undefined
Since the implicit wrapping produces a new String object each time it occurs, the update to the first wrapper object has no lasting effect. There’s really no point to setting properties on primitive values, but it’s worth being aware of this behavior. It turns out to be another instance of where JavaScript can hide type errors: If you set properties on what you expect to be an object, but use a primitive value by mistake, your program will simply silently ignore the update and continue. This can easily cause the error to go undetected and make it harder to diagnose.
Things to Remember
- Object wrappers for primitive types do not have the same behavior as their primitive values when compared for equality.
- Getting and setting properties on primitives implicitly creates object wrappers.