JavaScript数据类型

本文最后更新于:1 年前

JavaScript 数据类型

基本类型

ECMAScript 有 7 种简单数据类型(也称为原始类型):
UndefinedNullBooleanNumberStringSymbolBigInt(ES2020) 。
Symbol(符号)是 ECMAScript 6 新增的,BigInt 是 ES2020 新增
还有一种复杂数据类型叫 Object(对象)。Object 是一种无序名值对的集合。
BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()。

1
2
3
4
const theBiggestInt = 9007199254740991n
//使用 typeof 测试时, BigInt 对象返回 "bigint"
typeof 1n === 'bigint' // true
typeof BigInt('1') === 'bigint' // true

原始值与引用值

ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象

原始值(基本类型)UndefinedNullBooleanNumberStringSymbol BigInt(ES2020)。保存原始值的变量是 按值(by value)访问的,因为我们操作的就是存储在变量中的实际值。 特点:直接存储在栈(stack)中的数据

引用值(引用数据类型):Object、Array、Function。
引用值是保存在内存中的对象。 在操作对象时,实际上操作的是对该对象的
引用
(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的。 特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

动态属性

原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。对于引用值而言,可以随时添加、修改和删除其属性和方法。 在此之后,就可以访问这个新属性,直到对象被销毁或属性被显式地删除。

1
2
3
let person = new Object()
person.name = 'Nicholas'
console.log(person.name) // "Nicholas"

原始值不能有属性,尽管尝试给原始值添加属性不会报错。

1
2
3
let name = 'Nicholas'
name.age = 27
console.log(name.age) // undefined

只有引用值可以动态添加后面可以使用的属性。原始类型的初始化可以只使用原始字面量形式。
如果使用的是 new 关键字,则 JavaScript 会创建一个 Object 类型的实例,但其行为类似原始值。

1
2
3
4
5
6
7
8
let name1 = 'Nicholas'
let name2 = new String('Matt')
name1.age = 27
name2.age = 26
console.log(name1.age) // undefined
console.log(name2.age) // 26
console.log(typeof name1) // string
console.log(typeof name2) // object

复制值

在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。 这两个变量可以独立使用,互不干扰

1
2
let num1 = 5
let num2 = num1


在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象

1
2
3
4
let obj1 = new Object()
let obj2 = obj1
obj1.name = 'Nicholas'
console.log(obj2.name) // "Nicholas"

传递参数

ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样
在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。(这在 ECMAScript 中是不可能的。)

1
2
3
4
5
6
7
8
function addTen(num) {
num += 10
return num
}
let count = 20
let result = addTen(count)
console.log(count) // 20,没有变化
console.log(result) // 30

如果 num 是按引用传递的,那么 count 的值也会被修改为 30。这个事实在使用数值这样的原始值时是非常明显的 。如果变量中传递的是对象,就没那么清楚了

1
2
3
4
5
6
function setName(obj) {
obj.name = 'Nicholas'
}
let person = new Object()
setName(person)
console.log(person.name) // "Nicholas

在函数内部, obj 和 person 都指向同一个对象。结果就是,即使对象是按值传进函数的, obj 也会通过引用问对象。当函数内部 给 obj 设置了 name 属性时,函数外部的对象也会反映这个变化, 因为 obj 指向的对象保存在全局作用域的堆内存上。

1
2
3
4
5
6
7
8
function setName(obj) {
obj.name = 'Nicholas'
obj = new Object()
obj.name = 'Greg'
}
let person = new Object()
setName(person)
console.log(person.name) // "Nicholas"

这个例子前后唯一的变化就是 setName() 中多了两行代码, 将 obj 重新定义为一个有着不同 name 的新对象。当 person 传 入 setName() 时,其 name 属性被设置为 “Nicholas” 。然后变 量 obj 被设置为一个新对象且 name 属性被设置为 “Greg” 。如果 person 是按引用传递的,那么 person 应该自动将指针改为指向 name 为 “Greg” 的对象。可是,当我们再次访问 person.name 时,它的值是 “Nicholas” ,这表明函数中参数的值改变之后,原始的引用仍然没变。当 obj 在函数内部被重写时, 它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。

注意 ECMAScript 中函数的参数就是局部变量。

typeof 操作符

因为 ECMAScript 的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。typeof 操作符最适合用来判断一个变量是否为原始类型。更确切地说,它是判断一个变量是否为字符串、数值、布尔值或 undefined 的最好方式。如果值是对象或 null ,那么 typeof 返回 “object”
对一个值使用 typeof 操作符会返回下列字符串之一:

  • “undefined” 表示值未定义;
  • “boolean” 表示值为布尔值;
  • “string” 表示值为字符串;
  • “number” 表示值为数值;
  • “object” 表示值为对象(而不是函数)或 null ;
  • “function” 表示值为函数;
  • “symbol” 表示值为符号。
  • “bigint” 表示值为任意大的整数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let s = "Nicholas";
let b = true;
let i = 22;
let u;
let a=1n
let n = null;
let o = new Object();
console.log(typeof s); // string
console.log(typeof i); // number
console.log(typeof b); // boolean
console.log(typeof u); // undefined
console.log(typeof a); // bigint
console.log(typeof n); // object
console.log(typeof o); // object

注意 typeof 在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用 typeof null 返回的是 “object” 。这是因为特殊值 null 被认为是一个对空对象的引用。

instanceof 操作符

typeof 虽然对原始值很有用,但它对引用值的用处不大。我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象。为了解决这个问题,ECMAScript 提供了 instanceof 操作符

1
result = variable instanceof constructor

如果变量是给定引用类型的实例,则 instanceof 操作符返回 true

1
2
3
console.log(person instanceof Object); // 变量persion是Object吗?
console.log(colors instanceof Array); // 变量colors是Array吗?
console.log(pattern instanceof RegExp); // 变量pattern是RegExp吗?

所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true 。类似地,如果用 instanceof 检测原始值,则始终会返回 false ,因为原始值不是对象

Undefined 类型

Undefined 类型只有一个值,就是特殊值 undefined 。当使 用 var 或 let 声明了变量但没有初始化时,就相当于给变量赋予 了 undefined 值:

1
2
let message
console.log(message == undefined) // true

注意,包含 undefined 值的变量跟未定义变量是有区别的

1
2
3
4
5
let message; // 这个变量被声明了,只是值为undefined

// let age // 确保没有声明过这个变量
console.log(message); // "undefined"
console.log(age); // 报错

在对未初始化的变量调用 typeof 时,返回的结果 是 “undefined” ,但对未声明的变量调用它时,返回的结果还 是 “undefined” 。

1
2
3
4
5
let message; // 这个变量被声明了,只是值为undefined
// make sure this variable isn't declared
// let age
console.log(typeof message); // "undefined"
console.log(typeof age); // "undefined"

无论是声明还是未声明, typeof 返回的都是字符串 “undefined” 。

1
2
3
4
5
6
7
8
9
10
11
let message; // 这个变量被声明了,只是值为undefined
// age没有声明
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这里会报错
}

