JavaScript 网页编程从入门到精通 (清华社"视频大讲堂"大系·网络开发视频大讲堂)
上QQ阅读APP看书,第一时间看更新

5.1 运算符和表达式概述

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

5.1.1 认识运算符

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

表5-1 JavaScript运算符

续表

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

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

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

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

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

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

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

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

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

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

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

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

      alert(n=5-2*2);                                       //返回1
      alert(n=(5-2)*2);                                     //返回6
      alert((n=5-2)*2);                                     //返回6

在上面代码中,虽然第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分别被转换为数字,然后参与比较运算,并返回布尔值。

      alert(3>"5");                                         //返回false
      alert(3>"2");                                         //返回true

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

      alert("string">5);                                    //返回false

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

      alert(10+20);                                         //返回30
      alert("10"+"20");                                     //返回"1020"

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

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

5.1.2 使用运算符

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

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

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

      var a;
      a = 0;
      a++;
      alert(a);                                //返回1

修改上述表达式:

      var a;
      a = 1;
      a = (a++)+(++a)-(a++)-(++a);
      alert(a);                                //返回-4

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

      var a;
      a=1;                                 //变量初始化
      b=a++;                               //b等于1,变量a先把值1赋值给变量b,然后再递加为2
      c=++a;                               //c等于3,变量a先递加为3后,再把值3赋值给变量c
      d=a++;                               //d等于3,变量a先把值3赋值给变量d,然后再递加为4
      e=++a;                               //e等于5,变量a先递加为5后,再把值5赋值给变量e
      alert(b+c-d-e);                      //返回-4

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

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

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

5.1.3 认识表达式

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

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

      1                                       //数值直接量,计算之后返回值为数值1
      "string"                                //字符串直接量,计算之后返回值为字符串"string"
      false                                   //布尔直接量,计算之后返回值为布尔值false
      null                                    //null直接量,计算之后返回值为直接量null
      /regexp/                                //正则表达式直接量,计算之后返回值为正则表达式自身
      {a:1, b:"1"}                            //对象直接量,计算之后返回值为对象自身
      [1, "1"]                                //数组直接量,计算之后返回值为数组自身
      function(a, b){return a+b}              //函数直接量,计算之后返回值为函数自身
      a                                       //变量,计算之后返回值为变量存储的值

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

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

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

例如,表达式1+2*3可以是子表达式2*3运算之后,再参与到与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岁的人:

      if(age >= 6 && age < 18){
        //学龄期行为
      }

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

      if(age >= 6 && age < 18 || age >= 65){
        //所有弱势年龄人群行为
      }

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

      if((age >= 6 && age < 18) || age >= 65){
        //所有弱势年龄人群行为
      }

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

图5-1 人的思维模型图

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

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

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

      if(( 6 <= age && age < 18) || 65 <= age ){
        //所有弱势年龄人群行为
      }

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

【示例3】表达式的另一个难点就是布尔型表达式的重叠所带来的理解障碍。

例如,对于下面这个条件表达式,该如何进行思维。

      if ! (! isA || ! isB) {
          //真真假假迷惑人
      }

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

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

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

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

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

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

      if(a.d){
        var a.b = new c(a.e(1));
      }else{
        var a.b = new c(a.f(0));
      }