二、数据类型
0. 数据类型总览&typeof操作符
-
ECMAScript的数据类型如下:
(1)6中简单数据类型(也称原始类型):Undefined,Null,Boolean,Number,String,Symbol(ES6新增)
(2)1中复杂数据类型:Object——一种无序名值对的集合。 -
在JS中不能定义自己的数据类型,所有值都可以用上述的7种数据类型之一来表示,只有7种数据类型似乎不足以表示全部数据,但是js数据类型很灵活,一种数据类型可以当做多种数据类型来使用。
-
typeof操作符用来确定任意变量的数据类型:
let message = "some string"; // "string"
console.log(typeof message); // "string"
console.log(typeof (message)); // "number"
console.log(typeof 95);
- typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数)。
- 注意typeof在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用typeof null返回的是“object”。这是因为特殊值null被认为是一个对空对象的引用。严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeof操作符来区分函数和其他对象。
1.Undefined类型
- Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值。但是注意,包含undefined值的变量跟未定义变量是有区别的:
let message; //这个变量被声明了,只是值为undefined
//确保没有声明过这个变量
//let age
console.log(message); //“undefined”
console.log(age); //报错
/*对于未声明的变量,只能执行一个有用的操作,就是typeof,还有一个
没什么用的操作,就是delete,而且这个操作在严格模式下还是会报错。
*/
- undefined是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可能的值同样是假值。所以一定要明确自己想检测的就是undefined这个字面值,而不仅仅是假值
let message;//这个变量被声明了,只是值为undefined
//age没有声明
if(message){
//这个块不会执行
if(!message){
//这个块会执行
}
if(age){
//这里会报错
}
}
2.Null类型
- Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回’‘object’'的原因。
- 在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用
- 即使null和undefined有关系,他们的用途也是完全不一样的。永远不必显式地将变量值设置为undefined。但是null不是这样的,任何时候,只要变量要保存对象,而当时又没有哪个对象可以保存,就要用null来填充该变量,这样就可以保持null是空对象指针的语义,并且进一步将其与undefined区分开来
- 同样的,null是一个假值,因此,如果需要,可以用更简洁的方式检测它,不过要记住,也有很多其他可能的值同样是假值。所以一定要明确自己想检测的就是null这个字面值,而不仅仅是假值。
let message = null;
let age;
if(message){
//这个块不会执行
}
if(!message){
//这个块会执行
}
if(age){
//这个块不会执行
}
if(!age){
//这个块会执行
}
3.Boolean类型
- Boolean类型是JS中使用最频繁的类型之一,有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0。
- 虽然布尔值只有两个,但是所有其他的js类型的值都有相应的布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数:
//Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值,什么值能转换为true或false的规则取决于数据类型和实际的值。
let message = "Hello world!";
let messageAsBoolean = Boolean(message);
附件——不同类型与布尔值之间的转换规则:
·对于基本类型的数据:
·null和undefined直接转化为false
·字符串:空字符串转化为false,其他全为true
·数字:0和NaN转化为false,其他全为true
·任意非null的对象Object为true(null为false)
·undefined为false
4.Number类型
- 首先介绍最基本的数值格式
//最基本的数值字面量格式是十进制整数,直接写出来即可:
let intNum = 55; //整数
//整数也可以用八进制或十六进制字面量表示。对于八进制字面量,第一个数字必须是零,然后是相应的八进制数字(数值0-7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数,如下所示:
let octalNum1 = 070; //八进制的56
let octalNum2 = 079; //无效的八进制值,当成79处理
let octalNum3 = 08; //无效的八进制值,当成8处理
//八进制和字面量在严格模式下是无效的,会导致js引擎抛出语法错误。
//要创建十六进制字面量,必须让真正的数值前缀0x(区分大小写),然后是十六进制数字(0-9以及A~F)。十六进制数字中的字母大小写均可。
let hexNum1 = 0xA; //十六进制10
let hexNum2 = 0x1f; //十六进制31
//使用八进制和十六进制格式创建的数值在所有数学操作中都被视为十进制数值。
//由于js保存数值的方式,实际中可能存在正零(+0)和负零(-0)。正零和负零在所有情况下都被认为是等同的,这里特地说明一下。
(1)浮点值
- 要定义浮点数,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但是推荐加上。另外,因为存储浮点值使用的内存空间是存储整数值的两倍,所以js总是想方设法地把值转换为整数。在小数点后面没有数字的情况下,数值会变成整数。类似的,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那么他也会被转换成整数。
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; //有效,但不推荐
let floatNum4 = 1.; //小数点后面没有数字,当成整数1处理
let floatNum6 = 10.0 //小数点后面是零,当成整数10处理
- 对于非常大或者非常小的数值,浮点值可以用科学技术法来表示。科学计数法用于表示一个应该乘以10的给定次幂的数值。js中科学计数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写字母e,再加上一个要乘的10的多少次幂。
let floatNum1 = 3.125e7; //等于31250000
let floatNum2 = 3e-17; //等于0.00000000000000003
/*默认情况下,js会将小数点后至少包含6个零的浮点值
转化为科学计数法(例如,0.0000003会被转换为3e-7)。*/
- 浮点值的精确度最高可以达到17位小数,但是在算数计算中远不如整数精确。例如,0.1+0.2得到的不是0.3,而是0.30000000000000004。由于这种微小的舍入错误,导致很难测试特定的浮点值,比如下面的例子:
if(a+b == 0.3){ //别这么干
console.log("You got 0.3!");
}
/*这里检测两个数值之和是否等于0.3,如果两个数值分别是0.05和0.25,或者
0.15和0.15,那没问题。但如果是0.1和0.2,如前所述,测试将失败。因此
永远不要测试某个特定的浮点值*/
/*之所以存在这种舍入错误,是因为使用了IEEE754数值,这种错误并非js所独有,
其他使用相同格式的语言也有这个问题*/
(2)值的范围
由于内存的限制,js并不支持表示这个世界上的所有数值,js可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;可以表示的最大数只保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.7976931348623157e+308。如果某个计算得到的数值结果超过了js可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以Infinity(正无穷大)表示。
如果计算返回正 Infinity或者负Infinity,则该值将不能再进一步用于任何计算。这是因为Infinity没有可以用于计算的数值表示形式。要确定一个值是不是有限大(即介于js能表示的最小值和最大值之间),可以使用isFinite()函数,如下所示:
let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); //false
//虽然超出优先数值范围的计算并不多见,但是总归还是有可能的。因此在计算非常大或者非常小的数值时,有必要检测一下计算结果是否超出范围。
//注意,使用Number.NEGATIVE_INFINITY和Number.POSTIVIE_INFINITY也可以获取正、负Infinity。没错,这两个属性包含的值分别就是-Infinity和Infinity。
(3)NaN
有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但是在js中,0,+0,或者-0相除都会返回NaN,(ps:如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity)。
NaN有几个独特的属性。首先,任何涉及NaN的操作始终返回NaN,在连续多步计算时,这可能是个问题。其次,NaN不等于任何值,包括他自己。
为此,js提供了isNaN()函数。该函数接受了一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给isNaN()后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串"10"或者布尔值。任何不能转换为数值的值都会导致这个函数返回true:
console.log(isNaN(NaN)); //true
console.log(isNaN(10)); //true
console.log(isNaN("10")); //true
console.log(isNaN("blue")); //true
console.log(isNaN(true)); //true
/*上述的例子测试了5个不同的值,首先测试的NaN本身,显然会返回true。接着测试了数值10和字符串"10",都返回了false,因为他们的数值都是10.字符串“blue”不能转换为数值,因此函数返回true。布尔值true可以转换成数值1,因此返回false*/
(4)数值转换
- 有三个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这3个函数执行的操作也不同:
一、Number( ):
(1)如果是Boolean值,true和false将分别转换为1和0。
(2)如果是数字值,只是简单的传入和返回。
(3)如果是null值,返回0。
(4)如果是undefined,返回NaN。
(5)如果是字符串,遵循下列规则:
如果字符串截去开头和结尾的空白字符后,不是纯数字字符串,那么最终返回结果为NaN。
如果是字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即“1”变成1,“123”会变成123,而“011”会变成11(前导的零被忽略了);
如果字符串中包含有效的浮点格式,如“1.1”,则将其转换为对应的浮点数值(同样也会忽略前导零);
如果字符串中包含有效的十六进制格式,例如”0xf”,则将其他转换为相同大小的十进制整数值;
如果字符串是空的(不包含任何字符),则将其转换为0;
如果字符串中包含除上述格式之外的字符,则将其他转换成NaN.
(6)如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用的对象的toString()方法,然后再次依照前面的规则转换返回的字符串值。
var num1=Number(“Hello World”); //NaN
var num2=Number(""); //0
var num3=Number(“000011”); //11
var num4=Number(true); //1
var num5=Number(“num123”) //NaN
二、parseInt( ):
parseInt()函数可以将字符串转换成一个整数,与Number()函数相比,parseInt()函数不仅可以解析纯数字字符串,也可以解析以数字开头的部分数字字符串(非数字部分字符串在转换过程中会被去除)。
(1)如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN; 也就是说,用parseInt()转换空字符串会返回NaN。
(2)如果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。
(3)如果字符串以”0x”开头且后跟数字字符,就会将其当作一个十六进制整数。
(4)如果字符串以”0”开头且后跟数字字符,就会将其当作一个八进制整数。
(5)parseInt()函数增加了第二参数用于指定转换时使用的基数(即多少进制)。
(6)当parseInt()函数所解析的是浮点数字符串时,取整操作所使用的方法为“向下取整”。
var num1=parseInt(“num123”); //NaN
var num2=parseInt(""); //NaN
var num3=parseInt(“123.45”) //123
var num4=parseInt(“101010”,2) //42
var num5=parseInt(“123num”) //123
var num6=parseInt(“0xff”) //255
三、parseFloat( ):
与parseInt()一样,parseFloat()也可以解析以数字开头的部分数字字符串(非数字部分字符串在转换过程中会被去除)。与parseInt()不同的是,parseFloat()可以将字符串转换成浮点数;但同时,parseFloat()只接受一个参数,且仅能处理10进制字符串。
(1)字符串中的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。
(2)如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后面都是零),parseFloat()会返回整数。
var num1=parseFloat(“1234blue”); //1234
var num2=parseFloat(“0xA”); //0
var num3=parseFloat(“0908.5”); //908.5
var num4=parseFloat(“3.125e7”); //31250000
var num5=parseFloat(“123.45.67”) //123.45
var num6=parseFloat("") //NaN
var num7=parseFloat(“num123”) //NaN
5.String类型
-
String(字符串)数据类型表示零或多个16位的Unicode字符序列。字符串可以使用双引号(")、单引号(’)或者反引号(`)标示。跟某些语言中使用不同的引号会改变对字符串的解释方式不同,js语法中表示字符串的引号没有区别。不过要注意的是,以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。
(1)字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符:
\0 Null字符(\u0000)
\b 退格符(\u0008)
\t 水平制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
" 双引号(\u0022)
’ 撇号或单引号(\u0027)
\ 反斜杠(\u005C)
\xXX 由 2 位十六进制数值 XX 指定的 Latin-1 字符
\uXXXX 由 4 位十六进制数值 XXXX 指定的 Unicode 字符
\XXX 由 1~3 位八进制数值(000 到 377)指定的 Latin-1 字符,可表示 256个 字符。如 \251 表示版本符号。注意,ECMAScript 3.0 不支持,考虑到兼容性不建议使用。 -
这些字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释:
let text = "This is the letter sigma: \u03a3."
//在这个例子中,即使包含6个字符长的转义序列,变量text仍然是28个字符长。因为转义序列表示一个字符,所以只算一个字符。
//另外,字符串的长度可以通过其length属性获取,这个属性返回字符串中16位字符的个数。
console.log(text.length); //28
//注意,如果字符串中包含双字节字符,那么length属性返回的值可能不是准确的字符数。
(2)字符串的特点
- js中的字符串是不可变的,意思是一旦创建了,他们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串, 然后将包含新值的另一个字符串保存到该变量。
let lang = 'Java';
lang = lang + "Script";
/*这里,变量lang一开始包含字符串"Java"。紧接着,lang被重新定义为
包含"Java"和"Script"的组合,也就是"JavaScript"。整个过程首先
会分配一个足够容纳10个字符的空间,然后填充上"Java"和"Script"。最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了,所有的处理都是在后台发生的,而这也是一些早期的浏览器(如FireFox1.0之前的版本和IE6.0)在拼接字符串时非常慢的原因。这些浏览器在后来的版本中都有针对性的解决了这个问题*/
(3)转换为字符串
- 有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法。这个方法惟一的用途就是返回当前值的字符串等价物。比如:
let age = 11;
let ageAsString = age.toString(); //字符串"11"
let found = true;
let foundAsString = found.toString() //字符串"true"
/*toString()方法可用于数值,布尔值、对象和字符串值。(没错,字符串值也有
toString()方法,该方法只是简单的返回自身的一个副本。)null和undefined值
没有toString()方法。*/
- 多数情况下,toString()不接收任何参数。不过,在对数值调用这个方法时,toString()可以接受一个底数参数,即以什么底数来输出熟知的字符串表示。默认情况下,toString()返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示,比如:
let num = 10;
console.log(num.toString()); //"10"
console.log(num.toString(2)); //"1010"
console.log(num.toString(8)); //"12"
console.log(num.toString(10)); //"10"
console.log(num.toString(16)); //"a"
- 如果你不确定一个值是不是null或者undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串。String()函数遵循如下规则:
·如果值有toString方法,则调用该方法()不传参数并返回结果。
·如果值是null,返回“null”
·如果值是undefined,返回“undefined”
let value1 = 10;
let value2 = true;
let value3 = null;
let value4;
console.log(String(value1)); //"10"
console.log(String(value2)); //"true"
console.log(String(value3)); //"null"
console.log(String(value4)); //"undefined"
//注意,用加号操作符给一个值加上一个空字符串""也可以将其转换为字符串。
(4)模板字面量(ES6)
- ES6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串:
let myMultiLineString = 'first line\nsecond line';
let myMultiLineTemplateLiteral = `first line
second line`;
console.log(myMultiLineString);
//first line
//second line
console.log(myMultiLineTemplateLiteral);
//first line
//second line
console.log(myMultiLineString === myMultiLineTemplateLiteral); //true
//顾名思义,模板字面量在定义模板时特别有用,比如下面这个HTML模板:
let pageHTML = `
<div>
<a href='#'>
<span>Jake</span>
</a>
</div>`;
//注意,由于模板字面量会保持反引号内部的空格,因此在使用时要格外的注意。格式正确的模板字符串看其阿里可能会缩进不当:
//这个模板字面量在换行符之后有25个空格符
let myTemplateLiteral = `first line
second line`;
console.log(myTemplateLiteral.length); //47
//这个模板字面量以一个换行符开头
let secondTemplateLiteral =`first line
second line`;
console.log(secondTemplateLiteral[0] === '\n') ; //true
//这个模板字面量没有意料之外的字符
let thirdTemplateLiteral = `first line
second line`;
console.log(thirdTemplateLiteral);
//first line
//second line
(5)字符串插值(ES6)
- 模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的js句法表达式,只不过求值后得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从他们最接近的作用域中取值。
·字符串插值通过在${}中使用一个js表达式实现:
let value = 5;
let exponent = 'second';
//以前,字符串插值是这样实现的:
let interpolatedString =
value + ' to the ' + exponent + ' power is ' + (value * value);
//现在,可以用模板字面量这样实现
let interpolatedTemplateLiteral =
`${ value } to the ${ exponent } power is ${ value * value }`;
console.log(interpolatedString); //5 to the second power is 25
console.log(interpolatedTemplateLiteral); //5 to the second power is 25
//所有插入的值都会使用toString()强制转型为字符串,而且任何js表达式都可以用于插值。嵌套的模板字符串无须转义:
//console.log(`Hello,${ World }!`); //Hello,World!
//将表达式转换为字符串时会调用toString();
let foo = {toString:() => 'World'};
console.log(`Hello,${ foo }!`); //Hello,World!
//在插值表达式中可以调用函数和方法:
function capitalize(word){
return `${ word[0].toUpperCase()}`
}
6.Object类型
- js中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建。开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法。
let o = new Object();
/*这个语法类似Java,但是js只要求在给构造函数提供
参数时使用括号。如果没有参数,如上面的例子所示,那
么完全可以省略括号(不推荐)*/
let o = new Object; //合法但是不推荐
- Object的实例本身并不是很有用,但是理解与他相关的概念非常重要。类似Java中的java.lang.Object,js中的Object也是派生其他对象的基类。Object类型的所有属性和方法在派生的对象上同样存在。
- 每个Object实例都有如下属性和方法(因为在js中Object是所有对象的基类,所以任何对象都有这些属性和方法。):
·constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
·hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty(“name”))或符号。
·isprototypeOf(object):用于判断当前对象是否为另一个对象的原型。
·propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
·toLocaleString():返回对象的字符串表示
·valueOf():返回对象对应的字符串、数值或者布尔值表示。通常与toString()的返回值相同.
注意,严格来讲,ECMA-262中对象的行为不一定适合js中的其他对象。比如浏览器环境中的BOM和DOM对象,都是由宿主环境定义和提供的素主对象。而宿主对象不收ECMA-262约束,所以他们可能会也可能不会继承Object。