Primitive wrapper objects

Are primitives objects?

Objects can have properties and methods to perform some action. Primitives are not objects: they have no properties or methods. However, we can do things like this:


console.log( (123456).toExponential() ); // logs: "1.23456e+5"

let greeting = "hello";
console.log( greeting.toUpperCase() ); // logs: HELLO 

The method toUpperCase() is performed on the variable with primitive string value "hello". So apparently JavaScript provides a way to perform some standard built-in methods on primitives. What's going on here?

Autoboxing

JavaScript provides standard objects to "wrap around" the primitive values. All primitive values, except null and undefined, can be boxed in these so-called primitive wrapper objects. These objects provide a number of standard methods that can be performed on the primitives. Whenever trying to access a property or method on a primitive value, JavaScript will automatically wrap the primitive with an equivalent wrapper object and resolve the property or method reference.


let myString = 'Hello World!'; // a variable gets a string primitive assigned.
let myNewString = myString.substring(6); // String object's method "substring(6)" is to be invoked on the primitive string.
console.log(myNewString);  // logs: "World!"

To understand what is happening "under the hood" you can think of something like this:

	
let myString = 'Hello World!';
let myNewString = myString.substring(6);
//=> let temp = new String(myString); // JS creates a wrapper object "temp". The "new" operator and the String() constructor function create an instance of a wrapper object.
//=> myNewString = temp.substring(6); // perform the String method on wrapper object "temp" and assign the resulting value to myNewString.
console.log(myNewString);  // logs: "World!"

BTW: In a later chapter more about operator new and object constructors.

All primitives, i.e. the values itself, are immutable: they cannot be altered. However, a variable can be replaced by assigning a new value.


let myString = 'Hello World!'; // a variable gets a string primitive assigned.
myString.toUpperCase(); // a String method doesn't mutate the primitive value itself
console.log(myString); // logs: "Hello World!"
myString = myString.toUpperCase(); // the variable is re-assigned a newly created value.
console.log(myString);  // logs: "HELLO WORLD!"

Think of something like this that happened under the hood:


let myString = 'Hello World!';
myString.toUpperCase();
//=> let temp1 = new String(myString); // JS creates a wrapper object "temp1".
//=> temp1.toUpperCase(); // This creates a new string value, but it is not assigned to anything.
console.log(myString); // logs: "Hello World!" // Nothing happened to myString yet.
myString = myString.toUpperCase();
//=> let temp2 = new String(myString); // JS creates a NEW wrapper object again:
//=> myString = temp2.toUpperCase(); // the variable is re-assigned a newly created value.
console.log(myString);  // logs: "HELLO WORLD!"

You also cannot create custom properties or custom methods for primitive values.


const twoPi = Math.PI * 2; // a constant gets a primitive number assigned.
twoPi.name = '2π'; // define an object property for primitive twoPi
twoPi.print = function() { console.log(`This constant represents an approximation of ${this.name}: ${this}`); }  // define a object method for primitive twoPi

twoPi.print(); // TypeError: twoPi.print is not a function

The last statement in the above example throws an error because this statement creates a new wrapper object with no custom objects or properties. The print method was defined for a previous temporary wrapper object in the second-to-last statement.

Instances of wrapper objects

So, wrapper objects are generally used under the hood by JavaScript to box primitive values in temporary objects. However, instances of wrapper objects can also be defined explicitly, by using the new operator and a wrapper object constructor function. To get the primitive value of a explicitly defined wrapper object you can use the method valueOf(). More about this in the chapter "Object to primitive conversion".


const twoPi = Math.PI * 2;
const twoPI = new Number(twoPi);
/*
twoPi is a number primitive, twoPI is a number object!!
*/
console.log(twoPi); // logs: 6.283185307179586
console.log(twoPI); // logs: Number { 6.283185307179586 }
console.log(twoPI.valueOf()); // logs: 6.283185307179586		

// adding properties and methods to object twoPI:
twoPI.name = '2\u{3A0}'; // \u{3A0} is the code point escape sequence for π
twoPI.print = function() { console.log(`This constant represents an approximation of ${this.name}: ${this}.`); }

twoPI.print(); // logs: "This constant represents an approximation of 2π: 6.283185307179586."

console.log(typeof twoPi); // logs: number
console.log(typeof twoPI); // logs: object	

The next example does something similar to creating a twoPI object by using the Number() constructor (example above).


const twoPI = {
	name: '2\u{3A0}',
	valueOf: function() { return Math.PI * 2; },
	print: function() { console.log(`This constant represents an approximation of ${this.name}: ${this.valueOf()}.`); }
}

console.log(twoPI); // logs: object twoPI
console.log(twoPI.valueOf()); // logs: 6.283185307179586	
twoPI.print(); // logs: "This constant represents an approximation of 2π: 6.283185307179586."
console.log(typeof twoPI); // logs: object.		

Thus, Number() is a wrapper object constructor function and with new it creates a new object: an instance of a wrapper object. But keep in mind that assigning a value to a variable or constant is best done by using primitives, most easy by using literals. It is not good practice to use instances of wrapper objects (using new) in place of primitives. A wrapper object constructor can also be called as a function (without using new), as will be explained in the next section.

Wrapper object constructor as a regular function

The constructor functions are more useful when they are called as a regular function. In that case it returns the argument converted to the corresponding primitive: for instance Number(argument) converts argument to a number, if it is not a number already. This works for all wrapper object constructor functions:


let myNumber = Number(10); // equivalent to 'let myNumber = 10;'
console.log(myNumber); // logs: 10

let stringToNumber = Number("10");
console.log(stringToNumber); // logs: 10

let myString = String("Hello"); // equivalent to 'let myString = "Hello";'
console.log(myString); // logs: 'Hello'

let numberToString = String(313);
console.log(numberToString); // logs: "313" // a string

let numberToBoolean = Boolean(NaN);
console.log(numberToBoolean); // logs: false

Calling a wrapper object as a function to explicitly convert a value to the corresponding data type is explained in more detail in the chapter "Explicit type conversion".