第5章 运算符和表达式

第5章 运算符和表达式

在计算机语言中,运算通过运算符来实现,运算符和运算数(或称操作数)组合在一起就形成了表达式。本章将详细介绍JavaScript运算符和表达式的基本知识和用法,并通过大量示例代码演示运算符的使用和表达式运算。


【学习重点】


▲ 熟悉运算符和表达式


▲ 正确使用位运算符和算术运算符


▲ 灵活使用逻辑运算符和关系运算符


▲ 掌握赋值运算符、对象操作运算符和其他运算符


5.1 运算符和表达式概述


运算符是执行各种运算操作的符号,大部分JavaScript运算符是用标点符号表示的,如“+”和“=”,也有些运算符是由关键字表示的,如delete和instanceof。本节将简单介绍JavaScript运算符和表达式的相关概念和基本使用。


5.1.1 认识运算符


JavaScript定义了51个运算符,详细说明如表5-1所示。



表5-1 JavaScript运算符


第5章 运算符和表达式


续表


第5章 运算符和表达式


提示:表5-1中各列说明如下。


☑ 优先级:表示运算符参与运算的先后顺序。数字越大,运算优先级就越高;数字相等,则运算等级相同,将根据位置决定运算优先顺序。


☑ 操作类型:表示运算符所要操作的数据类型。


☑ 运算顺序:表示运算符操作对象的方向,如从左到右或从右到左。



一般情况下,运算符与运算数配合才能使用。其中运算符指定执行运算的方式,运算数明确运算符要操作的对象。例如,1加1等于2,用符号表示就是“1+1=2”,其中1是被操作的数,简称为操作数(或运算数),符号“+”表示加运算的操作,符号“=”表示赋值运算的操作,“1+1=2”就表示一个表达式。


根据操作运算数的数量,运算符可以分为下面3类。


☑ 一元运算符:一个运算符仅对一个运算数执行某种运算,如值取反、位移、获取值类型、删除属性定义等。


☑ 二元运算符:一个运算符必须包含两个运算数。例如,两个数相加,两个值比较。大部分运算符都是对两个运算数执行运算。


☑ 三元运算符:一个运算符必须包含3个运算数。JavaScript仅有的一个三元运算符(?:运算符),该运算符就是条件运算符,它是if语句的简化版。


运算符的优先级控制执行操作的顺序。例如,1+23结果是7,而不是9,因为乘法优先级高,虽然加号在左侧。


小括号运算符的优先级最高,使用小括号可以改变运算符的优先顺序。例如,(1+2)3结果是9,而不再是7。


【示例1】看看下面这3行代码:



第5章 运算符和表达式

在上面代码中,虽然第2行与第3行返回结果相同,但是它们运算顺序是不同的。第2行先计算5减2,再乘以2,最后赋值给变量n,并显示变量n的值;而第3行先计算5减2,再把结果赋值给变量n,最后变量n乘以2,并显示两者所乘结果。


下面代码就会抛出异常:



     alert((1+n=5-2)2);  //返回异常


因为,加号运算符优先级高,先执行运算,但是此时的变量n还是一个未知数,所以也就错了。


一元运算符、三元运算符和赋值运算符都会遵循从右到左的顺序执行运算。


【示例2】根据运算符的运算顺序,下面代码是按着先右后左的顺序执行运算的。Typeof 5运算结果是number,而返回结果“number”又是一个字符串,所以typeof typeof 5最终返回string。



     alert(typeof typeof 5);  //返回string


关于每个运算符运算顺序可以参考表5-1的“运算顺序”列说明。对于上面代码,可以使用小括号标识它们的先后运算顺序:



     alert(typeof (typeof 5));  //返回string


而对于下面表达式:



     1+2+3+4


就等于:



     ((1+2)+3)+4


运算符只能操作特定类型的数据,运算返回值也是特定类型的数据。例如,加减乘除等算术运算符所返回的结果永远都是数值,而比较运算符所返回的结果也都是布尔值。


【示例3】下面代码中,两个运算数都是字符串,但是JavaScript会自动把两个操作数转换为数字,并执行减法运算,返回数字结果。



     alert(“10”-“20”);  //返回-10


下面代码中,数字0本是数值类型,JavaScript会把它转换为布尔值false,然后再执行条件运算。



     alert(0?1:2);  //返回-2


下面代码中,字符串5和2分别被转换为数字,然后参与比较运算,并返回布尔值。



第5章 运算符和表达式

而下面的数字5被转换为字符编码,参与字符串比较运算。



     alert(“string”>5);  //返回false


下面代码中,加号运算符能够根据数据类型执行相加或是相连的操作。



第5章 运算符和表达式

下面代码中,布尔值true被转换为数字1参与乘法运算,并返回值为5。



     alert(true"5");  //返回5


5.1.2 使用运算符


运算符一般不会对运算数本身产生影响,如算术运算符、比较运算符、条件运算符、取逆、位与等。例如,a=b+c,其中的运算数b和c不会因为加法运算而导致自身的值发生变化。


但是,在JavaScript中有一些运算符能够改变运算数自身的值,如赋值、递增、递减运算等。由于这类运算符自身的值会发生变化,具有一定的副作用,使用时应该保持警惕,特别是在复杂表达式中,这种副作用就非常明显。


【示例1】在下面代码段中,变量a经过赋值运算和递加运算之后,该变量的值发生了两次变化。



第5章 运算符和表达式

修改上述表达式:



第5章 运算符和表达式

如果直观判断,会误认为返回值为0,实际上变量a在参与运算的过程中,它自身的值是不断在发生变化的。这种变化很容易扰乱思维。为了方便理解,下面拆解(a++)+(++a)-(a++)-(++a)表达式:



第5章 运算符和表达式

从代码可读性考虑:一个表达式中不能够对于相同操作数执行两次或多次引起自身值变化的运算,除非表达式必须这样执行,否则应该避免制造歧义。


【示例2】下面代码行虽然看起来比较复杂,但是由于每个运算数仅执行了一次引起自身值变化的运算,所以不会制造歧义,也不会扰乱思维。



     a=(b++)+(++c)-(d++)-(++e);


5.1.3 认识表达式


表达式是JavaScript中的一个短语,由一个或多个运算符或运算数组成。它可以计算,并且需返回一个值。


【示例】简单的表达式就是一个直接量、常量或变量。



第5章 运算符和表达式

上述原始的表达式很少单独使用。一般情况下,表达式由运算符与运算数组合而成,运算数可以包括直接量、变量、函数返回值、对象属性值、对象方法的运行值、数组元素等。


表达式可以嵌套,组成复杂的表达式。JavaScript在解析时,先计算最小单元的表达式,然后把计算的值参与到外围或次级的表达式运算。依此类推,从而实现复杂表达式的运算操作。


表达式一般遵循从左到右的运算顺序来执行计算,但是也受到运算符的优先级影响。同时为了主动控制表达式的运算顺序,用户可以通过小括号运算符提升子表达式的优先级。


