Chapter 5

8 Language Types and Their Internal Representations

Here is a fact most developers don't know: the typeof null === 'object' bug has existed since 1995 and can never be fixed, because fixing it would break billions of web pages worldwide.

๐Ÿ”น Level 1 ยท What You Need to Know

The 8 Language Types at a Glance

JavaScript has exactly 8 language types. This is not convention โ€” it is mandated by Section 6 of the ECMAScript specification.

Type typeof return Literal example Can be null?
Undefined "undefined" undefined No
Null "object" โš ๏ธ null โ€”
Boolean "boolean" true, false No
String "string" "hello", '', `template` No
Number "number" 42, 3.14, NaN, Infinity No
BigInt "bigint" 42n, 9007199254740993n No
Symbol "symbol" Symbol('desc') No
Object "object" {}, [], new Date() Yes

Note: Functions are objects, but typeof function(){} returns "function" โ€” a special exception in the spec.

Complete typeof Return Value Table

typeof undefined        // "undefined"
typeof null             // "object"  โ† historical bug, unfixable
typeof true             // "boolean"
typeof "hello"          // "string"
typeof 42               // "number"
typeof 42n              // "bigint"
typeof Symbol()         // "symbol"
typeof {}               // "object"
typeof []               // "object"  โ† arrays are objects
typeof function(){}     // "function" โ† spec special case
typeof class Foo {}     // "function" โ† classes are functions too
typeof undeclaredVar    // "undefined" โ† no ReferenceError!

Implicit Type Creation Rules

The following operations implicitly create values of specific types:

// Creating undefined
let x;                  // x is undefined
function f() {}         // f() returns undefined
obj.nonexist            // accessing non-existent property returns undefined
void 0                  // void expression always returns undefined

// Creating null (must be explicit)
let y = null;

// Creating boolean
!!value                 // double negation
value ? a : b           // ternary operator condition
if (value)             // if statement condition
arr.includes(x)         // returns boolean

// Creating number (implicit conversions)
+"42"                   // 42 (unary plus)
"6" * "7"              // 42 (multiplication)
parseInt("42px")        // 42 (extracts leading number)
Number(true)            // 1
Number(false)           // 0
Number(null)            // 0  โ† surprising
Number(undefined)       // NaN โ† surprising
Number("")              // 0  โ† surprising

// Creating string (implicit conversions)
"" + 42                 // "42" (+ operator prefers string)
`${someValue}`          // template literal calls toString()
[1,2,3].join(",")      // "1,2,3"

5 Common Beginner Mistakes

Mistake 1: Using === undefined when you mean null or undefined

// โœ… Correct: checks both null and undefined (the ONE good use case for ==)
if (value == null) { ... }  // true when value is null OR undefined

// โŒ Wrong: misses null
if (value === undefined) { ... }

// โŒ Verbose: only use this when variable might be undeclared
if (typeof value === 'undefined') { ... }

Mistake 2: Comparing NaN with ===

// โŒ NaN is the only value not equal to itself
NaN === NaN  // false!

// โœ… Use Number.isNaN (strict, no type coercion)
Number.isNaN(NaN)        // true
Number.isNaN("NaN")      // false โ† it's not NaN, just a string

// โš ๏ธ Global isNaN does type coercion
isNaN("NaN")             // true โ† converts to number first, misleading
isNaN("hello")           // true โ† "hello" becomes NaN after conversion

Mistake 3: Forgetting null when checking typeof object

// โŒ This has a bug
function processObj(obj) {
  if (typeof obj === 'object') {
    obj.method(); // TypeError if obj is null!
  }
}

// โœ… Correct
function processObj(obj) {
  if (obj !== null && typeof obj === 'object') {
    obj.method();
  }
}

Mistake 4: Using typeof to check for arrays

// โŒ typeof [] is "object", can't distinguish from plain objects
typeof []  // "object"

// โœ… Use Array.isArray
Array.isArray([])   // true
Array.isArray({})   // false

Mistake 5: Thinking undefined can be shadowed safely

// In pre-ES5 environments, undefined could be reassigned:
// undefined = 42; โ† this was a real nightmare in legacy code

// In modern JS, undefined is a non-writable global property
// but it can still be shadowed in local scope (bad practice)
function bad() {
  let undefined = 42;  // local shadow โ€” terrible readability
  console.log(undefined); // 42
}

// โœ… Safe ways to check for undefined
typeof x === 'undefined'  // safest, works even if x is undeclared
x === void 0              // void 0 always evaluates to undefined

๐Ÿ”ธ Level 2 ยท How It Actually Runs

V8 Engine Internal Type Representations

V8 is the JavaScript engine used by Chrome and Node.js. It uses a sophisticated memory representation scheme to balance performance and flexibility.

Pointer Tagging

