ECMAScript有6种简单的数据类型(也成为原始类型):Undefined、Null、Boolean、Number、String和Symbol。Symbol(符号)是ECMAScript6新增的。还有一种复杂数据类型叫Object(对象)。
因为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"
因为typeof是一个操作符而不是一个函数,因此不需要参数,但可以使用参数。注意typeof在某些情况下返回的结果可能让人费解,但技术上讲是正确的。比如调用typeof null返回是"object"。这是因为特殊值null被认为是一个空对象的引用。typeof返回的值是字符串:
let message;
console.log(typeof typeof message); //string
严格来讲函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是函数也有自己特殊的属性,因此,就有必要通过typeof操作符来区分函数和其他对象。
Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值。
let message;
console.log(message == undefined); //true
在ECMA-262第3版之前是不存在的。增加这个特殊值目的是为了明确空对象指针null和未初始化变量的区别。
注意包含undefined值的变量和未定义变量是有区别的:
let message;
console.log(message); //undefined
console.log(age); //undefined
console.log(message); //undefined
console.log(age); //报错
无论是声明还是未声明,typeof返回的都是字符串undefined。我们建议在声明变量的同时进行初始化,这样在typeof返回undefined时,就会知道是未声明而不是声明了但未初始化。undefined是一个假值:
let message; //声明但未初始化
//age未声明
if (message) {
//这个块不会执行
}
if (!message) {
//这个块会执行
}
if (age) {
//报错
}
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是一个假值:
let car = null;
if(car){
//这个块不会执行
}
if(!car){
//这个块会执行
}
Boolean类型有两个字面值:true和false,这两个布尔值不同于数值,因此true不等于1,false不等于0。注意布尔值字面量true和false是区分大小写的,因此True和False是有效的标识符而不是布尔值。
虽然布尔值只有两个,但所有其他ECMAScript类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数:
let message = "Hello world";
let messageAsBoolean = Boolean(message);
在这个例子中字符串message会被转换为布尔值并保存在变量messageAsBoolean中,Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。下表总结了不同类型与布尔值之间的转换规则:
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
Boolean | true | false |
String | 非空字符串 | " " 空字符串 |
Number | 非0数值 | 0、NaN |
Object | 任意对象 | null |
Undefined | N/A(不存在) | undefined |
理解以上转换非常重要,因为像if等流程控制语句会自动执行其他类型的值到布尔值的转换,例如:
let message = "Hello world!";
if (message){
console.log("Value is true");
}
在这个例子中,console.log会输出字符串Value is true,因为字符串message会被自动转换为等价的布尔值true。
整数也可以用八进制或十六进制字面量表示,对于八进制字面量,第一个数字必须是0,然后是相应的八进制数字。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的0,后面的数字序列会被当成十进制数,如下:
let octalNum1 = 070; //8进制的56
let octalNum2 = 079; //无效的8进制,当成79处理
let octalNum3 = 08; //无效的8进制,当成8处理
要创建16进制字面量,必须让真正的数值前缀0x(区分大小写),然后是16进制数字(0~9以及0~F),十六进制数字中的字母大小写均可:
let hexNum1 = 0xA; //16进制的10
let hexNum2 = 0x1f; //16进制的31
(1)浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上:
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1;
因为存储浮点值使用的内存空间是存储整数值的两倍,因此ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数,如果数值本身就是整数,只是小数点后面跟着0,那它也会被转换为整数
科学计数法:ECMAScript中科学计数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写字母e,再加上一个要乘的10的多少次幂:
let floatNum = 3.125e7;
浮点值的精确度最高可达17位小数,但是算数计算中远不如整数精确,例如0.1+0.2得到的不是0.3,而是0.30000000000000004,由于这种微小的舍入错误,导致很难测试特定的浮点值:
let a = 0.3,
b = 0.1;
if (a + b == 0.3) {
console.log("You got 0.3");
}
(2)值的范围
ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.7976931348623157e+308。如果某个计算得到的值结果超出了JavaScript可以表示的范围,那么这个数值就会被自动转换为一个特殊的Infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以Infinity(正无穷大)表示。
要确定一个值是不是有限大,可以使用isFinite()函数。
(3)NaN
有一个特殊的数值叫NaN,意思是不是数值(Not a Number),用于表示本来要返回数值的操作失败了。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中0、+0或-0相除会返回NaN:
console.log(1 / 0); //Infinity
console.log(-1 / 0); //-Infinity
console.log(-0 / 0); //NaN
NaN的独特属性:任何涉及NaN的操作始终返回NaN,其次NaN不等于包含NaN在内的任何值。
console.log(NaN == NaN); //false;
为此,ECMAScript提供了isNaN()函数。该函数接受一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给isNaN()后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,任何不能转换为数值的值都会导致这个函数返回true:
console.log(isNaN(NaN));//true
console.log(isNaN(10));//false
console.log(isNaN("10"));//false
console.log(isNaN("blue")); //true
console.log(isNaN(true));//false
(4)数值转换
有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。Number()是转型函数,可以用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这三个函数执行的操作也不同。
Number()函数基于如下规则执行转换:
布尔值,true转换为1,false转换为0.
数值,直接返回。
null,返回0.
undefined,返回NaN
字符串,应用以下规则:
对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。
let num1 = Number("Hello world"); //NaN
let num2 = Number(""); //0
let num3 = Number("000011"); //11
let num4 = Number(true); //1
parseInt():
考虑到用Number()函数转换字符串时相对复杂而且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符串开始转换。如果第一个字符串不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这点跟Number()不一样,它返回0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直至字符串末尾,或碰到非数值字符。如“1234blue”会被转换为1234,因为blue会被完全忽略。类似地,“22.5”会被转换为22,因为小数点不是有效的整数字符。
parseInt()函数也能识别不同的整数格式(十进制、八进制、十六进制)。如果字符串以"0x"开头,就会被解释为16进制,如果字符串以"0"开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为8进制整数:
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()也接受第二个参数,用于指定底数,如果知道要解析的值是16进制,那么可以传入16作为第二个参数,以便正确解析:
let num = parseInt("0xAF",16);
事实上,如果提供了16进制参数,那么字符串前面的0x可以省略。
parseFloat():
parseFloat()函数的工作方式跟parseInt()函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。
parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的0.这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的0始终被忽略)。16进制数值始终会返回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
字符串可以使用双引号(")、但引号(’)或反引号(`)标识,因此下面的代码都是合法的:
let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`;
ECMAScript语法中表示字符串引号没有区别。
(1)字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符:
字面量 | 含义 |
---|---|
\n | 换行 |
\t | 制表 |
\b | 退格 |
\r | 回车 |
\f | 换页 |
\ | 反斜杠() |
’ | 单引号 |
" | 双引号 |
` | 反引号 |
\xnn | 以十六进制编码nn表示的字符(其中)n是十六进制数字0~F,例如\x41等于"A" |
\unnn | 以十六进制编码nnnn表示的Unicode字符(其中n是十六进制数字0~F),例如\u03a3等于希腊字母“ Σ \Sigma Σ” |
let firstName = "John";
console.log(firstName.length);
转义序列表示一个字符,字符串的长度可以通过其length属性获取。
(2)字符串的特点
ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。
(3)转换为字符串
有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法,这个方法唯一的用途就是返回当前值的字符串等价物:
let age = 11;
let ageAsString = age.toString();
let found = true;
let foundAsString = found.toString();
toString()方法可见于数值、布尔值、对象和字符串值。null和undefined值没有toString()方法。
如果你不确定一个值是不是null或者undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串。
let value1 = 10;
let value2 = true;
let value3 = null;
let value4;
console.log(String(value1));
console.log(String(value2));
console.log(String(value3));
console.log(String(value4));
(4)模板字面量
ECMAScript6新增了使用模板字面量定义字符串能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串:
let value1 = 'first line\nsecond line'
let value2 = `first line
second line`;
console.log(value1);
//first line
//second line
console.log(value2);
//first line
//second line
顾名思义,模板字面量在定义模板时特别有用,比如下面这个HTML模板:
let pageHTML = `
`;
由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模板字符串看起来可能缩进不当。
(5)字符串插值
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最近的作用域中取值。
字符串插值通过在${}中使用一个JavaScript表达式实现:
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);
console.log(interpolatedTemplateLiteral);
所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript表达式都可以用于插值。在插值表达式中可以调用函数和方法:
function capitalize(word) {
return `${
word[0].toUpperCase()}${
word.slice(1)}`;
}
console.log(`${
capitalize('hello')},${
capitalize('world')}!`);