HomepageExploring JavaScript (ES2025 Edition)
You can support this book: buy it or donate
(Ad, please don’t block.)

14 Values

In this chapter, we’ll examine what kinds of values JavaScript has.

Icon “reading”Supporting tool: ===

In this chapter, we’ll occasionally use the strict equality operator. a === b evaluates to true if a and b are equal. What exactly that means is explained in “Strict equality (=== and !==)” (§15.5.1).

14.1 What’s a type?

For this chapter, I consider types to be sets of values – for example, the type boolean is the set { false, true }.

14.2 JavaScript’s type hierarchy

Figure 14.1: A partial hierarchy of JavaScript’s types. Missing are the classes for errors, the classes associated with primitive types, and more.

Figure 14.1 shows JavaScript’s type hierarchy:

14.3 The types of the language specification

The ECMAScript specification only knows a total of eight types. The names of those types are (I’m using TypeScript’s names, not the spec’s names):

14.4 Primitive values vs. objects

The specification makes an important distinction between values:

In contrast to Java (that inspired JavaScript here), primitive values are not second-class citizens. The difference between them and objects is more subtle. In a nutshell:

Other than that, primitive values and objects are quite similar: they both have properties (key-value entries) and can be used in the same locations.

Next, we’ll look at primitive values and objects in more depth.

14.5 Primitive values (short: primitives)

14.5.1 Primitives are immutable

We can’t change, add, or remove properties of primitives:

const str = 'abc';
assert.equal(str.length, 3);
assert.throws(
  () => { str.length = 1 },
  /^TypeError: Cannot assign to read only property 'length'/
);

14.5.2 Primitives are passed by value

Primitives are passed by value: variables (including parameters) store the contents of the primitives. When assigning a primitive value to a variable or passing it as an argument to a function, its content is copied.

const x = 123;
const y = x;
// `y` is the same as any other number 123
assert.equal(y, 123);

Icon “details”Observing the difference between passing by value and passing by identity

Due to primitive values being immutable and compared by value (see next subsection), there is no way to observe the difference between passing by value and passing by identity (as used for objects in JavaScript).

14.5.3 Primitives are compared by value

Primitives are compared by value: when comparing two primitive values, we compare their contents.

assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);

To see what’s so special about this way of comparing, read on and find out how objects are compared.

14.6 Objects

Objects are covered in detail in “Objects” (§30) and the following chapter. Here, we mainly focus on how they differ from primitive values.

Let’s first explore two common ways of creating objects:

14.6.1 Objects are mutable by default

By default, we can freely change, add, and remove the properties of objects:

const obj = {};

obj.count = 2; // add a property
assert.equal(obj.count, 2);

obj.count = 3; // change a property
assert.equal(obj.count, 3);

14.6.2 Objects are passed by identity

Objects are passed by identity (new term): Variables (including parameters) store the identities of objects. The identity of an object is a transparent reference (think pointer) to the object’s actual data on the heap (the shared main memory of a JavaScript process). When assigning an object to a variable or passing it as an argument to a function, its identity is copied.

Each object literal creates a fresh object on the heap and returns its identity:

const a = {}; // fresh empty object
// Pass the identity in `a` to `b`:
const b = a;

// Now `a` and `b` point to the same object
// (they “share” that object):
assert.equal(a === b, true);

// Changing `a` also changes `b`:
a.name = 'Tessa';
assert.equal(b.name, 'Tessa');

JavaScript uses garbage collection to automatically manage memory:

let obj = { prop: 'value' };
obj = {};

Now the old value { prop: 'value' } of obj is garbage (not used anymore). JavaScript will automatically garbage-collect it (remove it from memory), at some point in time (possibly never if there is enough free memory).

14.6.3 Objects are compared by identity

Objects are compared by identity (new term): two variables are only equal if they contain the same object identity. They are not equal if they refer to different objects with the same content.

const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content

14.6.4 Passing by reference vs. passing by identity (advanced)

If a parameter is passed by reference, it points to a variable and assigning to the parameter changes the variable – e.g., in the following C++ code, the parameters x and y are passed by reference. The invocation in line A affects the variables a and b of the invoker.

void swap_ints(int &x, int &y) {
  int temp = x;
  x = y;
  y = temp;
}
int main() {
  int a = 1;
  int b = 2;

  swap_ints(a, b); // (A)
  // Now `a` is 2 and `b` is 1

  return 0;
}

