第2章 表达式与运算符

本章内容主要介绍构成表达式的要素,包括常量、变量、子表达式、函数的返回值等形式的操作数,以及多种不同的运算符。

2.1 操作数

编程语言中的表达式主要由操作数(operand)和运算符(operator)构成。其中,操作数可以是常量(constant)、变量(variable)、子表达式(subsidiary expression)或是特定函数(function)的返回值(return value)。

2.1.1 常量(ES6)

在各种编程语言中,常量(constant)是指固定而明确的数据。例如:

(1)整数(integer)常量:25、770、-110。

(2)浮点数(floating-point number)常量:25.625、-23.71、Math.PI、Math.LN2、Math.SQRT2。

(3)字符串(string)的字面量(literal):'Alex'、"Happily Ever After"、 '=.=+'。

(4)正则表达式(regular expression)的字面量:/\w+\s*.(\w\d+){1,3}/g、/^\w{2}\d{5}\s*$/。

(5)布尔(boolean)常量:true、false。

(6)原始(primitive)常量:NaN、null、undefined。

(7)编程人员在源代码中,自行声明(declare / declaration)的常量,可简称为自声明常量。例如在源代码片段【const num01 = 157, num02 = 268;】中,num01和num02即是自声明常量。

关于自声明常量,可参考如下示例。

【2-1-1-constants.js】

    const E = 299792458 ;

    console.log('Speed of light is greater than 2.9E8?', E > 2.9e8) ;

【相关说明】

    const E = 299792458 ;

声明数值为299792458的常量E。

    console.log('Speed of light is greater than 2.9E8?', E > 2.9e8) ;

在浏览器调试工具的【Console】面板里显示出【Speed of light is greater than 2.9E8? true】的信息。其中,2.9e8代表2.9×108,也就是290000000。所以,【E > 2.9e8】等价于【E > 290000000】,会返回布尔值true,这是因为自声明常量E的数值为299792458,大于290000000。

在上述简短的源代码中,常量E刻意被声明成整数常量299792458,因此常量E在源代码的运行期间,不可被变更为其他数据。

2.1.2 变量(ES6)

在各种编程语言中,变量(variable)的标识符(identifier)/名称(name)用来识别特定存储位置中的可变数据。所谓的可变,是指在运行期间,其数据是可被变更的。

在JavaScript语言中,变量的作用范围(scope)大致可如下区分:

• 全局(global)范围

• 局部(local)范围

• 块(block)范围

下面通过示例,介绍如何运用全局范围和局部范围的变量。

【2-1-2-e1-global-and-local-scope-variables.js】

    let start = 123 ;
    const STEP = 3 ;
    var result = 0 ;

    function some_steps(step_count = 1)
    {
      var output ; // or let output ;

      output = start + STEP * step_count ;

      console.log(`in function: start = ${start}, STEP = ${STEP}, result = ${result}`) ;
      console.log(`in function: output = ${output}\n\n`) ;

      return output ;
    }

    result = some_steps(5) ;

    console.log(`outer: start = ${start}, step = ${STEP}, result = ${result}`) ;
    console.log(`outer: output = ${output}`) ;

【相关说明】

    let start = 123 ;
    const STEP = 3 ;
    var result = 0 ;

声明全局范围的变量start与result,以及全局范围的自声明常量STEP。既然是全局范围的变量和常量,即可让后续的任意表达式,加以访问。STEP是一个常量,所以在后续的源代码里,无法被赋予新数据。

    function some_steps(step_count = 1)
    {
      var output ; // or let output ;

      output = start + STEP * step_count ;

      console.log(`in function: start = ${start}, step= ${STEP}, result = ${result}`) ;
      console.log(`in function: output = ${output}\n\n`) ;

      return output ;
    }

定义函数some_steps(),并在内部声明局部范围的变量output。既然是局部范围的,变量output只能在函数some_steps()内部被访问。变量output接着被赋予【带有全局范围变量start和常量STEP】的表达式的结果值。这个函数内部的多条语句,访问了全局范围的变量start、常量STEP,以及局部范围的变量output。

    result = some_steps(5) ;
    console.log(`outer: start = ${start}, step= ${STEP}, result = ${result}`) ;

访问全局范围的变量start、result与常量STEP。

    console.log(`outer: output = ${output}`) ;

因为访问了函数some_steps()内的局部范围的变量output,所以会显示出错误信息【Uncaught ReferenceError: output is not defined】。

关于块范围的理解,可参考如下示例。

【2-1-2-e2-block-scope-variables.js】

    // different heights measured in meters.
    {
      // height of the building.
      let height = 100 ;

      {
        // height of one floor.
        let height = 4 ;

        {
          // height of a room.
          let height = 3 ;
          {
            // height of a desk.
            let height = 1 ;

            console.log('height of a desk =', height) ;
          }

          console.log('height of a room =', height) ;
        }

        console.log('height of one floor =', height) ;
      }

      console.log('height of the building =', height) ;
    }

    console.log('') ;

    for (var m = 1; m < 10; m++)
    {
      for(var n = 1; n < 10; n++)
      {
        console.log(`kernel count = (${m},${n})`) ;
      }

      console.log(`inner count = (${m},${n})\n\n`) ;
    }

    console.log(`outer count = (${m},${n})`) ;
    console.log('') ;

    for (let i = 1; i < 10; i++)
    {
      for(let j = 1; j < 10; j++)
      {
        console.log(`kernel count = (${i},${j})`) ;
      }

      console.log(`inner count = (${i},${j})\n\n`) ;
    }

    // console.log(`outer count = (${i},${j})`) ;

【相关说明】

    // different heights measured in meters.
    {
      // height of the building.

      let height = 100 ;

此为第1层大括号内的块范围变量height,所以第1层大括号之外的语句是访问不到的。声明块范围的变量,必须通过关键字let才行;由关键字var声明的变量,只会成为全局范围或局部范围的变量。在此,可将此层块范围的变量height视为建筑物的高度。

      {

        // height of one floor.
    let height = 4 ;

此为第2层大括号内的块范围变量height,因此第2层大括号之外的语句是访问不到的。在此,可将此层块范围的变量height,视为建筑物特定楼层的高度。

    {

      // height of a room.
      let height = 3 ;

此为第3层大括号内的块范围变量height,因此第3层大括号之外的语句是访问不到的。在此,可将此层块范围的变量height,视为特定楼层中特定房间的高度。

    {

      // height of a desk.
      let height = 1 ;

此为第4层大括号内的块范围变量height,因此第4层大括号之外的语句是访问不到的。在此,可将此层块范围的变量height,视为特定房间中特定桌子的高度。

            console.log('height of a desk =', height) ;
          }
          console.log('height of a room =', height) ;
        }
        console.log('height of one floor =', height) ;
      }
      console.log('height of the building =', height) ;
    }

通过此源代码片段,可供读者调试出每层块范围变量height的对应值。

    console.log('') ;

    for (var m = 1; m < 10; m++)
    {
      for(var n = 1; n < 10; n++)
      {
        console.log(`kernel count = (${m},${n})`) ;
      }
      console.log(`inner count = (${m},${n})\n\n`) ;
    }
    console.log(`outer count = (${m},${n})`) ;

测试此源代码片段,可看出通过关键字var声明的变量m与n,在带有大括号的for循环语句结束之后,依然可以被访问到。这也就意味着,关键字var声明的,无法成为块范围的变量。

    console.log('') ;

    for (let i = 1; i < 10; i++)
    {
      for(let j = 1; j < 10; j++)
      {
        console.log(`kernel count = (${i},${j})`) ;
      }
      console.log(`inner count = (${i},${j})\n\n`) ;
    }
    // console.log(`outer count = (${i},${j})`) ;

测试此源代码片段,可看出通过关键字let声明的变量m与n,只能被访问于for循环语句大括号内的块范围里。这也就意味着,关键字let声明的,可以成为块范围的变量。复原最后一行成为注释的语句,并进行测试之后,可在网页浏览器的调试工具【Console】面板中,看到因为访问不到块范围变量i与j的错误信息。

