JavaScript编程思想:从ES5到ES9
上QQ阅读APP看书,第一时间看更新

第1章 ECMAScript简介

本章内容主要介绍ECMAScript的历史、版本以及语法特性和多种集成开发环境,让读者对ECMAScript有一个初步的认识,以便为后续学习打好基础。

1.1 概述

成立于1961年之极具影响力的国际组织ECMA(European Computer Manufacturers Association,欧洲制造商协会),现今专门制定信息通信系统的标准与技术报告,以促进和规范信息通信技术与消费电子产品。

ECMA至今已经主动贡献了超过400个标准和100个技术报告,其中三分之二以上的部分,已在国际上得到了广泛的使用。其中,由Ecma制定的ECMA-262(ISO/IEC 16262)的ECMAScript,是具有商标的脚本编程语言(script programming language)的规范。

1.1.1 ECMAScript各版本

从1997年6月的第1版,到2018年6月的第9版(ECMAScript 9),已问世的ECMAScript各版本,主要被用来标准化JavaScript脚本编程语言。目前业界的应用主要以ECMAScript 5 / 6为主、ECMAScript 7 / 8 / 9为辅。从ECMAScript 6开始的各版本存在如表1-1所示的昵称。

表1-1 ECMAScript的各版本

1.1.2 关于JavaScript

可简称为JS的JavaScript,是一个动态高级解释脚本编程语言(dynamic high-level interpreting scripting programming language)。对于万维网而言,HTML、CSS与JavaScript并列为网页制作的核心编程语言。

JavaScript主要用来实现网页程序的用户交互机制(interactive mechanism),并通过ECMAScript规范,被创建和内置于网页浏览器(web browser)的JavaScript引擎中。

JavaScript支持事件驱动(event-driven)、函数调用(function invocation / call)、面向对象编程(OOP,object-oriented programming)等特性,并具备处理文本、数组、日期、正则表达式(regular expression)和文档对象模型(DOM,document object model)的应用程序编程接口(API,application programming interface)。

JavaScript引擎早期仅实现于网页浏览器当中,现今则被实现于其他类型的软件里,例如网页服务器、数据库服务器、文字处理器,以及用来编写移动或桌面应用程序的集成开发环境(IDE,integrated development environment)等软件里。

1.1.3 其他脚本语言

通过ECMAScript规范实现的编程语言,除了JavaScript之外,主要还有Adobe的ActionScript和Microsoft的JScript,其语法非常相似于JavaScript。

JScript被用于Internet Explorer浏览器,ActionScript主要被用于Adobe Animate CC集成开发环境中。

1.2 语法的实现

计算机编程语言的语法是一种规则,用来定义此编程语言表面形式的符号组合,以正确组成此语言的源代码(source code),进而堆砌成为片段或文件。

1.2.1 源代码

源代码(source code)通常以纯文本方式,存在于文档里,并作为输入数据,由汇编器(assembler)、编译器(compiler)或解释器(interpreter),转换成计算机可理解的二进制机器代码(machine code)。

通过经常阅读他人编写的源代码,计算机程序员可增进其编程技术的成熟度。因此,开发者互相分享源代码,可算是对彼此的一种贡献。

移植特定软件到其他计算机平台上,通常是异常困难的。然而,若有此软件的源代码,移植任务就会变得简单许多。

1.2.2 语句

在计算机编程当中,语句(statement)是命令式编程语言的语法单元,以表示欲指示计算机进行的一连串动作。

JavaScript编程语言的语句大致分为简单语句(simple statement)和复合语句(compound statement)两种形式。

1.简单语句

简单语句的末尾应该加上分号,例如:

(1)

    let v01, v02 ;

变量v01与v02的声明。

(2)

    var list01 = ['apple','banana', 'cherry'] ;

数组变量list01的声明与数据设置。

(3)

    profile = {name: 'Alex', gender: 'male', age: '40'} ;

对象变量profile的数据设置。

(4)

    document.writeln ('<h3>world peace</h3>') ;

