JavaScript提升系列(四):数据类型

前言

前方高能预警:本文中Number类型及其基础、硬核且无聊,导致大部分人觉得这些内容没什么用。如果你中途坚持不住,建议点赞,收藏后,改日再战。

对于初次接触JavaScript的朋友来说,本文信息量略为丰富,建议收藏,多多复习,练习。

如果你为了追求极致的学习速度,想知道JavaScript与C++/Java不同之处,请直接翻到文末小结部分。

数据类型

ECMAScript中有5种基本数据类型:Undefined, Null, Boolean, NumberString和一种复杂数据类型Object
ECMAScript不支持任何创建自定义类型的机制,所有值最终都将是上述6种数据类型之一。

typeof操作符

对一个值使用typeof操作符,可能会返回下列某个字符串:

  • "undefined",变量未被初始化,或变量未被定义。
  • "boolean",布尔值。
  • "string",字符串。
  • "number",数值。
  • "object",对象或null
  • "function",函数。

Undefined类型

Undefined类型只有一个值,就是undefined
在使用var声明变量但未对其进行初始化时,该值为undefined
包含undefined值的变量和尚未定义的变量是不一样的,如下例:

var message;   //该变量默认为undefined。

//下面变量未被声明
//var age; 

alert(message);  //undefined
alert(age);     //ReferenceError: age is not defined.

迷惑的是,在使用typeof操作符时,两者的值都是undefined,如下例:

alert(typeof message);  //"undefined"
alert(typeof age);          //"undefined"

所以,强烈推荐显式的初始化变量。这样,我们就可以知道被检测的变量的状态是没声明,而不是尚未初始化。**

Null类型

Null类型也只有一个值,就是null
从逻辑角度来看,null值表示一个对象的空指针,事实上,这也就解释了为什么在使用typeof操作符检测null时,会返回"object"。如下例所示。

var car = null;
alert(typeof car);  //"object"

如果定义的变量要用于保存对象的话,最好将该变量初始化为null这样只需要检查null值就可以知道相应的变量是否已经保存了一个变量引用。

var car = null;
if( car!=null ){
  //对car对象执行某些操作
}

需要注意的是,undefined的值是派生自null值的,因此ECMA-262规定对它们进行相等性测试时,必须返回true。

alert( undefined==null );    //true

无论在什么情况下都没必要把一个变量的值显式设置为undefined,这样可以有助于区分undefinednull

Boolean类型

Boolean类型是ECMAScript中使用最多的一种类型,该类型只有两个字面值truefalse(注意:truefalse是区分大小写的)
ECMAScript中所有类型的值都可以通过Boolean()函数来转换为相应的等价值。

var message = "Hello World!";
alert(Boolean(message));    //true

下表为各类数据类型与Boolean类型的转换规则。

数据类型 转换为ture 转换为false
Boolean true false
String 任何非空字符串 ""
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined undefined

Number类型

最基本的字面值格式是十进制整数,如下:

var intNum = 123;

除十进制外,整数还可以通过八进制或十六进制的字面值来表示。

八进制

八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7)。如果字面值中的数值超出了范围,那么前导0将会被忽略,后面的数值按照十进制解析。如下例:

var octalNum1 = 070;  //八进制的56
var octalNum2 = 079;  //无效的八进制数值,解析为79
var octalNum3 = 08;    //无效的八进制数据,解析为8

八进制在严格模式下是无效的,会导致支持的JavaScript引擎抛出错误。

十六进制

十六进制的前两位必须是0x,后跟任何十六进制数字(0~9及A~F)。其中,A~F字母即可以大写,也可以小写。

var hexNum1 = 0xA;  //十六进制的10
var hexNum2 = 0x1f;  //十六进制的31

在进行算数运算时,所有以八进制和十六进制表示的数值最终都将转换为十进制数值。

浮点数值

浮点数值,该数值中必须包含一个小数点,并且小数点后面至少得有一位数字。
小数点前面可以省略整数,但不推荐。

var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1;    //有效,但不推荐。

由于保存浮点整数需要的内存空间是保存整数的两倍,所以ECMAScript会不失时机的将浮点数值转换为整数值。

var floatNum1 = 1.;      //小数点后面没有数字,解析为1。
var floatNum2 = 10.0;  //整数,解析为10。

对于极大或极小的值,可以使用e表示法(科学计数法)表示的浮点数表示。

var floatNum1 = 3.125e7;  //3.125*10^7 = 31250000
var floatNum2 = 3e-17;      //3*10^(-17) = 0.00000000000000003

浮点数值的最高精度是17位小数,但在算术计算时,其精度远远不如整数。

alert(0.1+0.2);       //0.30000000000000004
alert(0.1+0.2==0.3);  //false

数值范围

