JavaScript数据类型
本文最后更新于:1 年前
JavaScript 数据类型
基本类型
ECMAScript 有 7 种简单数据类型(也称为原始类型):
Undefined、 Null、 Boolean、 Number、 String、 Symbol 和 BigInt(ES2020) 。
Symbol(符号)是 ECMAScript 6 新增的,BigInt 是 ES2020 新增
还有一种复杂数据类型叫 Object(对象)。Object 是一种无序名值对的集合。
BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()。
1 |
|
原始值与引用值
ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象
原始值(基本类型) :Undefined、 Null、 Boolean、 Number、 String、 Symbol 和 BigInt(ES2020)。保存原始值的变量是 按值(by value)访问的,因为我们操作的就是存储在变量中的实际值。 特点:直接存储在栈(stack)中的数据
引用值(引用数据类型):Object、Array、Function。
引用值是保存在内存中的对象。 在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的。 特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
动态属性
原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。对于引用值而言,可以随时添加、修改和删除其属性和方法。 在此之后,就可以访问这个新属性,直到对象被销毁或属性被显式地删除。
1 |
|
原始值不能有属性,尽管尝试给原始值添加属性不会报错。
1 |
|
只有引用值可以动态添加后面可以使用的属性。原始类型的初始化可以只使用原始字面量形式。
如果使用的是 new 关键字,则 JavaScript 会创建一个 Object 类型的实例,但其行为类似原始值。
1 |
|
复制值
在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。 这两个变量可以独立使用,互不干扰
1 |
|
在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象
1 |
|
传递参数
ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样
在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。(这在 ECMAScript 中是不可能的。)
1 |
|
如果 num 是按引用传递的,那么 count 的值也会被修改为 30。这个事实在使用数值这样的原始值时是非常明显的 。如果变量中传递的是对象,就没那么清楚了
1 |
|
在函数内部, obj 和 person 都指向同一个对象。结果就是,即使对象是按值传进函数的, obj 也会通过引用问对象。当函数内部 给 obj 设置了 name 属性时,函数外部的对象也会反映这个变化, 因为 obj 指向的对象保存在全局作用域的堆内存上。
1 |
|
这个例子前后唯一的变化就是 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 |
|
注意 typeof 在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用 typeof null 返回的是 “object” 。这是因为特殊值 null 被认为是一个对空对象的引用。
instanceof 操作符
typeof 虽然对原始值很有用,但它对引用值的用处不大。我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象。为了解决这个问题,ECMAScript 提供了 instanceof 操作符
1 |
|
如果变量是给定引用类型的实例,则 instanceof 操作符返回 true
1 |
|
所有引用值都是 Object 的实例,因此通过 instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true 。类似地,如果用 instanceof 检测原始值,则始终会返回 false ,因为原始值不是对象
Undefined 类型
Undefined 类型只有一个值,就是特殊值 undefined 。当使 用 var 或 let 声明了变量但没有初始化时,就相当于给变量赋予 了 undefined 值:
1 |
|
注意,包含 undefined 值的变量跟未定义变量是有区别的
1 |
|
在对未初始化的变量调用 typeof 时,返回的结果 是 “undefined” ,但对未声明的变量调用它时,返回的结果还 是 “undefined” 。
1 |
|
无论是声明还是未声明, typeof 返回的都是字符串 “undefined” 。
1 |
|
Null 类型
Null 类型同样只有一个值,即特殊值 null 。逻辑上讲, null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 “object” 的原因
1 |
|
undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等
1 |
|
用等于操作符( == )比较 null 和 undefined 始终返回 true 。
只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。
1 |
|
Boolean 类型
Boolean (布尔值)类型是 ECMAScript 中使用最频繁的类型之 一,有两个字面值: true 和 false 。这两个布尔值不同于数值, 因此 true 不等于 1,
虽然布尔值只有两个,但所有其他 ECMAScript 类型的值都有相应 布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的 Boolean() 转型函数
1 |
|
字符串 message 会被转换为布尔值并保存在变 量 messageAsBoolean 中。 Boolean() 转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值
数据类型 | 转换为 true 的值 | 转换为 false 的值 |
---|---|---|
Boolean | true | false |
String | 非空字符串 | “” (空字符串) |
Number | 非零数值(包括无穷值、bigint) | 0 、 NaN (参见后面的相关内容) |
Object | 任意对象 | null |
Undefined | N/A(不存在) | undefined |
理解以上转换非常重要,因为像 if 等流控制语句会自动执行其 他类型值到布尔值的转换,例如:
1 |
|
Number 类型
最基本的数值字面量格式是十进制整数,直接写出来即可:
1 |
|
整数也可以用八进制(以 8 为基数)或十六进制(以 16 为基数)字面量表示。对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值 0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数,如下所示:
1 |
|
注意 由于 JavaScript 保存数值的方式,实际中可能存在正零(+0) 和负零(-0)。正零和负零在所有情况下都被认为是等同的
浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至 少有一个数字。虽然小数点前面不是必须有整数,但推荐加上。
1 |
|
因为存储浮点值使用的内存空间是存储整数值的两倍,所以 ECMAScript 总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。
ECMAScript 中科学记数法的格式要求是一个数值(整数或浮点 数)后跟一个大写或小写的字母 e,再加上一个要乘的 10 的多少次幂。比如:
1 |
|
浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确。例如,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04。由于这种微小的舍入错误,导致很难测试特定的浮点值
1 |
|
值的范围
由于内存的限制,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 |
|
NaN
有一个特殊的数值叫 NaN ,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用 0 除任意数值在其他语言中通常都会导致错误, 从而中止代码执行。但在 ECMAScript 中,0、+0 或-0 相除会返回 NaN
1 |
|
ECMAScript 提供了 isNaN()
函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。
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 |
|
考虑到用 Number() 函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用 parseInt() 函数。
parseInt() 函数更专注于字符串是否包含数值模式。
如果第一个字符不是数值字符、加号或减号, parseInt() 立即返回 NaN 。这意味着空字符串也会返回 NaN (这一点跟 Number() 不一样,它返回 0)
1 |
|
不同的数值格式很容易混淆,因此 parseInt() 也接收第二个参数,用于指定底数(进制数)。如果知道要解析的值是十六进制,那么可以传入 16 作为第二个参数,以便正确解析:
1 |
|
通过第二个参数,可以极大扩展转换后获得的结果类型
1 |
|
parseFloat() 函数的工作方式跟 parseInt() 函数类似, 都是从位置 0 开始检测每个字符
它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。因此,”22.34.5” 将转换成 22.34
parseFloat() 函数的另一个不同之处在于,它始终忽略字符串开头的零。 十六进制数值始终会返回 0。 因为 parseFloat() 只解析十进制值,因此不能指定底数
1 |
|
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 |
|
转换为字符串
有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()
方法。这个方法唯一的用途就是返回当前值的字符串等价物。
1 |
|
toString() 方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有 toString() 方法,该方法只是简单地返回自身的一个副本。)null 和 undefined 值没有 toString() 方法。
toString() 返回数值的十进制字符串表示。而通过传入参 数,可以得到数值的二进制、八进制、十六进制,或者其他任何 有效基数的字符串表示
1 |
|
如果你不确定一个值是不是 null 或 undefined ,可以使用 String() 转型函数,它始终会返回表示相应类型值的字符串。String()
函数遵循如下规则。
- 如果值有 toString() 方法,则调用该方法(不传参数) 并返回结果。
- 如果值是 null ,返回 “null” 。
- 如果值是 undefined ,返回 “undefined” 。
1 |
|
因为 null 和 undefined 没有 toString() 方法,所以 String() 方法就直接返回了这两个 值的字面量文本。
模板字面量
ECMAScript 6 新增了使用模板字面量定义字符串的能力。与使用 单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串
1 |
|
由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意
字符串插值
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值
字符串插值通过在${}
中使用一个 JavaScript 表达式实现
1 |
|
模板字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果
原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。为此,可以使用默认的 String.raw 标签函数:
1 |
|
Symbol 类型
Symbol (符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
符号的基本用法
符号需要使用 Symbol() 函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol
1 |
|
Symbol() 函数不能用作构造函数,与 new 关键字一起使用。这样做是为了避免创建符号包装对象
1 |
|
如果你确实想使用符号包装对象,可以借用 Object() 函数:
1 |
|
BigInt 类型
在 ES2020 之前,JavaScript 只有一种数值类型:number(数字),而之后为了安全表达比 -9007199254740991 ~ 9007199254740991 安全范围之外的数字。引入了 BigInt 类型
BigInt 可以表示任意大的整数。
语法
- 直接在数字后面加一个 n
- 调用 BigInt()构造函数
1 |
|
1 |
|
注意, BigInt() 不是构造函数,因此不能使用 new 操作符
类型判断
我们可以通过 typeof 操作符来判断是否为 BigInt 类型(返回字符串”bigint”)
1 |
|
同样的,我们也可以用最通用的 Object.prototype.toString 方法(返回字符串”[object BigInt]”)
1 |
|
使用 Object 包装后, BigInt 被认为是一个普通 “object” :
1 |
|
运算
以下操作符可以和 BigInt 一起使用: +、*、-、**、% 。
除 >>> (无符号右移)之外的 位操作 也可以支持。(因为 BigInt 都是有符号的 >>> (无符号右移)不能用于 BigInt)。
符号 | 名称 |
---|---|
+ | 加法 |
* | 乘法 |
- | 减法 |
% | 求余 |
** | 求幂 |
<< | 左移位 |
>> | 右移位 |
当 BigInt 使用/操作符时,带小数的运算会被取整。
1 |
|
BigInt 类型虽然和 Number 很像,可以做各种数学运算,但是在运算过程中要注意两点:
- BigInt 类型不能用 Math 对象中的方法。
- 不能和 Number 示例混合运算。因为 JavaScript 在处理不同类型的运算时,会把他们先转换为同一类型,而 BigInt 类型变量在被隐式转换为 Number 类型时,可能会丢失精度,或者直接报错
1 |
|
BigInt 类型和其他类型比较
BigInt 和 Number 不是严格相等的,但是宽松相等的
1 |
|
Number 和 BigInt 可以进行比较
1 |
|
两者也可以混在一个数组内并排序。
1 |
|
BigInt 在需要转换成 Boolean 的时表现跟 Number 类似:如通过 Boolean 函数转换;用于 Logical Operators ||, &&, 和 ! 的操作数;或者用于在像 if statement 这样的条件语句中。
1 |
|
它在某些方面类似于 Number ,但是也有几个关键的不同点:不能用与 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。
不允许隐式类型转换
因为隐式类型转换可能丢失信息,所以不允许在 bigint 和 Number 之间进行混合操作。当混合使用大整数和浮点数时,结果值可能无法由 BigInt 或 Number 精确表示。
1 |
|
BigInt 和 String
难免会遇到数字和字符串的转换,BigInt 也不例外,不过可惜的是 BigInt 转为 String 时,其标志性的 n 会被省略,如
1 |
|
零值
BigInt 没有 Number 的正零(+0)和负零(-0)之分。因为 BigInt 表示的是整数
无穷和 NaN 判断
很有趣的现象
1 |
|
由此我们可以看出 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 |
|
但 ECMAScript 只要求在给构造函数提供参数时使用括号。如果没有参数,如上面的例子所示,那么完全可以省略括号(不推荐):
1 |
|
每个 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 操作符用于确保值的引用类型
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!