写入带有文本“world peace”的h3元素实例到当前网页里。

(5)

    confirm ('Are you sure to delete it?') ;

在网页上,显示带有“Are you sure to delete it?”信息并带有确认按钮和取消按钮的模式对话框。

(6)

    username.style.color = 'RoyalBlue' ;

设置id为username元素实例的颜色样式为宝蓝色。

(7)

    break ;

中断循环语句(loop statement),或确认切换语句(switch statement)的分支(branch)。

(8)

    continue ;

在循环语句中,终止当前的循环,并立即进行下一次的循环。

(10)

    debugger ;

设置调试断点(breakpoint),以暂停后续源代码的执行,并启动调试机制。

(11)

    "use strict" ;

限制以严格模式执行源代码。

(12)

    return result ;

终止函数的执行,并返回变量result的数据。

2.复合语句

复合语句大都带有一对大括号,例如:

(1)

    let result = 0 ;
    alert ("You're welcome!") ;
    {
      let num01 = 75, num02 = 64 ;
      result = num01 + num02 ;
    }

在前述大括号中的源代码,连同大括号在内,可被称为复合语句。其中,变量num01与num02只有在大括号里才允许被访问。

(2)

    with (send_button.style)
    {
      color = 'Gold' ;
      backgroundColor = 'DodgerBlue' ;
      fontSize = '1.5em' ;
    }

with复合语句包含一对大括号,并且可以简化对象实例属性的访问语法。在此,【send_button.style.color = 'Gold' ;】的语句,在复合语句【with (send_button.style) 】的大括号里,可被简化为【color = 'Gold' ;】。

(3)

    if (score >= 60)
    {
      passer_count++ ;
      saying = 'You have passed!' ;
    }

if条件复合语句可包含一对大括号,当小括号里的条件【(score >= 60)】为真时,大括号里的各语句就会被执行。

(4)

    for (let i = 0 ; i < 10 ; i++)
    {
      count++ ;
      sum += count * i ** 2 ;
    }

for循环复合语句可包含一对大括号,并借助小括号【(let i = 0 ; i < 10 ; i++)】中的3个子语句,使得大括号中的各语句,可被迭代而执行10次。

(5)

    switch (choice)
    {
      case 1:
        grade = 'A+' ;
        break ;
      case 2:
        grade = 'A' ;
        break ;
      case 3:
        grade = 'B' ;
        break ;
      case 4:
        grade = 'C' ;
        break ;
      default:
        grade = '@_@' ;
    }

switch复合语句包含一对大括号,并通过小括号【(choice)】中变量choice的数值,来决定并执行大括号中的特定case或default分支的子语句。

1.2.3 表达式

编程语言中的表达式(expression)内含运算符(operator)以及代表操作数(operand)的常量(constant)、变量(variable)、函数返回值(function return value)与子表达式(subsidiary expression)。

一般情况下,表达式的结果数据通常是原始数据类型(primitive data type)之一,例如数字(number)、字符串(string)或布尔型(boolean)。

JavaScript编程语言的表达式大致可分为以下几种。

1.算术表达式(arithmetic expression)

例如:

(1)v01 = v02 + 5 ;

将变量v02的数值,加上5之后的结果值,赋给变量v01。

(2)v03 = v02 ** 3 ;

将变量v02的数值,进行3次方运算的结果值,赋给变量v03。

(3)v04 = 6 * (v03 + 10) ;

将6乘以【变量v03加上10】之后的积,赋给变量v04。

(4)v05 = v04 % 2 + v04 / 2 ;

将变量v04除以2的余数,加上变量v04除以2的结果值,赋给变量v05。

(5)c01 = c01 + 1 ;

将变量c01的数值,加上1的结果值,赋给变量c01本身。

(6)c01 += 1 ;

将变量c01的数值增加1 / 递增(increment)。

(7)c01++ ;

先返回c01的数值,再递增变量c01。

(8)++c01 ;

先递增变量c01的数值,再返回c01。

