JavaScript 是一种多范式的动态语言,它包含类型、运算符、标准内置(built-in)对象和方法。它的语法来源于 Java 和 C,所以这两种语言的许多语法特性同样适用于 JavaScript。
数据类型
JavaScript 是一种有着动态类型的动态语言。JavaScript 中的变量与任何特定值类型没有任何关联,并且任何变量都可以分配(重新分配)所有类型的值。
let str = 'Hello World!'
str = 123
最新的 ECMAScript 标准定义了 8 种数据类型:
- 七种基本数据类型:
null
:空,表示空对象的特殊关键字,JavaScript 大小写敏感的,因此null
与Null
、NULL
或变体完全不同。undefined
:未定义,和null
一样是特殊关键字,表示变量未赋值时的属性。Boolean
:布尔,有 2 个值分别是:true 和 false。Number
:数字,包括整数或浮点数。String
:字符串,一串表示文本值的字符序列。Symbol
:符号,表示一种实例是唯一且不可改变的数据类型(ECMAScript 2015 新增类型)。BigInt
:任意精度的整数,可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。
- 以及对象(Object):它用于存储各种键值集合和更复杂的实体。
虽然这些数据类型相对来说比较少,但是通过他们你可以在程序中开发有用的功能。对象和函数是这门语言的另外两个基本元素。你可以把对象当作存放值的一个命名容器,然后将函数当作你的程序能够执行的步骤。
Function
:函数,每个 JavaScript 函数实际上都是一个Function
对象。Array
:数组,一个有序的元素集合,通过索引(index)来访问元素。Date
、RegExp
、Error
、Math
、Map
、Set
等标准内置对象。
原始值
除了 Object
以外,所有类型都定义了表示在语言最低层面的不可变值,我们将这些值称为原始值。除了 null
,所有原始类型都可以使用 typeof
运算符测试。typeof null
返回 object
,因此必须使用 === null
来测试 null
。
除了 null
和 undefined
,所有原始类型都有它们相应的对象包装类型,这为处理原始值提供可用的方法。当在原始值访问属性时,JavaScript 会自动将值包装到相应的包装对象中,并访问对象上的属性。然而,在 null
或 undefined
访问属性时,会抛出 TypeError
异常,这需要采用可选链运算符。
原始值 | typeof返回值 | 包装对象 |
---|---|---|
Null | "object" | N/A |
Undefined | "undefined" | N/A |
Boolean | "boolean" | Boolean |
Number | "number" | Number |
String | "string" | String |
BigInt | "bigint" | BigInt |
Symbol | "symbol" | Symbol |
Null 类型
Null 类型只有一个值:null
。
Undefined 类型
Undefined 类型只有一个值:undefined
。从概念上讲,undefined
表示没有任何值,null
表示没有任何对象(这也可以构成 typeof null === "object"
的接口)。当某些东西没有值时,该语言通常默认为 undefined
。
- 没有值(
return;
)的return
语句,隐式返回undefined
。 - 访问不存在的对象属性(
obj.iDontExist
),返回undefined
。 - 变量声明时没有初始化(
let x;
),隐式初始化为undefined
。 - 许多如
Array.prototype.find()
和Map.prototype.get()
的方法,当没有发现元素时,返回undefined
。
null
是一个关键字,但是 undefined
是一个普通的标识符,恰好是一个全局属性。在实践中,这两个差异很小,因为 undefined
不应该被重新定义或者遮蔽。
Boolean 类型
Boolean 类型表示一个逻辑实体并且包括两个值:true
和 false
。
Number 类型
Number 类型是一种基于 IEEE 754 标准的双精度 6* 位二进制格式的值。它能够存储 \(2^{-1074}\)(Number.MIN_VALUE)和 \(2^{1024}\)(Number.MAX_VALUE)之间的正浮点数,以及 \(-2^{-1074}\) 和 \(-2^{1024}\) 之间的负浮点数,但是它仅能安全地存储在 \(-(2^{53} − 1)\)(Number.MIN_SAFE_INTEGER)到 \(2^{53} − 1\)(Number.MAX_SAFE_INTEGER)范围内的整数。超出这个范围,JavaScript 将不能安全地表示整数;相反,它们将由双精度浮点近似表示。
±(\(2^{-1074}\) 到 \(2^{1024}\)) 范围之外的值会自动转换:
- 大于 Number.MAX_VALUE 的正值被转换为 +Infinity。
- 小于 Number.MIN_VALUE 的正值被转换为 +0。
- 小于 -Number.MAX_VALUE 的负值被转换为 -Infinity。
- 大于 -Number.MIN_VALUE 的负值被转换为 -0。
NaN(“Not a Number”)是一个特殊种类的数值,当算术运算的结果不表示数值时,通常会遇到它。它也是 JavaScript 中唯一不等于自身的值。
BigInt 类型
BigInt 类型在 Javascript 中是一个数字的原始值,它可以表示任意大小的整数。使用 BigInt,你可以安全地存储和操作巨大的整数,甚至超过 Number 的安全整数限制(Number.MAX_SAFE_INTEGER)。BigInt 是通过将 n 附加到整数末尾或调用 BigInt() 函数来创建的。
String 类型
String 类型表示文本数据并编码为 UTF-16 代码单位的 16 位无符号整数值序列。字符串中的每个元素在字符串中占据一个位置。第一个元素的索引为 0,下一个是索引 1,依此类推。字符串的长度是它的元素的数量。
- 使用 substring() 获取原始的子字符串。
- 使用串联运算符(+)或 concat() 将两个字符串串联。
表示文本数据时候推荐使用字符串。当需要表示复杂的数据时,使用字符串解析并使用适当的抽象。
Symbol 类型
Symbol 是唯一并且不可变的原始值并且可以用来作为对象属性的键(如下)。在某些程序语言当中,Symbol 也被称作“原子类型”(atom)。symbol 的目的是去创建一个唯一属性键,保证不会与其他代码中的键产生冲突。
数字转换为字符串
- 模板字符串:`${x}` 为嵌入的表达式执行上面的字符串强制转换步骤。
String()
函数:String(x)
使用相同的算法去转换x
,只是Symbol
不会抛出TypeError
,而是返回"Symbol(description)"
,其中description
是对Symbol
的描述。- 使用
+
运算符:"" + x
将其操作数强制转为原始值,而不是字符串,并且对于某些对象,其行为与普通字符串强制转换完全不同。
字符串转换为数字
- 一元运算符:
±x
完全按照数值强制转换步骤来转换。 - 算术运算符:除了
+
有可能把运算子转为字符串,-
/*
//
会把运算子自动转成数值。 Number()
函数:Number(x)
使用相同的算法转换x
,除了BigInt
不会抛出TypeError
,而是返回它的Number
值,并且可能损失精度。Number.parseFloat()
和Number.parseInt()
与Number()
相似,但只转换字符串,并且解析规则略有不同。
其他类型转换为布尔值
可使用一元运算符 `!` 进行显示转换。默认系统内部会自动调用 Boolean()
函数,因此除了 undefined
、null
、±0
、NaN
和 ''
(空字符串) 五个值,其他都是自动转为 true
。
类型强制转换
JavaScript 也是一个弱类型语言,这意味着当操作涉及不匹配的类型是否,它将允许隐式类型转换,而不是抛出一个错误。
const foo = 42
const result = foo + '1'
强制隐式转换是非常方便的,但是如果开发者不打算转换,或者打算向另一个方向转换,则会存在潜在的隐患。对于 symbol
和 BigInt
,JavaScript 总是不允许某些隐式类型转换。
原始值类型强制转换
强制原始值转换用于得到一个期望的原始值,但对实际类型应该是什么并没有强烈的偏好。通常情况下可以接受 String
、Number
或 BigInt
。
如果值已经是原始值,则此操作不会进行任何转换。对象按以下顺序调用它的 [@@toPrimitive]()
(将 hint 作为 default)、valueOf()
和 toString()
方法,将其转换为原始值。注意,原始值转换会在 toString()
方法之前调用 valueOf()
方法,这与强制数字类型转换的行为相似,但与强制字符串类型转换不同。
[@@toPrimitive]()
方法,如果存在,则必须返回原始值——返回对象,会导致 TypeError
。对于 valueOf()
和 toString()
,如果其中一个返回对象,则忽略其返回值,从而使用另一个的返回值;如果两者都不存在,或者两者都没有返回一个原始值,则抛出 TypeError
。
在强制转换为任意的原始类型时,[@@toPrimitive]()
方法总是优先调用。原始值的强制转换的行为通常与强制 number
类型类似,因为优先调用 valueOf()
;然而,有着自定义 [@@toPrimitive]()
方法的对象可以选择返回任意的原始值。
Date
和 Symbol
对象是唯一重写 [@@toPrimitive]()
方法的对象。Date.prototype[@@toPrimitive]()
将 "default" hint 视为 "string",而 Symbol.prototype[@@toPrimitive]()
忽略 hint 并始终返回一个 symbol
。
数字类型强制转换
有两种数字类型:number
和 BigInt
。有时候,该语言尤其希望是 number
或 BigInt
;其他时候,它可能容忍并且根据运算对象的类型不同执行不同的运算。
强制数字类型转换与强制 number
类型转换几乎相同,只是 BigInt
会按原样返回,而不是引起 TypeError
。强制数字类型转换用于所有算术运算,因为它们重载了 number
和 BigInt
类型。唯一例外的是一元加,它总是强制 number
类型转换。
- 对于
Number
则总是返回自己。 undefined
变成了NaN
。null
变成了0
。true
变成了1
;false
变成了0
。
- 如果它们包含数字字面量,字符串通过解析它们来转换。如果解析失败,返回的结果为 NaN。与实际数字字面量相比,它们有一些细微的差别。
- 忽略前导和尾随空格/行终止符。
- 前导数值
0
不会导致该数值成为八进制文本(或在严格模式下被拒绝)。 +
和-
允许在字符串的开头指示其符号然而,该标志只能出现一次,不得后跟空格。Infinity
和-Infinity
被当作是字面量。在实际代码中,它们是全局变量。- 空字符串或仅空格字符串转换为
0
。 - 不允许使用数字分隔符。
BigInt
抛出TypeError
,以防止意外的强制隐式转换损失精度。Symbol
抛出TypeError
。- 对象首先按顺序调用
[@@toPrimitive]()
(将 "number" 作为 hint)、valueOf()
和toString()
方法将其转换为原始值。然后将生成的原始值转换为数值。
整数转换
一些操作需要整数,最值得注意的是那些适用于数组/字符串索引、日期/时间组件和数值基数的整数。执行上述数值强制转换步骤后,结果被截断为整数(通过丢弃分数部分)。如果数值为 ±Infinity
,则按原样返回。如果数值是 NaN
或 -0
,则返回为 0
。因此,结果总是整数(不是 -0
)或 ±Infinity
。
值得注意的是,当转换到整数时,undefined
和 null
都会变成 0
,因为 undefined
被转换为 NaN
,NaN
也变成了 0
。
固定宽度数值转换
JavaScript 有一些较低级别的函数,用于处理整数的二进制编码,最值得注意的是按位运算和 TypedArray
对象。按位运算总是将操作数转换为 32 位整数。在这些情况下,将值转换为数值后,然后首先截断小数部分,然后在整数的二进制的补码编码中取最低位,将数值归一化为给定的宽度。
字符串类型强制转换
许多内置操作首先将它们的参数强制转换为字符串。
- 字符串按原样返回。
undefined
转换成"undefined"
。null
转换成"null"
。true
转换成"true"
;false
转换成"false"
。- 使用与
toString(10)
相同的算法转换数字。 - 使用与
toString(10)
相同的算法转换BigInt
。 Symbol
抛出TypeError
。- 对于对象,首先,通过依次调用其
[@@toPrimitive]()
(hint 为 "string")、toString()
和valueOf()
方法将其转换为原始值。然后将生成的原始值转换为一个字符串。
对象类型强制转换
许多内置操作首先将它们的参数强制转换为对象。
- 对象则按原样返回。
undefined
和null
则抛出TypeError
。Number
、String
、Boolean
、Symbol
、BigInt
等基本类型被封装成其对应的基本类型对象。
在 JavaScript 中实现相同效果的最佳方式是使用 Object()
构造函数。Object(x)
可以将 x
转换为对象,对于 undefined
或 null
,它会返回一个普通对象而不是抛出 TypeError
异常。使用对象强制转换的地方包括:
for...in
循环的object
参数。Array
方法的this
值。Object
方法的参数,如Object.keys()
。- 当访问基本类型的属性时进行自动转换,因为基本类型没有属性。
- 在调用非严格函数时的
this
值。基本类型值被封装为对象,而null
和undefined
被替换为全局对象。
其他类型强制转换
所有的数据类型,除了 Null、Undefined 以及 Symbol,都有它们各自的强制过程。有三种不同的路径可以将对象转换为原始值:
- 强制原始值转换:
[@@toPrimitive]("default")
→valueOf()
→toString()
- 强制数字类型转换、强制
number
类型转换、强制BigInt
类型转换:[@@toPrimitive]("number")
→valueOf()
→toString()
- 强制字符串类型转换:
[@@toPrimitive]("string")
→toString()
→valueOf()
在所有情况下,[@@toPrimitive]()
如果存在,必须可调用并返回原始值,而如果它们不可调用或返回对象,valueOf
或 toString
将被忽略。在过程结束时,如果成功,结果保证是原始值。然后,由此产生的原始值会进一步强制类型转换,具体取决于上下文。