关于特殊变量的理解,可参考如下示例。

【2-1-2-e3-special-variables.js】

    document.body.style.backgroundColor = 'RoyalBlue' ;

    console.log(document.body.style.backgroundColor) ;

    document.body.style.fontSize = '3em' ;

    console.log(document.body.style.fontSize) ;

    file_selector.style.color = 'Gold' ;

    console.log(file_selector.style.color) ;

    file_selector.style.zoom = 2 ;

【相关说明】

    file_selector.style.zoom = 2 ;

此语句中的子属性zoom,因为可被设置新数据,所以算是变量的一种;然而,此语句中的子属性style,则无法被设置新数据,也因此算是常量的一种。

通过JavaScript语法,可访问文档对象模型(DOM, document object model)中的特定元素实例(element instance)。元素实例可内含常量属性,只可被读取其数据,而不允许被写入新数据。下面举出4个元素实例中的常量属性:

• file_selector.style

• document.body.style

• document.body

• window.document(可被简写为document)

以下可变更数据的是字符串(string)类型的属性,亦是网页浏览器内置的,所以可认为是网页程序在运行时的特殊变量

• document.body.style.backgroundColor

• document.body.style.fontSize

• file_selector.style.color

• file_selector.style.zoom

2.1.3 子表达式

一般而言,特定表达式可再分割成为子表达式(subsidiary expression)。关于子表达式的运用,请参考如下示例。

【2-1-3-subsidiary-expressions.js】

    var r = 10 ;
    var volume = 4 / 3 * Math.PI * Math.pow(r, 3) ;

    circumference = r => 2 * Math.PI * r ;

    circle_area = r => Math.PI * Math.pow(r, 2) ;

    sphere_volume = r => 4 / 3 * Math.PI * Math.pow(r, 3) ;

    cylinder_volume = (r, h) => circle_area(r) * h ;

    cylinder_surface_area = (r, h) => 2 * circle_area(r) + circumference(r) * h ;

    rounded_circle_area = circle_area(10).toFixed(3) ;
    rounded_cylinder_volume = cylinder_volume(10, 20).toFixed(3) ;
    rounded_cylinder_surface_area = cylinder_surface_area(10, 20).toFixed(3) ;

    console.log(rounded_circle_area) ;
    console.log(rounded_cylinder_volume) ;
    console.log(rounded_cylinder_surface_area) ;

【相关说明】

    var r = 10 ;

此语句内含左侧表达式。

    var volume = 4 / 3 * Math.PI * Math.pow(r, 3) ;

此主要表达式内含如下子表达式:

• 左侧表达式:【var volume =】和【Math.】

• 算术表达式:【4 / 3 * Math.PI * Math.pow(r, 3)】

• 主要表达式:【Math.PI】和【Math.pow(r, 3)】

    circumference = r => 2 * Math.PI * r ;

此主要表达式内含如下子表达式:

• 左侧表达式:【circumference =】和【Math.】

• 箭头函数表达式:【r => 2 * Math.PI * r】

• 算术表达式:【2 * Math.PI * r】

• 主要表达式:【Math.PI】

    circle_area = r => Math.PI * Math.pow(r, 2) ;

此表达式内含如下子表达式:

• 左侧表达式:【circle_area =】和【Math.】

• 箭头函数表达式:【r => Math.PI * Math.pow(r, 2)】

• 算术表达式:【Math.PI * Math.pow(r, 2)】

• 主要表达式:【Math.PI】和【Math.pow(r, 2)】

    sphere_volume = r => 4 / 3 * Math.PI * Math.pow(r, 3) ;

此表达式内含如下子表达式:

• 左侧表达式:【sphere_volume =】和【Math.】

• 箭头函数表达式:【r => 4 / 3 * Math.PI * Math.pow(r, 3)】

• 算术表达式:【4 / 3 * Math.PI * Math.pow(r, 3)】

• 主要表达式:【Math.PI】和【Math.pow(r, 3)】

    cylinder_volume = (r, h) => circle_area(r) * h ;

此表达式内含如下子表达式:

• 左侧表达式:【cylinder_volume =】

• 箭头函数表达式:【(r, h) => circle_area(r) * h】

• 算术表达式:【circle_area(r) * h】

• 主要表达式:【circle_area(r)】

    cylinder_surface_area = (r, h) => 2 * circle_area(r) + circumference(r) * h ;

此表达式内含如下子表达式:

• 左侧表达式:【cylinder_surface_area =】

• 箭头函数表达式:【(r, h) => 2 * circle_area(r) + circumference(r) * h】

• 算术表达式:【2 * circle_area(r) + circumference(r) * h】

• 主要表达式:【circle_area(r)】和【circumference(r)】

    rounded_circle_area = circle_area(10).toFixed(3) ;

此表达式内含如下子表达式:

• 左侧表达式:【rounded_circle_area =】和【circle_area(10).】

• 主要表达式:【circle_area(10).toFixed(3)】、【circle_area(10)】和【toFixed(3)】

    rounded_cylinder_volume = cylinder_volume(10, 20).toFixed(3) ;

此表达式内含如下子表达式:

• 左侧表达式:【rounded_cylinder_volume =】和【cylinder_volume(10, 20).】

• 主要表达式:【cylinder_volume(10, 20).toFixed(3)】、【cylinder_volume(10, 20)】和【toFixed(3)】

    rounded_cylinder_surface_area = cylinder_surface_area(10, 20).toFixed(3) ;

此表达式内含如下子表达式:

• 左侧表达式:【rounded_cylinder_surface_area =】和【cylinder_surface_area(10, 20).】

• 主要表达式:【cylinder_surface_area(10, 20).toFixed(3)】、【cylinder_surface_area(10, 20)】和【toFixed(3)】

    console.log(rounded_circle_area) ;
    console.log(rounded_cylinder_volume) ;
    console.log(rounded_cylinder_surface_area) ;

如上3个语句排除分号【;】的其余部分,皆为主要表达式。

2.1.4 函数的返回值

在多种编程语言中,关键字return开头的语句,除了会终止当前函数内源代码的执行,并返回调用函数的子表达式之外,亦会使得前述子表达式,被取代成为返回值(return value),成为原始表达式的操作数。关于返回值的理解,可参考如下示例。

【2-1-4-function-return-values.js】

    // cv stands for "cubic volume".
    function cv01(l, w, h)
    {
      let cubic_volume = l * w * h ;

      console.log(`cubic volume of (${l}, ${w}, ${h}) = ${cubic_volume}`) ;

      // return ;
    }

    function cv02(l, w, h)
    {

      let cubic_volume = l * w * h ;

      return cubic_volume ;
    }

    let result01 = cv01(3, 5, 10) ;

    let result02 = cv02(3, 5, 10) ;

    let result03 = cv02(1, 3, 5) + cv02(2, 4, 6) ;

    console.log(`result01 = ${result01}`) ;
    console.log(`result02 = ${result02}`) ;
    console.log(`result03 = ${result03}`) ;

【相关说明】

    // cv stands for "cubic volume".
    function cv01(l, w, h)
    {
      let cubic_volume = l * w * h ;
      console.log(`cubic volume of (${l}, ${w}, ${h}) = ${cubic_volume}`) ;
      // return ;
    }

• 在上述函数内的末尾处,并未放置return语句,或是return语句仅仅衔接分号【;】。所以,此段源代码定义了未带返回值的函数cv01()。

• 函数cv01()可在网页浏览器的调试工具【Console】面板里,显示出长、宽、高可能不同的立方体积。

    function cv02(l, w, h)
    {
      let cubic_volume = l * w * h ;
      return cubic_volume ;
    }

• 此源代码片段定义了具有返回值的函数cv02(),因为在上述函数内的末尾处,放置了语句【return cubic_volume ;】。

• 函数cv02()亦可计算出长、宽、高可能不同的立方体积。

    let result01 = cv01(3, 5, 10) ;