If a parameter is passed by identity (which is a new, new term), the identity of an object (a transparent reference) is passed by value. Assigning to the parameter only has a local effect. This approach is also called passing by sharing.

Acknowledgement: The term passing by identity was suggested by Allen Wirfs-Brock in 2019.

14.6.5 Identity in the ECMAScript specification (advanced)

The ECMAScript specification uses the term identity as follows (source):

At the language level:

14.7 The operators typeof and instanceof: what’s the type of a value?

The two operators typeof and instanceof let us determine what type a given value x has:

if (typeof x === 'string') ···
if (x instanceof Array) ···

How do they differ?

Icon “tip”Rule of thumb: typeof is for primitive values; instanceof is for objects

14.7.1 typeof

xtypeof x
undefined'undefined'
null'object'
Boolean'boolean'
Number'number'
Bigint'bigint'
String'string'
Symbol'symbol'
Function'function'
All other objects'object'

Table 14.1: The results of the typeof operator.

Table 14.1 lists all results of typeof. They roughly correspond to the 7 types of the language specification. Alas, there are two differences, and they are language quirks:

These are a few examples of using typeof:

> typeof undefined
'undefined'
> typeof 123n
'bigint'
> typeof 'abc'
'string'
> typeof {}
'object'

Icon “exercise”Exercises: Two exercises on typeof

14.7.2 instanceof

This operator answers the question: has a value x been created by a class C?

x instanceof C

For example:

> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
true

Primitive values are not instances of anything:

> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false

For more information on this operator, see “The instanceof operator in detail (advanced)” (§31.7.3).

Icon “exercise”Exercise: instanceof

exercises/values/instanceof_exrc.mjs

14.8 Classes and constructor functions

JavaScript’s original factories for objects are constructor functions: ordinary functions that return “instances” of themselves if we invoke them via the new operator.

ES6 introduced classes, which are mainly better syntax for constructor functions.

In this book, I’m using the terms constructor function and class interchangeably.

Classes can be seen as partitioning the single type object of the specification into subtypes – they give us more types than the limited 7 ones of the specification. Each class is the type of the objects that were created by it.

14.8.1 Constructor functions associated with primitive types

Each primitive type (except for the types undefined and null) has an associated constructor function (think class):

Each of these functions plays several roles – for example, Number:

14.8.1.1 Wrapper classes for primitive values (advanced)

If we new-invoke a constructor function associated with a primitive type, it returns a so-called wrapper object. This is the standard way of converting a primitive value to an object – by “wrapping” it.

The primitive value is not an instance of the wrapper class:

const prim = true;
assert.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);

The wrapper object is not a primitive value:

const wrapper = Object(prim);
assert.equal(typeof wrapper, 'object'); // not 'boolean'
assert.equal(wrapper instanceof Boolean, true);

We can unwrap the wrapper object to get back the primitive value:

assert.equal(wrapper.valueOf(), prim); // unwrap

14.9 Converting between types

There are two ways in which values are converted to other types in JavaScript:

14.9.1 Explicit conversion between types

The function associated with a primitive type explicitly converts values to that type:

> Boolean(0)
false
> Number('123')
123
> String(123)
'123'

We can also use Object() to convert values to objects:

> typeof Object(123)
'object'

The following table describes in more detail how this conversion works:

xObject(x)
undefined{}
null{}
booleannew Boolean(x)
numbernew Number(x)
bigintAn instance of BigInt (new throws TypeError)
stringnew String(x)
symbolAn instance of Symbol (new throws TypeError)
objectx

14.9.2 Coercion (automatic conversion between types)

For many operations, JavaScript automatically converts the operands/parameters if their types don’t fit. This kind of automatic conversion is called coercion.

For example, the multiplication operator coerces its operands to numbers:

> '7' * '3'
21

Many built-in functions coerce, too. For example, Number.parseInt() coerces its parameter to a string before parsing it. That explains the following result:

> Number.parseInt(123.45)
123

The number 123.45 is converted to the string '123.45' before it is parsed. Parsing stops before the first non-digit character, which is why the result is 123.

Icon “exercise”Exercise: Converting values to primitives

exercises/values/conversion_exrc.mjs