例如,表达式1+23可以是子表达式23运算之后,再参与到与1的加法运算中。而表达式(1+2)3却借助小括号运算符提升了加号运算符的优先级,从而改变了逻辑的执行顺序,也就是说子表达式1+2运算之后,再参与到与3的乘法运算中。



提示:表达式的形式多样,除了上述原始表达式外,常用表达式还有:对象和数组初始化表达式、函数定义表达式、属性访问表达式、调用表达式、创建对象表达式等。这些表达式将会在后续各章中详细说明,本节就不再展开。



5.1.4 案例:优化表达式


同一个表达式如果稍加改动就会改变表达式的运算顺序,用户可以借助这个技巧来优化表达式的结构,但不改变表达式的运算顺序和结果,以提高代码的可读性。


【示例1】对于下面这个复杂表达式,可能会让人迷惑:



     (a+b > c && a - b < c || a > b > c)


如果进行如下优化,则逻辑运算的顺序就非常清楚了。



     ((a+b > c) && ((a - b < c) || (a > b > c)))


虽然增加这些小括号并没有影响到表达式的实际运算,但更方便阅读。使用小括号运算符来优化表达式内部的逻辑层次,是一种好的设计习惯。


但是在复杂表达式中一些不良的逻辑结构与人的思维结构相悖,也会影响人对代码的阅读和思考,这时就应该根据人的思维习惯来优化表达式的逻辑结构。


【示例2】设计一个表达式,筛选学龄人群。如果使用表达式来描述就是年龄大于等于6岁,且小于18岁的人:



第5章 运算符和表达式

直观阅读,表达式age>=6 && age<18可以很容易被每一个人所理解。但是继续复杂化表达式:筛选所有弱势年龄人群,以便在购票时实施半价优惠。如果使用表达式来描述就是年龄大于等于6岁,且小于18岁,或者年龄大于等于65岁的人:



第5章 运算符和表达式

从逻辑上分析,上面表达式没有错误。但是在结构上分析就感觉比较模糊,为此用户可以使用小括号来分隔逻辑结构层次,以方便人去阅读:



第5章 运算符和表达式

但是,此时如果使用人的思维来思考条件表达式的逻辑顺序时,会发现它很紊乱,与人的一般思维发生了错位。人的思维是一种线性的、有联系、有参照的一种思维品质,如图5-1所示。



第5章 运算符和表达式

图5-1 人的思维模型图



而对于表达式(age >= 6 && age < 18) || age >= 65来说,它的思维品质如果使用模型图来描述,则如图5-2所示。通过模型图的直观分析,会发现该表达式的逻辑是非线性的,呈现多线思维的交叉型,这种思维结构对于机器计算来说基本上没有任何影响。但是对于人脑思维来说,就需要停顿下来认真思考之后,才能够把这个表达式中各个小的表达式逻辑单元串联在一起,形成一个完整的逻辑线。



第5章 运算符和表达式

图5-2 该表达式的思维模型图



直观分析,这个逻辑结构的错乱是因为随意混用大于号、小于号等运算符造成的。如果调整一下表达式的结构顺序,则阅读起来就会非常清晰。



第5章 运算符和表达式

这里采用了统一的大于小于号方式,即所有参与比较的项都按着从左到右、从小到大的思维顺序进行排列。而不再恪守变量始终居左,比较值始终居右的编写习惯。


【示例3】表达式的另一个难点就是布尔型表达式的重叠所带来的理解障碍。例如,对于下面这个条件表达式,该如何进行思维。



第5章 运算符和表达式

对上述表达式进行优化,以方便阅读。



     (! isA || ! isB)=! (isA && isB)
(! isA && ! isB)=! (isA || isB)


【示例4】?:运算符在函数式编程中使用频率比较高,但是这种连续思维不容易阅读。这时可以使用if语句对?:运算符表达式进行分解。


例如,下面这个复杂表达式,如果不仔细进行分析,很难理清它的逻辑顺序。



     var a.b=new c(a.d ? a.e(1) : a.f(1))


但是使用if条件语句之后,则逻辑结构就非常清晰:



第5章 运算符和表达式

5.2 位运算符


JavaScript定义了7个位运算符,它们可以分为两类。


☑ 逻辑位运算符:位与(&)、位或(|)、位异或(^)和位非(~)。


☑ 移位运算符:左移(<<)、右移(>>)和无符号右移(>>>)。


5.2.1 位运算概述


位运算就是对二进制数进行的计算。


位运算是整数的逐位运算。例如,1+1=2,这在十进制计算中是正确的;但是在二进制计算中,1+1= 10;而对于二进制数100取反,则就等于001,而不是-100。


在JavaScript中,位运算要求运算数必须是32位的整数,如果位运算数是非整型,或者大于32位的整数,则将返回NaN。


在默认情况下,所有整数都是带有符号的整数。带有符号的整数中,第1~31位表示整数的值,第32位表示整数的符号,0表示正数,1表示负数。因此整数值的范围为-2147483648~2147483647。


对于正整数来说,它以二进制形式进行存储,第1~31位中每一位都表示2的幂,序数从0开始。第1位表示20,第2位表示21 ,第3位表示22,依此类推。对于没有用到的位使用0来进行填充。例如,对于十进制数值12,使用二进制表示法如图5-3所示。



第5章 运算符和表达式

图5-3 12的二进制表示法



其中第1~4位为有效位,如果使用如下方法返回对应的二进制值,可以看到返回值仅是有效位,其他数位由于并不重要,因此就没有返回。



     alert((12).toString(2));                //返回二进制值1100


实际上利用有效位,对于二进制值1100,可以很轻松地使用如下方法转换为十进制值:



     (231)+(221)+(210)+(200)
=8+4+0+0
=12


对于负数来说,二进制的存储方式一般采用二进制补码的方式来实现:


第1步,先计算负数对应的正数的二进制值。例如,对于-12,则先计算12的二进制值,如图5-3所示。


第2步,对第一步中计算的二进制值进行反码。所谓反码就是把位值逐位取反。例如,0取反为1,1取反为0。针对图5-3所示的12的二进制值反码之后,则如图5-4所示。



第5章 运算符和表达式

图5-4 12的二进制值反码



第3步,为二进制反码值加1,如图5-5所示。



第5章 运算符和表达式

图5-5 为二进制值反码加1



第4步,最终,-12的二进制值为:11111111111111111111111111110100。


但是如果使用如下方法返回-12的二进制值:



     alert((-12).toString(2));                //返回值-1100


则返回值不是11111111111111111111111111110100,而是-1100,即使用数字绝对值的二进制值加上负号的形式返回。


在处理带有符号的整数时,JavaScript是不允许访问第32位符号位的值。


