The Secret Life of JavaScript Primitives

阅读更多

 

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: undefinednullbooleanstring 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 BooleanString 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.

你可能感兴趣的:(The Secret Life of JavaScript Primitives)