• 此语句调用了函数cv01(3, 5, 10),显示出长、宽、高各为3、5、10的立方体积。

• 因为函数cv01()并无返回值,所以变量result01会被赋予原始常量undefined。

    let result02 = cv02(3, 5, 10) ;

此语句调用了函数cv02(3, 5, 10),并将长、宽、高各为3、5、10的立方体积结果值,返回到此语句,成为新的操作数150,使得此语句等价于【let result02 = 150 ;】,进而让变量result02的数值变成150。

    let result03 = cv02(1, 3, 5) + cv02(2, 4, 6) ;

此语句调用了函数cv02(1, 3, 5)和cv02(2, 4, 6),并将长、宽、高各为1、3、5与2、4、6的个别立方体积结果值,返回到此语句,成为新的操作数15与48,使得此语句等价于【let result02 = 15+ 48 ;】,进而让变量result02的数值变成63。

    console.log(`result01 = ${result01}`) ;

此语句显示出变量result01的数据为undefined。

    console.log(`result02 = ${result02}`) ;
    console.log(`result03 = ${result03}`) ;

这两个语句分别显示出变量result02的数值150,以及变量result03的数值63。

2.2 运算符

各种计算机编程语言均支持一系列的运算符,并和参与其中的操作数,构成特定形态的表达式,以满足计算机各种复杂的演算、分析与归纳。

2.2.1 算术运算符(ES7)

JavaScript语言的算术运算符如表2-1所示。

表2-1 JavaScript的运算符

下面通过示例介绍各种算术运算符的运用。

【2-2-1-arithmetic-operators.js】

    let num01 = 125, num02 = 10, num03 = 5 ;
    let result = 0 ;

    result = num01 % num02 ;
    console.log(result) ;

    result = num01 / num02 ;
    console.log(result) ;

    result = num01 * num02 ;
    console.log(result) ;

    result = num01 + num02 - num03 ;
    console.log(result) ;

    result = ++num01 ;
    console.log(result, num01) ;

    result = num01++ ;
    console.log(result, num01) ;

    ++num01 ;
    num01++ ;
    console.log(num01) ;

    num01 += 1 ;
    console.log(num01) ;

    num01 = num01 + 1 ;
    console.log(num01) ;

    result = --num02 ;
    console.log(result, num02) ;

    result = num02-- ;
    console.log(result, num02) ;

    --num02 ;
    num02-- ;
    console.log(num02) ;

    num02 -= 1 ;
    num02 = num02 - 1 ;
    console.log(num02) ;

    result = num03 ** 3 ** 2 ;
    console.log(result) ;

    result = num03 ** (3 ** 2) ;
    console.log(result) ;

    result = (num03 ** 3) ** 2 ;
    console.log(result) ;

【相关说明】

    let num01 = 125, num02 = 10, num03 = 5 ;
    let result = 0 ;

这两个语句声明了具有初始数据的变量num01、num02、num03与result。

    result = num01 % num02 ;

此语句使得变量result,被赋予了【变量num01的数值除以变量num02的数值】的余数。

    result = num01 / num02 ;

此语句使得变量result,被赋予了【变量num01的数值除以变量num02的数值】的结果值。

    result = num01 * num02 ;

此语句使得变量result,被赋予了【变量num01的数值乘以变量num02的数值】的结果值。

    result = num01 + num02 - num03 ;

此语句使得变量result,被赋予了【变量num01的数值加上变量num02的数值,再减去变量num03的数值】的结果值。

    result = ++num01 ;

此语句被执行之前,变量num01的数值为125。

因为运算符++出现在变量num01的左侧,也就意味着num01的数值要先递增为126,再执行【result = num01】。

所以,result的数值最终为126。

    result = num01++ ;

此语句被执行之前,变量num01的数值已经变成126。

因为运算符++出现在变量num01的右侧,也就意味着要先执行【result = num01】,之后num01的数值才递增为127。

所以,result的数值最终仍然为126。

    ++num01 ;
    num01++ ;

这两个语句被执行之前,变量num01的数值已经变成127。因为表达式++num01与num01++均单独出现在语句中,所以可简单视为变量num01的数值,被进行了2次递增,变成129。

    num01 += 1 ;

此语句被执行之前,变量num01的数值已经变成129。此语句等同于递增变量num01的数值,使得num01的数值成为130。

    num01 = num01 + 1 ;

此语句被执行之前,变量num01的数值已经变成130。此语句亦等同于递增变量num01的数值,成为131。

    result = --num02 ;

此语句被执行之前,变量num02的数值为10。因为运算符--出现在变量num02的左侧,也就意味着num02的数值要先递减为9,再执行【result = num02】。所以,result的数值最终为9。

    result = num02-- ;

此语句被执行之前,变量num02的数值已经变成9。因为运算符--出现在变量num02的右侧,也就意味着要先执行【result = num02】,之后num02的数值才递减为8。所以,result的数值最终仍然为9。

    --num02 ;
    num02-- ;

这两个语句被执行之前,变量num02的数值已经变成8。因为运算符--num02与num02--均单独出现在语句中,所以可简单视为变量num01的数值,被进行了两次递减,变成6。

    num02 -= 1 ;

此语句被执行之前,变量num02的数值已经变成6。此语句等同于递减变量num02的数值,成为5。

    num02 = num02 - 1 ;

此语句被执行之前,变量num02的数值已经变成5。此语句亦等同于递减变量num02的数值,成为4。

    result = num03 ** 3 ** 2 ;

在此,变量num03的数值是5。因为求幂运算符**具有右结合的特征,所以【3 ** 2】要先被评估成为32,也就是9,然后再评估【num03 ** 9】,结果值为59,也就是1953125。

    result = num03 ** (3 ** 2) ;

在此,借助小括号运算符()的辅助,可明确得知【(3 ** 2)】会优先被评估成为32,也就是9,然后评估【num03 ** 9】的结果值为59,也就是1953125。

    result = (num03 ** 3) ** 2 ;

在此,使用小括号运算符()的辅助,可明确得知【(num03 ** 3)】会优先被评估成为53,也就是125,然后再评估【125** 2】的结果值为1252,也就是15625。

2.2.2 赋值运算符

JavaScript编程语言的赋值运算符(assignment operator)存在基本形式的等号【=】,以及如表2-2所示的复合形式。

表2-2 JavaScript的赋值运算符

关于赋值运算符的运用,可参考如下示例。

【2-2-2-assignment-operators.js】

    let a = 18, b = 5, c = 2 ;
    let result = 0 ;

    result = a + b + c ;
    console.log(result) ;

    // a = a + b ;
    a += b ;
    console.log(a) ;

    // a = a - b ;
    a -= b ;
    console.log(a) ;

    // a = a * b ;
    a *= b ;
    console.log(a) ;

    // a = a / b ;
    a /= b ;
    console.log(a) ;

    // a = a % b ;
    a %= b ;
    console.log(a) ;

    // b = b ** c ;
    b **= c ;
    console.log(b) ;

    // b = b << c ;
    b <<= c ;
    console.log(b) ;

    // b = b >> c ;
    b >>= c ;
    console.log(b) ;

    // b = b >>> c ;
    b >>>= c ;
    console.log(b) ;

    // b = b & c ;
    b &= c ;
    console.log(b) ;

    // b = b ^ c ;
    b ^= c ;
    console.log(b) ;

    // b = b | c ;
    b |= c ;
    console.log(b) ;

【相关说明】

    let a = 18, b = 5, c = 2 ;
    let result = 0 ;

这两个语句声明了具有初始数据的变量a、b、c与result。

    result = a + b + c ;

此语句里的赋值运算符【=】,使得变量result的数值,成为变量a、b、c各数值的总和。

    // a = a + b ;
    a += b ;

此语句里的赋值运算符【+=】,使得变量a的数值,成为【变量a本身的数值加上变量b的数值】的结果值。

    // a = a - b ;
    a -= b ;