对于无符号的整数来说,第32位不再是符号位,而是用来表示值,所以它的数值范围为0~4294967295。如果数值小于等于214783647的整数来说,无符号整数与有符号整数表示是一样的,而大于214783647的整数,则第32位为1,此时与有符号整数表示是不一样的。当然,JavaScript所有数值默认都是带有符号位的,只有数值参与到位运算之后,才能生成无符号的整数。


5.2.2 逻辑位运算


逻辑位运算符与逻辑运算符的运算方式相同,但是针对的对象不同。逻辑位运算符针对的是二进制的整数值,而逻辑运算符针对的是非二进制的其他类型数据。


&运算符(位与)表示布尔AND操作。它对二进制值逐位进行比较,并根据表5-2所示的换算表返回结果。



表5-2 &运算符


第5章 运算符和表达式

【示例1】12和5进行位与运算,则返回值为4。



     alert(12&5);                //返回值4


如图5-6所示以算式的形式解析12和5进行位与运算的过程。通过位与运算,只有第3位的值为全true,故返回true,其他位均返回false。



第5章 运算符和表达式

图5-6 12和5进行位与运算




提示:在位运算中数值1表示true,0表示false,反之亦然。



|运算符(位或)表示布尔OR操作。位或运算将根据表5-3所示的换算表返回结果。



表5-3 |运算符


第5章 运算符和表达式

【示例2】12和5进行位或运算,则返回值为13。



     alert(12|5);               //返回值13


如图5-7所示以算式的形式解析12和5进行位或运算的过程。通过位或运算,只有第2位的值为false外,其他位均返回true。



第5章 运算符和表达式

图5-7 12和5进行位或运算



^运算符(位异或)表示布尔XOR操作。位异或运算将根据表5-4所示的换算表返回结果。



表5-4 ^运算符


第5章 运算符和表达式

【示例3】12和5进行位异或运算,则返回值为9。



     alert(12^5);                    //返回值9


如图5-8所示以算式的形式解析12和5进行位异或运算的过程。通过位异或运算,第1、4位的值为true,而第2、4位的值为false。



第5章 运算符和表达式

图5-8 12和5进行位异或运算



~运算符(位非)表示布尔NOT操作。位非运算将根据3步操作进行处理:


第1步,把运算数转换为32位的二进制整数。


第2步,逐位反码,即逐位进行取反操作。


第3步,把二进制反码转换为十进制浮点数。


【示例4】对12进行位非运算,则返回值为-13。



     alert(~12);                  //返回值-13


如图5-9所示以算式的形式解析对12进行位非运算的过程。



第5章 运算符和表达式

图5-9 对12进行位非运算



位非运算本质上就是对数字进行求负运算,然后再减1。因此,使用如下方法也可以获得相同的结果。



     ~12=-12-1;


如果使用如下语句则可以检测它们是否相等:



     alert(~12 == -12-1);                  //返回true


5.2.3 移位运算


移位运算就是对二进制值进行有规律移位,移位运算可以设计很多奇妙的效果,这在图形图像编程中应用比较广泛。


【示例1】把数字5向左移动2位,则返回值为20。



     alert(5<<2);                    //返回值20


如果用算式图进行演示则如图5-10所示。



第5章 运算符和表达式

图5-10 把5向左移位2位运算



在移位运算过程中,右侧空出的位置,JavaScript自动使用0进行填充,确保整个值是一个完整的32位二进制值。在左移操作中,符号位始终保持不变。在移位过程中,如果超出32位的值将自动被丢弃。


右移运算包含有符号和无符号两种形式。有符号右移运算与左移运算操作相反,它把32位数字中的所有有效位整体右移。移位后的空位位于有效位的左侧,但是位于符号位的右侧,此时JavaScript会使用符号位的值来填充空位。移动过程中超出的值将被自动丢弃。


【示例2】把数值1000向右移8位,则返回值为3。



     alert(1000>>8);                  //返回值3


如果用算式图进行演示则如图5-11所示。



第5章 运算符和表达式

图5-11 把1000向右移位8位运算



【示例3】把数值-1000向右移8位,则返回值为-4。



     alert(-1000>>8);                //返回值-4


如果用算式图进行演示则如图5-12所示。可以看到当符号位值为1时,则有效位左侧的空位全部使用1进行填充。


对于无符号右移,可以使用>>>运算符来表示,它将无符号的32位整数所有数位整体右移。对于无符号或正数右移运算,则无符号右移与有符号右移运算的结果是相同的。



第5章 运算符和表达式

图5-12 把-1000向右移位8位运算



【示例4】下面两行表达式的返回值都是相同的。



第5章 运算符和表达式

【示例5】对于负数来说,无符号右移将使用0来填充所有的空位,同时会把负数看作正数来处理。所以所得的结果一般都会非常大。



第5章 运算符和表达式

如果用算式图进行演示则如图5-13所示。左侧的空位就不再用符号位的值来填充,而是用0来填充。



第5章 运算符和表达式

图5-13 把-1000向右无符号移位8位运算



在执行运算时,JavaScript会把这个数值转换为无符号的等价形式,尽管第32位是一个符号位,但是此时会把它看作一个数值位进行运算。所以,在使用无符号右移运算符时,要特别小心,避免出现意想不到的错误。


5.3 算术运算符


算术运算符包括加(+)、减(-)、乘()、除(/)、余数运算符(%)、数值取反运算符(-)。


5.3.1 加法运算


【示例1】特殊运算数的运算结果比较特殊,需要特别留意。



第5章 运算符和表达式

【示例2】加运算符能够根据运算数的数据类型,尽可能地把数字转换成可以执行相加或相连运算的数值或字符串。



第5章 运算符和表达式

【示例3】下面两个表达式中,由于空字符串的位置不同,运算结果也不同。在第一行代码中,3.0和4.3都是数值类型,因此加号运算符就执行相加操作,由于第3个运算数是字符串,则把第一个加号运算结果转换为字符串并与空字符串进行相连操作。而第二行代码中则不同,第一个加号运算符首先把数值3.0转换为字符串,然后执行连接操作,所以结果也就不同。



第5章 运算符和表达式


提示:为了避免误解,使用加法运算符时,应先检查运算数的数据类型是否符合需要。



5.3.2 减法运算


【示例1】特殊运算数的运算结果比较特殊,需要特别留意。



第5章 运算符和表达式

【示例2】在减法运算中,如果有一个运算数不是数字,则返回值为NaN;如果数字为字符串,则会把它转换为数值之后,再进行运算。



第5章 运算符和表达式

利用减法运算可快速把一个值转换为数字。例如,由于HTTP请求值一般都是字符串数字,可以让这些字符串减去0快速转换为数值。这与调用parseFloat()方法结果相同,但减法运算符更高效、更快捷。减法运算符的隐性转换如果失败,则返回NaN,这与使用parseFloat()方法执行转换时返回值是不同的。


【示例3】对于字符串来说,减法运算符能够完全匹配进行转换,如果字符串是非数字的值,则返回NaN;而parseFloat()方法则通过逐字符解析并努力转换为数值。