ECMAScript能够表示最小的数值保存在Number.MIN_VALUE,大多数浏览器中,其值为5e-324
ECMAScript能够表示最大的数值保存在Number.MAX_VALUE中,大多数浏览器中,其值为1.7976931348623157e+308

当某次计算的结果得到了超出JavaScript数值范围的值时,这个数值会自动转换为Infinity值。
当该值为正无穷时,得到的是Infinity值;该值为负无穷时,得到的是-Infinity值。

当某次计算的值返回了正或负Infinity值时,该值无法继续参与下一次计算,因为Infinity不是能够参与计算的数值。
我们可以通过isFinite()函数来判断一个数值是否是无穷。

alert(isFinite(0));                     //false
alert(isFinite(Number.MAX_VALUE+1));    //true

NaN

NaN,非数值,是一个特殊的数值,用于表示一个本来要返回数值的操作未返回数值的情况。例如:

alert(1/0);    //NaN

NaN本身有两个非比寻常的特点。

  • 任何涉及与NaN操作,结果都是NaN(好比0乘任何数都得0)
  • NaN不与任何数值相等,包括NaN自己。
alert(0*NaN);        //NaN
alert(0/NaN);        //NaN
alert(0+NaN);       //NaN
alert(0-NaN);        //NaN
alert(NaN==NaN);  //false

因此,ECMAScript定义了isNaN()函数,用来确定参数是否为NaN
该函数接受到值后,会尝试将该值转换为数值,任何不能被转换为数值的值都会导致这个函数返回true。

alert(isNaN(NaN));          //true
alert(isNaN(0));               //false
alert(isNaN("Hello"));      //true, "Hello"不能被转换为数值。
alert(isNaN(isNaN(NaN)))  //false

数值转换

有三个函数,可以把非数值转换为数值。

  • Number(),将任何数据类型转换为整型数值。
  • parseInt(),将字符串转为整型数值。
  • parseFloat(), 将字符串转为浮点型数值。

Number()转换规则如下:

  • Boolean类型,truefalse被转换为10
  • 数字类型,只是简单的传入和返回。
  • null,返回0
  • undefined,返回NaN
  • 字符串遵循以下规则:
    • 如果字符串中只包含数字,则将其转换为十进制数。
    • 如果字符串中包含有效的浮点数格式,则转换为相应的浮点数值。
    • 如果字符串中包含有效的16进制格式,则转换为相同大小的10进制数值。
    • 如果字符串为"",返回0。
    • 如果字符串中包含上述格式之外的字符,则转换为NaN
  • 如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,然后再依照前面的规则转换返回的字符。
var num1 = Number("1111");        //1111
var num2 = Number(true);            //1
var num3 = Number("");                //0
var num4 = Number("123a");        //NaN,因为包含字母a
var num5 = Number("0xf");            //15

相比较Number()函数,parseInt()函转换规则就宽松多了,parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。
它会忽略字符串前面的空格,直至找到第一个非空格字符。
如果第一个字符不是负号或数字,那么parseInt()函数会返回NaN
如果第一个字符是数字,parseInt()函数会继续解析第二个字符,直到解析完成,或者遇到非数字字符。
如果字符串以0x开头,parseInt()函数会将其当作16进制整数。

var num1 = parseInt("           123");    //123
var num2 = parseInt("123a");            //123
var num3 = parseInt("");                    //NaN
var num4 = parseInt("0xf");                //15
var num5 = parseInt("07");                //ECMAScript3中返回56,ECMAScript5以上返回7

在使用parseInt()函数解析八进制字面值数值时,ECMAScript3和ECMAScript5存在分歧,为了消除上述困惑,可以为这个函数提供第二个参数:转换时的基数。
如果知道当前字符串是16进制,那么可以指定基数为16,例子如下:

var num1 = parseInt("f",16);    //15
var num2 = parseInt("70",8);    //56
var num3 = parseInt("10",2);    //2

不指定基数意味着让parseInt()函数决定如何解析输入的字符串,为了避免错误解析,强烈建议无论在什么情况下都明确指定基数。

parseFloat()函数与parseInt()函数类似,也是从第一个字符开始解析每一个字符,而且也是一直解析到字符串末尾,或者解析遇到了一个无效的浮点数字符为止。
parseFloat()函数会始终忽略前导的0。由于parseFloat()函数只解析10进制数,因此它没有第二个参数指定基数的用法。
如果字符串包含的是一个可解析为整数的数,parseFloat()函数会返回整数。

var num1 = parseFloat("1234abcd");          //1234,解析到整数值,返回整数1234
var num2 = parseFloat("0xA");                    //0,遇到x无效字符,返回0
var num3 = parseFloat("22.5");                  //22.5
var num4 = parseFloat("22.34.5");              //22.34,遇到.无效字符,返回22.34
var num5 = parseFloat("0908.5");                //908.5,忽略前导0,返回908.5
var num6 = parseFloat("3.125e7");            //31250000

String类型