Null 类型

Null 类型同样只有一个值,即特殊值 null 。逻辑上讲, null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 “object” 的原因

1
2
let car = null
console.log(typeof car) // "object"

undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等

1
console.log(null == undefined) // true

用等于操作符( == )比较 null 和 undefined 始终返回 true 。
只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let message = null;
let age;
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这个块不会执行
}
if (!age) {
// 这个块会执行
}

Boolean 类型

Boolean (布尔值)类型是 ECMAScript 中使用最频繁的类型之 一,有两个字面值: true 和 false 。这两个布尔值不同于数值, 因此 true 不等于 1,
虽然布尔值只有两个,但所有其他 ECMAScript 类型的值都有相应 布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的 Boolean() 转型函数

1
2
let message = 'Hello world!'
let messageAsBoolean = Boolean(message)

字符串 message 会被转换为布尔值并保存在变 量 messageAsBoolean 中。 Boolean() 转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值

数据类型 转换为 true 的值 转换为 false 的值
Boolean true false
String 非空字符串 “” (空字符串)
Number 非零数值(包括无穷值、bigint) 0 、 NaN (参见后面的相关内容)
Object 任意对象 null
Undefined N/A(不存在) undefined

理解以上转换非常重要,因为像 if 等流控制语句会自动执行其 他类型值到布尔值的转换,例如:

1
2
3
4
let message = 'Hello world!'
if (message) {
console.log('Value is true')
}

Number 类型

最基本的数值字面量格式是十进制整数,直接写出来即可:

1
let intNum = 55 // 整数

整数也可以用八进制(以 8 为基数)或十六进制(以 16 为基数)字面量表示。对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值 0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数,如下所示:

1
2
3
let octalNum1 = 070; // 八进制的56
let octalNum2 = 079; // 无效的八进制值,当成79处理
let octalNum3 = 08; // 无效的八进制值,当成8处理

