这篇文章继续分享高程4第三章的内容。
3.4.3 Null 类型
Null 类型同样只有一个值,即特殊值 null 。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 "object" 的原因。
在定义将来要保存对象值的变量时,建议使用 null 来初始化,不要使用其他值。这样,只要检查这个变量的值是不是 null 就可以知道这个变量是否在后来被重新赋予了一个对象的引用。
undefined 值是由 null 值派生而来的,因此ECMA-262将它们定义为表面上相等(也就是“null == undefine”结果是true, "null === undefine"结果是false)
即使 null 和 undefined 有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将变量值设置为 undefined 。但null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持null 是空对象指针的语义,并进一步将其与 undefined 区分开来
null 是一个假值。因此,如果需要,可以用更简洁的方式检测它。一定要明确自己想检测的就是 null 这个字面值,而不仅仅是假值。
3.4.4 Boolean 类型
Boolean (布尔值)类型是ECMAScript中使用最频繁的类型之一,有两个字面值: true 和 false 。这两个布尔值不同于数值,因此 true 不等于1, false 不等于0。
注意,布尔值字面量 true 和 false 是区分大小写的,因此True 和 False (及其他大小混写形式)是有效的标识符,但不是布尔值。
其他ECMAScript类型的值可以转成布尔值,转换需要调用特定的 Boolean() 转型函数。(Boolean方法如果跟着new一起用,那就是构造函数,否则是在进行类型转换。)什么值能转换为 true或 false 的规则取决于数据类型和实际的值。
除了假值以外,任何值转成布尔值,结果都为true。假值包括如下几个:false,""(空字符串),0,NaN,null和undefined
我们会直接判断某个变量是否为假值,来控制流程(if语句会把结果隐式转成布尔值) ,就可以知道它有没有被正常地赋值过。比如这样:
let message = "Hello world!";
if (message) {
console.log("Value is true");
}
3.4.5 Number 类型
Number 类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式。
最基本的数值字面量格式是十进制整数,直接写出来即可。
整数也可以用八进制(以8为基数)或十六进制(以16为基数)字面量表示。对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值0~7)如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数。八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误(ECMAScript 2015或ES6中的八进制值通过前缀 0o(小写字母o) 来表示,前缀0会被设为语法错误)
要创建十六进制字面量,必须让真正的数值前缀 0x (区分大小写,这里是小写字母),然后是十六进制数字(0~9以及A~F)。十六进制数字中的字母大小写均可。
使用八进制和十六进制格式创建的数值在所有数学操作中都被视为十进制数值。
注意 由于JavaScript保存数值的方式,实际中可能存在正零(+0)和负零(-0)。正零和负零在所有情况下都被认为是等同的,这里特地说明一下。(在《你不知道的JavaScript》中有说明“正零”和“负零”的诞生,假如一个正数乘0,得到的是正零,假如一个负数乘0,得到的是负零。即使是用===,也无法区分正零和负零。区分正零和负零的方法是:让1除以它们,除以正零得到的是“正无穷”,否则得到“负无穷”)
1. 浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上(小数点前面如果没有整数,则表示“零点几”)
因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。小数点如果后面没有数字也是合法的,但是会当成整数来处理;小数点后面的数字如果是“0”,也会当成整数处理。如:
let floatNum1 = 1.; // 小数点后面没有数字,当成
整数1处理
let floatNum2 = 10.0; // 小数点后面是零,当成整数
10处理
对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10的给定次幂的数值,格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂。如:
let floatNum = 3.125e7; // 等于31250000
科学记数法也可以用于表示非常小的数值,例如0.000 000 000 000000 03。这个数值用科学记数法可以表示为3e-17。默认情况下,ECMAScript会将小数点后至少包含6个零的浮点值转换为科学记数法
浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.300 000 000 000 00004。由于这种微小的舍入错误,导致很难测试特定的浮点值。
如果两个数值分别是0.05和0.25,或者0.15和0.15,那没问题。但如果是0.1和0.2,如前所述,测试将失败。因此永远不要测试某个特定的浮点值。
注意 之所以存在这种舍入错误,是因为使用了IEEE 754数值,这种错误并非ECMAScript所独有。其他使用相同格式的语言也有这个问题。
2. 值的范围
由于内存的限制,ECMAScript并不支持表示这个世界上的所有数值。ECMAScript可以表示的最小数值保存在Number.MIN_VALUE 中,这个值在多数浏览器中是5e-324;可
以表示的最大数值保存在 Number.MAX_VALUE 中,这个值在多数浏览器中是1.797 693 134 862 315 7e+308。
如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity (无穷)值。任何无法表示的负数以 -Infinity (负无穷大)表示,任何无法表示的正数以 Infinity (正无穷大)表示。
如果计算返回正 Infinity 或负 Infinity ,则该值将不能再进一步用于任何计算。这是因为 Infinity 没有可用于计算的数值表示形式。要确定一个值是不是有限大(即介于JavaScript能
表示的最小值和最大值之间),可以使用 isFinite() 函数。
let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); // false
在计算非常大或非常小的数值时,有必要监测一下计算结果是否超出范围。
注意 使用 Number.NEGATIVE_INFINITY 和Number.POSITIVE_INFINITY 也可以获取正、负Infinity 。没错,这两个属性包含的值分别就是 -Infinity 和 Infinity 。
3. NaN
有一个特殊的数值叫 NaN ,意思是“不是数值”(Not aNumber),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在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); // -Infinity
NaN 有几个独特的属性。首先,任何涉及 NaN 的操作始终返回 NaN;其次, NaN 不等于包括 NaN 在内的任何值。(这句话应该理解成,NaN不等于它本身,NaN是唯一一个不等于本身的值)
ECMAScript提供了 isNaN() 函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给 isNaN() 后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串 "10" 或布尔值。任何不能转换为数值的值都会导致这个函数返回 true(比如含有字母的字符串之类的,明明它们并不是NaN)。
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() 可以用于测试对象。此时,首先会调用对象的 valueOf() 方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用 toString() 方法,并测试其返回值。
4. 数值转换
有3个函数可以将非数值转换为数值: Number() 、parseInt() 和 parseFloat() 。
Number() 是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。
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()遵循相同的转换规则)
考虑到用 Number() 函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用 parseInt() 函数。
parseInt() 函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号, parseInt() 立即返回
NaN 。这意味着空字符串也会返回 NaN (这一点跟Number() 不一样,它返回0)如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。(因为我们是要转成整数,所以小数点也算非数值字符)
假设字符串中的第一个字符是数值字符, parseInt() 函数也能识别不同的整数格式(十进制、八进制、十六进制)。换句话说,如果字符串以 "0x" 开头,就会被解释为十六进制整数。如果字符串以 "0" 开头,且紧跟着数值字符,就会被解释为八进制整数。
(我个人给Number和parseInt做个小总结:1.在使用对象上,Number对任意类型使用,parseInt使用的对象是字符串类型的;2.同样针对字符串转换的时候,Number遇到一个非数字合法值的字符,就得到NaN,parseInt在前面的字符是数字合法值(+,-,数字)的时候还是能得到数字; 3.Number如果遇到空字符串,会得到0,而parseInt由于第一个字符就无法转换而得到NaN; 4.Number转换结果是十进制的,而parseInt可以把数值解析成八进制或十六进制 ,)
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() 也接收第二个参数,用于指定底数(进制数)(也就是指定要把这个值当成几进制的数)如果提供了十六进制参数,那么字符串前面的 "0x" 可以省略:
let num = parseInt("0xAF", 16); // 175
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,按十六进制解析
因为不传底数参数相当于让 parseInt() 自己决定如何解析,所以为避免解析出错,建议始终传给它第二个参数。
parseFloat() 函数的工作方式跟 parseInt() 函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现
的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。
parseFloat() 函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回0。因为 parseFloat() 只解析十进制值(因为其他进制的前缀都以0开头,忽略了0就是忽略了进制),因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则 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"); //31250000