例如,对于字符串"100aaa"而言,parseFloat()方法能够解析出前面几个数字,而对于减法运算符来说,则必须是完整的数字时,可以进行完全匹配转换。



第5章 运算符和表达式

对于布尔值来说,parseFloat()方法能够把true转换为1,把false转换为0,而减法运算符视其为NaN。


对于对象来说,parseFloat()方法直接尝试调用对象的toString()方法进行转换,而减法运算符先尝试调用对象的valueOf()方法进行转换,失败之后再调用toString()方法进行转换。


5.3.3 乘法运算


两个正数相乘,则为正数;两个负数相乘,则为正数;一正一反相乘,则为负数。


【示例】特殊运算数的运算结果比较特殊,需要特别留意。



第5章 运算符和表达式

5.3.4 除法运算


两个正数相除,则为正数;两个负数相除,则为正数;一正一反相除,则为负数。


【示例】特殊运算数的运算结果比较特殊,需要特别留意。



第5章 运算符和表达式

5.3.5 余数运算


也称模运算,通俗地说就是求余数。例如:



     alert(3 % 2);                  //返回余数1


模运算主要针对整数执行操作,但是它也适用浮点数,例如:



     alert(3.1 % 2.3);                 //返回余数0.8000000000000003


【示例】特殊运算数的运算结果比较特殊,需要特别留意。



第5章 运算符和表达式

5.3.6 取反运算


取反运算符是一元运算符,或称一元减法运算符。


【示例】下面列举特殊运算数的取反运算结果。



第5章 运算符和表达式


提示:与一元减法运算符相对应的还有一个一元加法运算符,在实际开发中,一元加法运算符很少使用,不过可以利用它把非数值型的数字快速地转换为数值型数值。



5.3.7 递增和递减


递增(++)和递减(–)运算就是通过不断加1或减1以实现改变自身值的一种简洁方法。递增运算符和递减运算符是一元运算符,只能够作用于变量、数组元素或对象属性,这是因为在运算过程中会执行赋值运算,赋值运算左侧必须是一个变量、数组元素或对象属性,只有这样赋值才得以实现。


【示例1】下面代码是错误用法:



     alert(4++);             //返回错误


下面代码是正确的用法:



第5章 运算符和表达式

递增运算符和递减运算符有位置讲究,位置不同所得运算结果也不同。


【示例2】下面递增运算符是先执行赋值运算,然后再执行递加运算。即先计算表达式的返回值,最后才把自身值递加。



第5章 运算符和表达式

而下面的递增运算符是先执行递加运算,再返回表达式的值。



第5章 运算符和表达式

【示例3】下面代码可以直观演示每个表达式与变量n的值并非都是同步的。



第5章 运算符和表达式

递增运算符和递减运算符是相反操作的一对。它们在运算之前都会试图转换值为数值类型,如果失败则返回NaN。


5.4 逻辑运算符


逻辑运算与布尔值紧密相关联,故也称布尔代数。所谓布尔代数就是布尔值(true和false)的“算术”运算。逻辑运算常与比较运算结合使用,在条件表达式中经常应用。


逻辑运算符包括与(&&)、或(||)和非(!)3种逻辑运算类型。


5.4.1 逻辑与运算符


逻辑与运算符(&&)实际上就是两个运算数的AND布尔操作,只有当两个条件都为true时,它才返回true,否则返回false,详细描述如表5-5所示。



表5-5 逻辑与运算符


第5章 运算符和表达式

逻辑与运算符(&&)的逻辑解析:


首先,计算第一个运算数,即左侧表达式。如果左侧的表达式的计算值可以被转换为false(如null、0、underfined等),那么就会结束计算,直接返回第一个运算数的值。


然后,当第一个运算数的值为true时,则将计算第二个运算数的值,即位于右侧的表达式,并返回这个表达式的值。



第5章 运算符和表达式

【示例1】下面代码利用逻辑与运算检测变量初始值。如果变量user为null,则!user就会返回true,如果逻辑与运算符左侧返回值为true,则会执行右侧的表达式,否则就会忽略。也就是说逻辑与运算符右侧的表达式可以被执行,也可以不被执行。对于上面表达式,使用条件语句可以进行如下表示:



第5章 运算符和表达式

【示例2】由于逻辑与运算符右侧的表达式将根据左侧的表达式的值来决定是否执行,在程序中常利用它来设计结构简洁的条件运算。



     var n=3;
(n == 1) && alert(1);
(n == 2) && alert(2);
(n == 3) && alert(3);
(! n) && alert(“null”);


上面代码等价于下面的多条件逻辑结构:



第5章 运算符和表达式

【示例3】利用逻辑与运算符替代条件结构,是一种便捷的设计技巧,但是在使用时应该慎重。



第5章 运算符和表达式

上面代码设计思路:如果变量没有赋值,则其值为null,转换为布尔值就是false,然后利用逻辑与运算符来判断变量是否初始化。当变量user值为0时,转换为布尔值,则为false。所以,当变量赋值之后,依然提示变量没有赋值。


为了安全起见,用户在设计时必须确保逻辑与左侧的表达式返回值是可以预期的,同时右侧表达式不应该包含赋值、递增、递减和函数调用等有效运算。


逻辑与运算的运算数可以是任意类型数据,如果运算数不是布尔值,则逻辑与运算并非要求必须返回布尔值,而是根据表达式的结果实事求是地进行返回。


【示例4】下面介绍几种特殊运算数应用技巧。


对象被转换为布尔值时为true。例如,一个空对象与一个布尔值进行逻辑与运算。



第5章 运算符和表达式

如果运算数中包含null,则返回值总是null。例如,字符串"null"与null类型值进行逻辑与运算,不管位置如何,始终都返回null的类型object。



第5章 运算符和表达式

如果运算数中包含NaN,则返回值总是NaN。例如,字符串"NaN"与NaN类型值进行逻辑与运算,不管位置如何,始终都返回NaN的类型number。



第5章 运算符和表达式

对于Infinity特殊值来说,将被转换为true,与普通数值一样参与逻辑与运算。



     alert(typeof(“Infinity” && Infinity))
//返回第二个运算数Infinity的类型,即返回number
alert(typeof(Infinity &&“Infinity”))
//返回第二个运算数"Infinity"的类型,即返回string


如果运算数中包含undefined,则返回错误。例如,字符串"undefined"与undefined类型值进行逻辑与运算,不管位置如何,始终都返回undefined的类型undefined。



     alert(typeof(“undefined” && undefined))
alert(typeof(undefined &&“undefined”))


5.4.2 逻辑或运算符


当逻辑或运算符(||)左右两侧运算数的值都是布尔值时,则它将执行布尔OR操作。如果两个运算数的值为true,或者其中一个为true,那么它就返回true,否则就会返回false。详细描述如表5-6所示。



表5-6 逻辑或运算符


第5章 运算符和表达式

逻辑或运算符(||)的逻辑解析:


首先,计算第一个运算数。如果左侧的表达式的计算值可以被转换为true,那么就直接返回第一个运算数的值,忽略第二个运算数(即不执行)。


然后,当第一个运算数的值为false时,则将计算第二个运算数的值,即位于右侧的表达式,并返回这个表达式的值。


【示例】针对下面3个表达式:



     var n=3;
(n == 1) && alert(1);
(n == 2) && alert(2);
(n == 3) && alert(3);
(! n) && alert(“null”);


可以使用逻辑或对其进行合并:



第5章 运算符和表达式

由于&&运算符的优先级高于||运算符的优先级,所以不用使用小括号。不过使用小括号运算符更方便阅读:



第5章 运算符和表达式

或者分行书写:



第5章 运算符和表达式

即使逻辑或运算符的运算数不是布尔值,但是仍然可以将它看作布尔OR的操作,也不管运算数的值是什么类型,都可以被转换为布尔值。


逻辑或运算和逻辑与运算是两个互为反的操作,对于null、NaN特殊值都返回相应的null或NaN,而对于undefined将返回错误。


5.4.3 逻辑非运算符


逻辑非运算符(!)是一元运算符,直接放在运算数之前,将对运算数执行布尔取反操作(NOT),并返回布尔值。


【示例1】如果对于运算数执行两个逻辑非运算操作,实际上它相当于把运算数转换为布尔值数据类型。



第5章 运算符和表达式


提示:逻辑与和逻辑或运算符所执行的操作返回的未必都是布尔值,但是对于逻辑非运算符来说,它的返回值一定是布尔值。



【示例2】下面列举一些特殊的运算数的逻辑非运算返回值。



第5章 运算符和表达式

5.4.4 案例:逻辑运算训练


对于逻辑与(&&)和逻辑或(||)运算符来说,它们并不改变运算数的数据类型,同时也不会强制逻辑运算的结果是什么数据类型。它们具有如下特性:


☑ 在逻辑运算时,与和或运算都会把运算数视为布尔值,即使不是布尔值,也将对其进行转换,然后根据布尔值执行下一步的操作。


☑ 逻辑与(&&)和逻辑或(||)运算并非完整地执行所有运算数,它们可能仅执行第一个运算数,从而忽略第二个运算数。


【示例1】在下面条件结构中,由于字符串变量a的逻辑值可以转换为true,则逻辑或运算符在执行左侧的a =“string"赋值表达式之后,就不再执行逻辑或运算符右侧的定义对象结构体。所以,最后在执行条件结构内的alert(b.a);语句时,就会返回对象b没有定义的错误提示。



第5章 运算符和表达式

如果把其中的逻辑或运算符替换为逻辑与运算符,则当第一个运算数值转换为true,将继续执行右侧的运算数,该运算数是一个复杂的结构体,定义了一个对象并赋值给变量b。这样在条件结构中执行对象调用时,会显示字符串"string”。



第5章 运算符和表达式

在下面结构中,由于if条件最终返回false,所以不管对象b是否被定义,最后并没有执行调用b对象的属性a这个语句。



第5章 运算符和表达式

通过上面演示示例,可以看到逻辑与和逻辑或运算时,并没有改变运算数的数据类型,也没有改变这些表达式的值,返回值依然保持表达式的运算值,而不是被转换的布尔值。


【示例2】逻辑与和逻辑或是两个相互补充的逻辑操作,结合它们可以设计出很多结构复杂而又巧妙的逻辑运算表达式。例如,下面结构是一个复杂的嵌套结构,它根据变量a的布尔值来判断是否执行一个循环体。



第5章 运算符和表达式

对于这样一个复杂的循环结构,可以使用逻辑与和逻辑或运算符进行简化:



第5章 运算符和表达式

如果把上面的逻辑运算表达式转换为如下嵌套结构就不对了:



第5章 运算符和表达式

因为在a&&b++<10这个逻辑与表达式中可能会存在这样一种情况:如果逻辑与运算符左侧的运算数返回值为false,那么就不再继续执行逻辑与运算符右侧的运算数了。


5.5 关系运算符


关系运算符也称为比较运算符,它反映了运算数之间关系的一类运算,因此这类运算符一般都是二元运算符,关系运算返回的值总是布尔值。


5.5.1 大小比较


基本大小关系的比较运算,以及对应运算符说明如表5-7所示。



表5-7 基本比较运算


第5章 运算符和表达式

比较运算中的运算数不局限于数值,可以是任意类型的数据。但是在执行运算时,它主要根据数值的大小,以及字符串中字符在字符编码表中的位置值来比较大小。所以对于其他类型的值,将会被转换为数字或字符串,然后再进行比较。


【示例】在比较运算中,运算数的转换操作规则说明如下。


☑ 如果运算数都是数字,或者都可以被转换成数字,则将根据数字大小进行比较。



第5章 运算符和表达式

但是对于下面代码来说,就比较特殊了。两个运算数虽然都可以被转换为数字,但是由于它们都是字符串,则不再执行数据类型转换,而是直接根据字符串进行比较。



     alert(“4”>“3”);            //返回true,以字符串进行比较,而不是数字大小进行比较


☑ 如果运算数都是字符串,或者都被转换为字符串,那么将根据字符在字符编码表中的位置大小进行比较。同时字符串是区分大小写的,因为大小写字符在表中的位置不同。一般小写字符大于大写字符。如果比较中不区分大小写,则建议使用toLowerCase()或toUpperCase()方法把字符串统一为小写或大写形式。



第5章 运算符和表达式

☑ 如果一个运算数是数字,或者被转换为数字,另一个是字符串,或者被转换为字符串,则比较运算符调用parseInt()将字符串强制转换为数字,不过对于非数字字符串来说,将被转换为NaN值,最后以数字方式进行比较。运算数是NaN,则比较结果为false。



第5章 运算符和表达式

☑ 如果运算数都无法转换为数字或字符串,则比较结果为false。


☑ 如果一个运算数为NaN,或者被转换为NaN,则始终返回false。


☑ 如果对象可以被转换为数字或字符串,则执行数字或字符串比较。


5.5.2 案例:包含检测


in运算符能够判断左侧运算数是否为右侧运算数的成员。其中左侧运算数应该是一个字符串,或者可以转换为字符串。右侧运算数则应该是一个对象或数组。


【示例1】下面的代码演示了如何利用in运算符检测属性a、b、c、valueOf是否为对象o的成员。



第5章 运算符和表达式

使用instanceof运算符检测对象实例是否属于某个类或构造函数。其中instanceof运算符左侧运算数是对象实例名,右侧运算数是类名或构造器名。


【示例2】下面的代码演示了如何使用instanceof运算符检测数组a是否为Array、Object和Function的实例。



第5章 运算符和表达式

如果左侧运算数不是对象,或右侧运算数不是类或构造函数,则将返回false。如果右侧运算数不是对象,则将返回错误。


5.5.3 案例:等值检测


