DrillLab
第 16 / 105 道16 / 105 · #278

原始值 vs 引用值

Primitive data types vs Reference data types

先自己答,再往下看Answer it yourself first

一句话:原始值存在栈上、直接存值;引用值存在堆上、栈上只存一个地址。

七种原始类型(背下来,会让你数):string、number、boolean、undefined、null、symbol、bigint。
其余全是引用类型:对象、数组、函数、Date、Map、Set、正则……(数组和函数本质都是对象)。

栈 / 堆是便于理解的模型(规范并不规定,V8 里字符串这类原始值也放在堆上);真正可观察的差别是值语义与引用语义,有三条:

  • 不可变性—— 原始值本身改不了。s.toUpperCase()是返回新字符串,原来那个没动。
  • 比较—— 原始值比值,引用值比地址。所以{} === {} 是 false,[1] === [1] 也是 false。
  • 赋值—— 原始值复制一份;引用值只复制地址,两个变量指向同一个对象。这就是 #284。

会追问:「typeof null 是什么?」——"object",这是个沿用了 30 年的 bug,因为兼容性一直没修。判断 null 要用x === null。
「怎么可靠地判断数组?」——Array.isArray(x),别用 typeof(会得到 "object")。

In one line: a primitive sits on the stack and holds the value itself; a reference value sits on the heap, and the stack holds only an address.

Seven primitive types (learn the list — they will make you count them): string, number, boolean, undefined, null, symbol, bigint.
Everything else is a reference type: objects, arrays, functions, Date, Map, Set, regexes… (arrays and functions are objects underneath).

The stack / heap picture is a mental model (the spec does not prescribe it, and V8 keeps strings and other primitives on the heap too); what you can actually observe is value semantics versus reference semantics, in three ways:

  • Immutability — a primitive value cannot be changed.s.toUpperCase() returns a new string; the original never moved.
  • Comparison — primitives compare by value, references compare by address. So {} === {} is false, and so is [1] === [1].
  • Assignment — a primitive gets copied; a reference copies only the address, so two variables point at the same object. That is #284.

Follow-up: “What is typeof null? ” — "object", a bug that has survived 30 years because fixing it would break too much. Test for null with x === null.
“How do you reliably detect an array?” — Array.isArray(x). Never typeof, which just says "object".

JavaScript值和地址Values and addresses示意Illustrative
1// 原始值:复制值
2let a = 1;
3let b = a;
4b = 2;
5console.log(a); // 1 —— a 没变
6
7// 引用值:复制地址
8let o1 = { n: 1 };
9let o2 = o1; // 同一个对象的第二个遥控器
10o2.n = 2;
11console.log(o1.n); // 2 ← 变了!
12
13// 比较
14console.log({} === {}); // false(两个不同的地址)
15console.log("a" === "a"); // true (比的是值)
16console.log(typeof null); // "object" ← 历史 bug
17console.log(Array.isArray([])); // true ← 判断数组要用这个
1// Primitive value: the value is copied
2let a = 1;
3let b = a;
4b = 2;
5console.log(a); // 1 —— a did not change
6
7// Reference value: the address is copied
8let o1 = { n: 1 };
9let o2 = o1; // a second remote control for the same object
10o2.n = 2;
11console.log(o1.n); // 2 ← it changed!
12
13// Comparing
14console.log({} === {}); // false (two different addresses)
15console.log("a" === "a"); // true (the values are compared)
16console.log(typeof null); // "object" ← a historical bug
17console.log(Array.isArray([])); // true ← use this to test for an array