此语句里的赋值运算符【-=】,使得变量a的数值,成为【变量a本身的数值减去变量b的数值】的结果值。

    // a = a * b ;
    a *= b ;

此语句里的赋值运算符【*=】,使得变量a的数值,成为【变量a本身的数值乘以变量b的数值】的结果值。

    // a = a / b ;
    a /= b ;

此语句里的赋值运算符【/=】,使得变量a的数值,成为【变量a本身的数值除以变量b的数值】的结果值。

    // a = a % b ;
    a %= b ;

此语句里的赋值运算符【%=】,使得变量a的数值,成为【变量a本身的数值除以变量b的数值】的余数。

    // b = b ** c ;
    b **= c ;

此语句里的赋值运算符【**=】,使得变量b的数值,成为【变量b本身数值的c幂次】bc的结果值。

    // b = b << c ;
    b <<= c ;

此语句里的赋值运算符【<<=】,使得变量b的二进制数值,向偏移变量c所代表的比特位(bit)个数

    // b = b >> c ;
    b >>= c ;

此语句里的赋值运算符【>>=】,使得变量b的二进制数值,在保留正负号的前提下,向右偏移变量c所代表的比特位(bit)个数。所谓的保留正负号就是:

• 若该二进制数值为负值,则其最左侧的符号位(sign bit)是1,并在向右偏移的同时,保持其符号位为1。

• 若该二进制数值为正值,则其最左侧的符号位是0,并在向右偏移的同时,保持其符号位为0。

    // b = b >>> c ;
    b >>>= c ;

此语句里的赋值运算符【>>>=】,使得变量b的数值,成为【变量b的二进制数值,向右偏移变量c所代表的比特位(bit)个数,并在其左侧补上相同个数的二进制0】之后的结果值。

在其左侧补上相同个数的二进制0,也就意味着,一开始无论其最左侧的符号位是0(正值)或是1(负值),后续皆在向右偏移的同时,保持符号位成为0。

    // b = b & c ;
    b &= c ;

此语句里的赋值运算符【&=】,使得变量b的数值,成为【变量b本身的二进制数值与变量c的二进制数值,进行按位与(bitwise and)运算】之后的结果值。

    // b = b ^ c ;
    b ^= c ;

此语句里的赋值运算符【^=】,使得变量b的数值,成为【变量b的二进制数值与变量c的二进制数值,进行按位异或(bitwise exclusive or)运算】之后的结果值。

    // b = b | c ;
    b |= c ;

此语句里的赋值运算符【^=】,使得变量b的数值,成为【变量b的二进制数值与变量c的二进制数值。进行按位或(bitwise or)运算】之后的结果值。

2.2.3 比较运算符

在各编程语言中,比较运算符(comparison operator)是用来决定其两侧操作数相等或不相等的关系。JavaScript编程语言的比较运算符如表2-3所示。

表2-3 JavaScript语言的比较运算符

关于比较运算符的综合运用,可参考如下示例。

【2-2-3-comparison-operators.js】

    let v01 = 100 , v02 = 250, v03 = 500 ;
    let s01 = '100', s02 = '250', s03 = '500' ;

    console.log(v01 == s01) ;
    console.log(v01 === s01) ;

    console.log(v02 > s01) ;
    console.log(v02 < s03) ;

    console.log(v03 >= s03) ;
    console.log(v03 <= s03) ;

【相关说明】

    let v01 = 100 , v02 = 250, v03 = 500 ;

此语句声明了初始数值为整数常量的变量v01、v02和v03。

    let s01 = '100', s02 = '250', s03 = '500' ;

此语句声明了初始数据为字符串字面量的变量s01、s02与s03。

    console.log(v01 == s01) ;

判断变量v01与s01的数据是否相同,并显示判断为真的返回值true,于浏览器的调试工具【Console】面板中。

    console.log(v01 === s01) ;

判断变量v01与s01的数据是否相同,以及其数据类型是否也相同,并显示判断为假的返回值false,于浏览器的调试工具【Console】面板中。

    console.log(v02 > s01) ;

判断变量v02的数值是否大于变量s01的数据,并显示判断为真的返回值true,于浏览器的调试工具【Console】面板中。

    console.log(v02 < s03) ;

判断变量v02的数值是否小于变量s03的数据,并显示判断为真的返回值true,于浏览器的调试工具【Console】面板中。

    console.log(v03 >= s03) ;

判断变量v03的数值是否大于或者等于变量s03的数据,并显示判断为真的返回值true于浏览器调试工具的【Console】面板中。

    console.log(v03 <= s03) ;

判断变量v03的数值是否小于或者等于变量s03的数据,并显示判断为真的返回值true,于浏览器的调试工具【Console】面板中。

2.2.4 逻辑运算符

在各种编程语言中,逻辑运算符(logical operator)主要用来串联带有比较含义的表达式。JavaScript编程语言的逻辑运算符如表2-4所示。

表2-4 JavaScript语言的逻辑运算符

关于逻辑运算符的综合运用,可参考如下示例。

【2-2-4-logical-operators.js】

    let v01 = 10, v02 = 50, v03 = 60 ;
    let values = [10, 20, 30, 40, 50] ;
    let person = {name: 'Gary', gender: 'male', age: '25'} ;

    // result = true && true ;
    result = v01 < v02 && v02 < v03 ;
    console.log(result) ;

    // result = false || true ;
    result = v01 > v02 || v03 > v02 ;
    console.log(result) ;

    // result = ! false ;
    result = ! (v01 > v02) ;
    console.log(result) ;

    result = 2 in values ;
    console.log(result) ;

    result = 'length' in values ;
    console.log(result) ;

    result = 'round' in Math ;
    console.log(result) ;

    result = 'gender' in person ;
    console.log(result) ;

【相关说明】

    let v01 = 10, v02 = 50, v03 = 60 ;

此语句声明了初始数值为整数常量的变量v01、v02与v03。

    let values = [10, 20, 30, 40, 50] ;

此语句声明了初始数据为数组实例的变量values。

    let person = {name: 'Gary', gender: 'male', age: '25'} ;

此语句声明了初始数据为对象实例的变量person。

    // result = true && true ;
    result = v01 < v02 && v02 < v03 ;

【v01 < v02】为真,所以返回true;【v02 < v03】为真,所以返回true。因此,【v01 < v02 &&v02 < v03】亦为真而返回true,使得变量result的数据成为布尔值true。

    // result = false || true ;
    result = v01 > v02 || v03 > v02 ;

【v01 > v02】为假,所以返回false;【v03 > v02】为真,所以返回true。因此,【v01 > v02 ||v03 > v02】亦为真而返回true,使得变量result的数据成为布尔值true。

    // result = ! false ;
    result = ! (v01 > v02) ;

【v01 > v02】为假,所以返回false。因此,【! (v01 > v02)】为真而返回true,使得变量result的数据成为布尔值true。

    result = 2 in values ;

变量values的数据是数组实例[10, 20, 30, 40, 50],所以values[0]可访问到数组实例的元素值10;values[4]可访问到数组实例的元素值50。

在数组实例名称values右侧的中括号[]里面,例如values[3],存在作为索引值(index value)的整数值3,以访问其索引值3所代表的特定元素40。

在此,因为索引值2(第3个)对应到values[2]所代表的第3个元素值30,所以【2 in values】为真而返回true,使得变量result的数据成为布尔值true。

    result = 'length' in values ;

变量values的数据是数组实例,因此变量values具有属性length,可用来获取values.length所代表元素个数的整数值5。所以,【'length' in values】为真而返回true,使得变量result的数据成为布尔值true。

    result = 'round' in Math ;

内置的对象Math具有函数round(),所以【'round' in Math】为真而返回true,进而使得变量result的数据成为布尔值true。

    result = 'gender' in person ;

变量person的数据是对象实例{name: 'Gary', gender: 'male', age: '25'},所以属性gender可用来访问到person.gender所代表属性的数据'male'。所以,【'gender' in person】为真而返回true,使得变量result的数据成为布尔值true。