JavaScript提供了4个等值检测运算符:全等(=)和不全等(!)、相等()和不相等(!=)。详细说明如表5-8所示。



表5-8 等值运算


第5章 运算符和表达式

在相等运算中,一般遵照如下基本规则进行比较:


☑ 如果运算数是布尔值,在比较之前先转换为数值。其中false转换为0,true转换为1。


☑ 如果一个运算数是字符串,另一个运算数是数字,在比较之前先尝试把字符串转换为数字。


☑ 如果一个运算数是字符串,另一个运算数是对象,在比较之前先尝试把对象转换为字符串。


☑ 如果一个运算数是数字,另一个运算数是对象,在比较之前先尝试把对象转换为数字。


☑ 如果两个运算数都是对象,那么比较它们的引用值(引用地址)。如果指向同一个引用对象,则相等,否则不等。


【示例1】下面是一些特殊运算数的比较。



第5章 运算符和表达式

NaN与任何值都不相等,包括它自己。null和undefined值相等,但它们是不同类型的数据。在相等比较中,null和undefined是不允许被转换为其他类型的值。


【示例2】下面两个变量的值虽然是通过计算得到,但它们的值是相等的。



第5章 运算符和表达式

对于值类型的数据而言,数值和布尔值的相等比较运算效果比较高,但是字符串需要消耗大量资源,因为字符串需要逐个字符进行比较,才能够确定它们是否相等。


在全等运算中,一般遵照如下基本规则进行比较:


☑ 如果运算数都是值类型,则只有数据类型相同,且数值相等时才能够相同。


☑ 如果一个运算数是数字、字符串或布尔值(值类型),另一个运算数是对象等引用类型,则它们肯定不相同。


☑ 如果两个对象(引用类型)比较,则比较它们的引用地址。


【示例3】下面是特殊运算数的全等比较。



第5章 运算符和表达式

【示例4】下面是两个对象的比较,由于它们都引用相同的地址,所以返回true。



第5章 运算符和表达式

但是对于下面两个对象来说,虽然它们的结构相同,由于地址不同,所以也不全等。



第5章 运算符和表达式

【示例5】对于引用类型的值进行比较,主要比较引用的地址是否相同,而不是比较它们的值。



第5章 运算符和表达式

在上面示例中,两个对象的值相等,但是它们的引用地址不同,所以它们既不相等,也不全等。事实上,对于引用类型的值来说,相等()和全等(=)运算符操作的结果是相同的,没有本质区别。


【示例6】对于值类型而言,只要类型相同,值相等,它们就应该完全全等,这里不需要考虑比较运算数的表达式数据类型变化,也不用考虑变量的引用地址。



第5章 运算符和表达式

【示例7】表达式(a > b || a == b)与表达式(a >= b)并不完全相等。



第5章 运算符和表达式

如果为变量a和b分别赋值为null和undefined,则返回值为false,说明这两个表达式并非完全等价。



第5章 运算符和表达式

因为nullundefined等于true,所以(a > b || a == b)表达式返回值就为true。但是表达式null>= undefined返回值为false。


5.6 赋值运算符


赋值是一种运算,但习惯上,把赋值独立成行,故称之为赋值语句。



第5章 运算符和表达式

赋值运算符的左侧运算数必须是变量、对象属性或数组元素。


【示例1】下面的写法是不对的,因为左侧的值是一个直接量,是不允许操作的。



     1=100;                 //返回错误


JavaScript提供了两种类型的赋值运算符:简单赋值运算符(=)和附加操作的赋值运算符。


简单的赋值运算符,就是把右侧的运算数的值直接复制给左侧变量。


附加操作的赋值运算符,就是赋值之前还要对右侧运算数执行某种操作,然后再复制,详细说明如表5-9所示。



表5-9 附加操作赋值运算符


第5章 运算符和表达式

【示例2】由于赋值运算符可以参与表达式运算,用户可以设计很多复杂的赋值操作,如连续赋值表达式。



     var a=b=c=d=e=f=100;                    //连接赋值


由于赋值运算符是从右向左进行计算,所以连续赋值运算并不会发生错误。


在条件表达式中进行赋值:



第5章 运算符和表达式

【示例3】在下面这个复杂的表达式中,逻辑与左侧的运算数是一个赋值表达式,右侧的运算数也是一个赋值表达式。但左侧仅是一个简单的数值赋值,而右侧的是把一个函数对象赋值给变量b。在逻辑与运算中,左侧的赋值并没有真正的复制给变量a,当逻辑与运算执行右侧的表达式时,该表达式是把一个函数赋值给变量b,然后利用小括号运算符调用这个函数,返回变量a的值,结果并没有返回变量a的值为6,而是undefined。



第5章 运算符和表达式

由于赋值运算作为表达式使用具有副作用,即它能够改变变量的值。因此在使用时要慎重,确保不要引发潜在的危险。经过上面示例代码,可以看到赋值运算符参与表达式运算时给变量a带来了不可预测的返回值。因此,对于上面表达式,更安全的写法是:



第5章 运算符和表达式

5.7 对象操作运算符


对象操作运算符主要是指对对象、数组、函数执行特定任务操作的一组运算符,主要包括in、instanceof、new、delete、.(点号)、[](中括号)和()(小括号)运算符。


5.7.1 new运算符


new运算符可以根据构造函数创建一个新的对象,并初始化该对象。其语法如下:



     new constructor(arguments)


constructor必须是一个构造函数表达式,其后面应该是利用小括号包含的参数列表,参数可有可无,参数之间通过逗号进行分隔。如果函数调用时没有参数,可以省略小括号。


【示例1】下面的代码使用了new运算符实例化Array,并演示了3种不同的使用方法。



第5章 运算符和表达式

new运算符被执行时,首先会创建一个新对象,接着new运算符调用指定的构造函数(类),这里是Array数组构造函数,并根据是否指定参数来初始化构造函数,利用这个初始化的构造函数结构和数据(如果传递参数的话)初始化新对象。


【示例2】下面代码使用自定义类创建新的对象。



第5章 运算符和表达式

对于自定义类来说,只能够通过new运算符来进行实例化。


【示例3】下面方法将返回undefined。因为虽然把类的数据结构赋值给变量b,但是由于没有实例化,所以无法访问。



第5章 运算符和表达式

【示例4】对于下面这个对象结构来说,则可以使用赋值运算符进行快速引用:



第5章 运算符和表达式

5.7.2 delete运算符


delete运算符能够删除指定对象的属性、数组元素或变量。


【示例1】下面的代码使用了delete运算符删除对象a的属性x。



第5章 运算符和表达式

执行delete运算时,如果删除操作成功,将返回true;如果不能够被删除,则返回false。



第5章 运算符和表达式

【示例2】不是所有对象成员或变量都可以被删除的,如某些内置对象的预定义成员和客户端对象成员,使用var语句声明的变量也是不允许删除的。



第5章 运算符和表达式

