3.4:数据类型
ECMAScript有6种简单数据类型(也称为原始类型):Undefined、Null、Boolean、Number、String和Symbol(符号)是ECMAScript6新增的.还有一种复杂数据类型叫做Object(对象)。Object是一种无序名值对的集合
3.4.1: typeof操作符
ECMAScript的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。typeof就是未来来检测数据类型的存在.
- "undefined":表示值未定义
- "boolean":表示值为布尔值
- "string":表示值为字符串
- "number":表示值为数值;
- "object":表示值为对象(而不是函数)或null;
- "function":表示值为函数
- "symbol":表示值为符号
例子如下:
let message = "some string";
console.log(typeof message); //"string"
console.log(typeof(message)) //"string"
console.log(typeof 95) //"number"
在上面这个例子中,把一个变量(message)和一个数值传给了typeof操作符。因为typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数),注意typeof在某些情况下返回的结果可能让人费解,但技术上讲是正确的,调用typeof null 返回时 "object"。因为特殊值 null 被认为是一个对空对象的引用
[注意] 严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是, 函数也有自己特殊的属性。为此,就有必要通过 typeof 操作符来区分函数和其他对象。
3.4.2:Undefined类型
Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值:
let message;
console.log(message == undefined) //true
在上面这个例子中,变量message在声明的时候并未初始化。而在比较它和undefined的字面值时,两者是相等的。这个例子等同如下实例:
let message = undefined;
console.log(message == undefined) //true
注意的点,包含undefined值的变量跟未定义的变量是有区别的,例子如下:
let message; //这个变量被声明了,只是值为undefined
//确保没有声明过这个变量
let age;
console.log(message) //'undefined'
console.log(age)// 报错
在上面的例子,第一个console.log()会指出变量message的值,即"undefined"。而第二个console.log()要输出一个未声明的变量age的值,因此会导致报错,对未声明的变量,只能执行一个有用的操作,就是对它调用typeof。(对未声明的变量调用delete也不会报错,但这个操作没什么卵用)
在对未初始化的变量调用typeof时,返回的结果是"undefined",但对未声明的变量调用它时,返回的结果还是"undefined",这就有点让人不懂💤
let message; //这个变量被声明了,只是值为undefined
//确保没有声明过这个变量
let age;
console.log(typeof message) // "undefined"
console.log(typeof age) // "undefined"
无论是声明还是未声明,typeof返回的都是字符串"undefined",虽然严格来讲这两个变量存在根本性差异,但它们都是无法执行实际操作。
[注意] 要清楚知道自己想要检测的是undefined这个字面值,undefined是一个价值,其他也有很多同样的值是假值。
let message; // 这个变量被声明了,只是值为undefined
// age 没有声明
if (message) { // 这个块不会执行
}
if (!message) { // 这个块会执行
}
if (age) {
// 这里会报错
}
3.4.3:Null类型
Null类型同样只有一个值,即特殊值null,逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回 "object"的原因:
let car = null;
console.log(typeof car) //"object"
在定义将来要保存对象的变量时,建议时使用null来进行初始化,不要使用其他值,这样,只要检查这个变量的值是不是null就可以知道这个便利那个是否在后来被重新赋予了一个对象的引用,例子如下:
if(car != null) {
//car是一个对象的引用
}
undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等,如下面例子所示:
console.log(null == undefined); //true
用等于操作符(==)比较null 和 undefined 返回的始终是true,但⚠️的点,这个操作符会为了比较而转换它的操作数
即使null 和 undefined有关系,它们的用途也是完全不一样的,如前所述,永远不必显示地将变量值设置为undefined,但null不是这样,任何时候,只要变量保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义,并进一步将其与undefined区分开来
null是一个假值。因此可以用更简洁的方式检测它,不过要注意的点是也有很多其他可能的值是假值,所以还是那句话,先清楚自己想检测的就是null这个字面值,而不仅仅是假值
let message = null;
let age;
if (message) {
//这个块不会执行
}
if(!message) {
//这个块会执行
}
if (age) {
//这个块不会执行
}
if(!age) {
//这个块会执行
}
3.4.4:Boolean类型
Boolean(布尔值)类型是ECMAScript中使用最频繁的类型之一,有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0.例子如下:
let found = true;
let lost = false;
注意:布尔值true和false是区分大小写的,因此True和False是有效的标识符,但不是布尔值.
虽然布尔值只有两个,但所有其他ECMAScript类型的值都有相应布尔值等价形式,可以用Boolean()把其他类型的值转换成布尔值。
let message = "Hello world";
let messageAsBoolean = Boolean(message)
上面这个例子中,message被转换成布尔值并保存在变量messageAsBoolean中,Boolean()转型函数可以在任何类型的数据上调用,而且始终都返回true or false,规则如下:
| 数据类型 | 转换为true的值 | 转换为false的值 |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | ""(空字符串) |
| Number | 非零数值(包括无穷值) | 0,NaN(后面文章解释) |
| Object | 任意对象 | null |
| Undefined | N/A(不存在) | undefined |
理解以上转换规则非常的重要‼️,因为像if等流程控制语句会自动执行其他类型值到布尔值的转换
let message = "Hello world";
if(message) {
console.log("Value is true")
}
请根据表格理解上面代码块的执行为什么这么执行?
3.4.5:Number类型
ECMAScript中最有意思的数据类型maybe就是Number了,Number类型使用了IEEE 754格式表示整数和浮点值(双精度值)。不同的数值类型也相应有不同的数值字面量格式
最基本的数值字面量就是十进制整数,直接写出来即可:
let intNum = 55; //整数
整数也可以用八进制(以8为基数)或十六进制(以16为基数)字面量表示。对八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值0 ~ 7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制,如下所示:
let octalNum1 = 070; //八进制的56
let octalNum2 = 079; //无效的八进制值,当成79处理
let octalNum3 = 08;//无效的八进制值,当成8处理
注意:八进制字面量在严格模式下是无效的,会导致语法错误
要创建十六进制字面量,必须让真正的数值前缀0x (区分大小写),然后是十六进制数字(0-9以及 A ~ F)。十六进制数字中的字母大小写都问题不大,例子如下:
let hexNum1 = 0xA; // 十六进制10
let hexNum2 = 0x1f; // 十六进制31
3.4.5.1:浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字,虽然小数点前面不是必须有整数,但推荐加上。例子如下:
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; //有效,但不推荐
因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换成为整数,在小数点后面没有数字的情况下,数值会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),也会转成整数
let floatNum1 = 1.; // 小数点后面没有数字,当成整数 1 处理
let floatNum2 = 10.0; // 小数点后面是零,当成整数 10 处理
对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10的给定次幂的数值。ECMAScript记数格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂,比如:
let floatNum = 3.125e7 // 等于31250000
在这个例子中,floatNum等于31 250 000,只不过科学记数法显得更加简洁,其实意思就是说以 3.125作为系数,乘以 10 的7次幂。
3.4.5.2:值的范围
由于内存的限制,ECMAScript并不支持表示这个世界所有数值,ECMAScript可以表示的最小数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是 5e-324;可以表示的最大数值保存在Number.MAX_VALUE 中,这个值在多数浏览器都是1.797 693 134 862 315 7e+308,如果某个计算得到的 数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity(无 穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以 Infinity(正 无穷大)表示。
如果计算返回infinity或负infinity可以使用isFinite()函数。
let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); //false
3.4.5.3:NaN
又一个特殊的值叫做NaN(Not a Number),用于表示返回数值的操作失败。JavaScript 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在内的任何值
console.log(NaN == NaN); //false
为此,ECMAScript 提供isNaN()函数,该函数接收一个参数,可以是任意数据类型,判断参数是否 ”不是数值“。把一个值传给isNaN()h后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值。
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 可以转换成数值
[注意] 任何不能转换为数值的值都会导致这个函数返回 true
以上五个不同的值,首先测试的是NaN本身,显然会返回true,10和字符串值 "10",都返回false是因为可以转换,字符串"blue"不能转换为数值,因此返回true,布尔值true是1,因此返回false
3.4.5.4:数值转换
有三个函数可以将非数值转换为数值:Number(),parseInt()和parseFloat()。Number()是转型函数,可用于任何数据类型.后两者主要用于将字符串转换为数值。对于同样的参数,这三个函数执行操作也不同。
Number()函数基于如下规则执行转换
-
布尔值,true转换为1,false转换为0
-
数值,直接返回。
-
null,返回0。
-
undefined,返回NaN。
-
字符串,应用以下规则
- 如果字符串包含数值字符,包括数值字符前面带 + - 号的情况,则转换为一个十进制数值,Number("1")返回1,Number("123")返回123,Number("011")返回11(忽略前面的0⃣️)
- 如果字符串包含有效的浮点值如"1.1",则会转换为相应的浮点值(忽略前面的零)
- 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值
- 如果是空字符串(不包含字符),则返回 0
- 如果字符串包含除上述情况之外的其他字符,则返回 NaN。
-
对象,调用 valueOf()方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用 toString()方法,再按照转换字符串的规则转换。
从不同数据类型到数值的转换有时比较复杂,看一看Number()的转换规则就知道了。具体例子如下:
let num1 = Number("hello world"); //NaN
let num2 = Number(""); //0
let num3 = Number("00001"); //11
let num4 = Number(true); 1
解释:字符串"hello world"转换之后是NaN,因为它找不到对应的数值,空字符串转换后是 0。字符串 000011 转换后是 11,因为前面的零被忽略了。最后,true 转换为 1。
Number函数转换字符串相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数,parseInt()函数更专注于字符串是否包含数值模式.字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数字字符、加号或减号,paseInt()立即返回NaN这意味着空字符串也会返回 NaN(这一点跟 Number()不一样,它返回 0)。如果第一个字符 是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如, "1234blue"会被转换为 1234,因为"blue"会被完全忽略。类似地,"22.5"会被转换为 22,因为小数 6 点不是有效的整数字符
假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八 进制、十六进制)。换句话说,如果字符串以"0x"开头,就会被解释为十六进制整数。如果字符串以"0" 开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数,例子如下:
let num2 = parseInt("");// NaN
let num3 = parseInt("0xA");// 10,解释为十六进制整数
let num4 = parseInt(22.5);// 22
let num5 = parseInt("70");// 70,解释为十进制值 9
let num6 = parseInt("0xf");// 15,解释为十六进制整数
parseInt()也接收第二个参数,用于指定底数(进制数)如果知道要解析的值时十六进制,那么可以传入16作为第二个参数,以便正确解析:
let num = parseInt("0xAF",16)
如果提供了十六进制参数,那么字符串前面的0x可以省掉:
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开始检测每个字符
- 解析字符串末尾或者解析到一个无效的浮点值字符为重
- 第一次出现小数点是有效的,但第二次无效
- 例子: "22.34.5" = 22.34
parseFloat()不同处:
-
始终忽略字符串开头的零,能识别前面讨论的所有浮点格式
-
以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回 0。因为 parseFloat()只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小 数点后面只有一个零),则 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
小总结:Number类型的函数区别很大,number()与parseInt()和parseFloat()区别在于参数,和解析机制的不同,结果也不同,不同点与相同点要去阅读区分。