2.2.5 条件运算符

从C语言开始,许多后继的编程语言,均支持条件运算符(conditional operator)/三元运算符(ternary operator),并用来简化特定形式的if语句。关于条件运算符的运用,可参考如下示例。

【2-2-5-conditional-operator.js】

    let score, passed ;

    score = 58 ;

    passed = score >= 60 ? 'yes' : 'no' ;
    console.log(passed) ;

    score = 77 ;

    passed = score >= 60 ? 'yes' : 'no' ;
    console.log(passed) ;
    score = 60 ;

    if (score >= 60) passed = 'yes' ;
    else passed = 'no' ;

    console.log(passed) ;

【相关说明】

    let score, passed ;

此语句声明了变量score与passed。

    score = 58 ;

此语句使得变量score,被赋予整数值58。

    passed = score >= 60 ? 'yes' : 'no' ;

对于表达式【score >= 60 ? 'yes' : 'no'】而言,若【score >= 60】为真,则返回字符串'yes';否则返回字符串'no'。在此,其为假,所以返回字符串'no',进而使得变量passed的数据,成为字符串'no'。

    score = 77 ;

此语句使得变量score,被赋予整数值77。

    passed = score >= 60 ? 'yes' : 'no' ;

在此,表达式【score >= 60 ? 'yes' : 'no'】返回字符串'yes',进而使得变量passed的数据,成为字符串'yes'。

    score = 60 ;

此语句使得变量score,被赋予整数值60。

    if (score >= 60) passed = 'yes' ;
    else passed = 'no' ;

对于此条件语句而言,若【score >= 60】为真,则变量passed会被赋予字符串'yes';否则会被赋予字符串'no'。在此,其为真,所以返回字符串'yes',进而使得变量passed,被赋予字符串'yes'。

2.2.6 类型运算符

类型运算符(typeof operator)用来返回特定操作数(operand)的数据类型(data type)。关于类型运算符的综合运用,可参考如下示例。

【2-2-6-typeof-operator.js】

    let num01 = 33, num02 = 1.414 ;

    console.log(typeof num01) ;
    console.log(typeof 33) ;
    console.log(typeof num02) ;
    console.log(typeof 1.414) ;
    console.log('') ;

    console.log(typeof Math.PI) ;
    console.log(typeof NaN) ;
    console.log(typeof Infinity) ;
    console.log('') ;

    console.log(typeof '') ;
    console.log(typeof "") ;
    console.log(typeof "Hello, Earth!") ;
    console.log('') ;

    console.log(typeof true) ;
    console.log(typeof false) ;
    console.log(typeof (num01 > num02)) ;
    console.log('') ;

    console.log(typeof undefined) ;
    console.log(typeof num03) ;
    console.log('') ;

    console.log(typeof function() {}) ;
    console.log(typeof Array.isArray) ;
    console.log('') ;

    console.log(typeof Object()) ;
    console.log(typeof new Object()) ;
    console.log(typeof {}) ;
    console.log('') ;

    console.log(typeof Array()) ;
    console.log(typeof new Array()) ;
    console.log(typeof []) ;
    console.log('') ;

    console.log(typeof null) ;
    console.log('') ;

    console.log(typeof String('test')) ;
    console.log(typeof new String('test')) ;
    console.log('') ;

    console.log(typeof Number(123)) ;
    console.log(typeof new Number(123)) ;
    console.log('') ;

    console.log(typeof Date()) ;
    console.log(typeof new Date()) ;

【相关说明】

    let num01 = 33, num02 = 1.414 ;

此语句声明了具有初始数值的变量num01与num02。

    console.log(typeof num01) ;

表达式【typeof num01】会得出变量num01的数据类型名称,在此为字符串'number'。

    console.log(typeof 33) ;

表达式【typeof 33】会得出常量33的数据类型名称,在此为字符串'number'。

    console.log(typeof num02) ;

表达式【typeof num02】会得出变量num02的数据类型名称,在此为字符串'number'。

    console.log(typeof 1.414) ;

表达式【typeof 1.414】会得出常量1.414的数据类型名称,在此为字符串'number'。

    console.log(typeof Math.PI) ;

表达式【typeof Math.PI】会得出内置对象Math的常量属性PI的数据类型名称,在此为字符串'number'。

    console.log(typeof NaN) ;

表达式【typeof NaN】会得出原始常量NaN的数据类型名称,在此为字符串'number'。

    console.log(typeof Infinity) ;

表达式【typeof Infinity】会得出内置常量Infinity的数据类型名称,在此为字符串'number'。

    console.log(typeof '') ;

表达式【typeof''】会得出字符串''的数据类型名称,在此为字符串'string'。

    console.log(typeof "") ;

表达式【typeof ""】会得出字符串""的数据类型名称,在此为字符串'string'。

    console.log(typeof "Hello, Earth!") ;

表达式【typeof "Hello, Earth!"】会得出字符串"Hello, Earth!"的数据类型名称,在此为字符串'string'。

    console.log(typeof true) ;

表达式【typeof true】会得出布尔值true的数据类型名称,在此为字符串'boolean'。

    console.log(typeof false) ;

表达式【typeof false】会得出布尔值false的数据类型名称,在此为字符串'boolean'。

    console.log(typeof (num01 > num02)) ;

表达式【typeof (num01 > num02)】会得出子表达式【num01 > num02】返回值的数据类型名称,在此为字符串'boolean'。

    console.log(typeof undefined) ;

值得关注的是,原始常量undefined的数据类型亦是undefined。表达式【typeof undefined】会得出原始常量undefined的数据类型名称,在此为字符串'undefined'。

    console.log(typeof num03) ;

在此,num03未被声明为变量或函数名称,所以num03处于未定义(undefined)的状态。表达式【typeof num03】会得出未被声明的num03的数据类型名称,在此为字符串'undefined'。

    console.log(typeof function() {}) ;

表达式【typeof function() {}】会得出匿名函数的数据类型名称,在此为字符串'function'。

    console.log(typeof Array.isArray) ;

表达式【typeof Array.isArray】会得出内置对象Array的函数isArray()的数据类型名称,在此为字符串'function'。

    console.log(typeof Object()) ;

因为Object对象的构造函数Object(),会返回Object对象的实例;也因此表达式【typeof Object()】会得出Object对象实例的数据类型名称,在此为字符串'object'。

    console.log(typeof new Object()) ;

表达式【typeof new Object()】也会得出Object对象实例的数据类型名称,在此亦为字符串'object'。此外,JavaScript语言尚未界定【Object()】与【new Object()】的明显区别。

    console.log(typeof {}) ;

表达式【typeof {}】会得出空对象实例{}的数据类型名称,在此亦为字符串'object'。而在JavaScript编程语言中,如下3个语句是等价的:

• obj = {} ;

• obj = new Object() ;

• obj = Object() ;

    console.log(typeof Array()) ;

因为Array对象的构造函数Array(),会返回Array对象的实例;也因此表达式【typeof Array()】会得出Array对象实例的数据类型名称,在此竟然也为字符串'object'。

    console.log(typeof new Array()) ;

表达式【typeof new Array()】会得出Array对象实例的数据类型名称,在此亦为字符串'object'。此外,JavaScript编程语言尚未界定【Array()】与【new Array()】的明显区别。

    console.log(typeof []) ;

表达式【typeof []】会得出数组实例[]的数据类型名称,在此为字符串'object'。而在JavaScript语言中,如下3个语句是等价的:

• arr = [] ;

• arr = new Array() ;

• arr = Array() ;

在JavaScript语言里,将数组实例(array instance)的数据类型视为object。

    console.log(typeof null) ;

【typeof null】表达式会得出原始常量null的数据类型名称,在此为字符串'object'。

    console.log(typeof String('test')) ;

内置对象String的构造函数String('test'),只会返回字符串'test'。因此,表达式【typeof String('test')】等同于获得字符串'test'的数据类型名称,在此为字符串'string'。

    console.log(typeof new String('test')) ;