String类型用于表示零或多个16位Unicode组成的字符序列,即字符串。
通常有两种表示方法。(ECMAScript6中,新增一种表示方法)

var firstName = "Nicholas";
var lastName = 'Zakas';

单引号表示的字符串与双引号表示的字符串完全相同,但双引号开头必须双引号结尾,单引号开头必须单引号结尾。

字面值常量

String类型包含一些特殊的字符字面值,也叫转义序列,用于表示非打印字符,或其它用途的字符,这些字面值如下表所示:

字面量 含义
\n 换行
\t 制表
\b 空格
\r 回车
\f 进纸
\\ 斜杠
\' 单引号'
\" 双引号"
\xnn 以16进制代码nn表示字符,\x41表示"A"
\unnnn 以十六进制代码nnnn表示一个Unicode字符,\u03a3表示希腊字母

上述字符字面值可以出现在字符串中任意位置,而且也作为一个字符被解析。

var text = "This is the letter sigma: \u03a3.";
alert(text.length);      //28

如果字符串中包含双字节字符,那么length属性可能不会精确返回字符串中字符的数目。

字符串的特点

ECMAScript中的字符串是不可变的,也就是说,字符串一旦创建,他们的值就不能改变。
要改变某个变量保存的字符串,首先得先销毁原来的字符串,再用另一个新字符串填充该变量。

var str = "Hello";
str = str + " World!";    // Hello World!

转换为字符串

有两种方式可以将一个值转换为字符串:
除了nullundefined之外,每个值都有的toString()函数。

var x = 100;
var xString = x.toString();  //  "100"
var y = true;
var yString = y.toString();  // "true"

大多数情况下,toString()函数不必传递参数,但在调用数值的toString()函数时,可以传递一个参数:输出值的基数。

var num = 10;
var numString1 = num.toString();    // "10"
var numString2 = num.toString(2);  // "1010"
var numString3 = num.toString(8);  // "12"
var numString4 = num.toString(10);  // "10"
var numString5 = num.toString(16);  // "a"

在不知道值是否为nullundefined时,还可以使用转型函数String(),这个函数能够将任何类型的值转换为字符串。
String()函数遵循下列转换规则:

  • 如果值有toString()函数,则调用该方法,并返回相应的结果。
  • 如果值是null,则返回"null"
  • 如果值是undefined,则返回"undefined"
var v1 = 123;
var v2 = true;
var v3 = null;
var v4;
alert(String(v1));  // "123"
alert(String(v2));  // "true"
alert(String(v3));  // ”null“
alert(String(v4));  // "undefined"

Object类型

ECMAScript中的对象其实就是一组数据和功能的集合。对象可以通过执行new操作符后跟要创建的对象名称来创建。

var o = new Object();

这个语法与Java中创建对象的语法相似,但在ECMAScript中,如果不给构造函数传递参数,则可以省略后面的那对圆括号。(不推荐此做法)

在ECMAScript中,Object类型是所有它的实例的基础,换句话说,Object类型所具有的任何属性和方法也同样存在更具体的对象中。

Object的每个实例都有下列属性和方法。

  • Constructor,保存着用于创建当前对象的函数。
  • hasOwnProperty(propertyName),用于检查给定的属性在当前实例中是否存在,其中propertyName参数必须以字符串形式指定。
  • isPrototypeOf(object),用于检查传入的对象是否是另一个对象的原型。
  • propertyIsEnumerable(propertyName),用于检查给定的属性是否能使用for-in语句来枚举。
  • toLocaleString(),返回对象的字符串表示,该字符串与执行环境地区相对应。
  • toString(),返回对象的字符串表示。
  • valueOf(),返回对象的字符串,数值或布尔值表示。通常与toString()方法的返回值相同。

从技术角度来说,ECMA-262中对象的行为不一定适用于JavaScript中的其他对象。浏览器中的对象,比如BOM和DOM中的对象,都属于宿主对象,因为它们是由宿主实现提供和定义的。

总结

本文总结分为如下三大部分,以下总结不知道原理的,评论区见。

与C++/Java不同之处

  • JavaScript只有6种数据类型(Number, Boolean, Object, Undefined, Null, String),且不支持用户自定义类型。
  • JavaScript中当计算溢出时,结果会为正或负Infinity
  • JavaScript中有一个神奇的值NaN

严格模式

  • 在严格模式下,不能定义以八进制为字面值表示整数,会导致引擎抛出错误。

Effective小技巧

  • 强烈建议,定义变量时,显式的将其初始化。(为什么?)
  • 强烈建议,任何情况下,定义变量时,都不要将其初始化为undefined。(为什么?)
  • 强烈建议,定义要保存对象的变量时,将其初始化为null。(为什么?)
  • 强烈建议,使用parseInt()函数,而不是Number()函数。
  • 强烈建议,使用parseInt()函数时,明确指定基数。

推荐阅读更多精彩内容