数据类型决定了一个数据的特征,即限定了该数据必须按照一定的规则进行操作。在程序设计中也 是如此,特定数据类型的数据会有其相应的行为模式。
JavaScript 中有以下 5 种基本数据类型。
- 字符串型
- 数值型
- 布尔型
- null 型
- undefined 型
在这 5 种基本数据类型之外的都被称为 Object 类型。也就是说,总的来看,JavaScript 中的数据类型可以分为 6 种。
像 Java 这样,变量具有数据类型的语言,被称为静态数据类型语言;而像 JavaScript 这样,变量没有类型的语言,则被称为动态数据类型语言。
对于 Java 来说,内建类型(int 或 double 之类)之外的都是用户自定义类型。用户自定义类型又可以分为类和接口两种类型。Java 的用户自定义类型的使用方法,从其名称中就可略知一二,即开发者需要书写该类型的定义语句来定义该类型。而对象则作为这些由用户定义的数据类型的实例(实体)存在。这就是 Java 的基本特性。这种编程风格被称为基于类的语言风格。
另一方面,在 JavaScript 的语言规范中,不存在定义数据类型的语句。不需要使用特别的语句就能定义一个对象的属性或方法,而这样也就决定了该对象的类型。所谓类型也就是行为方式上的共性。由于每个对象都具有共同的行为方式,所以可以使用原型对象。这样的编程风格被称为基于原型的风格。
虽然 JavaScript 的变量不具有数据类型,但从概念上,JavaScript 变量可以分为基本数据类型变量和引用类型变量。基本数据类型变量直接保存有数值等类型的数据的值,而引用类型变量则保存有对象的引用。尽管表面上两者没有区别,但其内在是不同的。因此为了正确地理解其内部实现原理,就需要引入引用这一概念.
在 ECMAScript 标准中,内建数据类型(built-in type)分为 5 种基本数据类型以及 Object 类型。
avaScript 的字符串型是基本数据类型,而 Java 的字符串型并不是基本数据类型,这是两者的区别之一。不过,其实两者在本质上并没有太大的不同。因为在 Java 中,字符串型和字面量以及运算符一样,属于被特别对待的 Object 类型。字符串连接运算符(+ 号)在 Java 和 JavaScript 中的作用也是相同的。在 JavaScript 中,字符串值会被隐式地转换为字符串对象类型。熟悉 Java 的人很容易就能掌握JavaScript 中字符串型的用法。不过在 JavaScript 中不存在字符类型。如果需要表达某个字符的话,请使用长度为 1 的字符串值。
JavaScript 只有一种数值类的数据类型,其内部构造为 64 位的浮点小数,这相当于 Java 中的 double 类型。和 JavaScript的情况不同,在 Java 中有 5 种整数类型和 2 种浮点数类型。JavaScript 之所以只支持一种数值类型,是由于设计当初更多地着眼于降低编程难度,而非提升运行效率。如果有多种数值类型,就不得不在考虑在进行赋值时考虑是否会产生错误。而 JavaScript 数值只有一种数值数据类型,除了部分特殊情况,通常不会发生类型转换错误。从另一方面来说,由于数值类型能够与其他类型进行隐式转换,所以仍然存在大量陷阱。
布尔型在 Java 和 JavaScript 中是没有区别的,同样都是使用 true 和 false 的字面量。
null 类型的值只有 null 一种情况,并且 null 属于字面量。虽然 Java 中也存在 null 这一字面量,但是没有 null 类型,null只是一种可以被引用的值而已。尽管有这样的差别,但是 Java 中的 null 和 JavaScript 中的 null 在用法上几乎没有区别,只需要注意一下类型转换的问题即可。
undefined 类型是指未定义的值的类型,这一概念在 Java 中是不存在的。如果在 Java 中使用类似于 undefined 类型的值的话,就会发生编译错误。
// 字符串值可以通过字符串字面量来表示。 // 字符串字面量需要用双引号(")或单引号(')括起来。 var hzh = "hzh"; // 将字符串"hzh"值赋给变量hzh console.log("hzh:" + hzh); // 显示变量hzh的值 var hzh = 'hzh'; // 将字符串"hzh"值赋给变量hzh console.log("hzh:" + hzh); // 显示变量hzh的值
特殊字符可以通过转义字符(串)来表示。可以通过在转义符之后使用特定字符,来表达一些特殊的含义。转义符是反斜杠(\)。例如,\n 是换行符的表达方式。
// 因为可以用两种引号来包围字符串字面量 // 所以只要对此加以活用,就能够少用转义字符。 // 例如,在包含大量双引号的字符串字面量外使用单引号, // 就能够减少转义字符的出现。 // 对于双引号不需要使用转义字符(当然转义字符也没有问题) var hzh1 = '我说"黄子涵是帅哥!"'; console.log("hzh1 = " + hzh1);
// 如果在右侧书写的是值为字符串值的变量,也一样能将其值赋值给等号左侧的变量。 var hzh1 = '黄子涵'; // 将字符串值'黄子涵'赋值给变量hzh var hzh2 = hzh1; // 将变量hzh1赋值给变量hzh2 console.log("hzh1:" + hzh1); // 输出变量hzh1的值 console.log("hzh2:" + hzh2); // 变量hzh2的值为字符串'黄子涵' console.log("**************************************************"); console.log("**************************************************"); // 不过 JavaScript 确实和Java 一样,其字符串型是不可变类型。 var hzh3 = "黄子涵"; var hzh4 = "是帅哥!" var hzh5 = hzh3 + hzh4; // 连接字符串值 console.log("hzh3:" + hzh3); // 变量hzh5的值为字符串值“黄子涵是帅哥!” console.log("hzh4:" + hzh4); console.log("hzh5:" + hzh5); console.log("**************************************************"); console.log("**************************************************"); // += 运算符可以在连接字符串的同时进行赋值。 var hzh6 = "黄子涵"; hzh6 += "是靓仔!" console.log("hzh6:" + hzh6); // 变量hzh6的值为字符串值“黄子涵是靓仔” console.log("**************************************************"); console.log("**************************************************"); var hzh7 = "黄子涵"; var hzh8 = hzh7; hzh7 += "真厉害!" // 变量hzh7的值为字符串值“黄子涵真厉害” console.log("hzh7:" + hzh7); console.log("hzh8:" + hzh8); // 变量hzh8的值仍保持为字符串值“黄子涵” console.log("**************************************************"); console.log("**************************************************"); // 可以通过typeof运算符来获知值的数据类型 console.log(typeof "黄子涵"); // 对字符串字面量进行typeof运算 var hzh9 = "黄子涵"; console.log(typeof hzh9); // 对变量hzh9进行typeof运算 console.log(typeof (hzh9)); // 也可以添加括号 console.log(typeof (typeof (hzh9))); // typeof运算的结果也是字符串值
JavaScript 有两种等值运算符,即
===
和==
。与之对应,也有两种不等运算符!==
和!=
。
=== 和 == 的区别在于,在比较的时候是否会进行数据类型的转换。=== 在比较的时候不会对数据类型进行转换。在ECMAScript 标准中,将其称为严格相等(strict equal)。
// 如果只考虑字符串之间的比较,=== 和 == 的结果是没有区别的。 // 两种方式都会判断字符串的内容是否一致。 var hzh1 = "黄子涵"; var hzh2 = "是帅哥!"; var hzh3 = hzh1 + "是帅哥!"; console.log("hzh1 == hzh2?" + (hzh1 == hzh2)); console.log("hzh1 == hzh3?" + (hzh1 == hzh3)); console.log("hzh2 == hzh3?" + (hzh2 == hzh3)); console.log("hzh1 != hzh2?" + (hzh1 != hzh2)); console.log("hzh1 != hzh3?" + (hzh1 != hzh3)); console.log("hzh2 != hzh3?" + (hzh2 != hzh3)); console.log("**************************************************"); //对字符串值的比较基于 Unicode 字符的编码值(编码位置)。 var hzh4 = "hzh"; var hzh5 = "HZH"; console.log("hzh4 < hzh5?" + (hzh4 < hzh5)); console.log("hzh4 <= hzh5?" + (hzh4 <= hzh5)); console.log("hzh4 > hzh5?" + (hzh4 > hzh5)); console.log("hzh4 >= hzh5?" + (hzh4 >= hzh5));
下面是比较大小时 Unicode 的编码位置的一些典型情况。要深入理解这部分的内容,需要有 Unicode 的相关知识,不过只要能记住以下的情况,就多少能进行运用了。
- 英文字母是字典顺序(ABC 顺序)
- 英文的大写字母在小写字母之前
- 数字和符号在英文字母之前(不过有些符号是在英文字母之后的)
- 日文的平假名在片假名之前
- 日文的平假名和片假名都是字典顺序(あいうえお顺序)
- 日文浊音和半浊音的顺序则是按以下的规律排列:へ、ほ、ぼ、ぽ、ま
- 日文汉字在平假名和片假名之后
- 日文汉字的排列顺序视计算机的具体情况而定(有些是按照音读的字典顺序)
在实际中,只有英语单词
之前提到,在 JavaScript 中字符串型是一种内建类型。不过 JavaScript 的字符串也有容易使人混淆的地方,即除了内建类型的字符串之外还存在一个字符串类。
字符串类的名称为 String。JavaScript 中字符串型和 String 类的关系,大致相当于 Java 中数值型和包装类型(Number 类和 Integer 类)的关系。字符串型和 String 类之间也同样支持隐式类型转换。在 Java 中存在装箱和拆箱转换,在 JavaScript 的字符串型和 String 类之间也有着类似的转换。这一转换通常是隐式进行的。
var hzh1 = '黄子涵是帅哥!' // 在形式上类似于读取字符串值的属性 console.log("数组hzh1的长度:" + hzh1.length); // 在形式上类似于读取字符串字面量的属性 console.log("'黄子涵是帅哥!'的长度:" + '黄子涵是帅哥!'.length); // 上述代码,其内部发生了字符串值到String对象的隐式数据类型转换
// 可以使用new运算符,来显示地生成一个字符串对象 var hzh1 = new String('黄子涵'); // 生成字符串对象 console.log("输出hzh1:" + hzh1); console.log("hzh1的类型:" + (typeof hzh1)); console.log("*****************************************"); // 隐式类型转换也能反向进行 // 将字符串对象隐式转换为了字符串值 var hzh2 = hzh1 + '是帅哥!'; console.log("输出hzh2:" + hzh2); console.log("hzh2的类型:" + (typeof hzh2)); console.log("*****************************************"); // 对象的相等运算,判断的是两者是否引用了同一对象 // 而并非两者的内容相同 var hzh3 = new String('黄子涵'); var hzh4 = new String('黄子涵'); // 虽然字符串的内容相同,但是并非引用了同一个对象,所以结果是false console.log("判断hzh3和hzh4是否相等:"); console.log(hzh3 == hzh4); // 虽然字符串的内容相同,但是并非引用了同一个对象,所以结果是false console.log("判断hzh3和hzh4是否严格相等:"); console.log(hzh3 === hzh4); console.log("*****************************************"); // 对于字符串值和字符串对象的等值判断, // 如果使用的是会进行隐式数据类型转换的 == 运算, // 则只会判定其内容是否相同,如果内容相同则结果为真。 var hzh5 = new String('黄子涵是帅哥!'); var hzh6 = '黄子涵是帅哥!'; // 进行数据类型转换的等值运算的结果为true console.log("判断hzh5和hzh6是否相等:"); console.log(hzh5 == hzh6); // 不进行数据类型转换的等值运算的结果为false console.log("判断hzh5和hzh6是否严格相等:"); console.log(hzh5 === hzh6); // 对于比较大小运算,字符串对象和字符串值一样,都是比较其字符串内容。 // 因此可以认为,这时字符串值和字符串对象之间没有区别。
// 可以使用 typeof 运算来判别一个字符串是字符串值还是字符串对象。 // 字符串对象的 typeof 运算结果为"object"。 var hzh1 = new String('黄子涵是帅哥!'); console.log("判断hzh1的数据类型:"); console.log(typeof hzh1); console.log("*********************************************"); // 应该避免显式地生成字符串对象。 // 需要使用字符串值的时候,一般都使用字符串字面量。 // 对于其余的情况,通过 String 函数进行显式的数据类型转换就足够了。 // 应该是积极地使用隐式数据类型变换,将字符串值转换为字符串对象。 // 转换为字符串对象后,只要在字符串值之后写上点运算符和属性名, // 就能对字符串进行各种各样的操作了。 var hzh2 = '黄子涵是帅哥!' console.log("返回字符串值下标为1的字符:"); console.log(hzh2.charAt(1)); console.log("对于字符串字面量也能像这样进行方法调用:"); console.log('黄子涵是帅哥!'.charAt(1));
// 通过调用 String 函数就可以生成字符串值。 // 一般来说,使用 String 函数是为了进行显式的数据类型转换。 var hzh1 = String('黄子涵是帅哥!'); // 变量hzh1的值是字符串型 console.log("判断hzh1的数据类型:"); console.log(typeof hzh1); // 由数值类型向字符串值类型的显式数据类型变换 var hzh2 = String(47); console.log("输出hzh2的值:"); console.log(hzh2); // 变量hzh2的值是字符串型 console.log("判断变量hzh2的数据类型:"); console.log(typeof hzh2);
// 通过数值属性获取指定下标的字符 // 其返回值是一个 String对象 var hzh1 = new String('黄子涵'); console.log("输出下标1的字符:"); console.log(hzh1[1]); // 由于有隐式数据类型转换, // 所以对字符串值也能进行这样的操作 console.log("字符串值'黄子涵'下标为2的字符:"); console.log('黄子涵'[2]);
// 字符串对象和字符串值一样,是不可变的。 // 也就是说,不能改写字符串的内容。 // 所有要改变字符串内容的方法,都会生成一个新的字符串对象然后将其返回。 var hzh1 = new String('hzh'); // 调用对象hzh1的toUpperCase方法 var hzh2 = hzh1.toUpperCase(); // 对象hzh1的内容不会发生变化 console.log("输出hzh1的值:"); console.log(hzh1); console.log("输出hzh2的值:"); console.log(hzh2); // 即使是 JavaScript 独有的 [] 运算也不会改写字符串的内容 hzh1[0] = 'HZH'; console.log("输出hzh1的值:"); console.log(hzh1); // 改变内部状态的方法被称为破坏性的方法。 // 一般来说,非破坏性的方法更好一些。 // 不过在有些时候,非破坏性方法的效率会相对较低。
在 JavaScript 中,数值的内部结构为 64 位的浮点小数。不过在实际的编程中,使用整数的情况会更多。不管内部构造如何,从 JavaScript 的代码上来看,只要是写为整数就能够作为整数使用,而不必考虑是否是浮点数的问题。因为所有的数值都是浮点小数,所以其运行效率多少会有些下降。因此对于比较注重运行效率的程序来说,JavaScript 可能并不是合适的选择。
由于整数型和浮点数型在使用上没有差别,所以不会发生和类型转换相关的错误。当然,浮点小数本身所具有的缺点依然存在。不过对于大部分现代程序设计语言来说,如果要使用小数,这些问题还是难以避免。
// 下面是使用数值字面量的代码示例 // 将数值1赋值给变量hzh1 var hzh1 = 1; // 将数值2赋值给变量hzh2 var hzh2 = 2; // 将变量hzh1的值域变量hzh2的值相加 console.log("输出hzh1和hzh2的和:"); console.log(hzh1 + hzh2); console.log("*********************************************"); // 可以通过 typeof 运算符来判断数值的类型。 // 对于数值来说,typeof 运算符返回的结果是字符串值 "number"。 var hzh3 = 1; // 对数值进行typeof运算 console.log("输出hzh3的数据类型:"); console.log(typeof hzh3); // 对数值字面量进行typeof运算 console.log("输出1的数据类型:"); console.log(typeof 1);
对于数值可以进行 +(加法)、-(减法)、*(乘法)、/(除法)四则运算。通过 % 符号则可以进行求模运算(即计算除法运算后的余数)。
需要注意的是,尽管从代码上来看进行的是整数运算,但其实在其内部进行的仍然是浮点数运算。例如,对 0 作除法并不会得到错误的结果,而是会得到一个特殊的数值。
var hzh1 = 1; var hzh2 = 2; console.log("输出1除以0的结果:"); console.log(hzh1/0); console.log("输出2除以0的结果:"); console.log(hzh2/0);
在 JavaScript 标准对象中有一个 Math 对象。该对象定义了圆周率 PI、自然对数的底数 E 等数学常量,以及一些相关的数学函数。例如,可以通过 Math.pow 函数计算 2 的 10 次方。
var hzh1 = Math; console.log("打印圆周率的e"); console.log(hzh1.E); console.log("打印圆周率的PI"); console.log(hzh1.PI); console.log("计算2的10次方:"); console.log(hzh1.pow(2,10));
// 对于浮点数来说,有时候并不能正确地表达小数点以后的部分。 // 实际上,能够正确表达一个数的值反而是一种例外, // 大部分情况下浮点数只能表达数值的近似值 console.log("计算0.1和0.2的和:"); console.log(0.1 + 0.2); console.log("判断0.1和0.2的和是否与0.3相等?"); console.log((0.1 + 0.2) == 0.3); console.log("判断0.1和0.2的和是否与0.3严格相等?"); console.log((0.1 + 0.2) === 0.3); console.log("输出1/3的值:"); console.log(1/3); console.log("输出(10/3)-3的值:"); console.log((10/3)-3); console.log("判断((10/3)-3)是否与1/3相等?"); console.log(((10/3)-3) == (1/3)); console.log("判断((10/3)-3)是否与1/3严格相等?"); console.log(((10/3)-3) === (1/3));
对于浮点数来说,要执行正确的实数运算是不可能的。稍加注意就会发现,这并不是个应该如何避免的问题,而是从原理上就是无法回避的。对于整数的情况,则可以保证在 53 位的范围内能有正确的结果。因此,如果只是使用 53 位以内的整数的话,就不会有任何问题。
如果需要用到数值正确的实数,就必须使用类似于 Java 中的 BigDecimal 类的实数库。目前,JavaScript 并没有标准的实数库。不过也有特殊情况,如果是在 JVM 下使用 Rhino(一种基于 Java 的开源 JavaScript 实现)的话,就能够直接使用 Java的 BigDecimal 类了。
正如存在字符串类(String 类),JavaScript 中也存在数值类(Number 类)。字符串值和字符串类在经过隐式数据类型转换之后,就基本能够以相同的方式使用,与此类似,经过数据类型转换之后,数值和数值对象也能被视为等价的。
// 为了区分小数点和点运算符而必须使用括号 var hzh1 = (1).toString(); console.log("输出(1).toString()的值:"); console.log(hzh1); // 确认是否确实是从数值转换为了字符串 console.log("判断(1).toString()的数据类型:"); console.log(typeof hzh1); console.log("***************************************************"); // 由于会进行隐式数据类型转换,因此数值和数值对象在外表上是没有什么区别的 // 如有必要,可以通过 typeof 运算对其进行判断 // 对数值对象执行 typeof 运算的结果为 "object" var hzh2 = new Number(1); console.log("判断Number(1)的数据类型:"); console.log(typeof hzh2);
// 和 String 函数类似,以通常的方式调用 Number 函数的话,将返回相应的数值。 // 在需要显式地进行数据类型转换的时候,可以使用 Number 函数。 var hzh1 = Number(1); // 变量hzh1的值为数值 console.log("判断Number(1)的数据类型:"); console.log(typeof hzh1); console.log("判断hzh1和1是否相等:"); console.log(hzh1 == 1); console.log("判断hzh1和1是否严格相等:"); console.log(hzh1 === 1); console.log("*****************************************************"); // 从字符串值至数值型的显式数据类型转换 var hzh2 = Number('1'); console.log("输出Number('1')的结果:"); console.log(hzh2); console.log("*****************************************************"); // 如果参数无法被转换为数值类型,Number 函数返回的结果将是 NaN var hzh3 = Number('x'); console.log("输出Number('x')的结果:"); console.log(hzh3); console.log("判断Number('x')的数据类型:"); console.log(typeof hzh3); console.log("*****************************************************"); var hzh4 = new Number('x'); console.log("输出new Number('x')的结果:"); console.log(hzh4); console.log("判断new Number('x')的数据类型:"); console.log(typeof hzh4);
// 可以通过 Number 对象的属性值来获知 64 位浮点数所支持的最大正值和最小正值 // 如果在其之前添加负号(运算符),就能够获得相应的最大负值和最小负值 var hzh1 = Number.MAX_VALUE; var hzh2 = Number.MIN_VALUE; var hzh3 = - Number.MAX_VALUE; var hzh4 = - Number.MIN_VALUE; console.log("输出Number.MAX_VALUE的结果:"); console.log(hzh1); console.log("输出Number.MIN_VALUE的结果:"); console.log(hzh2); console.log("输出- Number.MAX_VALUE的结果:"); console.log(hzh3); console.log("输出- Number.MIN_VALUE的结果"); console.log(hzh4); console.log("*******************************************************"); // 还可以通过 Number.MAX_VALUE.toString(16) 来获得相应的 16 进制数值 var hzh5 = Number.MAX_VALUE.toString(16); console.log("输出Number.MAX_VALUE.toString(16)的结果:"); console.log(hzh5);
// 在 JavaScript 中,浮点数的内部结构遵循 IEEE754 标准。 // 可以通过 Number 对象的属性值来获得在 IEEE754 中定义的一些特殊数值。 var hzh6 = Number.POSITIVE_INFINITY; var hzh7 = Number.NEGATIVE_INFINITY; var hzh8 = Number.NaN; console.log("输出Number.POSITIVE_INFINITY的结果:"); console.log(hzh6); console.log("输出Number.NEGATIVE_INFINITY的结果:"); console.log(hzh7); console.log("输出Number.NaN的结果"); console.log(hzh8);
从内部结构来看,这 3 个特殊数值(即正负无穷大与 NaN)都是基于 IEEE754 标准的比特位数值。虽然它们在形式上属于数值(typeof 运算符对它们的执行结果为 number),但是并不能作为数值进行计算。例如,将最大正数值乘以 2 之后能够得到正无穷大,但反之则不成立,正无穷大除以 2 之后无法得到最大正数值。
事实上,这 3 个特殊数值对于任何运算都无法得到通常的数值结果。
var hzh1 = Number.MAX_VALUE * 2; console.log("输出hzh1的结果:"); console.log(hzh1); // 将最大正数值乘以2之后能够得到正无穷大 console.log("输出(hzh1/2)的结果:"); console.log(hzh1/2); // 再除以2之后却无法得到原值 console.log("输出(hzh1*0)的结果:"); console.log(hzh1*0); // 即使乘以0,得到的结果也不是0
// 对 NaN 进行任何运算,其结果都是 NaN。 // 因此,如果在计算过程中出现了一次 NaN, // 最终的结果就一定会是 NaN console.log("输出(NaN + 1)的结果:"); console.log(NaN + 1); console.log("输出(NaN*0)的结果:"); console.log(NaN * 0); console.log("输出(NaN - NaN)的结果:"); console.log(NaN - NaN); console.log("*****************************************"); // NaN 不但不与其他任何数值相等, // 就算是两个 NaN 的等值判断,其结果也为假 console.log("输出(NaN == 1)的结果:"); console.log(NaN == 1); console.log("输出(NaN === 1)的结果:"); console.log(NaN === 1); console.log("输出(NaN == NaN)的结果:"); console.log(NaN == NaN); console.log("输出(NaN === NaN)的结果:"); console.log(NaN === NaN); console.log("*****************************************"); console.log("输出(NaN > 1)的结果:"); console.log(NaN > 1); console.log("输出(NaN >= 1)的结果:"); console.log(NaN >= 1); console.log("输出(NaN > NaN)的结果:"); console.log(NaN > NaN); console.log("输出(NaN >= NaN)的结果:"); console.log(NaN >= NaN);
// JavaScript 中预定义了一个全局函数 isNaN。 // isNaN 函数的返回值为真的条件是其参数的值为 NaN, // 或是在经过数据类型转换至数值类型后值为 NaN。 console.log("使用isNaN判断NaN:"); console.log(isNaN(NaN)); // NaN值的Number对象 var hzh1 = new Number(NaN); console.log("判断hzh1的数据类型:"); console.log(typeof hzh1); console.log("使用isNaN判断hzh1:"); console.log(isNaN(hzh1)); console.log("使用isNaN判断{}:"); console.log(isNaN({})); console.log("*****************************************"); // 而预定义全局函数 isFinite 可以对 3 个特殊数值 // (即 NaN与正负无穷大)之外的数值进行判断 console.log("使用isFinite判断数值1:"); console.log(isFinite(1)); console.log("使用isFinite判断数值NaN:"); console.log(isFinite(NaN)); console.log("使用isFinite判断数值Infinity:"); console.log(isFinite(Infinity)); console.log("使用isFinite判断数值-Infinity:"); console.log(isFinite(-Infinity));
// 在 ECMAScript 第 5 版中,已将 NaN 和 Infinity 改为了只读变量, // 因此不能再对它们进行数值更改。 // 需要注意的是,在对它们进行赋值的过程中并不会报错。 console.log("输出NaN:"); NaN = 7; console.log(NaN); console.log("输出Infinity:"); Infinity = 8; console.log(Infinity);
// 布尔型也被称为逻辑值类型或者真假值类型。 // 布尔型只能够取真(true)和假(false)两种数值。 // 除此以外,其他的值都不被支持。 var hzh1 = true; console.log("它们是true还是false?"); console.log("hzh1?" + hzh1); console.log("(!hzh1)?" + (!hzh1)); // true的否定为false console.log("(!!hzh1)?" + (!!hzh1)); // 否定的否定为肯定 console.log("**************************************************"); // 对布尔值进行 typeof 运算的话,得到的结果为 "Boolean"。 console.log("它们是什么数据类型?"); console.log("true?" + true); console.log("false?" + false); console.log("hzh1?" + hzh1); console.log("(!hzh1)?" + (!hzh1)); // true的否定为false console.log("(!!hzh1)?" + (!!hzh1)); // 否定的否定为肯定
// 布尔类(Boolean 类)是一种布尔型的包装类型。 // 其地位以及使用方法和 String 类以及 Number 类相同。 // 像下面这样对布尔值使用点运算符的话, // 就能够对布尔值进行隐式数据类型转换,将其转为布尔对象。 // 但是,布尔类中并没有什么实用的方法,所以基本上也很少会去使用 console.log("隐式数据类型转换为了Boolean对象:"); console.log(true.toString());
null 值的意义存在于对象引用之中。null 值最初的含义为“没有引用任何对象”。null 型只能够取 null 这一个值。null 值是一个字面量。由于只支持 null 这个值,所以将 null 型称为一种类型未免有些奇怪。不过从语法规则上来看,null 型确实是一种数据类型。
然而,对 null 值进行 typeof 运算得到的结果也是 "object"(具体原因尚不得知)。因此,尽管其他的基本数据类型都可以通过 typeof 运算来进行类型判断,但对于 null 型来说,就必须通过和 null 值的等值判断才能确定其类型。
console.log("typeof运算的结果为'object':"); console.log(typeof null); // null 型没有与之相对应的 Null 类。 console.log("对null值进行点运算,就会产生TypeError异常:"); console.log(null.toString()); // 和其他程序设计语言一样,null 值可能引发各种各样的错误, // 其中大部分和数据类型转换以及一些运算有关。
// undefined 型只能够取 undefined 这一个值。 console.log("对undefined值进行typeof运算,其结果为undefined:"); console.log(typeof undefined); // 从代码上来看,undefined 值似乎和 null 值一样都是一种字面量。 // 但实际上,它并非字面量,而是一个预定义的全局变量 console.log("对名称为 undefined 的全局变量进行赋值:"); undefined = '黄子涵' console.log(undefined); console.log("判断undefined的数据类型:"); console.log(typeof undefined);
全局变量 undefined 与 undefined 值的关系其实就是这样的:首先有了 undefined 型的值,之后才将该值赋值给了全局变量 undefined。
null 是一种字面量而 undefined 是一个变量名,这并不是一种偶然。要使一个变量的值为 null,就必须将 null 以字面量的形式赋值给该变量。因此从语言规则的角度来说,null 必须是一种字面量。另一方面,undefined 值最多只能算是某个没有经过显式赋值的变量的初始值。所以根据字面含义,将其称为未定义值或是未初始化值都没有问题。
console.log("只是被声明了的变量:"); var hzh1; console.log("该变量的值为undefined值:"); console.log(typeof hzh1);
也就是说,从语法规则上来看,undefined 这一标识符并不是必需的。这是因为只需将全局变量undefined 赋值给没有被赋值的变量就可以了。null 值指的是没有引用任何对象的状态,尽管从含义上来看是否定的,但仍然是有其含义的。而 undefined 值则不同,像它的字面意思那样,仅仅指的是一个尚未定义的值。
对于 undefined 值来说,并不存在与之相对应的 Undefined 类。因此如果像下面这样对 undefined 值进行点运算,将会产生 TypeError 异常。
- 未初始化的变量的值
- 不存在的属性的值
- 在没有传入实参而调用函数时,该函数内相应参数的值
- 没有 return 语句或是 return 语句中不含表达式的函数的返回值
- 对 void 运算符求值的结果(常常会通过使用 void 0 来获取一个 undefined 值)
null 值可能会引起程序出错,而 undefined 值比它更容易引发错误。令情况更为混乱的是,如果对 null 值和 undefined 值做进行数据类型转换的等值运算(
==
),结果为真。此外,对于不进行数据类型转换的等值运算(===
),其结果则为假。
undefined 值是一种有着非常高的潜在出错风险的语言特性,所以在使用 undefined 值时请多加留心。
除了基本类型之外,其他的所有类型都是 Object 类型。在 5 种基本数据类型之外还有一种 Object 类型即可。
// 对 Object 类型进行 typeof 运算,得到的结果是 "object"。 var hzh1 = {}; // 生成空的对象 console.log("对变量hzh1所引用的对象进行typeof运算:"); console.log(typeof hzh1);
只要将 Function 类型认为是 Object 类型的一种类型就足够了。在此只需要了解,对一个函数进行
JavaScript 标准的类型判定方法,也就是其 typeof 运算的结果为字符串 "function"。
JavaScript 这种语言很容易在进行数据类型转换时发生错误。因为不具有强数据类型,所以会有大量的隐式数据类型转换。JavaScript 会根据上下文语境,自动地进行数据类型转换。例如,无论对 if 条件语句使用怎样的值,该值都将被转换为布尔型。
语句中所写的值也会被转换为和运算符相对应的值。例如,某个值与字符串值和连接运算符(+ 号)相连的话,不管该值是哪种类型,它都将被自动转换为字符串型。
这样的隐式数据类型转换,虽然有不需要进行显式的数据类型转换,以及不会产生类型不一致错误的优点,但是也有不足,且其中很多错误只有在运行时才能被发现。
不过,要是由于过分担心隐式数据类型转换可能造成的问题,而始终使用显式的数据类型转换的话,又不符合 JavaScript 编程的风格。JavaScript 注重的是灵活运用隐式数据类型转换,以写出简洁的代码。虽然确实存在着不少陷阱,但在必要的时候,应灵活使用显式的数据类型转换。
通常的做法是使用 Number 函数、parseInt 函数和 parseFloat 函数。Number 函数的书写最为简单,不过需要注意的是,对于像 '100x' 这样的包含非数字的字符串值,函数返回的结果将是 NaN。而 parseInt 和 parseFloat 将会忽略数字以外的其他字符,所以 '100x' 将被转换为 100。parseInt 函数还可以通过第二参数来指定转换时所采用的基数(radix)。如果省略该参数则默认进行 10 进制转换。
console.log("将字符串值'100'转换为数值100:"); console.log(typeof Number('100')); console.log("将字符串值'100x'转换为数值100:"); console.log(Number('100x')); console.log("输出parseInt('100')的结果:"); console.log(parseInt('100')); console.log("输出parseInt('100x')的结果:"); console.log(parseInt('100x')); console.log("输出parseInt('x')的结果:"); console.log(parseInt('x')); console.log("输出parseInt('ff', 16)的结果:"); console.log(parseInt('ff', 16)); console.log("输出parseInt('0xff', 16)的结果:"); console.log(parseInt('0xff', 16)); console.log("输出parseInt('ff', 10)的结果:"); console.log(parseInt('ff', 10)); console.log("输出parseInt('0.1')的结果:"); console.log(parseInt('0.1')); console.log("输出parseFloat('0.1')的结果:"); console.log(parseFloat('0.1')); console.log("*********************************************"); // 只要在数值运算操作数的位置上书写字符串值, // 该值就将被隐式地转换为数值类型。 console.log("字符串值'100'被转换为了数值100:"); console.log('100' - 1); console.log("两个操作数位置的字符串值被转换为了数值:"); console.log('100' - '1'); console.log("字符串转换为数值的惯用方式:"); console.log('100' - ''); console.log("*********************************************"); // 如果是加法运算,则不一定能获得期望的结果。 // 这是因为对于 + 运算来说,如果操作数中含有字符串值, // 它就将变为字符串连接运算。 // 于是发生的就不再是从字符串值到数值的数据类型转换, // 而是从数值到字符串值的数据类型转换了。 console.log("字符串连接运算:"); console.log('100' + 1); console.log("即使第一个操作数是数值,也是字符串连接运算:"); console.log(1 + '100'); console.log("*********************************************"); // + 运算在作为单目运算符的情况下则是正号运算。 // 这时操作数将被转换为数值类型。 // 不过由于正号运算没有任何实质意义, // 所以其作用就仅仅是将字符串值转换为数值。 console.log("将字符串值'100'转换为数值100:"); console.log(typeof + '100'); console.log("将字符串值'100'转换为数值100:"); var hzh1 = '100'; console.log(typeof + hzh1);
// 通常的做法是使用 String 函数, // 或是在对数值对象进行了隐式数据类型转换之后, // 再对其调用 toString方法。 console.log("将数值100转换为字符串值'100':"); console.log(typeof String(100)); console.log("将数值100转换为字符串值'100'。"); console.log("为了区分小数点和点运算而必须使用括号:"); console.log(typeof (100).toString()); console.log("将数值hzh1转换为字符串值:"); var hzh1 = 100; console.log(String(100)); console.log("将数值hzh1转换为字符串值:"); console.log(hzh1.toString()); console.log("***********************************************"); // 在字符串运算的操作数位置上写数值的话, // 就将对其进行隐式数据类型转换。 // + 运算符的操作数中如果含有字符串, // 则会作为字符串连接运算符使用。 console.log("数值100被转换为了字符串值'100':"); console.log('foo' + 100); console.log("就算数值位于左操作数的位置也一样会被转换为字符串值:"); console.log(100 + 'foo'); console.log("将数值hzh1转换为字符串值:"); var hzh1 = 100; console.log('foo' + hzh1);
可以通过多种方式来实现数据类型转换。具体哪种方法的运行速度更快和具体码的运行速度,还应当尽可能地缩短源代码的长度。只有缩短代码长度才能够减少网络传输的时间。因此,以较短的代码长度实现数据类型转换的写法成为了首选。
// 最为简短的数据类型转换写法.虽然使用String(n)或Number(s), // 代码可读性可能会更高一些,不过这样的写法能够实现更好的性能 // 从数值转换为字符串值 var hzh1 = 3; console.log("将数值3转换为字符串值'3'(利用了字符串连接运算符):"); console.log(hzh1 + ''); // 从字符串值转换为数值 var hzh2 = '3'; console.log("将字符串值'3'转换为了数值3(利用了正号运算):"); console.log( + hzh2);
隐式数据类型转换虽然简单,但容易引发错误。最为典型的错误就是在将字符串值转换为数值类型时,其结果可能会是 NaN。
一旦在运算中出现了 NaN,整个数值运算的结果都将变为 NaN。这样不仅无法得到正确的结算结果,而
且无法通过逆向的数据类型转换来得到原来的字符串。
在实际编程中,从其他类型向布尔型的数据类型转换是很重要的。在 if 语句或是 while 语句等的条件表达式中,会有很多这样的隐式数据类型转换。
- 数值0
- 数值NaN
- null值
- undefined值
- 字符串值""(空字符串值)
// 下面代码中在if语句的条件表达式写了数值0 // 在经过隐式数据类型转换之后,它将变为布尔型的false。 if(0) { console.log('黄子涵说0是真的'); } else { console.log('黄子涵说0是假的'); } console.log("*******************************************"); // 尽管也可以通过 Boolean 函数,将一个值显式地转换为布尔型。 // 使用 !! 来进行隐式的数据类型转换的。 // ! 运算是用于布尔型操作数的逻辑非运算。 // 在操作数不是布尔型的情况下会自动将其转换为布尔型。 // 因此只要使用 !! 这样的双重否定,就能够将值转换为布尔型。 console.log("输出(!!1)的结果:"); console.log(!!1); console.log("输出(!!x)的结果:"); console.log(!!'x'); console.log("输出(!!0)的结果:"); console.log(!!0); console.log("输出(!!'')的结果:"); console.log(!!null); console.log("*********************************************"); // 在进行布尔型的数据类型转换时,应当对 Object 类型的情况多加注意。 // Object 类型在被转换为布尔型之后结果必定为 true。 var hzh1 = new Boolean(false); if(hzh1) { console.log("黄子涵说hzh1是真的"); } else { console.log("黄子涵说hzh1是假的"); } console.log(""); var hzh2 = new Number(0); if(hzh2) { console.log("黄子涵说hzh2是真的"); } else { console.log("黄子涵说hzh2是假的"); } console.log(""); var hzh3 = new String(''); if(hzh3) { console.log("黄子涵说hzh3是真的"); } else { console.log("黄子涵说hzh3是假的"); } console.log("*********************************************"); // 像下面这样,通过函数调用方式获得的结果就不再是 Object类型, // 而是相应的内建类型了。 var hzh4 = Boolean(false); if(hzh4) { console.log("黄子涵说hzh4是真的"); } else { console.log("黄子涵说hzh4是假的"); } console.log(""); var hzh5 = Number(0); if(hzh5) { console.log("黄子涵说hzh5是真的"); } else { console.log("黄子涵说hzh5是假的"); } console.log(""); var hzh6 = String(''); if(hzh6) { console.log("黄子涵说hzh5是真的"); } else { console.log("黄子涵说hzh5是假的"); }
// 将 Object 类型分别显式地与隐式地转换为字符串型 // 生成空对象 var hzh1 = {}; console.log("将对象隐式地转换为字符串型(通过字符串连接运算符):"); console.log(hzh1 + ''); console.log("将对象显式地转换为字符串型:"); console.log(String(hzh1)); console.log("*************************************************"); // 上面的结果是调用了对象 obj 的 toString 方法而得到的。 // 如果像下面这样,改变了 toString 方法的实现的话, // 结果也将随之发生变化 console.log("确认当前toString方法的结果:"); console.log(hzh1.toString()); // 重写toString方法的实现 hzh1.toString = function() { return '黄子涵'; } console.log("确认toString方法的结果:"); console.log(hzh1.toString()); console.log("将对象隐式地转换为字符串型:"); console.log(hzh1 + ''); console.log("将对象显式地转换为字符串型:"); console.log(String(hzh1));
和基本数据类型之间的类型转换一样,Object 类型和基本数据类型之间的数据类型转换,也可以根据上下文语境隐式地进行。因此,实际编程中,并不太需要进行显式的数据类型转换,反倒是更应该注意避免由于隐式数据类型转换而引发的错误。
// 下面的代码并不会报错,而是会将变量hzh1的值转换为NaN var hzh1 = {}; console.log("该对象被隐式地转换为了数值型:"); console.log(hzh1++); console.log("变量hzh1的值为NaN:"); console.log(hzh1);
如果一个对象没有能返回恰当数值的 valueOf 方法,或是能返回可以被转换为恰当数值的字符串的toString 方法的话,它在被转换为数值型之后的结果将是 NaN。又因为对 NaN 进行 ++运算的结果为 NaN,所以变量 obj 的最终值将是 NaN。
使用性能分析工具,就能了解一个运行中的 JavaScript 程序正在执行哪一个函数、正在执行哪一行,以及花费了多少时间。使用性能分析工具的目的通常是为了检查程序中运行速度较慢的部分。这样的部分被称为运行瓶颈。根据经验,如果程序的运行很慢,很有可能是由于其中某一部分花费了较多的运行时间。因此,如果提高了瓶颈部分的执行速度,程序整体的运行性能也很可能得以提高。反过来说,如果不确定程序的瓶颈所在,就胡乱地进行性能优化,效果往往差强人意。
程序的执行时间并不仅仅由一些代码的处理时间决定。对于客户端 JavaScript 来说,以下几个因素相结合最终决定了用户的实际使用感受。
- JavaScript 代码的执行时间(这也是仅凭性能分析工具所能测得的)
- DOM 的渲染时间
- 网络响应时间
Firebug、IE 的开发者工具和 Chrome 的开发者工具都具有
基本的性能分析功能。此外,表 A 还列举了一些包含了其
他各方面的分析测试功能的性能分析工具