You may not know it but, in JavaScript, whenever you interact with string, number or boolean primitives you enter a hidden world of object shadows and coercion. So dust off your Sherlock Holmes outfit and read on…
The basics
Objects are aggregations of properties. A property can reference an object or a primitive. Primitives are values, they have no properties.
In JavaScript there are 5 primitive types: undefined
, null
, boolean
, string
and number
. Everything else is an object. The primitive types boolean, string and number can be wrapped by their object counterparts. These objects are instances of the Boolean
, String
and Number
constructors respectively.
typeof true; //"boolean" typeof Boolean(true); //"boolean" typeof new Boolean(true); //"object" typeof (new Boolean(true)).valueOf(); //"boolean" typeof "abc"; //"string" typeof String("abc"); //"string" typeof new String("abc"); //"object" typeof (new String("abc")).valueOf(); //"string" typeof 123; //"number" typeof Number(123); //"number" typeof new Number(123); //"object" typeof (new Number(123)).valueOf(); //"number"
.
If primitives have no properties, why does "abc".length
return a value?
Because JavaScript will readily coerce between primitives and objects. In this case the string value is coerced to a string object in order to access the property length. The string object is only used for a fraction of second after which it is sacrificed to the Gods of garbage collection – but in the spirit of the TV discovery shows, we will trap the elusive creature and preserve it for further analysis…
1
2
3
4
5
6
7
8
9
10
11
|
String.prototype.returnMe=
function
() {
return
this
;
}
var
a =
"abc"
;
var
b = a.returnMe();
a;
//"abc"
typeof
a;
//"string" (still a primitive)
b;
//"abc"
typeof
b;
//"object"
|
…and as with many well meaning scientific investigations we have now interfered with the natural progression of things and prevented the object from being garbage collected so long as b
is around. Heisenberg is alive and well
(Note that in strict mode, the elusive creature gets away – thanks @DmitrySoshnikov)
Here’s a more environmentally responsible example that verifies the object type without interfering with garbage collection:
1
2
3
4
5
|
Number.prototype.toString =
function
() {
return
typeof
this
;
}
(123).toString();
//"object"
|
.
By this means primitives have access to all the properties (including methods) defined by their respective object constructors.
And these objects can also be coerced to values?
Yes. Mostly. Objects of this type are merely wrappers, their value is the primitive they wrap and they will generally coerce down to this value as required. For full details see this article.
1
2
3
4
5
6
7
8
9
10
11
12
|
//object coerced to primitive
var
Twelve =
new
Number(12);
var
fifteen = Twelve + 3;
fifteen;
//15
typeof
fifteen;
//"number" (primitive)
typeof
Twelve;
//"object"; (still object)
//another object coerced to primitive
new
String(
"hippo"
) +
"potamus"
;
//"hippopotamus"
//object not coerced (because 'typeof' operator can work with objects)
typeof
new
String(
"hippo"
) +
"potamus"
;
//"objectpotamus"
|
Sadly boolean objects do not coerce so easily. And, to add insult to injury, a boolean object evaluates to true unless its value is null or undefined. Try this:
1
2
3
|
if
(
new
Boolean(
false
)) {
alert(
"true???"
);
}
|
Usually you have to explicitly ask boolean objects for their value. The following might be useful for determining whether the value is “truthy” of “falsey”….
1
2
|
var
a =
""
;
new
Boolean(a).valueOf();
//false
|
…but in practice its easier to do this…
1
2
|
var
a = Boolean(
""
);
a;
//false
|
..or even this…
1
2
|
var
a =
""
;
!!a;
//false
|
.
Does coercion allow me to assign values to primitives?
No.
1
2
3
4
|
var
primitive =
"september"
;
primitive.vowels = 3;
primitive.vowels;
//undefined;
|
If JavaScript detects an attempt to assign a property to a primitive it will indeed coerce the primitive to an object. But, as with the previous examples, this new object has no references and will immediately become fodder for garbage collection.
Here’s a pseudo-code representation of the same example to illustrate what really happens
1
2
3
4
5
6
7
8
|
var
primitive =
"september"
;
primitive.vowels = 3;
//new object created to set property
(
new
String(
"september"
)).vowels = 3;
primitive.vowels;
//another new object created to retrieve property
(
new
String(
"september"
)).vowels;
//undefined
|
So as you can see, its not only useless but pretty wasteful.
Wrap up
It turns out that the ability to assign properties is just about the only advantage of objects over their primitive counterparts, but in practice even this is a dubious quality. Strings, booleans and numbers have specific and well defined purposes and redefining them as state holders is likely just going to confuse people. Moreover, since primitives are immutable you cannot modify them by tweaking the properties of the object wrapper:
1
2
3
4
|
var
me =
new
String(
"Angus"
);
me.length = 2;
//(error in strict mode)
me.length;
//5 (not 2 - thanks @joseanpg)
me.valueOf();
"Angus"
|
Nevertheless, I do think that a good understanding of primitives and what happens under the covers when you interact with them is an important step towards a deeper knowledge of the language. I hope this helped.