Every value in V8 is represented by one machine word (64 bits on 64-bit systems). V8 uses the lowest bit of a pointer to distinguish two categories:

V8 Tagged Value (64-bit):

Lowest bit = 0 โ†’ SMI (Small Integer)
  [63-bit signed integer][0]
  The upper 63 bits directly store the integer value โ€” zero allocation cost

Lowest bit = 1 โ†’ Heap Pointer
  [62-bit heap address][01] or other tag
  Points to an object allocated on the heap

Smi vs HeapNumber: Two Fates of Numbers

Small integer example (42):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 64-bit SMI representing 42                              โ”‚
โ”‚ [0000...0000 0101 0100 0] [lowest bit=0 means SMI]      โ”‚
โ”‚  upper 63 bits = 42 in binary    tag bit                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
No heap allocation โ€” value is embedded directly in the pointer word

Large integer or floating point (3.14 or above 2^31):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ HeapNumber object (heap allocated)                      โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚ โ”‚ Map ptr  โ”‚ 64-bit IEEE 754 double value        โ”‚       โ”‚
โ”‚ โ”‚ (8 bytes)โ”‚ (8 bytes, storing 3.14)             โ”‚       โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ”‚ Total ~24 bytes (including object header overhead)      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Smi range: On 64-bit V8, Smi uses a 63-bit signed integer, so the range is -2^62 to 2^62-1 (approximately ยฑ4.6 ร— 10^18). Integers outside this range become HeapNumbers.

Performance implication: In integer loops, keeping the counter within Smi range significantly outperforms using floating-point numbers.

Two Internal String Representations

V8 chooses different internal representations depending on how a string was created:

SeqString (contiguous memory string):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Used for: string literals, results of short concatenationโ”‚
โ”‚                                                          โ”‚
โ”‚  Map ptr โ”‚ Hash  โ”‚ Length โ”‚ C h a r a c t e r s ...     โ”‚
โ”‚  (8 bytes)โ”‚(4 bytes)โ”‚(4 bytes)โ”‚ contiguous character dataโ”‚
โ”‚                                                          โ”‚
โ”‚ Pros: O(1) random access, CPU cache friendly            โ”‚
โ”‚ Cons: modification requires reallocation                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ConsString (linked concatenation string):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Used for: repeated concatenation like "a" + "b" + "c"   โ”‚
โ”‚                                                          โ”‚
โ”‚  Map ptr โ”‚ Length โ”‚ Left ptr  โ†’ "hello"                 โ”‚
โ”‚          โ”‚        โ”‚ Right ptr โ†’ " world"                โ”‚
โ”‚                                                          โ”‚
โ”‚ Structured as a binary tree, "flattened" only when      โ”‚
โ”‚ individual character access is needed                    โ”‚
โ”‚                                                          โ”‚
โ”‚        ConsString("hello world")                         โ”‚
โ”‚           /              \                               โ”‚
โ”‚    "hello"            " world"                           โ”‚
โ”‚                                                          โ”‚
โ”‚ Pros: O(1) concatenation, no immediate new allocation   โ”‚
โ”‚ Cons: random access needs tree traversal, worst O(n)    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Practical implication: When concatenating many strings with += in a loop, V8 first builds a ConsString tree, then allocates contiguous memory during "flattening". This is why heavy concatenation traditionally recommended Array.join('') โ€” though modern V8 has greatly optimized string concatenation.

undefined and null Special Handling

undefined and null in V8:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ undefined: global singleton (Oddball type)           โ”‚
โ”‚   Stored in V8's Root List                           โ”‚
โ”‚   Not allocated on the normal heap                   โ”‚
โ”‚   Every access returns the same pointer              โ”‚
โ”‚   Comparison is extremely fast (pointer equality)   โ”‚
โ”‚                                                      โ”‚
โ”‚ null: another global Oddball singleton               โ”‚
โ”‚   Also in the Root List                              โ”‚
โ”‚   typeof null === "object" due to legacy type tags  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Object Map (Hidden Class)

Every JS object has a hidden Map pointer (called Hidden Class in V8 โ€” not the JavaScript Map collection):

JS Object memory layout:

const obj = { x: 1, y: 2 };

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ JS Object                                           โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚ โ”‚ Map ptr    โ”‚ โ†’ Hidden Class describing shape   โ”‚  โ”‚
โ”‚ โ”‚ (8 bytes)  โ”‚                                   โ”‚  โ”‚
โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚ โ”‚ Properties โ”‚ โ†’ property storage (inline or    โ”‚  โ”‚
โ”‚ โ”‚ ptr(8 bytes)โ”‚   external)                      โ”‚  โ”‚
โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚ โ”‚ Elements   โ”‚ โ†’ array element storage           โ”‚  โ”‚
โ”‚ โ”‚ ptr(8 bytes)โ”‚                                  โ”‚  โ”‚
โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚ โ”‚ x: 1 (SMI)โ”‚ inline properties (up to 4)       โ”‚  โ”‚
โ”‚ โ”‚ y: 2 (SMI)โ”‚                                   โ”‚  โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Hidden Class defines property offsets, enabling property
access to be compiled as direct memory offsets rather than
hash table lookups โ€” approximately 10x faster.

