数据类型#
ECMAScript 有 7 种简单数据类型(也称为原始类型): Undefined、Null、Boolean、Number、String、Symbol 和 BigInt。Symbol(符号)是 ECMAScript 6 新增的,BigInt 是 ECMAScript 2020 新增的。此外,还有一种复杂数据类型叫 Object(对象)。本文将重点介绍 7 种基础数据类型。
| 类型 | 描述 | 示例 |
|---|---|---|
| Undefined | 未定义的值 | let a; |
| Null | 空对象指针 | let b = null; |
| Boolean | 逻辑值 true/false | let c = true; |
| Number | 整数/浮点数(含 Infinity/NaN) | let d = 3.14; |
| String | 文本数据 | let e = "hello"; |
| Symbol | 唯一不可变值(ES6) | let f = Symbol("id"); |
| BigInt | 任意大整数(ES2020) | let g = 12345678901234567890n; |
| Object | 复杂数据结构(对象、数组、函数等) | let h = { name: "Alice" }; |
typeof操作符#
typeof 操作符用于检测变量的数据类型。对于基础数据类型,它的返回值如下:
| 类型 | typeof 返回值 | 示例 |
|---|---|---|
| Undefined | "undefined" | typeof undefined |
| Null | "object" | typeof null |
| Boolean | "boolean" | typeof true |
| Number | "number" | typeof 42 |
| String | "string" | typeof "text" |
| Symbol | "symbol" | typeof Symbol() |
| BigInt | "bigint" | typeof 10n |
| Function | "function" | typeof function() {} |
注意:严格来讲,函数在 ECMAScript 中被认为是对象,并不代表一种数据类型。但由于其特殊性,
typeof操作符会为函数返回"function"。另外,typeof null返回"object"是一个历史遗留问题。
Undefined类型#
Undefined 类型只有一个值,就是特殊值 undefined。当使用 var 或 let 声明了变量但没有初始 化时,就相当于给变量赋予了 undefined 值:
let message
console.log(message == undefined) // true
let message // 这个变量被声明了,只是值为undefined // 确保没有声明过这个变量
// let age
console.log(typeof message) // "undefined"
console.log(typeof age) // "undefined"即使未初始化的变量会被自动赋予undefined值,但我们仍然建议在声明变量的 同时进行初始化。这样,当 typeof 返回"undefined"时,你就会知道那是因为给定的变 量尚未声明,而不是声明了但未初始化。
Null类型#
Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回"object"的原因:
let car = null
console.log(typeof car) // "object"undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等,如下面的例 子所示:
console.log(null == undefined) // true即使 null 和 undefined 有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将变量值设置为 undefined。但 null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个 对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其 与 undefined 区分开来。
Boolean类型#
下表总结了不同类型与布尔值之间的转换规则。
| 数据类型 | 转换为 true 的值 | 转换为 false 的值 |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | ""(空字符串) |
| Number | 非零数值(包括无穷值) | 0、NaN(参见后面的相关内容) |
| Object | 任意对象 | null |
| Undefined | N/A(不存在) | undefined |
Number类型#
Number 类型使用 IEEE 754 格式表示整 数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式。
浮点值#
浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确。例如,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04。由于这种微小的舍入错误,导致很难测试特定的浮点值。比如下 面的例子:
if (a + b == 0.3) {
// 别这么干!
console.log('You got 0.3.')
}NaN#
有一个特殊的数值叫 NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用 0 除任意数值在其他语言中通常都会导致错误,从而中止代码执 12 行。但在 ECMAScript 中,0、+0 或-0 相除会返回 NaN:
console.log(0 / 0) // NaN
console.log(-0 / +0) // NaN如果分子是非 0 值,分母是有符号 0 或无符号 0,则会返回 Infinity 或-Infinity:
console.log(5 / 0) // Infinity
console.log(5 / -0) // -InfinityNaN 有几个独特的属性。首先,任何涉及 NaN 的操作始终返回 NaN(如 NaN/10),在连续多步计算时这可能是个问题。其次,NaN 不等于包括 NaN 在内的任何值。例如,下面的比较操作会返回 false:
console.log(NaN == NaN) // false为此,ECMAScript 提供了 **isNaN()**函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给 isNaN()后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串"10"或布尔值。任何不能转换为数值的值都会导致这个函数返回 true。举例如下:
console.log(isNaN(NaN)) // true
console.log(isNaN(10)) // false,10 是数值
console.log(isNaN('10')) // false,可以转换为数值10
console.log(isNaN('blue')) // true,不可以转换为数值
console.log(isNaN(true)) // false,可以转换为数值1上述的例子测试了 5 个不同的值。首先测试的是 NaN 本身,显然会返回 true。接着测试了数值 10 和字符串"10",都返回 false,因为它们的数值都是 10。字符串"blue"不能转换为数值,因此函数返 回 true。布尔值 true 可以转换为数值 1,因此返回 false。
数值转换#
有 3 个函数可以将非数值转换为数值:Number()、parseInt()和 parseFloat()。Number()是 转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这 3 个 函数执行的操作也不同。Number()函数基于如下规则执行转换:
-
布尔值,true 转换为 1,false 转换为 0。
-
数值,直接返回。
-
null,返回 0。
-
undefined,返回 NaN。
-
字符串,应用以下规则:
-
如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。 因此,Number("1")返回 1,Number("123")返回 123,Number("011")返回 11(忽略前面的零)。
-
如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。
-
如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值。
-
如果是空字符串(不包含字符),则返回 0。
-
如果字符串包含除上述情况之外的其他字符,则返回 NaN。
-
-
对象,调用 valueOf()方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用 toString()方法,再按照转换字符串的规则转换。
考虑到用 Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN。
let num1 = parseInt('1234blue') // 1234
let num2 = parseInt('') // NaN
let num3 = parseInt('0xA') // 10,解释为十六进制整数
let num4 = parseInt(22.5) // 22
let num5 = parseInt('70') // 70,解释为十进制值
let num6 = parseInt('0xf') // 15,解释为十六进制整数不同的数值格式很容易混淆,因此 parseInt()也接收第二个参数,用于指定底数(进制数)。如 果知道要解析的值是十六进制,那么可以传入 16 作为第二个参数,以便正确解析:
let num = parseInt('0xAF', 16) // 175事实上,如果提供了十六进制参数,那么字符串前面的"0x"可以省掉:
let num1 = parseInt('AF', 16) // 175
let num2 = parseInt('AF') // NaN
let num1 = parseInt('10', 2) // 2,按二进制解析
let num2 = parseInt('10', 8) // 8,按八进制解析
let num3 = parseInt('10', 10) // 10,按十进制解析
let num4 = parseInt('10', 16) // 16,按十六进制解析parseFloat()函数的工作方式跟 parseInt()函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。因此,"22.34.5"将转换 成 22.34。
parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨 论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回 0。因为 parseFloat()只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小 数点后面只有一个零),则 parseFloat()返回整数。下面是几个示例:
let num1 = parseFloat('1234blue') // 1234,按整数解析
let num2 = parseFloat('0xA') // 0
let num3 = parseFloat('22.5') // 22.5
let num4 = parseFloat('22.34.5') // 22.34
let num5 = parseFloat('0908.5') // 908.5
let num6 = parseFloat('3.125e7') // 31250000String类型#
String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(")、 单引号(')或反引号(`)标示。
ECMAScript 中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量,如下所示:
let lang = 'Java'
lang = lang + 'Script'这里,变量 lang 一开始包含字符串"Java"。紧接着,lang 被重新定义为包含"Java"和"Script" 的组合,也就是"JavaScript"。整个过程首先会分配一个足够容纳 10 个字符的空间,然后填充上 "Java"和"Script"。最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了。
null和undefined 值没有toString()方法。多数情况下,toString()不接收任何参数。不过,在对数值调用这个方法时,toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示。默认情况下,toString()返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示,比如:
let num = 10
console.log(num.toString()) // "10"
console.log(num.toString(2)) // "1010"
console.log(num.toString(8)) // "12"
console.log(num.toString(10)) // "10"
console.log(num.toString(16)) // "a"null 和 undefined 值没有 toString()方法。
如果你不确定一个值是不是null或undefined,可以使用 String()转型函数,它始终会返回表示相应类型值的字符串。String()函数遵循如下规则。
- 如果值有 toString()方法,则调用该方法(不传参数)并返回结果。
- 如果值是 null,返回"null"。
- 如果值是 undefined,返回"undefined"。
下面看几个例子:
let value1 = 10
let value2 = true
let value3 = null
let value4
console.log(String(value1)) // "10"
console.log(String(value2)) // "true"
console.log(String(value3)) // "null"
console.log(String(value4)) // "undefined"模板字面量#
ECMAScript 6 新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量 保留换行字符,可以跨行定义字符串:
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"
console.log(myMultiLineString === myMultiLinetemplateLiteral) // true顾名思义,模板字面量在定义模板时特别有用,比如下面这个 HTML 模板:
let pageHTML = `
<div>
<a href="#">
<span>Jake</span>
</a>
</div>`字符串插值#
字符串插值通过在$中使用一个 JavaScript 表达式实现:
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 表达式都可以用于插值。嵌套的模板字符串无须转义:
在插值表达式中可以调用函数和方法:
console.log(`Hello, ${`World`}!`) // Hello, World!
function capitalize(word) {
return `${word[0].toUpperCase()}${word.slice(1)}`
}
console.log(`${capitalize('hello')}, ${capitalize('world')}!`) // Hello, World!此外,模板也可以插入自己之前的值:
let value = ''
function append() {
value = `${value}abc`
console.log(value)
}
append() // abc
append() // abcabc
append() // abcabcabcSymbol类型#
Symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
尽管听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为 Object API 提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字符串形式的对象属性。
符号的基本用法#
符号需要使用 Symbol()函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol。
let sym = Symbol()
console.log(typeof sym) // symbol调用 Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通 过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:
let genericSymbol = Symbol()
let otherGenericSymbol = Symbol()
let fooSymbol = Symbol('foo')
let otherFooSymbol = Symbol('foo')
console.log(genericSymbol == otherGenericSymbol) // false
console.log(fooSymbol == otherFooSymbol) // false符号没有字面量语法,这也是它们发挥作用的关键。按照规范,你只要创建 Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。
最重要的是,Symbol()函数不能与 new 关键字一起作为构造函数使用。这样做是为了避免创建符号包装对象,像使用 Boolean、String 或 Number 那样,它们都支持构造函数且可用于初始化包含原始值的包装对象:
let myBoolean = new Boolean()
console.log(typeof myBoolean) // "object"
let myString = new String()
console.log(typeof myString) // "object"
let myNumber = new Number()
console.log(typeof myNumber) // "object"
let mySymbol = new Symbol() // TypeError: Symbol is not a constructor如果你确实想使用符号包装对象,可以借用 Object()函数:
let mySymbol = Symbol()
let myWrappedSymbol = Object(mySymbol)
console.log(typeof myWrappedSymbol) // "object"新增数据类型:BigInt#
ECMAScript 2020 (ES11) 新增了 BigInt 数据类型,用于表示大于 $2^-1$(Number.MAX_SAFE_INTEGER)的整数。这使 JavaScript 能安全地进行大整数运算,解决了 Number 类型无法精确表示大整数的问题。
核心特性#
-
字面量表示:在整数末尾添加
nconst bigInt = 9007199254740991n const hexBigInt = 0x1fffffffffffffn // 十六进制 const binaryBigInt = 0b11111111111111111111111111111111111111111111111111111n // 二进制 -
构造函数:
BigInt()函数const bigIntFromString = BigInt('9007199254740991') // 9007199254740991n const bigIntFromNumber = BigInt(Number.MAX_SAFE_INTEGER) // 9007199254740991n -
类型检测:
typeof返回"bigint"console.log(typeof 123n) // "bigint"
运算规则#
-
支持算术操作:
+,-,*,**,/,%(除法会向下取整)const a = 10n, b = 3n console.log(a / b) // 3n(不是 3.333n) -
禁止与
Number混合运算:10n + 1 // TypeError: Cannot mix BigInt and other types -
比较运算:可与
Number比较(不严格相等)console.log(10n == 10) // true(宽松相等) console.log(10n === 10) // false(严格不等) console.log(10n > 5) // true
特殊行为#
-
布尔转换:
0n为false,非零为trueconsole.log(Boolean(0n)) // false console.log(5n ? 'true' : 'false') // "true" -
JSON.stringify问题:默认无法序列化JSON.stringify({ value: 100n }) // TypeError: BigInt value can't be serialized
使用场景#
-
大整数运算(加密、高精度时间戳)
-
解决金融计算精度问题
// Number 类型精度丢失 console.log(9007199254740992 === 9007199254740993) // true // BigInt 保持精度 console.log(9007199254740992n === 9007199254740993n) // false