JavaScript语言基础之数据类型

本文最后更新于 2025年7月30日 下午

ECMAScript 目前有数据类型:Undefined、Null、Boolean、Number、String、Symbol、BigInt、Object。
前 7 种为简单数据类型(也称基本类型、原始类型、基本值),最后 1 种为复杂数据类型(也称引用类型、引用值)。

在 ECMAScript 中不能定义自己的数据类型,所有值都可以用上述数据类型之一来表示。ECMAScript 数据类型很灵活,一种数据类型可以当作多种数据类型来使用。

Undefined 类型

Undefined 类型只有一个值:特殊值 undefined

当使用 varlet 声明了、但是没有初始化的变量时,就相当于给变量赋予了 undefined 值。一般来说,永远不用显式地给某个变量设 undefined 值。因为在默认情况下,任何未经初始化的变量的值都是 undefinedundefined 值主要用于比较,增加这个特殊值就是为了正式明确空对象指针(null)和未初始化变量(undefined)的区别。

无论是声明了但未初始化的变量还是未声明的变量,对其使用 typeof 返回的都是”undefined”。逻辑上讲这是对的,因为虽然严格来讲这两个变量存在根本性差异,但它们都无法执行实际操作。

需要注意的是,声明了但未初始化的变量和未声明的变量还是有区别的。声明了但未初始化的变量可以被打印,未声明定义的变量打印会报错。

1
2
3
4
5
6
7
8
9
10
11
// 未声明的变量 a
let b; // 声明了但未初始化的变量 b
let c = undefined; // 显式地设 undefined 值的变量 c

console.log(a); // Uncaught ReferenceError: a is not defined at <anonymous>:3:13
console.log(b); // undefined
console.log(c); // undefined
typeof a; // 'undefined'
typeof b; // 'undefined'
typeof c; // 'undefined'
b === c; // true

Null 类型

Null 类型只有一个值:特殊值 null

逻辑上讲,null 值表示一个空对象指针,这也是给 typeofnull 值返回”object”的原因。

undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等。用等于操作符(==)比较两者返回 true,用全等操作符(===)比较两者返回 false。

即使 nullundefined 有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将变量值设置为 undefined。但 null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。

Boolean 类型

Boolean 类型有两个字面值:truefalse

这两个布尔值不同于数值,true 不等于 1,false 不等于 0。

虽然布尔值只有两个,但所有其他 ECMAScript 类型的值都有相应布尔值的等价形式。要将一个其他类型值转换为布尔值,可以调用特定的 Boolean() 转型函数(将要转型的值作为参数传入)。Boolean() 转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。

下表总结了不同类型与布尔值之间的转换规则:

数据类型 转换为 true 的值 转换为 false 的值
Undefined undfined
Null null
String 非空字符串 空字符串
Nmuber 非零数值 0 、NaN
Symbol 任意值
BigInt 任意值
Object 任意值

Number 类型

Number 类型使用 IEEE 754 格式表示整数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式。

