JavaScript学习第六天

270 阅读14分钟

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的值
Booleantruefalse
String非空字符串""(空字符串)
Number非零数值(包括无穷值)0,NaN(后面文章解释)
Object任意对象null
UndefinedN/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()区别在于参数,和解析机制的不同,结果也不同,不同点与相同点要去阅读区分。