DEV Community

Cover image for Understanding Primitives in JavaScript
Michael Mathews
Michael Mathews

Posted on

Understanding Primitives in JavaScript

In JavaScript, every value is either a primitive or an object.

  • Primitives are immutable, passed by value, and cannot have properties.
  • Objects are everything else, including arrays, functions, dates, and regular objects. These are mutable and passed by reference. Objects can have properties and methods.

Meet the primitives

The latest ECMAScript standard (ECMAScript 2024 / ES15) defines seven primitive types.

Undefined

A variable that has been declared but not assigned a value has the undefined value:

let x;
console.log(x); // undefined
Enter fullscreen mode Exit fullscreen mode

Null

The null literal signifies the intentional absence of any value:

let reason = null;   // there is no reason
console.log(reason); // null
Enter fullscreen mode Exit fullscreen mode

Boolean

Two Boolean literals represent logical truth values:

true
false
Enter fullscreen mode Exit fullscreen mode

Number

All numeric values (including integers, floats, NaN, and Infinity) are of the Number type:

let a = 42;       // integer
let b = 3.14;     // float
let c = NaN;      // yes, the "not a number" value is a number
let d = Infinity; // and beyond!
Enter fullscreen mode Exit fullscreen mode

String

A sequence of characters, such as some words or text:

let greeting = "Hello";
let name = 'World';
let message = `Hi, ${name}!`;
Enter fullscreen mode Exit fullscreen mode

BigInt

Represents integers of arbitrary length, created by appending n to the end of an integer literal:

let big = 1234567890123456789012345678901234567890n;
Enter fullscreen mode Exit fullscreen mode

Symbol

A unique (within the runtime environment) value that can be used as object property keys:

let sym = Symbol("id");
Enter fullscreen mode Exit fullscreen mode

What's your type?

The typeof operator returns a string indicating the operand type. There are seven possible return values for primitive types.

console.log(typeof undefined);         // undefined
console.log(typeof true);              // boolean
console.log(typeof 42);                // number
console.log(typeof 9007199254740991n); // bigint
console.log(typeof "hello");           // string
console.log(typeof Symbol());          // symbol

// and then there's null... read more below
console.log(typeof null);              // object?
Enter fullscreen mode Exit fullscreen mode

null is a special case

I want to call attention to a common point of confusion in JavaScript: the null value. However, it is not an object and has no object wrapper. However, it blatantly lies and claims to be an object when you use the typeof operator on it.

let nope = null;
console.log(typeof nope);     // object (no, it's not)

console.log(nope.toString()); // TypeError: Cannot read properties of null (reading 'toString')
nope.x = 1;                   // TypeError: Cannot set properties of null (setting 'x')
Enter fullscreen mode Exit fullscreen mode

Brendan Eich, the Creator of JavaScript, admitted that typeof null === "object" is a bug, but fixing it would break a lot of code on the web. So, we must always work around this bug.

// randomly assign either an object or null
const value = Math.random() < 0.5 ? {} : null;

// don't forget to check for null
if (typeof value === "object" && value !== null) {
  console.log("The value is a (real) object.");
}
Enter fullscreen mode Exit fullscreen mode

Dressing primitives up like objects

Primitive values are not objects. They are immutable and cannot have methods or properties. You might be surprised by this statement, after all, it's pretty standard to see code that appears to call methods on and get/set properties on primitive values.

console.log("hello".length);         // 5
console.log("hello".toUpperCase());  // HELLO
console.log(10.0.toFixed(2));        // 10.00
console.log(true.toString());        // true
Enter fullscreen mode Exit fullscreen mode

What's happening here? The primitive types in the preceding example each have a corresponding object wrapper they can climb into whenever they need to do object-like things. For example, you can wrap a string primitive in a String object by calling new String("hello"). Think of the resulting object as a box with the primitive inside. You can always get the primitive value back by calling valueOf on the object.

JavaScript performs automatic "boxing" for you by temporarily wrapping any String, Number, or Boolean values in an object whenever you try to access properties or methods. For example, "hello".toUpperCase() works because JavaScript creates a String object behind the scenes before calling its toUpperCase method.

Wrapping primitives

JavaScript doesn't always wrap primitives in Objects for you. For example, undefined and null can't be wrapped.

undefined.toString(); // TypeError: Cannot read properties of undefined (reading 'toString')
null.toString();      // TypeError: Cannot read properties of null (reading 'toString')
Enter fullscreen mode Exit fullscreen mode

If you try to force them into object boxes explicitly, their values disappear.

Object(null);      // returns an empty object {}
Object(undefined); // returns an empty object {}
Enter fullscreen mode Exit fullscreen mode

Strings, Numbers, and Booleans have dedicated object wrappers you can call yourself if you wish. In practice, there are very few reasons for doing this, but you might find some APIs that require objects when all you have are primitives, for example, when creating an entry in a WeakMap that requires an object for the key.

const s = new String("hello");

console.log(typeof s);            // "object"
console.log(s instanceof String); // true
console.log(s.valueOf());         // "hello" (gets the primitive value)
console.log(s);                   // calls valueOf for you 
Enter fullscreen mode Exit fullscreen mode

Similarly, you can explicitly wrap numbers and booleans.

const n = new Number(42);
const b = new Boolean(true);
Enter fullscreen mode Exit fullscreen mode

The last two primitives don't have their own dedicated object wrapper but can be objectified using the Object() function if you need to.

const big = Object(123n);
console.log(typeof big);            // "object"
console.log(big instanceof BigInt); // true
console.log(big.valueOf());         // "123n" (gets the primitive value)
console.log(big);                   // [BigInt: 123n] (calls toString)

const sym = Object(Symbol("key"));
console.log(sym);                   // [Symbol: Symbol(key)]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)