内置对象String的构造函数String('test')会返回字符串'test'。再经过new关键字的处理,会返回内含字符串'test'的String对象实例。因此,表达式【typeof new String('test')】等同于获得String对象实例的数据类型名称,在此为字符串'object'。

    console.log(typeof Number(123)) ;

内置对象Number的构造函数Number(123),会返回整数常量123。因此,【typeof Number(123)】表达式等同于获得整数常量123的数据类型名称,在此为字符串'number'。

    console.log(typeof new Number(123)) ;

内置对象Number的构造函数Number(123),会返回整数常量123。再经过new关键字的处理,会返回内含整数常量123的Number对象实例。因此,表达式【typeof new Number(123)】等同于获得Number对象实例的数据类型名称,在此为字符串'object'。

    console.log(typeof Date()) ;

内置Date对象的构造函数Date(),会返回当前日期与时间的字符串。因此,表达式【typeof Date()】等同于获得当前日期与时间的字符串的数据类型名称,在此为字符串'string'。

    console.log(typeof new Date()) ;

内置Date对象的构造函数Date(),会返回当前日期与时间的字符串。再经过new关键字的处理,会返回内含当前日期与时间的Date对象实例。因此,表达式【typeof new Date()】等同于获得Date对象实例的数据类型名称,在此为字符串'object'。

2.2.7 按位运算符

JavaScript语言的按位运算符(bitwise operator),在被运算之前,其两侧操作数(operand)的数据,会被转换成为32个比特位的数据。关于按位运算符的综合运用,可参考如下示例。

【2-2-7-bitwise-operators.js】

    let num01 = 56, num02 = 77 ;
    let num03 = 124, num04 = -3 ;

    console.log(num01.toString(2)) ;
    console.log(num02.toString(2)) ;
    console.log(num03.toString(2)) ;
    console.log('') ;

    console.log(num01 == 0b111000) ;
    console.log(num01 == 0b00111000) ;
    console.log('') ;

    console.log(num02 == 0b1001101) ;
    console.log(num02 == 0b01001101) ;
    console.log('') ;

    console.log(num03 == 0b1111100) ;
    console.log(num03 == 0b01111100) ;
    console.log('') ;

    /*
     00111000
    &01001101
    ----------
     00001000
    */
    result = num01 & num02 ;

    console.log(result) ;
    console.log(result.toString(2)) ;
    console.log('') ;

    /*
     00111000
    |01001101
    ----------
     01111101
    */
    result = num01 | num02 ;

    console.log(result) ;
    console.log(result.toString(2)) ;
    console.log('') ;

    /*
     00111000
    ^01001101
    ----------
     01110101
    */
    result = num01 ^ num02 ;

    console.log(result) ;
    console.log(result.toString(2)) ;
    console.log('') ;

    /*
     ~ 01111100
    ------------
       10000011 (negative value)
    =
      -01111100
     + 00000001 (because of 2's complement)
    ------------
      -01111101
    */
    result = ~ num03 ;

    console.log(result) ;
    console.log(result.toString(2)) ;
    console.log('') ;

    /*
    << 001111100
    ------------
        011111000
    */
    result = num03 << 1 ;

    console.log(result) ;
    console.log(result.toString(2)) ;
    console.log('') ;

    /*
    >> 001111100
    ------------
        000111110
    */
    result = num03 >> 1 ;

    console.log(result) ;
    console.log(result.toString(2)) ;
    console.log('') ;

    /*
    but -3 actually is
         11111111111111111111111111111101 in 32-bit 2's complement.
    So,
    >>> 11111111111111111111111111111101
    --------------------------------------
         01111111111111111111111111111110
    */
    result = num04 >>> 1 ;

    console.log(num04.toString(2)) ;
    console.log(result) ;
    console.log((2 ** 31 - 1) - 1) ;
    console.log(result.toString(2)) ;

【相关说明】

    let num01 = 56, num02 = 77 ;
    let num03 = 124, num04 = -3 ;

这两个语句声明了初始数据为整数值的变量num01、num02、num03与num04。

    console.log(num01.toString(2)) ;
    console.log(num02.toString(2)) ;
    console.log(num03.toString(2)) ;

这3个语句中的【.toString(2)】,可分别将变量num01、num02与num03的整数值,转换成为字符串类型的二进制数码。

    console.log(num01 == 0b111000) ;

【num01 == 0b111000】可用来判断变量num01的数值,是否等于二进制整数【111000】。在此,其返回true。加上【0b】在二进制数码的左侧,可使得此代码被视为二进制数值。

在此亦可得知,浏览器中的JavaScript引擎,仍然将最左比特位(left-most bit)为1的数值,视为正值(positive value)。因此,若要变更成为负值(negative value),则修改为【- 0b111000】。

    console.log(num01 == 0b00111000) ;

【num01 == 0b00111000】可用来判断变量num01的数值,是否等于二进制整数【00111000】。在此,其返回true。

在此可得知,浏览器中的JavaScript引擎将【0b00111000】与【0b111000】,视为相同的二进制数值。

    console.log(num02 == 0b1001101) ;
    console.log(num02 == 0b01001101) ;

【num02 == 0b1001101】可用来判断变量num02的数值,是否等于二进制整数【1001101】。在此,其返回true。

【num02 == 0b01001101】可用来判断变量num02的数值,是否等于二进制整数【01001101】。在此,其返回true。

在此可得知,JavaScript引擎将【0b1001101】与【0b01001101】,视为相同的二进制数值。

    console.log(num03 == 0b1111100) ;
    console.log(num03 == 0b01111100) ;

【num03 == 0b1111100】可用来判断变量num03的数值,是否等于二进制整数【0b1111100】。在此,其返回true。

【num03 == 0b01111100】可用来判断变量num03的数值,是否等于二进制整数【0b01111100】。在此,其返回true。

由此可得知,JavaScript引擎将【0b1111100】与【0b01111100】,视为相同的二进制数值。

    /*
     00111000
    &01001101
    ----------
     00001000
    */
    result = num01 & num02 ;

【num01 & num02】可得到【变量num01与num02的二进制数据,进行按位和(bitwise and)运算】之后的结果值。

    /*
     00111000
    |01001101
    ----------
     01111101
    */
    result = num01 | num02 ;

【num01 | num02】可得到【变量num01与num02的二进制数据,进行按位或(bitwise or)运算】之后的结果值。

    /*
     00111000
    ^01001101
    ----------
     01110101
    */
    result = num01 ^ num02 ;

【num01 ^ num02】可得到【变量num01与num02的二进制数据,进行按位异或(bitwise exclusive or)运算】之后的结果值。

    /*
     ~ 01111100
    ------------
       10000011 (negative value)
    =
      -01111100
     + 00000001 (because of 2's complement)
    ------------
      -01111101
    */
    result = ~ num03 ;

