七种原始类型,以及其他一切
JavaScript 把值分成两大阵营。一边是七种原始类型:简单、不可变的值。另一边是对象:一切复合的、可变的或可调用的东西。在值的层面上,这就是整个类型系统。
七种原始类型如下:
不在这个列表里的东西,比如数组、函数、日期、正则表达式和普通的 {},都是对象。typeof 会在运行时告诉你值的类型,你也会注意到最后一行那个著名的瑕疵。typeof null 从 1995 年起就返回 'object',而且永远不会修复,因为太多现有代码依赖它。
原始值就是值本身,不是容器
最有帮助的思维模型是:原始值就是它的值。数字 3 不是一个装着 3 的盒子,它就是 3。两个保存 3 的变量保存的是同一个值,而不是指向某个共享对象的两份副本:
原始值按值比较,对象按引用比较。之后很多“为什么这里是 false?”的困惑都源于这一个区别,尤其是用 === 比较数组或对象的时候。
原始值是不可变的
你无法修改一个原始值。每个看起来像是修改的操作,实际上都会产生一个新值:
第一次调用构造了一个新字符串,但没有任何东西接收返回值,所以它就被丢弃了。第二次调用给 name 重新赋值。原来的 "ada" 从未被修改,也不可能被修改。数字也一样:x + 1 产生一个新数字,而不会修改 x。
所以对字符串或数字使用 const 是真正安全的。值本身无法改变,而 const 又阻止你给变量重新赋值。
数字、BigInt,以及为什么有两种
JavaScript 的 number 是 64 位浮点数。这意味着运算很快,但也有上限:整数只在 Number.MAX_SAFE_INTEGER(2^53 - 1)以内是精确的:
超过这个阈值后,不同的整数会开始“撞车”。bigint 就是为任意大小都要保持精确的整数而存在的。写法是在数字后面加上 n:
bigint 和 number 不能混在一起运算,否则额外的精度就失去了意义。处理数据库 ID、纳秒级时间戳或加密运算时,就用 bigint。普通运算继续用 number。
字符串也是原始类型
在 JavaScript 中,字符串是原始值,而不是对象,尽管它提供了 .length、.slice、.toUpperCase 这样的方法:
在底层,当你在字符串上调用方法时,JavaScript 会临时把它包装成一个 String 对象,让方法调用得以执行,然后丢掉这个包装。你不需要关心这个包装对象,只要知道:字符串虽然有丰富的方法,行为上却和值一样(不可变、按值比较)。
单引号、双引号和反引号创建的都是同一种类型。反引号还支持插值和多行字符串,下一篇文档会讲到。
null 与 undefined
有两个原始值都表示“没有值”,但它们不能互相替代。
undefined 表示某个东西从未被赋值:声明了但没有赋值的变量、缺失的函数参数、不存在的属性:
null 则是你想表达“这里有意为空”时自己写上的:
大致的约定是:undefined 是语言在说“这里什么都没有”,null 是程序员在说“这里什么都没有”。两者都是假值,和普通值比较相等时都不成立,后面也各自有专门的一篇。
Symbol:天生唯一
symbol 是用得最少的原始类型。你创建的每个 symbol 都是唯一的,即使两个 symbol 用的是同一个描述:
symbol 很适合用作不会与已有键冲突的对象键:库可以用 symbol 给你的对象附加元数据,并确保没有其他代码会覆盖它。在迭代器以及 Symbol.iterator 这类内置 symbol 中,你还会再见到它们。
在运行时检查类型
typeof 能处理大多数情况。记住下面这些怪癖:
对于 null,直接比较:value === null。对于数组,用 Array.isArray(value)。要判断“这是不是某种原始值?”,没有现成的内置方法,但惯用写法足够清晰:
原始值与对象:赋值时的陷阱
继续之前,还有一点值得看看。因为原始值是值、对象是引用,所以赋值的行为不同:
对于原始值,b = a 复制的是值。对于对象,y = x 复制的是引用,两个名字指向同一个底层对象。通过一个名字修改,另一个也会受影响。这是 JavaScript 中“等等,它怎么变了?”这类 bug 的头号来源。
要点回顾
- 七种原始类型:
string、number、bigint、boolean、null、undefined、symbol。其余的都是对象。 - 原始值不可变,按值比较;对象可变,按引用比较。
typeof在运行时告诉你类型,有两个怪癖值得记住:typeof null === "object"和typeof function === "function"。number是 64 位浮点数,整数有安全上限;bigint用来表示超过这个上限的精确整数。
下一篇:字符串与模板字面量
字符串是你最常接触的原始类型,而模板字面量(就是那些反引号字符串)让构造字符串变得轻松:插值、多行文本、标签模板。这就是下一页的内容。
常见问题
JavaScript 有几种原始类型?
七种:string、number、bigint、boolean、null、undefined 和 symbol。其余的一切,包括数组、函数、日期和普通对象,都是对象。你可以在运行时用 typeof 检查一个值的类型,只是有一个历史遗留的怪癖:typeof null 返回 'object'。
JavaScript 中原始值和对象有什么区别?
原始值是不可变的,按值比较:两个 3 就是同一个 3。对象是可变的,按引用比较:两个 {} 即使看起来一模一样,也是不同的对象。赋值原始值时复制的是值本身;赋值对象时复制的是指向同一份底层数据的引用。
JavaScript 的原始值真的不可变吗?
是的。你无法原地修改一个原始值:'hello'.toUpperCase() 返回一个新字符串,并不会改变原来的字符串。给变量重新赋值(x = x + 1)只是把变量的值换成另一个原始值,原来的值从未被修改。所以 const name = 'Ada' 之后,你仍然可以基于 name 构造新的字符串。
为什么 typeof null 返回 'object'?
这是 1995 年最初实现中的一个 bug,因为太多代码依赖这个行为,所以一直没有修复。要检查 null,用 === 比较:value === null。要检查 undefined,用 value === undefined 或 typeof value === 'undefined'。