注意 由于 JavaScript 保存数值的方式,实际中可能存在正零(+0) 和负零(-0)。正零和负零在所有情况下都被认为是等同的

浮点值

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

1
2
3
let floatNum1 = 1.1
let floatNum2 = 0.1
let floatNum3 = 0.1 // 有效,但不推荐

因为存储浮点值使用的内存空间是存储整数值的两倍,所以 ECMAScript 总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。
ECMAScript 中科学记数法的格式要求是一个数值(整数或浮点 数)后跟一个大写或小写的字母 e,再加上一个要乘的 10 的多少次幂。比如:

1
let floatNum = 3.125e7 // 等于31250000

浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确。例如,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04。由于这种微小的舍入错误,导致很难测试特定的浮点值

1
2
3
if (a + b == 0.3) { // 别这么干!
console.log("You got 0.3.");
}

值的范围

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

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

如果计算返回正 Infinity 或负 Infinity ,则该值将不能再进一步用于任何计算。
要确定一个值是不是有限大(即介于 JavaScript 能表示的最小值和最大值之间),可以使用isFinite() 函数

1
2
let result = Number.MAX_VALUE + Number.MAX_VALUE
console.log(isFinite(result)) // false

NaN

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

1
2
3
4
5
6
7
8
console.log(0/0); // NaN
console.log(-0/+0); // NaN

//如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity 或 -Infinity :
console.log(5/0); // Infinity
console.log(5/-0); // -Infinity
// NaN 不等于包括 NaN 在内的任何值
console.log(NaN == NaN); // false

ECMAScript 提供了 isNaN() 函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。

1
2
3
4
5
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

把一个值传给 isNaN() 后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串 “10” 或布尔值。 任何不能转换为数值的值都会导致这个函数返回 true

isNaN() 可以用于测试对象。此时, 首先会调用对象的 valueOf() 方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用 toString() 方法, 并测试其返回值

数值转换

有 3 个函数可以将非数值转换为数值: Number() parseInt()parseFloat()
Number() 是转型函数, 可用于任何数据类型。后两个函数主要用于将字符串转换为数值。

Number() 函数基于如下规则执行转换。
  • 布尔值, true 转换为 1, false 转换为 0。
  • 数值,直接返回。 null ,返回 0。
  • undefined ,返回 NaN 。
  • 字符串,应用以下规则。
    • 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。
    • 如果字符串包含有效的浮点值格式如 “1.1” ,则会转换为相应的浮点值(同样,忽略前面的零)。
    • 如果字符串包含有效的十六进制格式如 “0xf” ,则会转换为与该十六进制值对应的十进制整数值。
    • 如果是空字符串(不包含字符),则返回 0。
    • 如果字符串包含除上述情况之外的其他字符,则返回 NaN 。 对象,调用 valueOf() 方法,并按照上述规则转换返回的值。
    • 如果转换结果是 NaN ,则调用 toString() 方法,再按照转换字符串的规则转换
  • 对象,调用 valueOf() 方法,并按照上述规则转换返回的值。如果转换结果是 NaN ,则调用 toString() 方法,再按照转换字符串的规则转换。
1
2
3
4
let num1 = Number("Hello world!"); // NaN
let num2 = Number(""); // 0
let num3 = Number("000011"); // 11
let num4 = Number(true); // 1

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

parseInt() 函数更专注于字符串是否包含数值模式。

如果第一个字符不是数值字符、加号或减号, parseInt() 立即返回 NaN 。这意味着空字符串也会返回 NaN (这一点跟 Number() 不一样,它返回 0)

1
2
3
4
5
6
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 作为第二个参数,以便正确解析:

1
2
3
let num = parseInt("0xAF", 16); // 175
let num1 = parseInt("AF", 16); // 175
let num2 = parseInt("AF"); // NaN 转换检测到第一个字符就是非数值字符,随即自动停止并返回 NaN

通过第二个参数,可以极大扩展转换后获得的结果类型

1
2
3
4
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() 只解析十进制值,因此不能指定底数

1
2
3
4
5
6
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"); //31250000

String 类型

String (字符串)数据类型表示零或多个 16 位 Unicode 字符序列

字符字面量

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