最基本的数值字面量格式是十进制整数,直接写出来即可。整数也可以用八进制或者十六进制字面量表示。但是,使用八进制和十六进制格式创建的数值在所有数学操作中都被视为十进制数值。

  • 八进制字面量,第一个数字必须是 0(零),然后是相应的八进制数字(数值 0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数。
  • 十六进制字面量,必须让真正的数值前缀 0x(区分大小写),然后是相应的十六进制数字(0~9 及 A~F)。十六进制数字中的字母大小写均可。

正零(+0)和负零(-0)在所有情况下都被认为是等同的,除了使用 Object.is() 方法时。

浮点数

要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少含有一个非零数字。虽然小数点前面不是必须有整数,但推荐加上。

因为存储浮点值使用的内存空间是存储整数值的两倍,所以 ECMAScript 总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着 0(如 1.0),那它也会被转换为整数。

对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以 10 的给定次幂的数值。在 ECMAScript 中,科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写字母 e,再加上一个要乘的 10 的多少次幂。对非常小的数值,默认情况下,ECMAScript 会将小数点后至少包含 6 个零的浮点值转换为科学记数法。

浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确。因为使用了 IEEE 754 数值,所以存在舍入错误,因此永远不要测试某个特定的浮点值。

值的范围

由于内存的限制,ECMAScript 不支持表示这个世界上的所有数值。ECMAScript 可以表示的最小数值保存在 Number.MIN_VALUE 中,这个值在多数浏览器中是 5e-324;可以表示的最大数值保存在 Number.MAX_VALUE 中,这个值在多数浏览器中是 1.7976931348623157e+308。

如果某个计算得到的数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity(无穷)值。任何无法表示的负数以 -Infinity 表示,任何无法表示的正数以 Infinity 表示。

如果计算返回 +Infinity-Infinity,则该值将不能再进一步用于任何计算。这是因为 Infinity 没有可用于计算的数值表示形式。若要确定一个值是不是有限大(即介于 JavaScript 能表示的最小值和最大值之间),可以使用 isFinite() 函数(将要检测的值作为参数传入)。虽然超出有限数值范围的计算并不多见,但总归还是有可能的。因此在计算非常大或非常小的数值时,有必要监测一下计算结果是否超出范围。

此外,Number.NEGATIVE_INFINITYNumber.POSITIVE_INFINITY 这两个属性的值分别是 -InfinityInfinity

NaN

有一个特殊的数值叫 NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用 0 除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在 ECMAScript 中 0、+0 或 -0 相除会返回 NaN,如果分子是非 0 值,分母是有符号 0 或无符号 0,则会返回 Infinity-Infinity

NaN 有几个独特的属性:

  • 任何涉及 NaN 的操作始终返回 NaN
  • NaN 不等于包括 NaN 在内的任何值
  • ECMAScript 提供 isNaN() 函数(将要检测的值作为参数传入)。参数可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给 isNaN() 函数后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,任何不能转换为数值的值都会导致这个函数返回 true

虽然不常见,但 isNaN() 函数可以用于测试对象。此时,首先会调用对象的 valueOf() 方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用 toString() 方法,并测试其返回值。这通常是 ECMAScript 内置函数和操作符的工作方式。

数值转换

有 3 个函数可以将非数值转换为数值: Number()parseInt()parseFloat() 。第一个是转型函数,可用于任何数据类型,后两个主要用于将字符串转换为数据。对于同样的参数,这 3 个函数执行的操作也不同。

Number()

基于如下规则执行转换。

  • 布尔值,true 转换为 1,false 转换为 0。
  • 数值,直接返回。
  • null,返回 0。
  • undefined,返回 NaN
  • 字符串,应用以下规则。
    • 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。
    • 如果字符串包含有效的浮点值格式如”1.1”,则会转换为相应的浮点值(忽略前面的零)。
    • 如果字符串包含有效的十六进制格式如”0xf”,则会转换为与该十六进制值对应的十进制整数值。
    • 如果是空字符串(不包含字符),则返回 0。
    • 如果字符串包含除上述情况之外的其他字符,则返回 NaN
  • 对象,调用 valueOf() 方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用 toString() 方法,再按照转换字符串的规则转换。

parseInt()

Number() 函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用 parseInt() 函数。

parseInt() 函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN。这意味着空字符串也会返回 NaN(这一点跟 Number()不一样,它返回 0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如,”1234blue”会被转换为 1234,因为”blue”会被完全忽略。类似地,”22.5”会被转换为 22,因为小数点不是有效的整数字符。

假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八进制、十六进制)。换句话说,如果字符串以”0x”开头,就会被解释为十六进制整数。如果字符串以”0”开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数。

不同的数值格式很容易混淆,因此 parseInt() 也接收第二个参数,用于指定底数(进制数)。通过第二个参数,可以极大扩展转换后获得的结果类型。不传底数参数相当于让 parseInt()自己决定如何解析,所以为避免解析出错,建议始终传给它第二个参数。

parseFloat()

parseFloat() 函数的工作方式跟 parseInt() 函数类似,都是从位置 0 开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。因此,”22.34.5”将转换成 22.34。

parseFloat() 函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回 0。因为 parseFloat() 函数只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则 parseFloat() 返回整数。

String 类型

String 类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(”)、单引号(’)或反引号(`)来标示。跟某些语言中使用不同的引号会改变对字符串的解释方式不同,ECMAScript 语法中表示字符串的引号没有区别。不过要注意的是,以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。

字符字面量

字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符。如:

字面量 含义
\n 换行
\t 制表
\b 退格
\r 回车
\f 换页
\\ 反斜杠
\‘ 单引号
\“ 双引号
\` 反引号
\xnn 以十六进制编码 nn 表示的字符(其中 n 是十六进制数字 0~F)
\unnnn 以十六进制编码 nnnn 表示的 Unicode 字符(其中 n 是十六进制数字 0~F)

注意,因为转义序列表示一个字符,所以只算一个字符。

字符串特点

ECMAScript 中的字符串是不可变的,一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。

转为字符串

两种方式把一个值转换为字符串。首先是使用几乎所有值都有的 toString() 方法。这个方法唯一的用途就是返回当前值的字符串等价物。其次就是 String() 转型函数。

toString() 方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有 toString() 方法,该方法只是简单地返回自身的一个副本。)null 和 undefined 值没有 toString() 方法。多数情况下,toString() 不接收任何参数。不过,在对数值调用这个方法时可以接收一个底数参数,即以什么底数来输出数值的字符串表示。默认情况下,toString() 返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示。

如果不确定一个值是不是 nullundefined,可以使用 String() 转型函数,它始终会返回表示相应类型值的字符串。String() 函数遵循如下规则。

  • 如果值有 toString() 方法,则调用该方法(不传参数)并返回结果。
  • 如果值是 null,返回”null”。
  • 如果值是 undefined,返回”undefined”。

模板字面量

ECMAScript 6 新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串。所以,模板字面量在定义模板时特别有用。

1
2
3
4
5
6
7
8
9
let myMultiLineString = "first line\nsecond line";
let myMultiLineTemplateLiteral = `first line
second line`;
console.log(myMultiLineString);
// first line
// second line
console.log(myMultiLineTemplateLiteral);
// first line
// second line

由于模板字面量会保持反引号内部的空格,因此在使用它时要格外注意。格式正确的模板字符串看起来可能会缩进不当。

字符串插值

模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的 JavaScript 句法表达式,只不过求值后得到的是字符串。

模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。字符串插值通过在 ${} 中使用一个 JavaScript 表达式实现。

1
2
3
4
5
6
7
8
9
let value = 5;
let exponent = "second";
let interpolatedString =
value + " to the " + exponent + " power is " + value * value;
let interpolatedTemplateLiteral = `${value} to the ${exponent} power is ${
value * value
}`;
console.log(interpolatedString); // 5 to the second power is 25
console.log(interpolatedTemplateLiteral); // 5 to the second power is 25

所有插入的值都会用 toString() 强制转型为字符串,而且任何 JavaScript 表达式都可以用于插值。嵌套的模板字符串无须转义。在插值表达式中可以调用函数和方法,模板也可以插入自己之前的值。

标签函数

模板字面量也支持定义标签函数,通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为。标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果。这个函数的返回值是对模板字面量求值得到的字符串。

因为表达式参数的数量是可变的,所以通常应该使用剩余操作符将它们收集到一个数组中。对于有 n 个插值的模板字面量,传给标签函数的表达式参数的个数始终是 n,而传给标签函数的第一个参数所包含的字符串个数则始终是 n+1。

原始字符串

使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。为此可以使用默认的 String.raw 标签函数。

1
2
console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9

另外,也可以通过标签函数的第一个参数,即字符串数组的 .raw 属性取得每个字符串的原始内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function printRaw(strings) {
console.log("Actual characters:");
for (const string of strings) {
console.log(string);
}
console.log("Escaped characters:");
for (const rawString of strings.raw) {
console.log(rawString);
}
}
printRaw`\u00A9${"and"}\n`;
// Actual characters:
// ©
//(换行符)
// Escaped characters:
// \u00A9
// \n

Symbol 类型

Symbol 类型是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

尽管听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为 Object API 提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字符串形式的对象属性。

BigInt 类型

Object 类型

ECMAScript 中的对象其实就是一组数据和功能的集合。对象通过 new 操作符后跟对象类型的名称来创建。开发者可以通过创建 Object 类型的实例来创建自己的对象,然后再给对象添加属性和方法。

ECMAScript 只要求在给构造函数提供参数时使用括号。如果没有参数,那么完全可以省略括号(不推荐这样使用)。

Object 的实例本身并不是很有用,但理解与它相关的概念非常重要。ECMAScript 中的 Object 也是派生其他对象的基类。Object 类型的所有属性和方法在派生的对象上同样存在。

每个 Object 实例都有如下属性和方法:

  • constructor:用于创建当前对象的函数。
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串或符号。
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用 for-in 语句枚举。与 hasOwnProperty() 一样,属性名必须是字符串。
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString() 的返回值相同。

变量类型

typeof 操作符

ECMAScript 的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型,typeof 操作符就是为此而生的。对一个值使用 typeof 操作符会返回下列字符串之一:

字符串 表示值
“undefined” 未定义
“boolean” 布尔值
“number” 数值
“string” 字符串
“symbol” 符号
“bigint” 任意大的整数
“object” 对象或 null
“function” 函数

typeof 在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。如:

  • 对 null 使用 typeof 会返回”object”。这是因为,null 表示一个对空对象的引用。
  • 对函数使用 typeof 会返回”function”。这是因为,虽然函数被认为是可执行的 Object 类型变量,不代表一种数据类型,但函数有自己特殊的属性。为此,就有必要通过 typeof 操作符来区分函数和其他对象。

相关示例

1
2
3
4
5
6
null === null; // true
undefined === undefined; // true
1 + null; // 1
1 +
undefined[(1, 2, undefined, 4, 5)] + // NaN
10; // '1,2,,4,510'

JavaScript语言基础之数据类型
https://xuekeven.github.io/2021/07/17/JavaScript语言基础之数据类型/
作者
Keven
发布于
2021年7月17日
更新于
2025年7月30日
许可协议