【~ num03】会得到【变量num03的二进制数据,进行2的补码(two's complement)】之后的结果值。

    /*
    << 001111100
    ------------
        011111000
    */
    result = num03 << 1 ;

【num03 << 1】会得出【变量num03的2进位数据,向偏移1个比特位】之后的结果值。

    /*
    >> 001111100
    ------------
        000111110
    */
    result = num03 >> 1 ;

【num03 >> 1】会得出【变量num03的2进位数据,向偏移1个比特位】之后的结果值。

    /*
    but -3 actually is
         11111111111111111111111111111101 in 32-bit 2's complement.
    So,
    >>> 11111111111111111111111111111101
    --------------------------------------
         01111111111111111111111111111110
    */
    result = num04 >>> 1 ;

【num04 >>> 1】会得出【变量num04的2进位数据,向偏移1个比特位的同时,在其最左侧的符号位(sign bit),填入0】之后的结果值。

此语句被执行之前,变量num04的数值为负整数-3。此语句被执行之后,变量num04的数值却变成非常大的正整数2147483646,可见其符号位被填入了0。

    console.log((2 ** 31 - 1) - 1) ;

【(2 ** 31 - 1) - 1】会评估出(231 - 1) - 1,也就是正整数2147483646。

2.2.8 括号运算符

括号运算符包含:

• 小括号/圆括号(parentheses, round brackets)运算符():除了用于变更特定表达式的运算优先级之外,亦被用于if、switch、for、while等语句和函数的调用。

• 中括号/方括号(brackets, square brackets)运算符[]:主要用于数组(array)或字符串(string)相关的定义和访问。

• 大括号/花括号(braces, curly brackets)运算符{}:用于语句的分组(grouping)、函数的主体构造,以及对象(object)的定义。

关于括号运算符的综合运用,可参考如下示例。

【2-2-8-brackets-operators.js】

    let a = 10, b = 5, c = 3 ;
    let result = 0 ;

    result = a - b * c ;
    console.log(result) ;

    result = (a - b) * c ;
    console.log(result) ;

    result = a ** c ** 2 ;
    console.log(result) ;

    result = (a ** c) ** 2 ;
    console.log(result) ;
    console.log('') ;

    ///
    let now = new Date() ;

    console.log(now) ;
    console.log(now.toLocaleString()) ;
    console.log('') ;

    let obj = new Object() ;

    obj.name = 'Jasper' ;
    obj.gender = 'male' ;
    obj.age = 28 ;

    console.log(obj) ;
    console.log('') ;

    ///
    function display(choice, message)
    {
      if (choice == 1) alert(message) ;
      else if (choice == 2) confirm(message) ;
    }

    display(1, 'Hello, Earth!') ;
    display(2, 'Hello, are you human?') ;

    ///
    let fruits = ['apple', 'banana', 'cherry', 'durian'] ;

    console.log(fruits[1]) ;

【相关说明】

    let a = 10, b = 5, c = 3 ;
    let result = 0 ;

这两个语句声明了具有初始数值的变量a、b、c和result。

    result = a - b * c ;

表达式【a - b * c】会先被计算出【b * c】的结果值,再计算出整个表达式的结果值。

    result = (a - b) * c ;

因为小括号运算符的缘故,表达式【(a - b) * c】会先被计算出【a - b】的结果值,再计算出整个表达式的结果值。

    result = a ** c ** 2 ;

因为求幂运算符**具有右结合的特征,所以表达式【a ** c ** 2】会先被计算出【c ** 2】的结果值,再计算整个表达式的结果值。

    result = (a ** c) ** 2 ;

因为小括号运算符的缘故,表达式【(a ** c) ** 2】会先被计算出【a ** c】的结果值,再被计算出整个表达式的结果值。

    let now = new Date() ;

此语句使得变量now,具有初始数据为【内含当前日期与时间】的Date对象实例。

    console.log(now) ;

借助此语句,可在网页浏览器的调试工具【Console】面板中,显示出标准格式的日期与时间。例如:【Fri Dec 29 2017 01:16:16 GMT+XX00 (XX标准时间)】。

    console.log(now.toLocaleString()) ;

【now.toLocalString()】可将日期与时间,以精简的本地格式,显示出来。例如:【2017/12/29上午1:16:16】。

    let obj = new Object() ;

因为小括号运算符的缘故,使得Object对象的构造函数Object()被调用。此语句使得变量obj的初始数据,成为Object对象的实例(empty instance)。

    obj.name = 'Jasper' ;
    obj.gender = 'male' ;
    obj.age = 28 ;

这3个语句分别设置了变量obj的新属性name、gender、age和其个别数据'Jasper'、'male'、28。

    console.log(obj) ;

此语句可显示出变量obj的数据,也就是对象实例{name: "Jasper", gender: "male", age: 28}。

    function display(choice, message)

因为小括号运算符的缘故,网页浏览器或其他软件中的JavaScript引擎,可认出关键字function开头的此源代码片段,即是用来定义【带有参数choice和message,而且其名称为display】的函数。

    {
      if (choice == 1) alert(message) ;
      else if (choice == 2) confirm(message) ;
    }

借助大括号运算符{},JavaScript引擎可认出大括号{ ... }里的源代码,即是函数display()的主体结构。

在函数display()的主体结构里,仍然可以看到小括号运算符,出现在if语句中。另外,亦可看到小括号运算符,出现在内置函数alert()与confirm()的调用语法中。

    display(1, 'Hello, Earth!') ;
    display(2, 'Hello, are you human?') ;

这两个语句,通过传入不同参数值,而重复调用了函数display()。

    let fruits = ['apple', 'banana', 'cherry', 'durian'] ;

借助中括号运算符[],在此语句里的等号右侧,即是内含4个字符串元素的数组实例。

    console.log(fruits[1]) ;

此语句通过数组变量的名称fruits,以及中括号运算符里的元素索引值1,访问到fruits[1]所代表的字符串元素'banana'。

2.2.9 扩展运算符(ES6)

简单来说,由3个英文句点【...】构成的扩展运算符(spread operator),可被用来【卸除】特定数组的中括号或者特定对象的大括号。关于扩展运算符的综合运用,可参考如下示例。

【2-2-9-spread-operator.js】

    var greetings = ['Hi', 'Howdy', 'Hey, man', 'G\'day mate'] ;

    var extended_greetings = ['Long time no see', 'Nice to see you', 'Hiya' , ... greetings] ;

    console.log(extended_greetings) ;
    console.log('') ;

    var number_texts = ['one', 'two', 'three']
    var number_digits = '123' ;

    var numbers = [... number_texts, ... number_digits]

    console.log(numbers) ;
    console.log('') ;

    ///
    let birthday = new Date(1999, 11, 25, 20, 30) ;
    let now = new Date() ;

    console.log(birthday.toLocaleString()) ;
    console.log(now.toLocaleString()) ;
    console.log('') ;

    ///
    let arr01 = [1, 2, 3] ;
    let arr02 = [10, 20, 30] ;
    let arr03 = [... arr01, ... arr02, 100, 200, 300] ;

    let obj01 = {name: 'orange', amount: 10} ;
    let obj02 = {name: 'durian', amount: 5, origin: 'Thai'} ;
    let obj03 = {... obj01, ... obj02} ;

    console.log(arr03) ;
    console.log(obj03) ;
    console.log('') ;

【相关说明】

    var greetings = ['Hi', 'Howdy', 'Hey, man', 'G\'day mate'] ;

此语句声明了初始数据为数组实例的变量greetings。

    var extended_greetings = ['Long time no see', 'Nice to see you', 'Hiya' , ... greetings] ;

此语句声明了初始数据为另一数组实例的变量extended_greetings。扩展运算符【...】使得变量extended_greetings的数组实例,带有变量greetings的数组实例中的所有元素。

    var number_texts = ['one', 'two', 'three']

此语句亦声明了初始数据为另一数组实例的变量number_texts。

    var number_digits = '123' ;

此语句声明了初始数据为字符串'123'的变量number_digits。

    var numbers = [... number_texts, ... number_digits]

扩展运算符【...】使得变量numbers的初始数据,成为合并【变量number_texts与number_digits的个别数组实例】的新数组实例,进而使得变量numbers的数据,成为数组实例["one", "two", "three","1", "2", "3"]。

    let birthday = new Date(1999, 11, 25, 20, 30) ;

此语句声明了【初始数据为内含日期与时间1999/12/25 20:30:00的Date对象实例】的变量birthday。

需留意的是,构造函数Date()小括号中的第2个参数值为11,其实是代表12月份的含义。换句话说,此参数值若为0,则代表1月份。

    let now = new Date() ;

此语句声明了【初始数据为当前日期与时间的Date对象实例】的变量now。

    console.log(birthday.toLocaleString()) ;

此语句会显示出【1999/12/25下午8:30:00】的信息。

    console.log(now.toLocaleString()) ;

此语句显示出当前日期与时间的信息。

    let arr01 = [1, 2, 3] ;
    let arr02 = [10, 20, 30] ;

这两个语句声明了【初始数据为不同数组实例】的变量arr01与arr02。

    let arr03 = [... arr01, ... arr02, 100, 200, 300] ;

此语句声明了变量arr03,并借助扩展运算符【...】,使得其【初始数据为合并变量arr01与arr02的数组实例,再衔接子数组实例[100, 200, 300]】的新数组实例[1, 2, 3, 10, 20, 30, 100, 200,300]。

    let obj01 = {name: 'orange', amount: 10} ;
    let obj02 = {name: 'durian', amount: 5, origin: 'Thai'} ;

这两个语句声明了【初始数据为对象实例】的变量obj01与obj02。

    let obj03 = {... obj01, ... obj02} ;

此语句声明了变量obj03,并借助扩展运算符【...】,使得其【初始数据为合并变量obj01与obj02对象实例】的新对象实例{name: "durian", amount: 5, origin: "Thai"}。

值得注意的是,变量obj01与obj02均存在属性name与amount。经过扩展运算之后,变量obj03的属性name与amount却只有一个,而且其属性的数据,均个别与变量obj02的属性name和amount的数据,是相同的。

2.2.10 逗号运算符

逗号运算符(comma operator)主要用来并联多个赋值表达式或操作数。关于逗号运算符的运用,可参考如下示例。

【2-2-10-comma-operator.js】

    let a = 11, b = 21, c = 31 ;
    let d, e, f ;

    d = e = a + b, f = b + c ;

    console.log(d, e, f) ;

【相关说明】

    let a = 11, b = 21, c = 31 ;

此语句声明了初始数据均为整数的变量a、b与c。

    let d, e, f ;

此语句声明了未设置初始数据的变量d、e与f。

    d = e = a + b, f = b + c ;

此语句主要由两个表达式【d = e = a + b】与【f = b + c】构成。

• 第1个表达式先被计算出【a + b】的结果值,再赋给变量d和e。

• 第2个表达式先被计算出【b + c】的结果值,再赋给变量f。

修改此语句中的逗号运算符【,】,成为代表语句结束的分号【;】,并不影响其结果值。只是,原本的单一语句,就变成在同一行的两个语句了。

    console.log(d, e, f) ;

通过传入变量d、e和f的数值,作为函数console.log()的参数值,可让这些变量的数值,显示在同一行的信息里,例如【32 32 52】。

2.2.11 删除运算符

JavaScript语言中的删除运算符(delete operator),仅用来删除特定对象实例的特定属性。关于删除运算符的运用,可参考如下示例。

【2-2-11-delete-operator.js】

    let person = {name: 'Ivory', gender: 'female', age: '30'} ;
    let colors = ['RoyalBlue', 'GreenYellow', 'Gold', 'Cyan'] ;

    var num01 = 123 ;
    let num02 = 456 ;
    num03 = 789 ;

    console.log(num01, num02, num03) ;
    console.log('') ;

    delete person.name ;
    delete colors[1] ;

    delete num01 ;
    delete num02 ;
    delete num03 ;

    console.log(person.name) ;
    console.log(person) ;
    console.log('') ;

    console.log(colors[1]) ;
    console.log(colors) ;
    console.log('') ;

    console.log(num01) ;
    console.log(num02) ;
    console.log(num03) ;

【相关说明】

    let person = {name: 'Ivory', gender: 'female', age: '30'} ;

此语句声明了初始数据为对象实例的变量person。

    let colors = ['RoyalBlue', 'GreenYellow', 'Gold', 'Cyan'] ;

此语句声明了初始数据为数组实例的变量colors。

    var num01 = 123 ;
    let num02 = 456 ;
    num03 = 789 ;

无论有无通过关键字var或let进行声明,这3个语句分别声明了初始数据为不同整数的变量num01、num02与num03。

    delete person.name ;

此语句使用关键字delete,并配合点运算符【.】,可删除变量person的对象实例的属性name。

    delete colors[1] ;

此语句使用关键字delete,并配合中括号运算符[],可在变量colors数组实例中,仅删除索引值为1的元素数据'GreenYellow',但是此元素占用的缓存空间,仍然保留在其数组实例中。

    delete num01 ;
    delete num02 ;
    delete num03 ;

通过这3个语句,试图删除变量num01、num02与num03;然而实际上,仅有未通过var或let关键字,加以声明的变量num03,可以被成功删除。

    console.log(person.name) ;

【person.name】会返回undefined,即代表变量person的属性name,已被删除了。

    console.log(person) ;

此语句只会产生{gender: "female", age: "30"}的信息。由此可见,属性name和其数据'Ivory'已被删除了。

    console.log(colors[1]) ;

colors[1]在此返回undefined,即代表变量colors中的索引值为1的数据,已经被清除了。

    console.log(colors) ;

此语句会产生["RoyalBlue", empty, "Gold", "Cyan"]的信息,可看出colors[1]对应的元素数据已经被清除了,才会被标记为empty。

    console.log(num01) ;
    console.log(num02) ;

查看这两个语句所产生的信息,可得知变量num01与num02并未被删除!那是因为这两个变量,是借助var或let关键字,来加以声明的。

    console.log(num03) ;

执行此语句时,会产生【num03 is not defined参考错误(reference error)】的信息。由此可知,变量num03确实被删除了。

2.2.12 运算符的优先级(ES6)

在特定表达式中,运算符的优先级(operators precedence)可用来决定各子表达式的执行顺序。在相邻的子表达式中,其运算符优先级较高的子表达式,会先被执行。关于各运算符的优先级,如表2-5所示。

表2-5 运算符的优先级

2.3 练习题

1.在JavaScript语言里,下列哪些项目是常量?

770、Math.PI、'Nice Day'、"good"、/\w\s\d/g、/.\w.\d/、TRUE、FALSE、Undefined、Null。

2.在JavaScript语言里,应该通过什么语法声明名称为love_you_forever而代表着整数值201314的常量?

3.在JavaScript语言里,应该通过什么语法声明名称为love_me_longer而代表着浮点数值2591.8的常量?

4.在如下JavaScript源代码片段里,哪些是全局变量?哪些是局部变量?

    let value01 = 10 ;
    var value02 = 30 ;

    function func01(data, identity)
    {
      let result ;

      result = 21 + data + 2 * identity ;
      return result ;
    }

    var str01 = 'Finished', str02 = 'Error' ;

    function func02(amount, price)
    {
      let output ;

      output = (value01 + value02 + price) * amount ;
      return output ;
    }

5.在如下JavaScript源代码片段里,请至少列举出其中的5个表达式。

    sphere_volume = r => 4 / 3 * Math.PI * Math.pow(r, 3) ;

6.编写带有上底宽度、下底宽度和高度3个参数,可计算并返回梯形面积函数定义。

7.编写带有长轴长度、短轴长度2个参数,可计算并返回椭圆面积函数定义。

8.如下源代码被执行之后,变量result的结果数据是什么?

    let result, n01 = 10, n02 = 20 ;
    result = n01++ + ++n02 % 6 ;

9.如下源代码被执行之后,变量result的结果数据是什么?

    let result, n03 = 30, n04 = 40 ;
    result = n03-- - --n04 % 6 ;

10.如下源代码被执行之后,变量result的结果数据是什么?

    let result, n05 = 50, n06 = 60 ;
    result = (n05 / 5) ** 3 + (n06 / 10) ** 2 ;

11.如下源代码被执行之后,变量result的结果数据是什么?

    let result, n07 = 70, n08 = 80 ;
    result = (n07 / 10) ** 2 + (n08 / 20) ** 0.5  ;

12.通过编写条件运算符【? … :】相关的源代码来实现下述功能。

已知两个变量score和rating。当score的数值低于60时,变量rating的数据为字符串'failed';当score的数值大于或等于60而小于80时,变量rating的数据为字符串'passed';当score的数值大于或等于80而小于或等于100时,变量rating的数据为字符串'nice';当score的数值超过100时,变量rating的数据为字符串'error'。

13.至少列举类型运算符typeof表达式可返回的5种数据类型。