Primitive Data Types

Javascript only has and handful of primitive data types: string, number, boolean, null, and undefined. So what makes a data type primitive? For one thing, primitive data types have no members whatsoever; they are strictly a value that can be assigned to a variable or compared against. For another, any variable references to a primitive type are passed from function to function by value rather than by reference, as typical objects are. Have you ever done something like this?

var myString = "This is a primitive string.";
if (this.readyState == 4) {
    onloadedCallback();
}
if (found === true) {
    doSomethingAwesome();
}

Congratulations! You’ve assigned and/or compared primitive data type values. Another quirk of primitive types is that their values are immutable, meaning that once they’re set, there’s no way to change them, at least as far as their space in memory is concerned. If you do something like this:

var myString = "This is a test";
myString += " string";
console.log(myString); // Outputs: This is a test string

The variable value is stored and output properly, but as far as memory management is concerned, when you appended ” string” onto the end of the existing primitive string, you created an entirely new reference in memory to store it, and destroyed a reference to the old location in memory. The string value itself was not changed, but rather, the variable’s memory reference was. Conversely, when changing object values, any changes are made within the already-allocated memory space for that object instance.

The string, number, and boolean primitive types each have object constructors available, which wrap the primitive values and can be used to declare object instances of those types, but by their very nature, that’s not necessary. So why would you want to declare a primitive as an actual instance? Take the following example into consideration:

var myString = "This is a primitive string.";
myString.newProperty = true;
console.log(myString.newProperty);

What would you expect to see when the above code is run? An error, for trying to assign a member to a primitive data type? The value “true” written out to the console? In fact, you’ll find no error, and that a value of undefined is written to the console. Why is this? Javascript supports type coercion, also known as implicit typecasting. This is why you can do all kinds of fancy things like:

if (1) {}
if (myArray.length) {}
if (!myObject.somePropertyThatDoesntExist) {}

Each of these expressions will be coerced into a boolean type to satisfy the if statement one way or the other. What does this have to do with the primitive example statements up above? When we initialize myString to a primitive string, that’s what javascript sees it as, a primitive string data type, not an instance of a String object. However, javascript allows primitives to be implicitly converted to objects when they need to be, and reverted back to primitives when you’re done treating them as objects. For example:

"This is a primitive string.".replace(/primitive/, "replaced");

The compiler implicitly converts the primitive string into a String object just long enough to reference a replace() method on it, then reverts back to its primitive data type. Similarly, when you dynamically set the newProperty value onto your myString variable, the compiler implicitly casts this primitive string to a String object in order to let you add this property, and then immediately reverts back to a primitive data type, discarding the property that was just added. By the time we hit the console.log() statement, that newProperty no longer exists. The compiler implicitly converts it back to an object to dereference the newProperty for output in our console.log() statement, but at this point, newProperty is undefined, and that’s what you see in the console.

So, if you anticipate needing to store persistent state data or functions on a primitive data type, you should declare your variables as an actual object instance of those data types:

var myString = new String("This is now a String object"),
    myNumber = new Number(5),
    myBoolean = new Boolean(true);

Now you can add arbitrary members to each instance of these variables, just like any other object, but you’ll have to take the hit of an increased resource footprint in order to maintain that object state in memory.

The last 2 primitive data types, null and undefined, are similar in that they’re both falsey. Truthy and falsey are concepts that mean that a given reference or value will evaluate to either true or false depending on what it is and how it’s implicitly cast when you evaluate it as part of a condition. Truthy and falsey probably merit an entire post to themselves, but the quick and dirty version are that “” (empty string), the number value 0, null, and undefined will all evaluate as falsey when used on their own in a conditional check. Here’s what that means:

if ("") {}
if (0) {}
if (null) {}
if (undefined) {}

None of these conditional statements would pass their checks, because they would all be considered falsey. Similarly:

var myString = "",
    myNumber = 0,
    myObject = {
        prop1: null
    };

if (myString || myNumber || myObject.prop1 || myObject.prop2) {}

The above condition will also fail to execute its contents, because all of those variable checks return falsey values.

Primitive data types aren’t a concept that’s covered very often in the world of javascript. A lot of people don’t pay them any attention and just take them for granted as what they are, rather than contemplating what it might mean when they start treating them as objects. Hopefully, this has added an extra layer to that thought process, and you can start determining on a case-by-case basis whether a primitive or an actual object representation best suits whatever you’re working on in the future.

Leave a Reply

Your email address will not be published. Required fields are marked *