๐Ÿ”บ Level 3 ยท How the Spec Defines It

ECMAScript Spec Section 6: Data Types and Values

The specification divides types into two categories:

Language Types: Types whose values can be operated on directly by an ECMAScript program:

Specification Types: Types used internally by specification algorithms โ€” they do not correspond to actual JS values:

Spec Type Purpose
Completion Record Describes statement execution result (normal, break, continue, return, throw)
Reference Record Describes a property reference (base, name, strict flag)
Property Descriptor Describes object property attributes (value, writable, enumerable, configurable)
Environment Record Describes lexical scope bindings
Abstract Closure A function abstraction used within the spec
Data Block A raw byte sequence (used for ArrayBuffer)

Completion Record in Detail

Every statement execution returns a Completion Record:

Completion Record structure:
{
  [[Type]]:   normal | break | continue | return | throw
  [[Value]]:  an ECMAScript value, or empty
  [[Target]]: a string label (for labeled break/continue), or empty
}

Spec text (ECMA-262, Section 6.2.4):

The Completion Record Specification Type

The Completion Record specification type is used to explain the runtime propagation of values and control flow such as the behaviour of statements (break, continue, return and throw) that perform nonlocal transfers of control.

This means when you write:

function foo() {
  return 42;
}

At the spec level, the return 42 statement produces Completion Record { [[Type]]: return, [[Value]]: 42, [[Target]]: empty }, and the caller inspects [[Type]] to determine the next action.

Reference Record in Detail

Both property access and variable access are represented in the spec as Reference Records:

Reference Record structure:
{
  [[Base]]:            ECMAScript value | Environment Record | unresolvable
  [[ReferencedName]]:  String | Symbol
  [[Strict]]:          Boolean
  [[ThisValue]]:       ECMAScript value | empty
}

For example, obj.foo produces:

{
  [[Base]]: obj,
  [[ReferencedName]]: "foo",
  [[Strict]]: false,
  [[ThisValue]]: empty
}

This is why typeof undeclaredVar doesn't throw: The spec's typeof operator handling explicitly checks whether the Reference Record's [[Base]] is unresolvable โ€” if so, it returns "undefined" instead of throwing a ReferenceError.

Spec Definitions of Language Types

Core definitions from Section 6.1 of the spec:

6.1.1 The Undefined Type:

The Undefined type has exactly one value, called undefined. Any variable that has not been assigned a value has the value undefined.

6.1.2 The Null Type:

The Null type has exactly one value, called null.

6.1.4 The String Type (key excerpt):

The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values ("elements")... Each element is considered to be a UTF-16 code unit value.

This explicitly states that JS strings are sequences of UTF-16 code units, not Unicode code points. This is the fundamental reason why '๐Ÿ˜€'.length === 2.

6.1.6.1 The Number Type:

The Number type has exactly 18437736874454810627 (that is, 2^64 โˆ’ 2^53 + 3) values...

These ~18 quadrillion values include:


๐Ÿ’Ž Level 4 ยท Edge Cases and Traps

Trap 1: The 32-bit Truth Behind typeof null === 'object'

This is JavaScript's most famous bug, originating from the first version of JavaScript written by Netscape in 1995.

The 32-bit type tag system:

Early JS engines (32-bit) used the lowest 3 bits of a pointer as a type tag:

000 = Object
001 = Integer
010 = Double (floating point)
100 = String
110 = Boolean

Special values:
null  โ†’ used C's NULL pointer (0x00000000)
       lowest 3 bits = 000 โ†’ misidentified as object type!

Brendan Eich's own words (2012):

typeof null returning "object" is a consequence of Null values, from JavaScript's inception, being represented as a machine null pointer. In C, sizeof(NULL) is sizeof(void *) == sizeof(integer on most CPUs). The tag bits used for distinguishing types showed null pointers as objects.

Why it was never fixed: The TC39 committee proposed a fix during ES6 development (making typeof null === 'null'), but it was rejected because:

Correct ways to detect null:

// โŒ Unreliable
typeof value === 'object'  // null also passes this

// โœ… Explicit null check
value === null

// โœ… Check for a non-null object
value !== null && typeof value === 'object'

Trap 2: Why typeof function(){} Returns 'function' and Not 'object'

Functions are objects in ECMAScript (a subtype of the Object type), but typeof has a special case for callable objects.

