前言
一门编程如何进行工作这就涉及到其支持多少的类型集,简单理解就是数据类型,通过操作值或文本来进行工作。为了能够在需要时将某些值保留下来继续使用,就会把这个值赋值给变量并起一个名字方便调用。下面就来理解 JavaScript 中的类型、值和变量。
1. 数据类型概述
JavaScript 类型可以分为两类:原始类型和对象类型。
- 七种原始类型:
- 布尔值(Boolean),有 2 个值分别是:
true
和false
. - null,一个表明 null 值的特殊关键字。JavaScript 是大小写敏感的,因此
null
与Null
、NULL
或变体完全不同。 - undefined,和 null 一样是一个特殊的关键字,undefined 表示变量未赋值时的属性。
- 数字(Number),整数或浮点数,例如:
42
或者3.14159
。 - 任意精度的整数 (BigInt) ,可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。
- 字符串(String),字符串是一串表示文本值的字符序列,例如:“Howdy” 。
- 代表(Symbol)( 在 ECMAScript 6 中新添加的类型).。一种实例是唯一且不可改变的数据类型。
- 布尔值(Boolean),有 2 个值分别是:
- 以及对象(Object),一种复杂数据类型叫 Object(对象)。Object 是一种无序名值对的集合,在 JavaScript 中,任何不是数值、字符串、布尔值、符号、null 和 undefined 的值都是对象。对象(也就是对象类型的成员)是属性的集合,其中每个属性都有一个名字和一个值(原始值或其他对象)。
数组是一种特殊对象,数组表示一个数字值的有序集合。JavaScript 语言包括操作数组的特殊语法,而数组本身也具有区别于普通对象的行为。
除了基本的对象和数组之外,JavaScript 还定义了其他一些有用的对象类型。Set 对象表示一组值的集合,Map 对象表示键与值的映射。各种“定型数组”(typed array)类型便于对字节数组和其他二进制数据进行操作。RegExp 类型表示文本模式,可以实现对字符串的复杂匹配、搜索和替换操作。Date 类型表示日期和时间,支持基本的日期计算。Error 及其子类型表示 JavaScript 代码运行期间可能发生的错误。
JavaScript 与静态语言更大的差别在于,函数和类不仅仅是语言的语法,它们本身就是可以被 JavaScript 程序操作的值。与其他 JavaScript 非原始值一样,函数和类也是特殊的对象。
对象类型是可修改的
,而原始类型是不可修改的
。在 JavaScript 中,字符串也是不可修改
的。虽然可以按索引访问字符串中的字符,但 JavaScript 没有提供任何方式去修改已有字符串的字符。
2. Number 类型
Number 类型使用 IEEE 754 标准定义的64位浮点格式表示数值,这意味着 JavaScript 可以表示的最大整数是 ±1.797 693 134 862 3157×10308
,最小整数是 ±5×10-324
。这种数值格式可以让我们准确表示 -9 007 199254 740 992(-253)到 9 007 199 254 740 992(253)之间的所有整数(含首尾值)。
不同的数值类型相应地也有不同的数值字面量格式。
2.1 整数字面量
整数可以用十进制(基数为 10)、八进制(以8为基数)或十六进制(以16为基数)在 ES6 及之后的版本中,也可以通过二进制(基数为2)字面量表示。
-
十进制整数字面量由一串数字序列组成,且没有前缀 0。
-
八进制的整数以 0(或 0O、0o)开头,只能包括数字 0-7。
-
十六进制整数以 0x(或 0X)开头,可以包含数字(0-9)和字母 a~f 或 A~F。
-
二进制整数以 0b(或 0B)开头,只能包含数字 0 和 1。
十进制(基数为 10)可以直接写成数字序列。
let intNum = 10; // 整数
八进制,在非严格模式下第一个数字必须是零(0),然后是相应的八进制数字(数值0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数,如下所示:
let octalNum1 = 070; // 八进制的56
let octalNum2 = 079; // 无效的八进制值,当成79处理
let octalNum3 = 08; // 无效的八进制值,当成8处理
在严格模式下使用上述的方法表示八进制是无效的,会导致 JavaScript 引擎抛出语法错误。 ECMAScript 2015 或 ES6 中的八进制值通过前缀 0o 来表示;严格模式下,前缀 0 会被视为语法错误,如果要表示八进制值,应该使用前缀 0o。
十六进制字面量以 0x 或 0X 开头,后跟一个十六进制数字字符串。十六进制数字是数字 0 到 9 和字母 a(或A)到 f(或F),a 到 f 表示 10 到 15。
let hexNum1 = 0xA; // 十六进制10
let hexNum2 = 0x1f; // 十六进制31
使用八进制和十六进制格式创建的数值在所有数学操作中都被视为十进制数值。
2.2 浮点字面量
浮点数字面值可以有以下的组成部分:
-
一个十进制整数,可以带正负号(即前缀“+”或“ - ”),
-
小数点(“.”),
-
小数部分(由一串十进制数表示),
-
指数部分。
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上。下面是几个例子:
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效,但不推荐
因为存储浮点值使用的内存空间是存储整数值的两倍,所以 JavaScript 总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着 0(如1.0),那它也会被转换为整数,如下例所示:
let floatNum1 = 1.; // 小数点后面没有数字,当成整数1处理
let floatNum2 = 10.0; // 小数点后面是零,当成整数10处理
对于非常大或非常小的数值
,浮点值可以用科学记数法
来表示。科学记数法用于表示一个应该乘以 10 的给定次幂的数值。JavaScript 中科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母 e,再加上一个要乘的 10 的多少次幂。比如:
let floatNum = 3.125e7; // 等于31250000
科学记数法也可以用于表示非常小的数值,例如 0.000 000 000 000 000 03。这个数值用科学记数法可以表示为3e-17。默认情况下,JavaScript 会将小数点后至少包含6个零的浮点值转换为科学记数法(例如,0.000 000 3会被转换为3e-7)。
指数部分以“e”或“E”开头,后面跟着一个整数,可以有正负号(即前缀“+”或“-”)。浮点数字面量至少有一位数字,而且必须带小数点或者“e”(大写“E”也可)。
简言之,其语法是:
[(+|-)][digits][.digits][(E|e)[(+|-)]digits]
浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确。例如,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04。由于这种微小的舍入错误,导致很难测试特定的浮点值。比如下面的例子:
let a = 0.1;
let b = 0.2;
if (a + b == 0.3) { // 别这么干!
console.log("true");
} else {
console.log("false")
}
这里检测两个数值之和是否等于 0.3。如果两个数值分别是 0.05 和 0.25,或者 0.15 和 0.15,那没问题。但如果是 0.1 和 0.2,如前所述,测试将失败。因此永远不要测试某个特定的浮点值。
2.3 值的范围
由于内存的限制,JavaScript 并不支持表示这个世界上的所有数值。JavaScript可以表示的最小数值保存在 Number.MIN_VALUE
中,这个值在多数浏览器中是 5e-324
;可以表示的最大数值保存在 Number.MAX_VALUE
中,这个值在多数浏览器中是 1.797 693 134 862 315 7e+308
。
如果某个计算得到的数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity(无穷)值。任何无法表示的负数以 -Infinity(负无穷大)表示,任何无法表示的正数以 Infinity(正无穷大)表示。
我们可以通过除以 0 来得到它:
alert( 1 / 0 ); // Infinity
如果计算返回正 Infinity 或负 Infinity,则该值将不能再进一步用于任何计算。这是因为 Infinity 没有可用于计算的数值表示形式。要确定一个值是不是有限大(即介于 JavaScript 能表示的最小值和最大值之间),可以使用 isFinite() 函数,isFinite(value)
将其参数转换为数字,如果是常规数字而不是 NaN/Infinity/-Infinity
,则返回 true
:
alert( isFinite("15") ); // true
alert( isFinite("str") ); // false,因为是一个特殊的值:NaN
alert( isFinite(Infinity) ); // false,因为是一个特殊的值:Infinity
有时 isFinite
被用于验证字符串值是否为常规数字:
let num = +prompt("Enter a number", '');
// 结果会是 true,除非你输入的是 Infinity、-Infinity 或不是数字
alert( isFinite(num) );
- 注意使用 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以获取正、负Infinity。没错,这两个属性包含的值分别就是 -Infinity 和 Infinity。
- 在所有数字函数中,包括
isFinite
,空字符串或仅有空格的字符串均被视为0
。
2.4 NaN
有一个特殊的数值叫 NaN
,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。NaN
是全局对象的一个属性。换句话说,它是全局作用域中的一个变量。NaN
的初始值不是数字——与 Number.NaN
的值相同。在现代浏览器中,NaN
是一个不可配置、不可写的属性。即使不是这样,也要避免重写它。在程序中很少使用 NaN
。
有五种不同类型的操作返回 NaN
:
-
失败的数字转换(例如,显式转换,如
parseInt("blabla")
、Number(undefined)
,或隐式转换,如Math.abs(undefined)
) -
计算结果不是实数的数学运算(例如,
Math.sqrt(-1)
) -
不定式(例如,
0 * Infinity
、1 ** Infinity
、Infinity / Infinity
、Infinity - Infinity
) -
一个操作数被强制转换为
NaN
的方法或表达式(例如,7 ** NaN
、7 * "blabla"
)——这意味着NaN
具有传染性 -
将无效值表示为数字的其他情况(例如,无效的 Date
new Date("blabla").getTime()
、"".charCodeAt(1)
)
NaN
及其行为不是 JavaScript 发明的。它在浮点运算中的语义(包括 NaN !== NaN
)是由 IEEE 754 指定的,NaN
有几个独特的属性:
- 如果
NaN
涉及数学运算(但不涉及位运算),结果通常也是NaN
。(只有一个例外:NaN
** 0
结果为 1 )。 - 当
NaN
是任何关系比较(>
,<
,>=
,<=
)的操作数之一时,结果总是false
。 NaN
不等于(通过==
、!=
、===
和!==
)任何其他值——包括与另一个NaN
值。
要判断一个值是否为 NaN
,可以使用 Number.isNaN()
或 isNaN()
来最清楚地确定一个值是否为 NaN
。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给 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
请注意 isNaN()
和 Number.isNaN()
之间的区别:如果当前值是 NaN
,或者将其强制转换为数字后将是 NaN
,则前者将返回 true
。而后者仅当值当前为 NaN
时才为 true
:
isNaN("hello world"); // true
Number.isNaN("hello world"); // false
出于同样的原因,使用 BigInt 值时 isNaN()
会抛出错误,而 Number.isNaN()
不会:
isNaN(1n); // TypeError: Conversion from 'BigInt' to 'number' is not allowed.
Number.isNaN(1n); // false
此外,一些数组方法不能找到 NaN
,而另一些可以。也就是说,查找索引的(indexOf()
、lastIndexOf()
)不能找到 NaN
,而查找值的(includes()
)可以:
const arr = [2, 4, NaN, 12];
arr.indexOf(NaN); // -1
arr.includes(NaN); // true
// Methods accepting a properly defined predicate can always find NaN
arr.findIndex((n) => Number.isNaN(n)); // 2