【示例3】如果删除不存在的对象成员,或者非对象成员、数组元素、变量时,它会返回true,所以使用delete运算符时,应该注意这个问题,防止与成功删除操作相混淆。



第5章 运算符和表达式


提示:使用delete运算符应该注意几个问题:


第一,delete运算符只能删除值类型的数据。不影响变量、属性或数组元素存储的原引用对象。例如:



第5章 运算符和表达式

第二,delete运算符的删除操作不是清空值,即把变量、属性或数组元素的值设置为undefined,而是彻底删除它们占用的存储空间。在JavaScript 1.1和JavaScript 1.0版本中仅是把变量、属性或数组元素设置为null。


第三,除了使用delete运算符手动清除不用的内存外,JavaScript主要是利用内置的一个垃圾回收程序来自动对系统进行清理,所以并不需要手动调用delete运算符来释放对象所占用的空间。


第四,灵活使用delete运算符,配合in运算符,可以很方便地操作对象成员、数组元素等,如检测、插入、删除或更新操作。



第5章 运算符和表达式


5.7.3 中括号和点号运算符


中括号和点号都属于存取运算符,用于访问对象或数组。使用中括号运算符([])可以存取数组元素值,使用点号运算符(.)可以存取对象属性值。用法如下:



第5章 运算符和表达式

在上面代码中,运算数a表示对象,运算数b表示一个标识符,如属性名。如果属性值是函数,应在标识名后面增加小括号运算符,实现方法调用操作。注意,运算数b是不能使用字符串,也不能够使用值为字符串的表达式。


运算数c可以是数组,也可以是对象。如果左侧运算数是数组,则中括号包含的运算数应是一个值为正整数的表达式(下标值)。如果左侧运算数是对象,则中括号包含的运算数应是一个值为字符串的表达式,它与对象属性名的字符串对应。


【示例1】中括号运算符([])不仅可以存取数组元素的值,还可以存取对象属性值。


☑ 读取数组元素的值



第5章 运算符和表达式

对于数组来说,可以通过数组下标来指定元素在数组中的位置,起始位置为0。


☑ 写入数组元素的值



第5章 运算符和表达式

☑ 读取对象属性值



第5章 运算符和表达式

对于对象来说,可以通过对象属性名称字符串来指定成员在对象中的位置。


☑ 重置对象属性值



第5章 运算符和表达式

【示例2】点号运算符(.)可以存取对象属性值,它比中括号灵活、方便,因为点号运算符右侧可以直接指定属性的标识符,而不是属性名称的字符串或变量。



第5章 运算符和表达式

对于中括号运算符可以通过变量或字符串表达式来传递特定值。



第5章 运算符和表达式

中括号运算符能够对第二个运算数执行运算,并对返回值的类型进行转换。这种类型转换与关系运算符的类型转换规则类似。


【示例3】对于下面两种方法都可以读取数组a中第二个元素的值。虽然说a[“1”]中参数是一个字符串,但是中括号运算符能够把它转换为数字。



第5章 运算符和表达式

与关系运算符不同,如果中括号运算符中第二个运算数为对象时,会使用toString()方法进行转换,如果失败,则会调用valueOf()方法转换。同时对于布尔值true和false将被转换为字符串"true"和"false",而不是1和0。



第5章 运算符和表达式

当对象被用作关联数组时,由于对象的属性名是动态生成的,所以不能够使用点号运算符来准确操作对象属性。但是如果使用中括号运算符来操作对象属性时,反而更方便,借助for循环语句可以实现自动化读写操作。


【示例4】下面代码能够遍历客户端window对象的所有属性以及属性值。这里主要使用了中括号运算符来操作document对象的属性,这种批量读取属性及其值的操作,如果使用点号运算符来实现是非常困难的,甚至是不可能的。



第5章 运算符和表达式

如果点号运算符右侧的标识符不存在,在读取该成员时返回undefined值,而不是返回错误。例如:



第5章 运算符和表达式

如果点号运算符右侧的标识符不存在,而为该标识符写入值时,会创建新的对象成员。例如:



第5章 运算符和表达式

5.7.4 小括号运算符


小括号是一个特殊的运算符,它没有固定数目的运算数。其中第一个运算数必须是一个函数名或者引用函数的表达式,其后附加小括号运算符,小括号中可以包含数量没有限制的运算数,它们之间通过逗号进行分隔。语法如下:



     f (a,b,c……)


其中运算数f是一个函数名或者引用函数的表达式,a、b、c……是数目不详的参数,这些参数可以是任意类型的表达式。


【示例】下面的代码演示了如何使用小括号运算符调用函数的过程。



第5章 运算符和表达式

小括号运算符在执行时是这样的:先对每个运算数进行计算,然后调用第一个运算数所指的函数,同时把余下的运算数的值传递给函数作为它的参数。


5.8 其他运算符


下面介绍其他没有分类的运算符。这些运算符在程序中经常使用,也是非常重要的。


5.8.1 条件运算符


条件运算符与条件语句在逻辑上是相同的。但条件运算符侧重于连续运算,它自身可以作为表达式,也可以作为子表达式使用;而条件语句侧重于逻辑结构,在结构中执行不同的运算。条件运算符拥有函数式特性,而条件语句具有面向对象的编程结构。


条件运算符是JavaScript唯一的三元运算符,语法形式如下:



     a ? x : y


其中a、x、y是它的3个运算数。a运算数必须是一个布尔型的表达式,即返回值必须是一个布尔值,一般使用比较表达式来表示。x和y是任意类型的值。如果运算数a返回值为true时,将执行x运算数,并返回该表达式的值。如果运算数a返回值为false时,将执行y运算数,并返回该表达式的值。


【示例】定义变量a,然后检测a是否被赋值,如果赋值则使用该值,否则使用默认值给它赋值。



第5章 运算符和表达式

条件运算符可以转换为条件语句:



第5章 运算符和表达式

条件运算符也可以转换为逻辑表达式:



第5章 运算符和表达式

在上面表达式中,如果a为true,则执行(a=a)表达式,执行完毕就不再执行逻辑或运算符后面的(a =“default value”)表达式。如果a为false,则不再执行逻辑与运算符后面的(a=a)表达式,同时将不再继续执行逻辑或运算符前面的表达式a && (a=a),转而执行逻辑或运算符后面的表达式(a =“default value”)。



提示:上面代码仅是演示,在实战中用户需要考虑假值的影响。因为,当变量赋值0、null、undefined、NaN等假值时,它们被转换为逻辑值也是false。



5.8.2 逗号运算符


逗号运算符是二元运算符,它能够先执行运算符左侧的运算数,然后再执行右侧的运算数,最后仅把右侧运算数的值作为结果返回。


【示例1】逗号运算符可以实现连续声明多个变量并赋值。



     var a=1, b=2, c=3, d=4;


等于:



     var a=1;
var b=2;
var c=3;
var d=4;


多个逗号运算符可以联排使用,从而设计一种多重计算的功效。



     var a= b=2, c= d=4;