字面量 含义
\n 换行
\t 制表
\b 退格
\r 回车
\f 换页
\\ 反斜杠( \ )
' 单引号( ‘ ),在字符串以单引号标示时使 用,例如 ‘He said, 'hey.'‘
" 双引号( “ ),在字符串以双引号标示时使 用,例如 “He said, "hey."“
\` 反引号( ` ),在字符串以反引号标示时 使用,例如 `He said, \`hey.\``
\xnn 以十六进制编码 nn 表示的字符(其中 n 是 十六进制数字 0~F),例如 \x41 等于 “A”
\unnnn 以十六进制编码 nnnn 表示的 Unicode 字符 (其中 n 是十六进制数字 0~F),例如 \u03a3 等于希腊字符 “Σ”

字符串的特点

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

1
2
let lang = 'Java'
lang = lang + 'Script' //JavaScript

转换为字符串

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

1
2
3
4
let age = 11;
let ageAsString = age.toString(); // 字符串"11"
let found = true;
let foundAsString = found.toString(); // 字符串"true"

toString() 方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有 toString() 方法,该方法只是简单地返回自身的一个副本。)null 和 undefined 值没有 toString() 方法
toString() 返回数值的十进制字符串表示。而通过传入参 数,可以得到数值的二进制、八进制、十六进制,或者其他任何 有效基数的字符串表示

1
2
3
4
5
6
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 ,可以使用 String() 转型函数,它始终会返回表示相应类型值的字符串。
String() 函数遵循如下规则。

  • 如果值有 toString() 方法,则调用该方法(不传参数) 并返回结果。
  • 如果值是 null ,返回 “null” 。
  • 如果值是 undefined ,返回 “undefined” 。
1
2
3
4
5
6
7
8
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"

因为 null 和 undefined 没有 toString() 方法,所以 String() 方法就直接返回了这两个 值的字面量文本。

模板字面量

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

1
2
3
4
5
6
7
let pageHTML = `
<div>
<a href="#">
<span>Jake</span>
</a>
</div>`;

由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意

字符串插值

模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值
字符串插值通过在${}中使用一个 JavaScript 表达式实现

1
2
3
4
5
6
7
8
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

模板字面量标签函数

模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果

原始字符串

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

1
2
3
4
// Unicode示例
// \u00A9是版权符号
console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9

Symbol 类型

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

符号的基本用法

符号需要使用 Symbol() 函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol

1
2
let sym = Symbol()
console.log(typeof sym) // symbol

Symbol() 函数不能用作构造函数,与 new 关键字一起使用。这样做是为了避免创建符号包装对象

1
let mySymbol = new Symbol() // TypeError:Symbol is not a constructor

如果你确实想使用符号包装对象,可以借用 Object() 函数:

1
2
3
let mySymbol = Symbol()
let myWrappedSymbol = Object(mySymbol)
console.log(typeof myWrappedSymbol) //"object"

BigInt 类型

在 ES2020 之前,JavaScript 只有一种数值类型:number(数字),而之后为了安全表达比 -9007199254740991 ~ 9007199254740991 安全范围之外的数字。引入了 BigInt 类型
BigInt 可以表示任意大的整数。

语法

  • 直接在数字后面加一个 n
  • 调用 BigInt()构造函数
1
BigInt(value) //value: 创建对象的数值。可以是字符串或者整数。
1
2
3
4
5
const bigInt = 9007199254740992n; //通过直接在数字后面加n
const bigNumber = BigInt(9007199254740992); // 对十进制数字使用BigInt函数
const bigString = BigInt("9007199254740992"); //对String类型的使用BigInt函数,先隐式转换为十进制的数字,再显式转换为BigIn类型
const bigHex = BigInt(0x20000000000000); // 对十六进制数字使用BigInt函数
const bigBin = BigInt(0b100000000000000000000000000000000000000000000000000000); //对二进制数字使用BigInt函数

注意, BigInt() 不是构造函数,因此不能使用 new 操作符

类型判断

我们可以通过 typeof 操作符来判断是否为 BigInt 类型(返回字符串”bigint”)

1
2
typeof 1n === 'bigint' // true
typeof BigInt('1') === 'bigint'; // true

同样的,我们也可以用最通用的 Object.prototype.toString 方法(返回字符串”[object BigInt]”)

1
Object.prototype.toString.call(10n) === '[object BigInt]' // true

使用 Object 包装后, BigInt 被认为是一个普通 “object” :

1
typeof Object(1n) === 'object' // true

运算

以下操作符可以和 BigInt 一起使用: +、*、-、**、% 。
除 >>> (无符号右移)之外的 位操作 也可以支持。(因为 BigInt 都是有符号的 >>> (无符号右移)不能用于 BigInt)。