(9)c02 = c02 - 1 ;

将变量c02的数值,减去1 / 递减(decrement)的结果值,赋给变量c02本身。

(10)c02 -= 1 ;

将变量c02的值减去1 / 递减的结果值,赋给变量c02本身。

(11)c02-- ;

先返回c02的数值,再递减变量c02。

(12)--c02 ;

先递减变量c02的数值,再返回c02。

2.字符串表达式(string expression)

例如:

(1)

    let subject = 'Alex' ;
    let object = 'Jasper' ;
    let greeting = "'have a \"nice\" day'" ;
    let message = '' ;

    message = subject + ' said ' + greeting + ' to ' + object ;
    message = `${subject} said ${greeting} to ${object}` ;

在一对单引号或双引号内的文本是代表一种常量(constant)的字符串字面量(string literal)。

在字符串字面量里的单引号或双引号应该冠上反斜杠(back slash)【\】,成为【\'】或【\"】。

多个字符串字面量可通过加法运算符【+】,结合成为新的字符串。

通过一对反引号(back quote)【`】,可创建模板字面量(template literal),并内含语法【${特定变量的名称}】,以动态解析特定变量的数据,成为新字符串的一部分。

在前述源代码里,最后两行具有相同的效果。

(2)

    let sentence = 'Alice really \
    lovingly loves \
    lovely beloved of \
    Jason very much.' ;

借助反斜杠【\】来分割较长的字符串字面量。

在各行中,字符串片段的最后一个字符必须就是反斜杠【\】,不可以再有包括空白字符在内的其他字符。

前述源代码被执行之后,变量sentence的数据会是字符串"Alice really lovingly loves lovely beloved of Jason very much."。

3.关系与逻辑表达式(relational and logical expression)

例如:

(1)

    x > y

检验若变量x的数值大于y的数值,则返回布尔值true。

(2)

    x <= y && x <=z

检验若变量x的数值同时小于或等于变量y和z的数值,则返回布尔值true。(3)

    a > 0 || b > 0 || c > 0

检验若变量a的数值大于0、变量b的数值大于0,或是变量c的数值大于0,则返回布尔值true。

(4)

    ! (v01 > 60 || v02 >= 90) && v03 >= 80

检验若【并非变量v01的数值大于60,或是变量v02的数值大于或等于90】,并且【变量v03的数值大于或等于80】,则返回布尔值true。

(5)

    name == 'admin' && /^\w{6,}$/.test(password)

检验若变量name的数据等于字符串字面量"admin",并且变量password的数据是由【0 ~ 9】、【大小写a ~ z】与【下画线_】所构成的最少6个字符的字符串,则返回布尔值true。

4.主要表达式(primary expression)

例如:

(1)

    let circle_area = function (r)
    {
      let result ;
      result = Math.PI * r ** 2
      console.log (`The circle area of radius ${r} = ${result}`) ;
      return result ;
    } ;

在所谓的函数表达式(function expression)中,通过关键字function,定义匿名函数(anonymous function),并以变量的名称circle_area,作为匿名函数的别名(alias)。

(2)

    let Cubic = class
    {
      constructor (l, w, h)
      {
        this.length = l ;
        this.width = w ;
        this.height = h ;
      }

      volume (l = this.length, w = this.width, h = this.height)
      {
        return l * w * h ;
      }

      surface_area (l = this.length, w = this.width, h = this.height)
      {
        return 2 * (l * w + w * h + h * l) ;
      }
    } ;

在所谓的类表达式(class expression)中,通过关键字class,定义匿名类(anonymous class),并以变量的名称Cubic作为匿名类的别名。

(3)

    let number_list = [15, 30, 75, 90, 180] ;
    console.log(number_list[2]) ;
    number_list[5] = 770 ;

这3个语句均可被视为主要表达式。在等号右侧,没有变量名称的一对中括号,是用来定义数组实例[15, 30, 75, 90, 180],并赋给等号左侧的数组变量number_list。在等号左侧,变量名称衔接的一对中括号,则用来访问数组变量number_list中特定索引值(2与5)对应的元素值(75与770)。

(4)

    var user_input = 'z1 = x1 ^ 2 + y1 * 3 + 6' ;
    var pattern = /[a-zA-Z]\d/g ;
    var matches = user_input.match (pattern) ;
    console.log (matches) ;

这4个语句均可被视为主要表达式。在此,通过一对斜杠符号【/】,创建用来匹配特定模式【由字母开头,后接一个数字的文本】的正则表达式字面量(regular-expression literal)【/[a-zA-Z]\d/g】,并赋给等号左侧的变量pattern。

5.箭头函数表达式(arrow function expression)

箭头函数表达式存在如下几种形式。

(1)

    (参数列) => { …} ;

通过带有参数列的一对小括号,衔接代表箭头符号的【=>】,与内含主体源代码的一对大括号,以定义没有名称、但可带有多个参数的箭头函数(arrow function)。

(2)

    单一参数名称 => { …} ;

通过单一参数,衔接代表箭头符号的【=>】,与内含主体源代码的一对大括号,以定义没有名称、仅有一个参数的箭头函数。

(3)

    单一参数名称 =>单一语句 ;

通过单一参数,衔接代表箭头符号的【=>】,和单一语句的主体源代码,以定义没有名称、仅有一个参数和语句的箭头函数。

(4)

    () => { …} ;

通过不带任何参数的一对小括号,衔接代表箭头符号的【=>】,与内含主体源代码的一对大括号,以定义没有名称和参数的箭头函数。

(5)

    () => 单一语句 ;

通过不带任何参数的一对小括号,衔接代表箭头符号的【=>】,和单一语句的主体源代码,以定义没有名称与参数、仅有单一语句的箭头函数。

6.左侧表达式(left-hand expression)

例如:

(1)

    profile.name = 'Jasper' ;

通过点号【.】,将字符串字面量'Jasper',赋给变量profile的实例属性name。(2)

    profile['age'] = 28 ;

借助一对中括号,将整数值28赋给变量profile的实例属性age。(3)

    super(10, 15) ;

通过关键字super与一对小括号,调用父类(parent class)的构造函数(constructor),并传入参数值10和15。

(4)

    super.cylinder_volume(10, 15) ;

借助关键字super、点号与一对小括号,调用在父类中作为成员函数(member function)的函数cylinder_volume(),并传入参数值10和15。

(5)

    x = x + 3 ;

在等号的左侧,设置变量x,并将变量x的数值加上3的结果值,赋给变量x本身。

(6)

    [a, b] = [7, 13] ;

在等号的左侧,编写内含变量a与b的一对中括号,并将右侧另一对中括号里的整数值7与13,分别赋给变量a与b。

(7)

    [c, d] = [a + 3, b + 7] ;

在等号的左侧,编写内含变量c和d的一对中括号,并将右侧另一对中括号里的a + 3与b + 7的结果值,分别赋给变量a与b。

1.2.4 子程序

在计算机编程中,子程序(subroutine)也可被称为函数(function)或方法(method),即是可重复被调用(call / invocation),以重新执行特定任务的一连串程序指令单元,进而节省开发与维护的成本,并提高质量与可靠性。

JavaScript编程语言中的子程序,主要被称为函数,可通过以下语法来加以定义。

(1)

    function sphere(r)
    {
      values = {} ;
      values.volume = 4 / 3 * Math.PI * Math.pow(r, 3) ;
      values.surface_area = 4 * Math.PI * r * r ;

      return values ;
    }

上述源代码是定义函数的标准语法。通过调用【sphere(10)】,可计算并返回【内含半径为10的球体积和球体表面积】相关数据的对象实例。

(2)

    display = function ()
    {

      console.log('Variable-type displaying.') ;
    } ;

上述源代码是所谓的函数表达式,使得变量display具备函数的特征。通过调用【display()】,可在网页浏览器的调试工具【Console】面板中,显示【Variable-type displaying】的信息。

(3)

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

上述源代码中的简洁语法,等同于定义了函数sphere_volume()。通过调用【sphere_volume(30)】,可计算并返回半径为30的球体积的结果值。

1.2.5 注释

在源代码里,注释(comment)是用来辅助程序员,加以理解各源代码片段的意义和用途,却不会被计算机加以执行的文本。

JavaScript编程语言的注释方式有如下两种。

(1)在注释文本的行首,加上紧连的两个斜杠符号【//】。例如:

    // calculate the summary values for bonus table.

(2)在注释文本的开头,加上紧连的斜杠符号与星号【/*】,并在注释文本的末尾,加上紧连的星号与斜杠符号【*/】。例如:

    /*
      ca(r) calculates circle area of radius r.
      ssa(r) calculates sphere surface area of radius r.
      cv(r, h) calculates cylinder volume of radius r and height h.
    */

1.2.6 关键字

在计算机编程语言中,被称为保留字(reserved word)的关键字(keyword),不能或不应该作为常量、变量、属性、函数/方法的标识符(identifier)/名称(name)的词汇。JavaScript编程语言的关键字如表1-2所示。

表1-2 JavaScript的关键字

1.3 开发环境

目前,知名的JavaScript集成开发环境(IDE,integrated development environment)有如下几种。

1.存在免费版本的

• Visual Studio Community或Visual Studio Code

https://www.visualstudio.com/zh-hans/downloads

• Eclipse

http://www.eclipse.org/downloads/eclipse-packages

• IntelliJ IDEA

https://www.jetbrains.com/idea

• NetBeans IDE

https://netbeans.org

• Sublime Text

https://www.sublimetext.com

• Atom IDE

https://ide.atom.io

• Brackets

http://brackets.io

• Cloud9(在线系统)

https://aws.amazon.com/cn/cloud9

• Codeanywhere(在线系统)

https://codeanywhere.com

2.仅有付费版本的

• WebStorm

https://www.jetbrains.com/webstorm

• Komodo IDE

https://www.activestate.com/komodo-ide

JavaScript编程语言主要用于开发网页相关应用程序,所以,网页浏览器成为执行与调试JavaScript源代码的主要软件。

因此,上述各个集成开发环境所产生的内含JavaScript源代码的网页应用,最终仍然在网页浏览器或其他内置JavaScript引擎的软件上,被进行必要的调试与执行。

1.3.1 浏览器

网页浏览器(web browser)是用来检索与呈现网络上信息资源(文本信息、图像、音频、视频、动画)的软件。现今较流行的网页浏览器为Google Chrome、Mozilla Firefox、Opera、Apple Safari、与Microsoft Edge。

上述的网页浏览器皆内置JavaScript调试工具。以Google Chrome浏览器为例,启动Chrome并按下Ctrl + Shift + I / J快捷键之后,即可看到如图1-1所示的开发者工具调试窗格。

图1-1

为了能顺利显示其JavaScript源代码于调试窗口中,可执行如下操作步骤。

步骤01 在Chrome浏览器,打开配书源代码示例文档中的网页文档【js_tester.html】。

步骤02 按Ctrl + Shift + I快捷键,启动【开发者工具】,并可看到默认显示的Console(控制台)窗口。

步骤03 请先按Ctrl+ R快捷键,以刷新页面,再单击网页中的【选择文件】按钮,并选择JavaScript源代码文档【js / 19-3-2-debugging- mechanism.js】之后,可在按钮右侧,明确看到被选定的文档名称和【Paused in debugger】信息。

步骤04 观察调试窗口的变化,从【Console】面板,自动切换至【Source】面板中。

被读取的【19-3-2-debugging-mechanism.js】内含的源代码片段【debugger ;】用来设置调试断点(debugging breakpoint),使得浏览器启动调试机制,进而自动切换至【Source】面板中,并在第1个断点上,暂停JavaScript源代码的执行,如图1-2所示。

图1-2

关于JavaScript源代码的相关调试,笔者将深入说明于本书19.3节。

关于操作扩展名亦为【.js】的【其他】示例文档,请完成至前述第3步即可。至于操作扩展名为【.html】的示例文档,则直接通过浏览器,加载浏览其网页内容。

接着,观察特定扩展名为【.html】的示例文档,出现在浏览器中的网页内容,以及在Console】面板里的对应信息,可进一步理解各个HTML、CSS和JavaScript源代码片段的工作原理。

1.3.2 Node.js

如同【ASP.NET】并非代表扩展名(file extension)为【.NET】的文档名称一样,Node.js实际上亦不是代表JavaScript源代码的文档名称,而是开源且跨平台的运行期环境(run-time environment),并用于执行服务器端的JavaScript源代码,以生成动态的网页内容。

因此,对于主要借助JavaScript编程语言,整合前端(front-end)与后端(back-end)Web应用程序开发的任务而言,Node.js如今已然成为基本要素之一。此外,Node.js具备事件驱动(event-driven)的运行架构,可实现数据异步的传递与接收,进而优化Web应用程序的处理能力和扩充灵活性。

1.3.3 其他JavaScript Shell

通过特定JavaScript Shell的协助,可在不刷新特定网页内容的情况下,辅助开发与调试JavaScript源代码。较有名的JavaScript Shell有如下几种:

• Node.js

https://nodejs.org

• JSDB

http://www.jsdb.org

• JavaLikeScript

http://javalikescript.free.fr

• GLUEscript

http://gluescript.sourceforge.net

• jspl

http://jspl.msg.mx

• ShellJS

http://documentup.com/shelljs/shelljs

1.4 练习题

1.截至2018年,ECMAScript总共有几个版本?

2.可被并列为网页制作的核心编程语言,除了JavaScript之外,还包括什么语言?

3.网页浏览器相对应的英文短语是什么?

4.除了JavaScript之外,通过ECMAScript规范实现的编程语言主要还有哪两个?

5.依序翻译在编程语言中的专有名词:machine code、source code、programming language、syntax、subroutine、variable、constant和invocation。

6.依序翻译在编程语言中的专有名词:assembler、compiler和interpreter。

7.在如下的源代码片段里,有哪些简单语句

    with (send_button.style)
    {
      color = 'Gold' ;
      backgroundColor = 'DodgerBlue' ;
      fontSize = '1.5em' ;
    }

8.在如下的源代码片段里,有哪些简单语句

    for (let i = 0 ; i < 10 ; i++)
    {
      count++ ;
      sum += count * i ** 2 ;
    }

9.在如下的源代码片段里,哪些应该是变量的名称

    message = subject + ' said ' + greeting + ' to ' + object ;

10.在源代码中的表达式里,哪些可以作为操作数

11.在如下源代码片段里,存在哪些不同的运算符?

    ! (v01 > 60 || v02 >= 90) && v03 >= 80

12.在如下源代码片段里,存在哪些不同的常量?

    let number_list = [15, 30, 75, 90, 180] ;
    console.log (number_list[2]) ;
    number_list[5] = 770 ;

13.在如下源代码片段里,存在哪些不同的常量?

    let user_input = 'z1 = x1 ^ 2 + y1 * 3 + 6' ;
    let pattern = /[a-zA-Z]\d/g ;
    let matches = user_input.match(pattern) ;
    console.log(matches) ;

14.在JavaScript语言里,如何调用如下被定义的函数,进而将半径为50的球体积球体表面积,赋给变量result?

    function sphere(r)
    {
      values = {} ;
      values.volume = 4 / 3 * Math.PI * Math.pow(r, 3) ;
      values.surface_area = 4 * Math.PI * r * r ;
      return values ;
    }

15.在JavaScript语言里,下列哪些可以作为变量名称

    catchup、extends、finally、super、class、import、export和throw

16.特定网页被浏览于Google Chrome浏览器窗口内时,有什么方式可以快速显示出浏览器开发者工具的Console面板