与条件运算符或逻辑运算符根据条件来决定是否执行所有或特定运算数不同,逗号运算符会执行所有的运算数,但并非返回所有运算数的结果,它只返回最后一个运算数的值。


【示例2】下面代码中,变量a的值是逗号运算之后,通过第二个运算数c=2的执行结果赋值得到。第一个运算数的执行结果没有返回,但是这个表达式被执行了。



第5章 运算符和表达式

逗号运算符可以作为仅需执行表达式的工具,这些表达式不需要返回值,但必须要运算。在特定环境中,可以在一个表达式中包含多个子表达式,通过逗号运算符仅让它们全部执行,而不用返回全部结果。


【示例3】下面代码中,for语句的条件表达式中仅能够包含3个表达式,第一个表达式是初始化循环值,第二个表达式是布尔值,第三个表达式是循环变量的递增值。为了能够在3个表达式中完成各种计算任务,这里把逗号运算符发挥到极致。但是,要确保在第二个循环条件的第二个表达式、最后一个表达式返回一个逻辑值,否则会导致循环出现错误。



第5章 运算符和表达式

【示例4】逗号运算符的优先级是最低的。在下面代码中,赋值运算符优先于逗号运算符,也就是说数值1被赋值给变量b之后,继续赋值给变量a,最后才执行逗号运算符。



第5章 运算符和表达式

5.8.3 void运算符


void是一元运算符,它可以出现在任意类型的运算数之前,执行运算数,却忽略运算数的返回值,结果总返回一个undefined。void多用于URL中执行JavaScript表达式,但不需要表达式的计算结果。


【示例1】在下面的代码中,使用void运算符让表达式返回undefined。



第5章 运算符和表达式

由于void运算符的优先级比较高(14),高于普通运算符的优先级,所以在使用时应该使用小括号明确void运算符操作的运算数,避免发生错误。


【示例2】在下面两行代码中,由于第一行代码没有使用小括号运算符,则void运算符优先执行,返回值undefined再与1执行减法运算,所以返回值为NaN。在第二行代码中由于使用小括号运算符明确void的运算数,减法运算符先被执行,然后再执行void运算,最后返回值是undefined。



第5章 运算符和表达式

【示例3】在下面代码中,undefined是一个变量,由于void运算符返回值是undefined,所以该变量的值就等于undefined。由于早期IE浏览器对undefined数据类型支持不是很好,如果直接调用undefined就会出错,但是如果使用变量undefined来代替直接量undefined就能够避开这个Bug。



     var undefined=void null;


也可以调用一个空函数,其返回值为undefined,来定义变量undefined:



     var undefined=function(){}();


使用下面代码来定义undefined变量:



     var undefined=void 0;


【示例4】void运算符也能像函数一样使用,如void(0)也是合法的。在特殊环境下一些复杂的语句可能不方便使用void关键字形式,而必须要使用void的函数形式。



第5章 运算符和表达式

5.8.4 typeof运算符


typeof是一元运算符,其后可以跟随任意类型的运算数,计算之后将返回一个字符串,描述运算数的数据类型。针对不同类型的数据所返回的字符串如表5-10所示。



表5-10 数据类型的typeof运算返回值


第5章 运算符和表达式

【示例】在下面代码中,使用typeof运算符获取变量的类型,以此判断它是否被定义或初始化,如果没有定义或初始化,则重新为其赋值。



第5章 运算符和表达式

这种方法比下面这种使用逻辑运算符来检测会安全很多:



第5章 运算符和表达式

因为不管a的值是什么,只要赋值,它的类型都不会是undefined。不过为其赋值为undefined例外,因为赋值undefined将返回undefined类型:



第5章 运算符和表达式

5.9 案例实战


本节将通过多个题型化案例强化读者对表达式的运算,同时训练运算符的灵活应用能力。在训练之前,建议先不看参考答案和解析。


5.9.1 表达式计算


下面列举14个表达式,请快速计算出它们的值:


1.1 && 3


2.1 &&“foo” || 0


3.1 ||“foo” && 0


4.(1,2,3)


5.{foo:1}[0]


6.[true, false][+true, +false]


7.++‘52’.split(’’)[0]


8.“b”+45


9.[1,2,3,4,5][0…toString.length]


10.({}+‘b’ > {}+‘a’)


11.x=1; (function(){return x; var x=2;}())


12.‘foo’ == new function(){ return String(‘foo’); };


13.‘foo’.split(’’)+[]


14.vars: var vars=vars


答案和解析:


1.3


解析:布尔值在"&&“运算时,如果左为true时,总是返回右边,反之则直接返回左边。


2.“foo”


解析:布尔值在”||“运算时,如果左为false时,总是返回右边,反之则直接返回左边。


3.1


解析:原因同上一道题。


4.3


解析:”,“运算,直接输出最后一个。


5.underfined


解析:{foo:1}中无key为“0”的属性,没有该属性,则返回默认值underfined。


6.true


解析:[+true, +false]为[1,0],整体[true, false][1,0]中,[1,0]可看作”,“运算,所以整体简化为[true, false][0]。


7.6


解析:‘52’.split(’’)为[“5”,“2”],++[“5”,“2”][0]为6。


8.“b45”


解析:隐式转换为字符串相连接。


9.2


解析:[0…toString.length]相当于(0).toString.length。


10.true


解析:相当于比较字符串”[object Object]b"和"[object Object]a"。


11.undefined


解析:没有变量接收自执行函数返回值。


12.false


解析:"foo"为字符串,new function为对象。


13.“f,o,o”


解析:先把字符串’foo’劈开为数组,然后与空数组相加,则直接返回字符串的值。


14.undefined


解析:第一个标识符vars为标签,第二个标识符vars为变量,把声明的变量vars赋值给自己,则返回值还是默认值undefined。


5.9.2 表达式描述


类似下面形式的表达式在编程实践中会经常使用,请说明这行代码是什么意思,为什么要这样写?



     foo=foo||bar


解析:


上面表达式可以转换为if语句:



     if(!foo) foo=bar;


如果变量foo存在,则保持该变量的值不变,否则把bar的值赋给foo,即设置变量foo的初始值。上面表达式俗称为短路表达式,它灵活应用了&&和||运算符。这类表达式在进行求值时,只要最终结果已经可以确定是真或假,求值过程便终止。


5.9.3 编程题


编写一个求和函数sum(),要求达到如下情况和返回值效果。



第5章 运算符和表达式

解析:


本题包含两个知识训练点:一是如何剔除非数字参数,二是如何处理浮点数溢出问题。


排除非数字参数,可以有多种方法,最简洁的方法是使用parseFloat()函数与||运算符结合。先使用parseFloat()函数把参数转换为浮点数,如果成功则加上转换后的数值,否则加0,排除转换中的NaN值。


浮点数溢出可以通过升位浮点数为整数相加,然后再降位实现,最后使用toFixed()方法去掉溢出小数即可。


参考:



第5章 运算符和表达式