本系列内容由ZouStrong整理收录
整理自《JavaScript权威指南(第六版)》,《JavaScript高级程序设计(第三版)》
ECMAScript不支持自定义数据类型
ECMAScript有5种简单数据类型(基本数据类型、原始数据类型)
1种复杂数据类型(引用数据类型)
由于JavaScript是松散类型的,怎么知道某个变量所保存的数据的类型呢——typeof运算符解决了这个问题
typeof varName
typeof(varName) //typeof是一个运算符,括号不是必须的
不管检测什么类型值,使用typeof返回的都是字符串
最后,typeof运算符的操作数可以是变量,也可以是一个字面量
var name="strong";
typeof name; //返回"string"
typeof "strong" //返回"string"
使用typeof运算符并不能区分普通对象、数组和正则表达式,要想区分它们,需要使用检测"类特性"字符串(后述)
Undefined类型只有一个值,即——undefined
使用var声明变量但未初始化时,这个变量的值就是undefined
var name;
typeof name; //返回"undefined"
对尚未声明的变量使用typeof运算符,也会返回"undefined",但与前者不同,对于尚未声明过的变量,只能执行一项操作,即使用typeof运算符检测其数据类型,其它操作都会产生 ReferenceError 错误
var name;
typeof name; //返回"undefined"
typeof abcd; //返回"undefined"
alert(name); //"undefined"
alert(abcd); //ReferenceError: abcd is not defined
如果变量中保存的值就是undefined,对其使用typeof会返回"undefined"
var name = undefined;
typeof name; //返回"undefined"
初始化变量是很有必要的,因为当typeof运算符返回undefined值时,我们就知道被检测的变量还没有被声明,而不是尚未初始化了
Null类型也只有一个值,即——null
null表示一个空对象指针(这正是使用typeof检测null时会返回"object"的原因)
如果定义的变量将来用于保存对象,最好将该变量初始化为null,这样只要直接检查变量是否为null值就可以知道相应的变量是否已经保存了一个对象的引用了
undefined值是派生自null的,所以
undefined == null ; // true,操作数转换
undefined === null ; // false,操作数不转换
尽管二者有如此关系,但无论如何都没有必要把一个变量的值显式地设置为undefined;而只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存null
null和undefined值,是仅存的两个没有方法的值
undefined是一个全局变量,而null是一个关键字
Boolean类型有两个值——true 和 false
这两个值与数字值不是一回事,因此true不一定等于1,而false也不一定等于0
虽然Boolean类型的字面值只有两个,但ECMAScript中所有数据类型都可以转换成布尔值
可借助转型函数Boolean()来实现
var val1 = Boolean("name"); // true
var val2 = Boolean(100); // true
几乎所有数据都会被转换成true,只有少数几种值会被转变成false
几乎不会显式使用Boolean()转型函数,这是因为存在自动(隐式)类型转换(一般发生在条件控制语句中),相当于“偷偷的”调用了Boolean()转型函数
var name = "strong";
if(name){
}
Number类型——包含所有数字(整数和浮点数,但JavaScript不区分整数值和浮点数值,所有数字均用浮点数表示)
在数字直接量前添加负号(-)可以得到它们的负值(但负号不属于数字直接量的一部分)
var a = 10; //十进制
var b = 023; //八进制
var c = 0x12ac; //十六进制
八进制第一位必须是0,后跟八进制序列(0~7),如果超出了范围,则忽略前导0,后面的数值当做十进制解析(089 —> 89)
十六进制前两位必须是0x或0X,后跟十六进制序列(0~9、a~f(不区分大小写)),如果超出了范围,则会报语法错误
ECMAScript标准不支持八进制,在严格模式下,会直接报错
在进行算术运算时,所有数值都最终被转换成十进制数值
alert(023); //19
alert(0x12a); //298
也就是包含小数点的数,并且小数点后至少一位数字
var a = 2.3;
var b = 0.5;
var c = .5 //有效但不推荐
保存浮点值所需的内存是整数值的两倍,ECMAScript会不失时机的将浮点数转换成整数
var c = 5. //解析成整数5
var c = 5.0 //解析成整数5
对于极大或者极小的数值,采用e(E)表示法(科学技术法)
var c = 3.14e7; //等于31400000
var c = 3.14E-7; //等于0.000000314
ECMASctipt会将那些小数点后面带有6个零以上的小于1的浮点数值转换为以e表示法表示的数值(例如,0.0000003会被转换成3e-7)
浮点数值的最高精度是17位小数,但在进行算术计算时其精确度远远不如整数,例如,0.1加0.2的结果不是0.3,而是0.30000000000000004,这个小小的舍入误差会导致无法测试特定的浮点数值
if (a + b == 0.3){
//不会执行
}
因此,永远不要测试某个特定的浮点数值
由于内存限制,ECMAScript能表示的数值范围有限
Number.MAX_VALUE //最大数
Number.MIN_VALUE //最小数
ECMAScript将超出范围的数转换成
Infinity == Number.POSITIVE_INFINITY //正无穷
-Infinity == Number.NEGATIVE_INFINITY //负无穷
在JavaScript中,0作为除数是不会报错的
正数/0 //返回 Infinity
负数/0 //返回-Infinity
0/0 //返回 NaN
ECMAScript提供了isFinite()函数来确定一个数是不是有穷的
var a = isFinite(100); // true
var a = isFinite(Infinity); // false
除此之外,当传入的参数不能被转化为数字时(使用转型函数Number()),也会返回false
NaN(not a number)——非数值,是一个特殊的数值
Infinity-Infinity //返回NaN
0/0 //返回NaN
首先,NaN是一个数值,之所以称为“非数值”,是因为不能参与算数运算
typeof NaN //返回"number"
其次,任何涉及NaN的操作都返回NaN
NaN-NaN //返回NaN
最后,NaN与任何值都不相等,包括自身
if(a!=a){
//该值是NaN
}
ECMAScript提供了isNaN()函数来确定一个数是不是“非数值”
var a = isNaN(100); // false
var a = isNaN("100"); // false
var a = isNaN(true); // false
var a = isNaN("sss"); // true
var a = isNaN(NaN); // true
isNaN()函数在接收到一个值之后,会尝试使用转型函数Number()将这个值转换为数值,转换规则如下
一元加(+)运算符的操作跟Number()转型函数一样,一元减在此基础上转换为负数
+undefined //NaN
+null //0
由于Number()转型函数在转换字符串时不够理想,因此还有两个专门用来转换字符串的函数,第一个就是parseInt()函数
它会忽略字符串前面的空格,直至找到第一个非空格字符,只要第一个非空格字符不是数字或者正负号,一律返回NaN, 如果第一个非空格字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符
parseInt("") //返回NaN (Number("")返回 0 )
parseInt("123S") //返回123
parseInt("12.4") //返回12
另一个是parseFloat()函数
它也会忽略字符串前面的空格,直至找到第一个非空格字符,只要第一个非空格字符不是数字或者正负号或者小数点,一律返回NaN, 如果第一个非空格字符是上述字符之一,parseFloat()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非浮点数值,也就是说,字符串中的第一个小数点是有效的,而第二个小数点就是无效的了
parseFloat("098.2") //返回98.2
parseFloat("123.23.23") //返回123.23
如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后都是零),parseFloat()会返回整数
parseFloat(“99.0”) //返回99
对于包含八进制的字符串来说,两个函数都会忽略前导的0,返回剩余部分
parseInt("023") //返回23
parseFloat("023") //返回23
对于包含十六进制的字符串来说,前者会返回对应的十进制形式,后者始终返回0
parseInt("0x23") //返回35
parseFloat("0x23") //返回0
String类型——包含所有字符串
字符串即用双引号(")或单引号(')括起来的字符序列
字符串长度可通过访问其length属性获得
var a = "zsz";
alert(a.length); //3
字符串中包含一些特殊的字符字面量,也叫转义字符(表示非打印字符),用 \ 表示
转义字符可出现在字符串中的任意位置,且长度为1
"\n\\".length //2
如要在字符串中显示 \ ,则必须使用\进行转义
"\\hello" //返回"\hello",长度为6
在ECMAScript3中,字符串直接量必须写在一行,而在ECMAScript5中,字符串可以被拆分成数行,但每行必须以反斜线(\)结束(反斜线和行结束符都不是字符串直接量的一部分)
"abc\ndef" //写在一行,但显示在两行的字符串,长度为7
"aaaa\
aaaa" //写在两行,但显示在单行的字符串,长度为8
ECMAScript中的字符串是不可变的(所有原始类型都是不可变的)
字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量
var lang = "Java";
lang = lang + "Script";
变量lang开始时包含字符串"Java"。而第二行代码把lang的值重新定义为"JavaScript"。实现这个操作的过程如下:首先创建一个能容纳10个字符的新字符串,然后在这个字符串中填充"Java"和"Script",最后一步是销毁原来的字符串"Java"和字符串"Script",因为这两个字符串已经没用了。这个过程是在后台发生的,而这也是在某些旧版本的浏览器中拼接字符串时速度很慢的原因所在
任何数据类型都可以转换为字符串,使用继承而来的toString()方法
var num = 99;
var str = num.toString(); //返回"99"
在这里有一个小技巧,对数值使用toString()方法方法时,可以传入一个数字基数,以此输出对应进制的字符串值
var num =10;
num.toString(); //"10"
num.toString(2); //"1010"
num.toString(8); //"12"
num.toString(16); //"a"
toString()方法不适用于null 和 undefined,因为它们没有方法
var str = null.toString(); //error
所以要使用类似Number和Boolean()的转型函数String(),该方法返回相应值的字符串形式,适用于所有值(对于null和undefined,直接返回其字符串形式,对于其它值,则调用toString()方法)
var str = String(null); //"null"
var strong;
String(strong); //"undefined"
最后,还可以使用 "+" 号运算符,来实现字符串转换
"" + 任意值 //相当于使用String()函数
Object类型——包含所有对象,对象是一组数据(属性)和功能(方法)的集合
对象可以通过执行new运算符后跟要创建的对象类型的名称来创建,而直接创建Object类型的实例并为其添加属性和(或)方法,就可以创建自定义对象
var o = new Object();
var o = new Object; // 有效,但不推荐省略圆括号
JavaScript中所有对象都继承自Object类型(所有对象都是Object的实例)Object类型所具有的任何属性和方法也同样存在于更具体的对象中
obj instanceof Object; //对于任何对象,都返回true(null除外)
Object的每个实例(每个对象)都具有下列基本的属性和方法,都是继承自Object.prototype
最后的三个方法,原本不会输出什么有用的信息,因此在不同的对象之间都被重写了,因此返回值可能不同(但是类特性检测的时候,还是需要使用Object.prototype上的原生toString()方法的)
ECMAScript对象的行为不一定适用于BOM和DOM对象,因为它们是由宿主实现提供和定义的。ECMAScript不负责定义宿主对象,因此宿主对象可能会也可能不会继承Object
在此之前,我们已经接触了几个运算符
typeof运算符
var运算符
一元运算符即只能操作一个值
正负号,可用于任何数据类型,应用于非数值时,相当于使用Number()转型函数
自增自减也可用于任何数据类型,应用于非数值时,相当于使用Number()转型函数,再加减1
注:前置型递增和递减,变量的值都是在语句被求值以前改变的(被称作副效应);后置型递增和递减操作是在包含它们的语句被求值之后才执行的
var a =3
alert(a--) //输出3,a变为2
alert(--a) //输出1,a变为1
var num1 = 2 , num2 = 20;
var num3 = --num1 + num2; // 等于21
var num4 = num1 + num2; // 等于21
var num1 = 2 , num2 = 20;
var num3 = num1-- + num2; // 等于22
var num4 = num1 + num2; // 等于21
没用过
逻辑非运算符总是返回布尔值,即相当于使用Boolean()转型函数之后,再求反, 而!!的作用完全相当于Boolean()函数
逻辑与运算符返回的不一定是布尔值(除非操作数本身就是布尔值)
第一个操作数如果经过Boolean()之后为false,则返回第一个操作数,第一个操作数如果经过Boolean()之后为true,则返回第二个操作数,仅此而已
逻辑与操作属于短路操作,如果第一个操作数经过转换后是false,则第二个操作数不会被计算或求值,即使第二个操作数包含了错误(因为第一个操作数就可以决定结果了)
var a=0;
var b=1;
var c=a && (++b); // b仍然为1
逻辑或运算符返回的不一定是布尔值(除非操作数本身就是布尔值)
第一个操作数如果经过Boolean()之后为true,则返回第一个操作数,第一个操作数如果经过Boolean()之后为false,返回第二个操作数
所以我们可以利用逻辑或的这一行为来避免为变量赋null或undefined值
var myObject = myObject || { }; //前面是优先值,后面是后备值
逻辑或操作也属于短路操作,如果第一个操作数经过转换后是true,第二个操作数就不会被计算或求值
数值正常计算即可,非数值会隐式调用Number()之后再计算,不能参与计算的返回NaN
除了常规的数值加减外,加减在JavaScript中有着特殊的行为
对于加法来说:
对于其他情况(null ,undefined,true,false,对象),都是先使用Number()之后,再相加
true+true = 2
true+null = 1
5 + 5 = 10
5+ true = 6
5 + null = 5
5 + "5" = "55"
对于减法来说:
与加法有很大不同,全部值都优先向数值(使用Number())转换,然后执行减法操作,不能运算的返回NaN
注:加法中出现字符串优先向字符串转换,而减法,乘法,除法则优先向数值转换
关系运算符总是返回布尔值
注:比较规则是这样的,两个字符串比较的是字符编码值(即使是数字字符串),除此之外,都是向数值转换后比较,包括只有一个字符串的情况
11 < "2" // false
"11" < "2" // true
NaN >3 //false
NaN <= 3 //false
相等和不相等——先转换数据类型再比较,转换时,遵循下列基本规则:
这两个运算符在进行比较时则要遵循下列规则
如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true;否则,返回 false。
true == 1 //true
null == undefined //true
“5” == 5 //true
全等和不全等——仅比较而不转换数据类型
true === 1 //false
null === undefined // false
“5” === 5 //fasle
var a=(boolean_expression) ? value1 : value2;
基于对boolean_expression求值的结果,决定给变量 a 赋什么值。如果求值结果为true,则给变量赋第一个值;如果求值结果为false,则给变量赋第二个值
本质上相当于
var a;
if( boolean_expression ){
a=value1;
}else{
a=value2;
}
常见应用
var a=(b>c)? b : c ; //求最大值
var a=(b<c)? b : c ; //求最小值
赋值运算符就是把右侧的值赋给左侧的变量
var a = 3;
a = a+10;
并且每个主要算术运算都有相应的复合赋值操作
b+=3; //即b = b + 3;
a*=3; //即a = a * 3;
使用逗号运算符可以在一条语句中执行多个操作,常用于声明多个变量
var a = 3 , b=4 , c=5;
逗号运算符还可以用于赋值,在用于赋值时,逗号运算符总会返回表达式中的最后一项
var a = (1,2,3,4,5); //a = 5
单独使用时,in运算符判断对象中有没有指定属性,无论该属性存在于实例还是原型中
alert("constructor" in Object.prototype); //true
for-in循环中, 用于遍历对象的可枚举属性
for(var pro in Obj){
//Obj[pro];
}
用于删除对象属性(删除不使用var定义的变量,删除数组元素时,会创建稀疏数组,后述)
用于检测对象是否是当前类型的实例
null instanceof Object //false
void是一元运算符,操作数可以是任意类型
操作数会正常计算,但是void总是返回undefined
由于void会忽略操作数的值,因此在操作数有副作用的时候使用void可以让程序更有语义
这个运算符通常用在客户端的URL中——javascript:url
因为总是返回undefined,因此可以阻止浏览器的默认行为
<a href="javascript:void(0);"></a>
有一些表达式具有副作用,前后的表达式会相互影响
赋值运算符是最明显的,如果给一个变量或者属性赋值,那么使用这个变量或者属性的表达式的值都会改变
自增(自减)运算符与此类似,因为包含隐式的赋值
delete运算符也有副作用,删除一个属性就像(但不完全一样)为这个属性赋值undefined
其它运算符都没有副作用
运算符的优先级和结合性决定了复杂表达式的运算顺序,但没有规定子表达式的计算过程中的元素顺序
JavaScript总是按照从左至右的顺序来计算表达式
w = x + y * z;
首先计算子表达式w,然后计算x、y、z的值,然后y和z相乘,再加上x的值。最后赋值给表达式w所指代的变量
给表达式添加圆括号,可以改变加法和乘法的关系,但是从左至右的顺序是不会变的
var a =1;
var b = (a++); //1
var c = 1;
var d = (c++)+c; //3