符号 名称
+ 加法
* 乘法
- 减法
% 求余
** 求幂
<< 左移位
>> 右移位

当 BigInt 使用/操作符时,带小数的运算会被取整。

1
2
const expected = 4n / 2n //2n
const rounded = 5n / 2n; //2n, not 2.5n

BigInt 类型虽然和 Number 很像,可以做各种数学运算,但是在运算过程中要注意两点:

  • BigInt 类型不能用 Math 对象中的方法。
  • 不能和 Number 示例混合运算。因为 JavaScript 在处理不同类型的运算时,会把他们先转换为同一类型,而 BigInt 类型变量在被隐式转换为 Number 类型时,可能会丢失精度,或者直接报错
1
2
3
const number = 1
const bigInt = 9007199254740993n
number + bigInt // TypeError: Cannot mix BigInt and other types

BigInt 类型和其他类型比较

BigInt 和 Number 不是严格相等的,但是宽松相等的
1
2
10n === 10 // false
10n == 10// true

Number 和 BigInt 可以进行比较

1
2
3
4
5
6
7
8
1n < 2;        // true
2n > 1; // true

2n < 2; // false
2n <= 2; // true

2n > 2; // false
2n >= 2; // true

两者也可以混在一个数组内并排序。

1
2
const mixed = [4n, 6, -12n, 10, 4, 0, 0n];    // [4n, 6, -12n, 10, 4, 0, 0n]
mixed.sort(); // [-12n, 0, 0n, 10, 4n, 4, 6]

BigInt 在需要转换成 Boolean 的时表现跟 Number 类似:如通过 Boolean 函数转换;用于 Logical Operators ||, &&, 和 ! 的操作数;或者用于在像 if statement 这样的条件语句中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (0n) {
console.log('Hello from the if!');
} else {
console.log('Hello from the else!');
} // ↪ "Hello from the else!"

0n || 12n // ↪ 12n

0n && 12n // ↪ 0n

Boolean(0n) // ↪ false

Boolean(12n) // ↪ true

!12n // ↪ false

!0n // ↪ true

它在某些方面类似于 Number ,但是也有几个关键的不同点:不能用与 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。

不允许隐式类型转换

因为隐式类型转换可能丢失信息,所以不允许在 bigint 和 Number 之间进行混合操作。当混合使用大整数和浮点数时,结果值可能无法由 BigInt 或 Number 精确表示。

1
2
10n + 1;    // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
Math.max(2n, 4n, 6n); // TypeError...
BigInt 和 String

难免会遇到数字和字符串的转换,BigInt 也不例外,不过可惜的是 BigInt 转为 String 时,其标志性的 n 会被省略,如

1
2
String(10n) // '10'
'' + 11n; // '11'
零值

BigInt 没有 Number 的正零(+0)和负零(-0)之分。因为 BigInt 表示的是整数

无穷和 NaN 判断

很有趣的现象

1
2
3
4
5
6
7
isFinite(10n);    // Uncaught TypeError: Cannot convert a BigInt value to a number
Number.isFinite(10n); // false

isNaN(10n); // Uncaught TypeError: Cannot convert a BigInt value to a number
Number.isNaN(10n); // false

Number.isFinite(10n)===Number.isNaN(10n) //true

由此我们可以看出 isFinite()和 Number.isFinite()、isNaN()和 Number.isNaN()的区别:
isFinite(val)/isNaN(val)的工作机制都是讲参数值 val 转为数值后再进行比较判断,而 Number.isFinite(val)/Number.isNaN(val)则可以理解为直接简单粗暴的变量全等判断(val === Infinity/val === NaN)

Object 类型

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

1
let o = new Object()

但 ECMAScript 只要求在给构造函数提供参数时使用括号。如果没有参数,如上面的例子所示,那么完全可以省略括号(不推荐):

1
let o = new Object() // 合法,但不推荐

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

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

总结

JavaScript 变量可以保存两种类型的值:原始值和引用值。原始值 可能是以下 7 种原始数据类型之一: Undefined 、 Null 、 Boolean 、 Number 、 String 、 Symbol 和 Bigint 。原始值和引用值有以下特点。

  • 原始值大小固定,因此保存在栈内存上。
  • 从一个变量到另一个变量复制原始值会创建该值的第二个副本。
  • 引用值是对象,存储在堆内存上。 包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
  • 从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。
  • typeof 操作符可以确定值的原始类型,而 instanceof 操作符用于确保值的引用类型