数据类型
1、概述
为了方便数据的计算和表示、尽可能地模拟客观世界的数据和物体,JavaScript提供了8种数据类型,其中除了Object属于对象类型(Object Type)之外,其余的7种属于基本类型(Primitive Type),全部的数据类型如下:
- Number:数字类型
- Boolean:布尔类型
- String:字符串类型
- Null:空类型
- Undefined:未定义类型
- Symbol:符号类型
- BigInt:超大整数类型
- Object:对象类型
对象是一种结构化的类型,利用它能够创建出多种多样的数据类型,例如JavaScript内置的Array、Map、WeakMap、Set、WeakSet等都属于对象类型。
typeof
由于JavaScript不提供定义类型的方式,而是动态地判断类型,所以就需要一些方法来判断当前的变量是哪种数据类型。JavaScript中提供了typeof关键字获取数据类型,在它后边写上要获取数据类型的字面值或变量,那么它就会返回实际数据类型的字符串表示形式,代码如下:
js
typeof 5; //"number"
typeof true; //"boolean"
let str="hello";
typeof str; //"string"
typeof{prop:"value"}; //"object"
typeof function(){}; //"function"
使用typeof可以准确地获取基本数据类型,但是对于对象类型,则一般会返回"object",并没有具体实际的意义,例如下方代码中的typeof都会返回"object",代码如下:
js
typeof[1,2,3];
typeof new Date();
typeofMath;
typeof/\w+/;
针对这种情况,JavaScript提供了instanceof关键字来判断一个值是否由某个对象创建出来的实例,这将在第7章对象部分再作介绍。不过,一般在判断特定对象类型时,该对象类型都提供了自己的逻辑进行判断,例如Array中的isArray()方法可以判断某个值是否为数组类型。对于自定义的对象类型,也可以自己编写一套判断逻辑。
2、Number
Number类型主要用于数学计算。常见的数字,例如1、2、3、2.45、-13.25等都属于Number类型,它们是Number类型的字面值。如果有其他编程语言基础,例如Java,则可能知道数字分为整型(Integer)、双精度浮点型(Double)和单精度浮点型(Float)等,而JavaScript则不一样,它只有一个Number数字类型,既可以用来表示整数,又可以用来表示小数,即实数。
js
let num=10;
typeof num;//"number"
JavaScript Number类型是使用64位双精度(IEEE 754)浮点数表示的,类似于Java中的double数据类型。它可以表示的最大值和最小值可以通过Number对象提供的MAX_VALUE和MIN_VALUE属性获取,代码如下:
js
Number.MAX_VALUE //1.7976931348623157e+308(e是科学记数法,代表10的n次幂
//这里即是10^308,^代表次幂,下同)
Number.MIN_VALUE //5e-324(即5*10^-324)
而它可以表示的整数范围可以通过内置的MAX_SAFE_INTEGER和MIN_SAFE_INTEGER属性获得,代码如下:
js
Number.M AX_SAFE_INTEGER //9007199254740991
Number.M IN_SAFE_INTEGER //-9007199254740991
如果超过这些范围,则会丢失精度,导致表示的数字不准确,从而引发计算错误,代码如下:
js
console.log(9007199254740993); //9007199254740992
console.log(-9007199254740993); //-9007199254740992
2.1 不同进制表示法
在JavaScript中,可以使用二进制、八进制、十进制和十六进制表示数字,它们底层都是用二进制表示的。这些不同进制的表示法在某些应用程序中会非常有帮助,尤其是涉及底层数据的操作,例如JavaScript中的TypedArray是一组操作内存数据的数组,它里边的元素都是以十六进制进行存储的。
在JavaScript中,不同进制的数字都属于Number类型,只是用特殊的标记来区分进制,常见的有:
0b开头表示二进制。0o开头表示八进制,注意后边的为小写字母o。0x开头表示十六进制。
js
0b011 //二进制使用0b或0B开头
0o107 //八进制使用0o或0O开头
0xF1A3 //十六进制使用0x或0X开头
2.2、数字分隔线
如果要表示的数字位数过长,或者需要按特定位数进行分组,则可以使用下画线对数字进行分隔(所有进制均可),以便于阅读,代码如下:
js
1000_0000_0000 //整数
22.4211_3677_7478 //小数
0b1100_0010_1101 //二进制
2.3、特殊数字
JavaScript中内置了表示特殊数字的方式,用于一些无法用具体数字表示的概念,例如无限大、非数字等,它们在用于进行数学计算和错误处理的时候会让某些特殊条件更易于表现。
表示无限大或者无限小可以分别使用Infinity和-Infinity。例如在数据结构和算法应用中,计算图(Graph)的最短路径时,如果最初默认所有节点到其他节点的距离都是无限远,则可以使用Infinity来表示。又例如在寻找一系列数字中的最小值时,可以先把最终结果初始化为-Infinity,当找到第1个最小的数字时再把-Infinity替换为该数字。
Number中还有表示非数字的NaN(Not a Number),这种情况一般是由于使用了数字和非数字类型进行数学计算,它们的返回结果就是NaN,代码如下:
js
10*"str"; //NaN
let maybeANumber=undefined;//不小心没赋值
maybeANumber/10; //NaN
判断计算结果是不是NaN,可以使用JavaScript全局对象中的isNaN()或者Number对象中的isNaN()方法,它会返回一个布尔类型的值,true代表非数字,false代表数字,代码如下:
js
isNaN(10*"str") //true
Number.isNaN(5-3) //false
关于JavaScript中的数字有一点需要注意:在使用Number类型计算小数时,由于JavaScript使用了IEEE 754规范表示数字,在此规范中,小数并不使用十进制表示,而是使用二进制,这就导致了一个常见的现象:0.1+0.2=0.30000000000000004。这是因为像0.1和0.2这种数字,并不能用二进制精确地表示。在十进制中,0.1可以写成是1/10,很容易表示,但是当用二进制表示时,分母10并不是2的倍数,所以最后表现出来的二进制数字是无限循环的,这个就好比是在十进制中表示1/3,它的结果是0.3333333......。那么0.1+0.2在这里取近似值之后,就得到了0.30000000000000004这样的数字。在JavaScript中进行精确数学计算时,最好不要使用小数,可以尝试把小数统一乘上10的倍数转换成整数之后,再除以相同的倍数,得到小数;或者使用开源的数学计算库进行计算。
3、Boolean
Boolean类型用来表示逻辑关系,它只有两个值,true和false,代表真和假。例如在比较两个数字是否相同时,相同则为true,不同则为false。
js
let result=true;
let isError=false;
typeof result //"boolean"
4、String
定义字符串有3种方式,双引号、单引号和反引号,字符串必须在成对的引号之间,代码如下:
js
let doubleQuote="这是用双引号定义的字符串";
let singleQuote= '这是用单引号定义的字符串';
let backtick=`这是用反引号定义的字符串`;
双引号字符串是最常见的定义方式,但是它里边不能有其他双引号,这样会导致字符串提前结束,而后边的字符会被JavaScript认为是非法字符。如果一段字符本身有双引号,则这时可以使用单引号的方式定义字符串。单引号可以用于字符串本身含有较多双引号的情况,例如定义一段HTML模板代码,代码如下:
js
'<div class="box"/>'
使用双引号或单引号定义的字符串需要放在一行代码中,如果需要换行,则需要在换行的地方使用\来接续,代码如下:
js
let multiLine="这是一个\
长字符串";
或者也可以使用+拼接多行字符串,代码如下:
js
let multiLine="这是一个"
+"长字符串";
如果需要完整地保留一段字符串的格式,则使用转义字符就会非常烦琐、难以阅读。这个时候,反引号定义的模板字符串(TemplateLiterals/Strings)就派上用场了。
模板字符串有两个作用:
- 保留字符串的原始格式。
- 可以使用JavaScript中的变量和表达式。
js
//chapter2/string1.js
const str=`这是一个多行文本。
这一行顶格写,输出的结果也是顶格的。
这一行有缩进,结果也会缩进。
这一行有"双引号"、'单引号'符号,它们也会原样输出。
转义字符\n仍然会生效。如果想再输出\`反引号\`,还需要转义。
`;
console.log(str);
js
let a=5;
`a+5=${a+5}`;//"a+5=10"
5、Null与Undefined
Null和Undefined是很容易混淆的两种类型,它们都用来表示空值,但是在意义和用法上还是有一定的区别的。Null类型的值有且只有一个:null。根据现行的ECMAScript规范,使用typeof获取null的类型会返回object。
如果想让一个变量代表的是空值,例如当收集用户输入的信息时,如果用户没有输入信息,就应该使用null,这表示变量是已定义的,只不过它的值是空白,代码如下:
js
const inputValue = null;
Undefined类型的值也只有一个:undefined,意思是未定义的,代表没有任何值的含义,也就是说这个值不存在。当定义一个变量但没有赋值时,这个变量的值就是undefined,代码如下:
js
let num;
console.log(num) //undefined(实际的undefined值)
typeof num; //"undefined"(undefined类型的字符串表示)
6、Object
对象(Object)是由一对大括号定义的,里边使用键值对(Key ValuePair)作为它的内容。键值对由3部分构成:字符串类型的键、冒号和任意类型的值,key:value。在对象里,多个键值对使用逗号隔开(最后一个键值对后边的逗号可以省略),代码如下:
js
let person={
name:"张三",
age:20,
student:true
}
typeof person; //"object"
键值对在对象中也叫作属性(Property),键是属性名,值为属性值,定义属性名时可以省略双引号,但是如果属性名中包括中画线、空格等无效字符,就需要使用双引号,代码如下:
js
let someObj={
"some prop":"value"
}
访问对象属性的值可以使用.号加上属性名,而对于含有无效字符的属性,则可以使用方括号语法访问它,在里边使用字符串形式写出要访问的属性的名字,代码如下:
js
person.name; //张三
someObj["some prop"];//"value"
修改对象属性的值,可以在访问对象属性的基础上,使用=来赋予它新值,代码如下:
js
person.name="李四";
someObj["some prop"]="other value";
另外使用方括号形式,也可以把属性的名字放到变量中,这样可以动态地使用变量的值作为对象的属性名,代码如下:
js
let prop="some prop";
someObj[prop]="other value";
上边修改属性的操作,如果指定了对象中不存在的属性,则会自动将一个新的属性添加到对象中,代码如下:
js
let person={
name:"张三"
}
person.age=20; //person:{name:'张三',age:20}
7、Symbol
Symbol类型是在ES6规范中才出现的数据类型,是一种值不会重复的数据类型。它可以作为对象的属性名,防止后续被覆盖。定义符号类型的值,可以使用Symbol()函数,然后在括号里边传递一个字符串类型的值,用于描述这个符号:let sym=Symbol("a symbol")。后边可以使用===运算符来验证它每次返回的值是不是相等,代码如下:
js
Symbol("a symbol")===Symbol("a symbol");//false
当把它用作对象中的属性时,可以防止已有的属性被修改,代码如下:
js
//chapter2/symbol1.js
let obj={};
let prop=Symbol("prop");
let prop2=Symbol("prop");
obj[prop]="value1";
obj[prop2]="value2";
obj[prop]; //value1
obj[prop2];//value2
可以看到,虽然给Symbol()传递的值都是一样的,但由于它返回的值是不同的,所以对象中会有两个使用Symbol表示的属性,分别对应了不同的值。
Symbol类型有一个全局符号注册表(Global Symbol Registry)的概念,它用来管理所有已创建的Symbol符号的值。使用注册表的方式定义符号时,如果符号已存在,就会返回已有的符号,如果不存在就会创建新的。在注册表中创建符号使用Symbol.for(),同样地,可以在小括号中给它传递一个描述字符串:Symbol.for("a symbol")。使用注册表创建符号的好处是,如果没有把符号保存到变量中,则还可以通过注册表找到这个符号。还用之前的对象例子,这次使用注册表形式,代码如下:
js
//chapter2/symbol2.js
let obj={};
obj[Symbol.for("prop")]="value1";
obj[Symbol.for("prop")]; //value1
obj[Symbol.for("prop")]="value2";
obj[Symbol.for("prop")]; //value2
8、BigInt
BigInt类型是在ES2020规范中定义的,可以用于超大整数的计算,这些数字可能大于Number.MAX_SAFE_INTEGER或小于Number.MIN_SAFE_INTEGER规定的范围。
要定义BigInt类型的字面值,可以在整数后面加上字母n:133245668733399999222n。超大数字之间可以使用数学运算符进行操作,代码如下:
js
console.log(133245668733399999222n+5888888888n);//133245668739288888110n
但是,BigInt类型不可以和Number类型进行数学计算,否则会抛出错误,代码如下:
js
//TypeError:Cannot mix BigInt and other types,use explicit conversions
//不可以混合使用BigInt和其他类型,请使用显式类型转换
console.log(133245668733399999222n+5888888888);//
9、Array
数组是用来保存一系列连续的数值的集合。确切地说,它并不是一个单独的类型,而是基于Object对象类型创建出来的特殊类型。数组类型可以方便地对一组数据进行循环访问、计算、变换和存储。现今流行的数据科学中的数据操作大部分以数组的形式进行。
定义数组使用方括号[],里边多个数值使用逗号分隔,这里建议数组里存放统一的类型,便于应用通用的处理逻辑,例如全部存储为数字、全部存储为字符串等,代码如下:
js
let nums=[1,2,3];
let names=["张三","李四","王五"];
因为数组是一个特殊的对象,所以它也有一些内置的属性,方便获取数组的信息。例如获取数组的长度,可以使用length属性,代码如下:
js
nums.length;//3
数组中的每个元素都有索引(Index),或称为下标,用于存取指定位置的元素,索引从0开始,一直到数组的长度减1。访问元素可以使用[]并在里边写上元素对应的索引,代码如下:
js
nums[0];//1
如果访问不存在的索引,则会返回undefined。
要修改某个位置的元素的值,可以在访问元素的基础上,直接使用=赋值,代码如下:
js
nums[1]=5;
nums[1]; //5,数组变成了[1,5,3]
10、数据类型转换
10.1、隐式类型转换
当JavaScript编译器能够自动转换类型时,就会发生隐式类型转换。类型转换多数情况是把任意类型的值转换为Number、String、Boolean类型。例如当数字类型与字符串类型做加法操作时,会把数字转换成字符串,代码如下:
js
1+"2";//"12"
数字与字符串相减时,则会把字符串转换为数字,前提是字符串的内容本身就是数字,代码如下:
js
"2"-1;//1
数字类型与布尔类型做数学计算时,会将布尔值转换为数字,将true转换为1,将false转换为0,代码如下:
js
3+true; //4
5+false;//5
当用在逻辑运算和if条件判断中时,里边的数据类型会转换成布尔类型,代码如下:
js
//true
if(1){}
//false
if(null){}
常见的隐式数据类型转换如表所示:

10.2、显式类型转换
每个基本数据类型(除了null和undefined之外),都有对应的包装对象,提供了更多的方法和属性,用于提供额外的信息和额外的操作。显式类型转换就是调用了包装对象中的方法(Method)实现的。基本类型的包装对象有以下几个:
- 数字类型的Number对象。
- 布尔类型的Boolean对象。
- 字符串类型的String对象。
- 符号类型的Symbol对象。
- 超大整数类型的BigInt对象
如果要把其他类型的值转换为目标类型,则只需调用它们的构造函数,构造函数将会在第7章对象中介绍,这里只需知道它跟对象的名字保持一致,然后可以像普通函数一样调用就可以了。例如把一个布尔类型的值转换为数字类型:Number(true)会返回1,把数字转换为字符串:String(10)会返回10。另外,这些包装对象还提供了toString()方法来直接把字面值转换为字符串(字面值可以自动转换为包装对象类型),代码如下:
js
true.toString();//"true"
true是Boolean类型的值,在调用.toString()时会先把true转换为使用Boolean对象表示,然后调用它里边的toString()方法。
这里需要注意的是,数字类型的字面值不可以直接使用.运算符访问包装对象中的方法,这是因为.在数字中的意义是表示小数,JavaScript会尽可能地把类似数字的值转换为数字,例如1.和5.这样值会被认为是1.0和5.0,那么要想访问Number对象中的方法,可以再多加一个点号,或者使用小括号,代码如下:
js
1..toString(); //"1"
(5).toString();//"5"