ECMAScript中有6种简单数据类型(也称为基本数据类型)undefined、null、boolean、number、string和es6新加的Symbol。还有一种复杂数据类型Object
undefined类型
undefined类型只有一个值,及特殊的undefined。在使用var声明变量但未对其加以初始化是,这个变量的值就是undefined,例如:
var message;
console.log(message == undefined) // true
null类型
Null类型是第二个只有一个值的数据类型,这个特殊的值是null。从逻辑角度来看,null值表示一个空对象指针,而这也正是使用typeof操作符检测null值是会返回‘object’的原因,例如:
var car = null;
console.log(typeof(car)); //object
如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其他值。这样一来。只要检查null值就可以知道响应的变量是否已经保存了一个对象的引用,例如:
if(car != null) {
//对car对象执行某些操作
}
实际上,undefined值是派生自null值的,因此ECMA-262规定对它们的想等性测试要返回true
console.log(null == undefined); //true
这里,位于null和undefined之间的想等操作符(==)总是返回true,不过要注意的是,这个操作符出于比较目的会转换其操作数。
尽管null和undefined有这样的关系,但它们的用途完全不同。如上所述,无论在什么情况下都没有必要把一个变量的值设置为undefined,可同样的规则对null却不适用。换句话说,只要意在保存对象的变量还没有真正保存对象,就应该明确的让该变量保存null值。这样做不仅可以提现null作为空指针的惯例,而且也有助于进一步区分null和undefined。
boolean类型
boolean类型是ECMAScript中使用最多的一种类型,该类型只有两个字面值:true和false。这两个值与数字值不是一回事,因此true不一定等于1,而false也不一定等于0。以下是为变量赋值boolean类型值的例子:
var found = true;
var lost = false;
需要注意的是,boolean类型的字面值true和false是区分大小写的,也就是说True和False都不是boolean值,只是标识符
虽然boolean类型的字面值只有两个,但是ECMAScript中所有的类型值都与这两个boolean值等价的值。要将一个值转换为其对应的boolean值,可以调用转型函数Boolean(),如下例所示:
var message = 'hello world';
var messageBoolean = Boolean(message) //true
在这个例子中,字符串message被转换成了一个boolean值,该值被保存在messageBoolean变量中。可以对任意数据类型的值调用Boolean()函数,而且总会返回一个boolean值。至于返回的这个值是true还是false,取决于要转换值的数据类型及实际值,下表给出了各种数据类型
数据类型 | 抓换为true的值 | 转换为false的值 |
---|---|---|
boolean | true | false |
String | 任何非空字符串 | “”空字符串 |
Number | 任何非零数字值(包括无穷大) | 0和NAN |
object | 任意对象 | null |
undefined | n/a | undefined |
这些转换规则对理解流控制语句(如if语句)自动执行响应的boolean转换非常重要,请看下面的代码:
var message = "Hello world";
if (message){
alert('Value is true);
}
运行这个示例,就会显示一个警告框,因为字符串message被自动转换成了对应的boolean值(true)。由于存在这种自动执行的boolean转换,因此确切的知道在流程控制语句使用的是什么变量至关重要。错误的使用一个对象而不是一个boolean值,就有可能彻底改变应用程序的流程。
Number类型
Number类型应该是ECMAScript中最令人关注的数据类型了,这种类型使用IEEE754格式来表示整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。为支持各种数值类型,ECMA-262定义了不同数值字面量格式。
最基本的数值字面量格式是十进制整数,十进制整数可以像下面这样直接在代码中输入:
var intnem = 55; //整数
除了10进制表示外,整数还可以通过8进制(以8为基数)或16进制(以16为基数)的字符值来表示。其中,8进制字面值的第一位必须是零(0),然后是八进制序列号(0~7)。如果字面值中的数值超出了范围,那么前导零将被忽略,后面的数值将被当作十进制解析。请看下面的例子:
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
在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。
1.浮点数值
所谓浮点数值,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。虽然小数点前面可以没有整数,但我们不推荐这种写法。以下是浮点数值的几个例子:
var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1; // 有效,但不推荐
由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值转换为整数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存。同样地,如果浮点数值本身表示的就是一个整数(如 1.0),那么该值也会被转换为整数,如下面的例子所示:
var floatNum1 = 1.; // 小数点后面没有数字——解析为 1
var floatNum2 = 10.0; // 整数——解析为 10
对于那些极大或极小的数值,可以用 e 表示法(即科学计数法)表示的浮点数值表示。用 e 表示法表示的数值等于 e 前面的数值乘以 10 的指数次幂。ECMAScript 中 e 表示法的格式也是如此,即前面是一个数值(可以是整数也可以是浮点数),中间是一个大写或小写的字母 E,后面是 10 的幂中的指数,该幂值将用来与前面的数相乘。下面是一个使用 e 表示法表示数值的例子:
var floatNum = 3.125e7; // 等于 31250000
在这个例子中,使用 e 表示法表示的变量 floatNum 的形式虽然简洁,但它的实际值则是 31250000。在此,e 表示法的实际含义就是“3.125 乘以 107”。也可以使用 e 表示法表示极小的数值,如 0.00000000000000003,这个数值可以使用更简洁的 3e-17表示。在默认情况下,ECMASctipt 会将那些小数点后面带有 6 个零以上的浮点数值转换为以 e 表示法表示的数值(例如,0.0000003 会被转换成 3e-7)。浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加 0.2的结果不是 0.3,而是 0.30000000000000004。这个小小的舍入误差会导致无法测试特定的浮点数值。例如:
if (a + b == 0.3){ // 不要做这样的测试!
alert("You got 0.3.");
}
在这个例子中,我们测试的是两个数的和是不是等于 0.3。如果这两个数是 0.05 和 0.25,或者是 0.15和 0.15 都不会有问题。而如前所述,如果这两个数是 0.1 和 0.2,那么测试将无法通过。因此,永远不要测试某个特定的浮点数值。
备注:关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于IEEE754 数值的浮点计算的通病,ECMAScript 并非独此一家;其他使用相同数值格式的语言也存在这个问题。
2.数值范围
由于内存的限制,ECMAScript 并不能保存世界上所有的数值。ECMAScript 能够表示的最小数值保存在 Number.MIN_VALUE 中——在大多数浏览器中,这个值是 5e-324;能够表示的最大数值保存在Number.MAX_VALUE 中——在大多数浏览器中,这个值是 1.7976931348623157e+308。如果某次计算的结果得到了一个超出 JavaScript 数值范围的值,那么这个数值将被自动转换成特殊的 Infinity 值。具体来说,如果这个数值是负数,则会被转换成-Infinity(负无穷),如果这个数值是正数,则会被转换成 Infinity(正无穷)。如上所述,如果某次计算返回了正或负的 Infinity 值,那么该值将无法继续参与下一次的计算,因为 Infinity 不是能够参与计算的数值。要想确定一个数值是不是有穷的(换句话说,是不是位于最小和最大的数值之间),可以使用 isFinite()函数。这个函数在参数位于最小与最大数值之间时会返回 true,如下面的例子所示:
var result = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result)); //false
尽管在计算中很少出现某些值超出表示范围的情况,但在执行极小或极大数值的计算时,检测监控这些值是可能的,也是必需的。
备注:访问 Number.NEGATIVE_INFINITY Number.POSITIVE_INFINITY也可以得到负和正 Infinity 的值。可以想见,这两个属性中分别保存-Infinity 和Infinity。。
3.NaN
NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。例如,在其他编程语言中,任何数值除以 0都会导致错误,从而停止代码执行。但在 ECMAScript中,任何数值除以 0会返回 NaN①,因此不会影响其他代码的执行。NaN 本身有两个非同寻常的特点。首先,任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。例如,下面的代码会返回 false:
alert(NaN == NaN); //false
针对 NaN 的这两个特点,ECMAScript 定义了 isNaN()函数。这个函数接受一个参数,该参数可以是任何类型,而函数会帮我们确定这个参数是否“不是数值”。isNaN()在接收到一个值之后,会尝试将这个值转换为数值。某些不是数值的值会直接转换为数值,例如字符串”10”或 Boolean 值。而任何不能被转换为数值的值都会导致这个函数返回 true。请看下面的例子:
alert(isNaN(NaN)); //true
alert(isNaN(10)); //false(10 是一个数值)
alert(isNaN("10")); //false(可以被转换成数值 10)
alert(isNaN("blue")); //true(不能转换成数值)
alert(isNaN(true)); //false(可以被转换成数值 1)
这个例子测试了 5 个不同的值。测试的第一个值是 NaN 本身,结果当然会返回 true。然后分别测试了数值 10 和字符串”10”,结果这两个测试都返回了 false,因为前者本身就是数值,而后者可以被转换成数值。但是,字符串”blue”不能被转换成数值,因此函数返回了 true。由于 Boolean 值 true可以转换成数值 1,因此函数返回 false。
备注:尽管有点儿不可思议,但 isNaN()确实也适用于对象。在基于对象调用 isNaN()函数时,会首先调用对象的 valueOf()方法,然后确定该方法返回的值是否可以转换为数值。如果不能,则基于这个返回值再调用 toString()方法,再测试返回值。而这个过程也是 ECMAScript 中内置函数和操作符的一般执行流程,。
4.数值转换
有 3 个函数可以把非数值转换为数值:Number()、parseInt()和 parseFloat()。第一个函数,即转型函数 Number()可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值。这 3 个函数对于同样的输入会有返回不同的结果。
Number()函数的转换规则如下。
- 如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0。
- 如果是数字值,只是简单的传入和返回。
- 如果是 null 值,返回 0。
- 如果是 undefined,返回 NaN。
- 如果是字符串,遵循下列规则:
- 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即”1”会变成 1,”123”会变成 123,而”011”会变成 11(注意:前导的零被忽略了);
- 如果字符串中包含有效的浮点格式,如”1.1”,则将其转换为对应的浮点数值(同样,也会忽略前导零);
- 如果字符串中包含有效的十六进制格式,例如”0xf”,则将其转换为相同大小的十进制整数值;
- 如果字符串是空的(不包含任何字符),则将其转换为 0;
- 如果字符串中包含除上述格式之外的字符,则将其转换为 NaN。
- 如果是对象,则调用对象的 valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是 NaN,则调用对象的 toString()方法,然后再次依照前面的规则转换返回的字符串值。
根据这么多的规则使用 Number()把各种数据类型转换为数值确实有点复杂。下面还是给出几个具体的例子吧。
var num1 = Number("Hello world!"); //NaN
var num2 = Number(""); //0
var num3 = Number("000011"); //11
var num4 = Number(true); //1
首先,字符串”Hello world!”会被转换为 NaN,因为其中不包含任何有意义的数字值。空字符串会被转换为 0。字符串”000011”会被转换为 11,因为忽略了其前导的零。最后,true 值被转换为 1。
由于 Number()函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是parseInt()函数。parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()就会返回 NaN;也就是说,用 parseInt()转换空字符串会返回 NaN(Number()对空字符返回 0)。如果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。例如,”1234blue”会被转换为 1234,因为”blue”会被完全忽略。类似地,”22.5”会被转换为 22,因为小数点并不是有效的数字字符。
如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式(即前面讨论的十进制、八进制和十六进制数)。也就是说,如果字符串以”0x”开头且后跟数字字符,就会将其当作一个十六进制整数;如果字符串以”0”开头且后跟数字字符,则会将其当作一个八进制数来解析。为了更好地理解 parseInt()函数的转换规则,下面给出一些例子:
var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10(十六进制数)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56(八进制数)
var num6 = parseInt("70"); // 70(十进制数)
var num7 = parseInt("0xf"); // 15(十六进制数)
在使用 parseInt()解析像八进制字面量的字符串时,ECMAScript 3 和 5存在分歧。例如:
//ECMAScript 3 认为是 56(八进制),ECMAScript 5 认为是 70(十制)
var num = parseInt("070");
在 ECMAScript 3 JavaScript 引擎中,”070”被当成八进制字面量,因此转换后的值是十进制的 56。而在 ECMAScript 5 JavaScript 引擎中,parseInt()已经不具有解析八进制值的能力,因此前导的零会被认为无效,从而将这个值当成”70”,结果就得到十进制的 70。在 ECMAScript 5 中,即使是在非严格模式下也会如此。
为了消除在使用 parseInt()函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换时使用的基数(即多少进制)。如果知道要解析的值是十六进制格式的字符串,那么指定基数 16 作为第二个参数,可以保证得到正确的结果,例如:
var num = parseInt("0xAF", 16); //175
实际上,如果指定了 16 作为第二个参数,字符串可以不带前面的”0x”,如下所示:
var num1 = parseInt("AF", 16); //175
var num2 = parseInt("AF"); //NaN
这个例子中的第一个转换成功了,而第二个则失败了。差别在于第一个转换传入了基数,明确告诉parseInt()要解析一个十六进制格式的字符串;而第二个转换发现第一个字符不是数字字符,因此就自动终止了。
指定基数会影响到转换的输出结果。例如:
var num1 = parseInt("10", 2); //2 (按二进制解析)
var num2 = parseInt("10", 8); //8 (按八进制解析)
var num3 = parseInt("10", 10); //10 (按十进制解析)
var num4 = parseInt("10", 16); //16 (按十六进制解析)
不指定基数意味着让 parseInt()决定如何解析输入的字符串,因此为了避免错误的解析,我们建议无论在什么情况下都明确指定基数。
备注:多数情况下,我们要解析的都是十进制数值,因此始终将 10 作为第二个参数是非常必要的。
与 parseInt()函数类似,parseFloat()也是从第一个字符(位置 0)开始解析每个字符。而且也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。也就是说,字符串中的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。举例来说,”22.34.5”将会被转换为 22.34。
除了第一个小数点有效之外,parseFloat()与 parseInt()的第二个区别在于它始终都会忽略前导的零。parseFloat()可以识别前面讨论过的所有浮点数值格式,也包括十进制整数格式。但十六进制格式的字符串则始终会被转换成 0。由于 parseFloat()只解析十进制值,因此它没有用第二个参数指定基数的用法。最后还要注意一点:如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后都是零),parseFloat()会返回整数。以下是使用 parseFloat()转换数值的几个典型示例。
var num1 = parseFloat("1234blue"); //1234 (整数)
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000
String类型
String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,即字符串。字符串可以由双引号(”)或单引号(’)表示,因此下面两种字符串的写法都是有效的:
var firstName = "Nicholas";
var lastName = 'Zakas';
与 PHP 中的双引号和单引号会影响对字符串的解释方式不同,ECMAScript 中的这两种语法形式没有什么区别。用双引号表示的字符串和用单引号表示的字符串完全相同。不过,以双引号开头的字符串也必须以双引号结尾,而以单引号开头的字符串必须以单引号结尾。例如,下面这种字符串表示法会导致语法错误:
var firstName = 'Nicholas"; // 语法错误(左右引号必须匹配)
1.字符字面量
String 数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者具有其他用途的字符。这些字符字面量如下表所示:
字面量 | 含义 |
---|---|
\n | 换行 |
\t | 制表 |
\b | 空格 |
\r | 回车 |
\f | 进纸 |
\ | 斜杆 |
‘ | 单引号(‘),在用单引号表示的字符串中使用。例如:’He said, ‘hey.’’ |
“ | 双引号(“),在用单引号表示的字符串中使用。例如:”He said, “hey.”” |
\xnn | 以十六进制代码nn表示的一个字符(其中n为0~F)。例如,\x41表示”A” |
\unnnn | 以十六进制代码nnnn表示的一个Unicode字符(其中n为0~F)。例如,\u03a3表示希腊字符Σ |
这些字符字面量可以出现在字符串中的任意位置,而且也将被作为一个字符来解析,如下面的例子所示:
var text = "This is the letter sigma: \u03a3.";
这个例子中的变量 text 有 28 个字符,其中 6 个字符长的转义序列表示 1 个字符。任何字符串的长度都可以通过访问其 length 属性取得,例如:
alert(text.length); // 输出 28
这个属性返回的字符数包括 16 位字符的数目。如果字符串中包含双字节字符,那么 length 属性可能不会精确地返回字符串中的字符数目。
2.字符串的特点
ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,例如:
var lang = "Java";
lang = lang + "Script";
以上示例中的变量 lang 开始时包含字符串”Java”。而第二行代码把 lang 的值重新定义为”Java”与”Script”的组合,即”JavaScript”。实现这个操作的过程如下:首先创建一个能容纳 10 个字符的新字符串,然后在这个字符串中填充”Java”和”Script”,最后一步是销毁原来的字符串”Java”和字符串”Script”,因为这两个字符串已经没用了。这个过程是在后台发生的,而这也是在某些旧版本的浏览器(例如版本低于 1.0 的 Firefox、IE6 等)中拼接字符串时速度很慢的原因所在。但这些浏览器后来的版本已经解决了这个低效率问题。
3.转换为字符串
要把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有的 toString()方法这个方法唯一要做的就是返回相应值的字符串表现。来看下面的例子:
var age = 11;
var ageAsString = age.toString(); // 字符串"11"
var found = true;
var foundAsString = found.toString(); // 字符串"true"
数值、布尔值、对象和字符串值(没错,每个字符串也都有一个 toString()方法,该方法返回字符串的一个副本)都有 toString()方法。但 null 和 undefined 值没有这个方法。多数情况下,调用 toString()方法不必传递参数。但是,在调用数值的 toString()方法时,可以传递一个参数:输出数值的基数。默认情况下,toString()方法以十进制格式返回数值的字符串表示。而通过传递基数,toString()可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值。下面给出几个例子:
var num = 10;
alert(num.toString()); // "10"
alert(num.toString(2)); // "1010"
alert(num.toString(8)); // "12"
alert(num.toString(10)); // "10"
alert(num.toString(16)); // "a"
通过这个例子可以看出,通过指定基数,toString()方法会改变输出的值。而数值 10 根据基数的不同,可以在输出时被转换为不同的数值格式。注意,默认的(没有参数的)输出值与指定基数 10 时的输出值相同。在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String(),这个函数能够将任何类型的值转换为字符串。String()函数遵循下列转换规则:
- 如果值有 toString()方法,则调用该方法(没有参数)并返回相应的结果;
- 如果值是 null,则返回”null”;
- 如果值是 undefined,则返回”undefined”。
下面在看几个例子:
var value1 = 10;
var value2 = true;
var value3 = null;
var value4;
alert(String(value1)); // "10"
alert(String(value2)); // "true"
alert(String(value3)); // "null"
alert(String(value4)); // "undefined"
这里先后转换了 4 个值:数值、布尔值、null 和 undefined。数值和布尔值的转换结果与调用toString()方法得到的结果相同。因为 null 和 undefined 没有 toString()方法,所以 String()函数就返回了这两个值的字面量。
备注:要把某个值转换为字符串,可以使用加号操作符(3.5 节讨论)把它与一个字符串(””)加在一起。
object类型
ECMAScript 中的对象其实就是一组数据和功能的集合。对象可以通过执行 new 操作符后跟要创建的对象类型的名称来创建。而创建 Object 类型的实例并为其添加属性和(或)方法,就可以创建自定义对象,如下所示:
var o = new Object();
这个语法与 Java 中创建对象的语法相似;但在 ECMAScript 中,如果不给构造函数传递参数,则可以省略后面的那一对圆括号。也就是说,在像前面这个示例一样不传递参数的情况下,完全可以省略那对圆括号(但这不是推荐的做法):
var o = new Object; // 有效,但不推荐省略圆括号
仅仅创建 Object 的实例并没有什么用处,但关键是要理解一个重要的思想:即在 ECMAScript 中,(就像 Java 中的 java.lang.Object 对象一样)Object 类型是所有它的实例的基础。换句话说,Object 类型所具有的任何属性和方法也同样存在于更具体的对象中。
Object 的每个实例都具有下列属性和方法。
- constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是 Object()。
- hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如:o.hasOwnProperty(“name”))。
- isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型(第 5 章将讨论原型)。
- propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句(本章后面将会讨论)来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。
- toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
- toString():返回对象的字符串表示。
- valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。
由于在 ECMAScript 中 Object 是所有对象的基础,因此所有对象都具有这些基本的属性和方法。
从技术角度讲,ECMA-262 中对象的行为不一定适用于 JavaScript 中的其他对象。浏览器环境中的对象,比如 BOM 和 DOM 中的对象,都属于宿主对象,因为它们是由宿主实现提供和定义的。ECMA-262 不负责定义宿主对象,因此宿主对象可能会也可能不会继承 Object。
详解
一、JavaScript有几种类型的值?
Javascript有两种数据类型,分别是基本数据类型和引用数据类型。其中基本数据类型包括Undefined、Null、Boolean、Number、String、Symbol (ES6新增,表示独一无二的值),而引用数据类型统称为Object对象,主要包括对象、数组和函数。接下来我们分别看下两者的特点。
二、基本数据类型
1.值是不可变的
var name = 'java';
name.toUpperCase(); // 输出 'JAVA'
console.log(name); // 输出 'java'
2.存放在栈区
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
3.值的比较
var a = 1;
var b = true;
console.log(a == b); // true
console.log(a === b); // false
== : 只进行值的比较,会进行数据类型的转换。 === : 不仅进行值得比较,还要进行数据类型的比较。
三、引用数据类型
1.值是可变的
var a={age:20};
a.age=21;
console.log(a.age)//21
上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的。
2.同时保存在栈内存和堆内存
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
3.比较是引用的比较
当从一个变量向另一个变量赋引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。
var a={age:20};
var b=a;
b.age=21;
console.log(a.age==b.age)//true
上面我们讲到基本类型和引用类型存储于内存的位置不同,引用类型存储在堆中的对象,与此同时,在栈中存储了指针,而这个指针指向正是堆中实体的起始位置。变量a初始化时,a指针指向对象{age:20}的地址,a赋值给b后,b又指向该对象{age:20}的地址,这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。
此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。
var a={age:20};
var b=a;
a = 1;
b // {age:20}
上面代码中,a和b指向同一个对象,然后a的值变为1,这时不会对b产生影响,b还是指向原来的那个对象。
四、检验数据类型
1.typeof
typeof返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型,但不能判断null、array等
typeof Symbol(); // symbol 有效
typeof ''; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效
数组和对象返回的都是object,这时就需要使用instanceof来判断
2.instanceof
instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
关于数组的类型判断,还可以用ES6新增Array.isArray()
Array.isArray([]); // true
instanceof 三大弊端:
对于基本数据类型来说,字面量方式创建出来的结果和实例方式创建的是有一定的区别的
console.log(1 instanceof Number)//false
console.log(new Number(1) instanceof Number)//true
从严格意义上来讲,只有实例创建出来的结果才是标准的对象数据类型值,也是标准的Number这个类的一个实例;对于字面量方式创建出来的结果是基本的数据类型值,不是严谨的实例,但是由于JS的松散特点,导致了可以使用Number.prototype上提供的方法。
只要在当前实例的原型链上,我们用其检测出来的结果都是true。在类的原型继承中,我们最后检测出来的结果未必准确。 ```javascript var arr = [1, 2, 3]; console.log(arr instanceof Array) // true console.log(arr instanceof Object); // true function fn(){} console.log(fn instanceof Function)// true console.log(fn instanceof Object)// true
- 不能检测null 和 undefined
**对于特殊的数据类型null和undefined,他们的所属类是Null和Undefined,但是浏览器把这两个类保护起来了,不允许我们在外面访问使用**。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/241577/1623835460245-4419da49-8b60-484d-a2bc-f7802edfde4d.png#clientId=u3efcb99d-98c3-4&from=paste&id=u12d79baf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=121&originWidth=617&originalType=url&ratio=2&size=78039&status=done&style=none&taskId=u9d285544-d566-431d-aad9-5948f584237)
<a name="DDiGA"></a>
### 3.严格运算符===
**只能用于判断null和undefined,因为这两种类型的值都是唯一的**。
```javascript
var a = null
typeof a // "object"
a === null // true
undefined 还可以用typeof来判断
var b = undefined;
typeof b === "undefined" // true
b === undefined // true
4.constructor
constructor作用和instanceof非常相似。但constructor检测 Object与instanceof不一样,还可以处理基本数据类型的检测。
var aa=[1,2];
console.log(aa.constructor===Array);//true
console.log(aa.constructor===RegExp);//false
console.log((1).constructor===Number);//true
var reg=/^$/;
console.log(reg.constructor===RegExp);//true
console.log(reg.constructor===Object);//false
constructor 两大弊端:
- null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
函数的 constructor 是不稳定的,这个主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测出来的结果就是不准确的
function Fn(){}
Fn.prototype = new Array()
var f = new Fn
console.log(f.constructor)//Array
5.Object.prototype.toString.call()
Object.prototype.toString.call() 最准确最常用的方式。首先获取Object原型上的toString方法,让方法执行,让toString方法中的this指向第一个参数的值。
关于toString重要补充说明:本意是转换为字符串,但是某些toString方法不仅仅是转换为字符串
- 对于Number、String,Boolean,Array,RegExp、Date、Function原型上的toString方法都是把当前的数据类型转换为字符串的类型(它们的作用仅仅是用来转换为字符串的)
- Object上的toString并不是用来转换为字符串的。
Object上的toString它的作用是返回当前方法执行的主体(方法中的this)所属类的详细信息即”[object Object]”,其中第一个object代表当前实例是对象数据类型的(这个是固定死的),第二个Object代表的是this所属的类是Object。
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window是全局对象global的引用
参考资料
【文章】[ JS 进阶 ] 基本类型 引用类型 简单赋值 对象引用(推荐)
JS判断数据类型的三种方法
JS中的数据类型及判断
Javascript 判断变量类型的陷阱 与 正确的处理方式
判断JS数据类型的四种方法