The decision logic from spec Section 13.5.3 (typeof UnaryExpression):

typeof algorithm (simplified):
1. If val is Undefined โ†’ return "undefined"
2. If val is Null โ†’ return "object"
3. If val is Boolean โ†’ return "boolean"
4. If val is Number โ†’ return "number"
5. If val is String โ†’ return "string"
6. If val is Symbol โ†’ return "symbol"
7. If val is BigInt โ†’ return "bigint"
8. If val is Object:
   a. If val has a [[Call]] internal method โ†’ return "function"  โ† key!
   b. Otherwise โ†’ return "object"

The [[Call]] internal method is the mark of a function object. Ordinary objects don't have [[Call]]; function objects do. All functions created via function, =>, or class have [[Call]]:

typeof function(){}   // "function" - ordinary function, has [[Call]]
typeof (() => {})     // "function" - arrow function, has [[Call]]
typeof class Foo {}   // "function" - class sugar, has [[Call]] and [[Construct]]
typeof {}             // "object"   - plain object, no [[Call]]
typeof []             // "object"   - array, no [[Call]]

Trap 3: undefined == null is Hardcoded in the Spec

This is not a result of any type conversion algorithm โ€” it is a hardcoded special case in Abstract Equality Comparison:

Spec Section 7.2.13 (IsLooselyEqual), rules 3 and 4:

Spec text:
3. If x is null and y is undefined, return true.
4. If x is undefined and y is null, return true.

This means the null == undefined check completely bypasses ToPrimitive, ToNumber, and every other conversion algorithm โ€” it directly returns true.

null == undefined   // true (spec rule 3)
undefined == null   // true (spec rule 4)
null === undefined  // false (strict equality, different types)

// Using this for safe nullish checks
function isNullish(val) {
  return val == null;  // catches both null and undefined
}
isNullish(null)       // true
isNullish(undefined)  // true
isNullish(0)          // false
isNullish("")         // false
isNullish(false)      // false

// Important: null does NOT equal other falsy values
null == 0       // false
null == ""      // false
null == false   // false

Trap 4: Object(null) and Object(undefined) Return Empty Objects

When the Object() constructor receives null or undefined, it behaves differently from other inputs:

Object(null)       // {}  โ† returns empty object, not a null wrapper!
Object(undefined)  // {}  โ† returns empty object, not an undefined wrapper!
Object(42)         // Number {42}  โ† returns Number wrapper object
Object("hello")    // String {"hello"} โ† returns String wrapper object
Object(true)       // Boolean {true} โ† returns Boolean wrapper object

Spec Section 20.1.1.1 (Object(value)) algorithm:

1. If NewTarget is not undefined, return OrdinaryCreateFromConstructor(NewTarget, ...)
2. If value is undefined or null:
   return OrdinaryObjectCreate(%Object.prototype%)  โ† returns plain empty object
3. return ToObject(value)  โ† wrap other values

Practical application:

// Safely convert any value to an object (even null/undefined)
const safeObj = Object(potentiallyNull);  // won't throw

// But this is usually not good practice; prefer:
const safeObj = potentiallyNull ?? {};

// The real use of Object(): checking if something is already an object
function isObject(val) {
  return val === Object(val);  // primitives: Object(val) !== val
}
isObject({})         // true
isObject([])         // true
isObject(null)       // false! Object(null) is a new object, !== null
isObject(undefined)  // false
isObject(42)         // false (Number wrapper !== 42)

Chapter Summary

  1. 8 language types are the core of the spec: Undefined, Null, Boolean, String, Number, BigInt, Symbol, Object. The typeof return values don't perfectly map to these 8 types โ€” null returns "object", and functions return "function".

  2. V8 uses Smi to optimize small integer performance: integers within range are embedded directly in pointer words (no heap allocation), while numbers outside range become HeapNumbers (heap objects, ~24 bytes). Keeping integers in the Smi range in performance-critical code avoids unnecessary heap allocations.

  3. Strings have two internal representations: SeqString is contiguous in memory with O(1) random access; ConsString is a linked concatenation tree with O(1) concatenation but O(n) worst-case random access. V8 flattens them at the appropriate time.

  4. typeof null === 'object' is a 30-year-old bug: rooted in a 32-bit machine where the null pointer's type tag bits were misread as "object". TC39 decided never to fix it because fixing it would break hundreds of millions of web pages.

  5. Specification types (Completion Record, Reference Record, etc.) are engine-internal concepts: they explain how return/break/throw propagates, how property access is resolved, and why typeof undeclaredVar doesn't throw. Understanding spec types is the key to deeply understanding JS engine behavior.

Rate this chapter
4.9  / 5  (68 ratings)

๐Ÿ’ฌ Comments