JavaScript for impatient programmers (ES2022 edition)
Please support this book: buy it or donate
(Ad, please don’t block.)

12 Values



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

  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 §13.4.2 “Strict equality (=== and !==)”.

12.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 }.

12.2 JavaScript’s type hierarchy

Figure 6: A partial hierarchy of JavaScript’s types. Missing are the classes for errors, the classes associated with primitive types, and more. The diagram hints at the fact that not all objects are instances of Object.

Fig. 6 shows JavaScript’s type hierarchy. What do we learn from that diagram?

12.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):

12.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.

12.4.1 Primitive values (short: primitives)

12.4.1.1 Primitives are immutable

You 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'/
);
12.4.1.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);

  Observing the difference between passing by value and passing by reference

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).

12.4.1.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.

12.4.2 Objects

Objects are covered in detail in §28 “Objects” 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:

12.4.2.1 Objects are mutable by default

By default, you 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);
12.4.2.2 Objects are passed by identity

Objects are passed by identity (my term): variables (including parameters) store the identities of objects.

The identity of an object is like a pointer (or a transparent reference) to the object’s actual data on the heap (think shared main memory of a JavaScript engine).

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).

  Details: passing by identity

“Passing by identity” means that the identity of an object (a transparent reference) is passed by value. This approach is also called “passing by sharing”.

12.4.2.3 Objects are compared by identity

Objects are compared by identity (my 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

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

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

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

How do they differ?

  Rule of thumb: typeof is for primitive values; instanceof is for objects

12.5.1 typeof

Table 2: The results of the typeof operator.
x typeof x
undefined 'undefined'
null 'object'
Boolean 'boolean'
Number 'number'
Bigint 'bigint'
String 'string'
Symbol 'symbol'
Function 'function'
All other objects 'object'

Tbl. 2 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'

  Exercises: Two exercises on typeof

12.5.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

  Exercise: instanceof

exercises/values/instanceof_exrc.mjs

12.6 Classes and constructor functions

JavaScript’s original factories for objects are constructor functions: ordinary functions that return “instances” of themselves if you 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.

12.6.1 Constructor functions associated with primitive types

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

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

12.6.1.1 Wrapping primitive values

The constructor functions related to primitive types are also called wrapper types because they provide the canonical way of converting primitive values to objects. In the process, primitive values are “wrapped” in objects.

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

const wrapped = Object(prim);
assert.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);

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

Wrapping rarely matters in practice, but it is used internally in the language specification, to give primitives properties.

12.7 Converting between types

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

12.7.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'

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

> typeof Object(123)
'object'

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

x Object(x)
undefined {}
null {}
boolean new Boolean(x)
number new Number(x)
bigint An instance of BigInt (new throws TypeError)
string new String(x)
symbol An instance of Symbol (new throws TypeError)
object x

12.7.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.

  Exercise: Converting values to primitives

exercises/values/conversion_exrc.mjs

  Quiz

See quiz app.