2.1 代码逻辑的灵魂——ECMAScript6基础
ECMAScript6简称ES6,严格来说,其并不是编程语言,而是一种规范。ECMAScript6定义了一套脚本编程语言的语法规则,JavaScript就是ECMAScript的一种实现。ECMAScript6是ECMAScript的第6代版本,其相比较之前的版本有了非常大的设计升级,支持更加强大且现代的语法特性。
ECMAScript是由ECMA通过的一种标准化的脚本程序设计语言规范。JavaScript和JScript都是ECMAScript的一种实现与扩展。ECMAScript6是ECMAScript的一次重要更新,新增了许多强大的特性,如模块和类、字典和集合数据类型、Promises和Generators相关特性等。在小程序中,支持使用ECMAScript6的语法规范进行JavaScript代码的编写。
2.1.1 测试JavaScript代码
JavaScript用来编写小程序中的核心逻辑。要学习JavaScript编程语言的基本语法,首先需要构建运行JavaScript的工作环境。互联网上提供了许多基于NodeJS平台的在线编辑和运行JavaScript代码的工具,具体如下:
上述网站提供了许多编程语言的在线编辑,并且可以将执行效果实时展示出来。本节学习的JavaScript语言大多可以在上面进行演示,读者也可以跟随本书一起测试自己编写代码的运行效果。
上述网站支持许多编程语言在线运行,如PHP、C、C++、Python、NodeJS等,在使用之前,需要将编程语言的类型修改为NodeJS,页面左侧部分为在线编码区域,页面右侧部分为运行结果区域,如图2-1所示。
图2-1 在线运行JavaScript代码
上面示例代码中的console.log()的作用是在控制台输出信息,上面代码就是JavaScript版本的HelloWorld程序。
2.1.2 使用变量
变量可以理解为存储信息的容器。在数学中,变量是指可能变化的量值。在编程中也是如此,JavaScript中使用var或let声明变量。例如,如下代码声明了一个命名为name和一个命名为age的变量,并将其分别赋值为“珲少”与“27”。
运行上述代码,从控制台可以看出,可以直接使用变量名取到变量所赋予的值。let关键字是ECMAScript6中的新特性,其和var的区别在于:let会受作用域的影响,不会产生全局的变量污染。在命名变量时,需要注意简洁和高效,简洁是指变量名尽量短,高效是指在命名时要能够见名知意。
JavaScript中的数据有类型之分,但变量并没有固定的类型,这和许多编程语言不同。示例代码如下:
上述代码声明了一个命名为age的变量,在声明时将其赋值为27,后面又将其值修改为字符串"26",这在JavaScript中是被允许的。
我们也可以在一条语句中一次声明多个变量,使用逗号进行分割即可,具体如下:
在声明变量的同时进行赋值,这种操作通常被称为定义变量。只声明而不赋值的变量默认值为undefined:
undefined是JavaScript中非常特殊的一种数据,其表示未定义。
2.1.3 7种重要的数据类型
JavaScript中有7种重要的数据类型,分别是数值、字符串、布尔值、数组、对象、Null和Undefined。
数值类型用来描述数学中的数值,可以是整数也可以是小数。示例代码如下:
上述代码中的1.11e2是JavaScript使用科学计数法描述数值的方式,其表示1.11乘以10的2次方。
字符串类型用来描述文本数据,在JavaScript中,通常使用一对双引号或一对单引号创建字符串。示例代码如下:
字符串中也可以再次嵌套字符串,但需要注意的是,如果外层字符串是使用双引号创建的,则内层字符串需要使用单引号,如果外层字符串是使用单引号创建的,则内层字符串需要使用双引号。示例代码如下:
布尔值类型用来描述逻辑值,逻辑值只有两种:true和false。逻辑值常用在条件语句中,其中,true表示条件成立,false表示条件不成立。示例代码如下:
数组用来存放一组数据,在JavaScript中,数组中的元素数据类型可以相同也可以不同。示例代码如下:
可以通过下标的方式获取数据中某个位置的元素,具体如下:
①注意:
数组中元素的下标是从0开始的,因此上面的代码获取的是数组中第3个元素。
除使用中括号的方式创建数组外,还可以使用Array构造方法创建数组,具体如下:
对象是应用开发中最常用到的数据类型,JavaScript中的对象通过键值对定义,具体如下:
对象内部除了可以定义用来存储数据的属性,还可以定义可执行的函数方法,关于对象的更多内容,后面会进行介绍。
除前面介绍的5种数据类型外,JavaScript中还有两种非常特殊的数据类型,分别为Null和Undefined。Null类型的值只有一个:null。Undefined类型的值也只有一个:undefined。null用来表示对象为空,undefined用来表示变量未定义。
2.1.4 强大的运算符
在小学学习数学时我们就已经接触到了许多运算符,如加号、减号、乘号、除号等。JavaScript中也提供了很多常用运算符,如表2-1所示。
表2-1 常用的算数运算符
在表2-1列举的常用的算数运算符中,除了自加与自减运算符,其他运算符都非常容易理解,自加或自减运算符是在当前变量的基础上进行自加1或自减1操作。需要注意的是,自加/自减运算符分前置和后置,如果作为前置运算符,则先进行自加/自减操作,再将结果返回,否则先将结果返回,再进行自加/自减操作,示例代码如下:
对于字符串类型的数据来说,加法运算符也可以将两个字符串进行拼接操作,示例代码如下:
在前面所学习的示例代码中,我们一直使用赋值运算符“=”进行变量的赋值,当赋值运算符与算数运算符组合使用时,就变成复合赋值运算符,如表2-2所示。
表2-2 复合赋值运算符
逻辑运算符是除算数和赋值运算外另一类常用的运算符,用来对布尔值进行运算,在循环条件判定、分支条件判定的语句中通常都会使用逻辑运算。JavaScript支持的逻辑运算符如表2-3所示。
表2-3 JavaScript支持的逻辑运算符
与逻辑运算符一样,关系运算符运算的结果也为布尔值,通常用来比较两个运算数之间的关系。关系运算符如表2-4所示。
表2-4 关系运算符
JavaScript中也提供了对位运算符的支持,在计算机中,数据实际上都是使用二进制方式进行存储的。对于二进制来说,每一数位上的数字非0即1,位运算是对二进制位进行的运算。
使用“~”进行按位取反运算:如果进行运算的二进制位为0,则运算结果为1;如果为1,则运算结果为0。
使用“&”进行按位与运算:如果进行运算的两个二进制位都为1,则运算结果为1;如果其中有一个二进制位为0,则运算结果为0。
使用“|”进行按位或运算:如果进行运算的两个二进制位有一个为1,则运算结果为1;如果两个二进制位都为0,则运算结果为0。
使用“^”进行按位异或运算:如果进行运算的两个二进制位不同,则运算结果为1;反之,运算结果为0。
除上面介绍的几种按位逻辑运算外,使用“<<”可以进行按位左移运算,使用“>>”可以进行按位右移运算,使用“>>>”进行无符号的按位右移运算。
到此,本节所列举的运算符既有一元运算符也有二元运算符。一元运算符是指只有一个操作数的运算符,如自加/自减运算符;二元运算符是指有两个操作数的运算符。JavaScript中还提供了一个三元运算符:条件运算符。条件运算符的作用是根据要判定的条件是否成立返回不同的结果,具体如下:
使用“?:”进行条件运算,当运算结果为true时,则返回冒号前表达式的值,否则返回冒号后表达式的值,条件运算符其实是简化的if-else语句。
2.1.5 条件语句
程序之所以智能,是因为其中包含了各种各样的逻辑。程序在运行过程中往往需要与用户进行交互,即根据用户的输入执行不同的逻辑。在JavaScript中,使用条件语句可以实现分支结构。
分支结构的作用是根据判定条件是否成立支持不同的逻辑代码,示例代码如下:
运行上面的代码会发现控制台只输出了“程序结束”,并没有输出“1>2”,if关键字后面的小括号中会跟随一个要进行判定的条件,判定条件往往为布尔值,或运算结果为布尔值的表达式,如果判定条件成立(布尔值true),则会执行其后大括号代码块中的代码,否则会跳过大括号代码块中的代码。
if通常会和else关键字结合使用,示例代码如下:
运行程序,观察输出可以发现程序执行了else后面的大括号中的代码。if-else结构首先会判定if后面的条件是否成立:如果成立,则执行if后面代码块的代码;如果不成立,则执行else后面代码块的代码。
JavaScript也支持依次进行多个条件判定的分支结构,示例代码如下:
2.1.6 多分支结构
多分支结构是指具有多个判定条件,根据条件的不同会有多个逻辑分支的结构,使用if-else结构可以实现多分支结构,示例代码如下:
上面的代码虽然可以实现多分支结构,但是看上去十分冗长,不够精简。在JavaScript中还提供了switch-case结构,用来实现多分支逻辑。使用switch-case重写上面的逻辑,示例代码如下:
如上述代码所示,switch结构中的每个case用来匹配res的值,如果匹配成功,则执行对应代码块中的代码。需要注意的是,在实际应用中,每个case块结束后,后面都要使用break语句进行跳出,否则,如果一个case匹配成功,后面的case代码块会被依次执行,直到遇到break语句产生中断。
switch-case结构的最后还可以添加一个default代码块,当所有的case都匹配失败后,会执行default代码块中的代码,示例代码如下:
2.1.7 循环结构
分支结构的作用是让程序更加灵活,可以根据输入做出不同的逻辑,循环结构则让程序大量重复执行某段代码。计算机非常善于执行大量重复的计算,并且速度非常快。JavaScript中常用的循环结构有3种,分别为while循环、do-while循环和for循环。
while循环是最简单的循环结构,其首先会进行循环条件的判定,如果条件成立,则继续执行循环体中的代码,执行完后会再次进行循环条件的判定,循环执行循环体中的代码,直到循环条件不成立为止,示例代码如下:
上述示例代码的功能是计算从0依次累加到100的结果。需要注意的是,由于循环体的循环次数是由循环条件决定的,在循环体内,常常需要根据情况对循环条件进行修改,循环条件始终成立会造成死循环,即程序永远无法跳出循环体执行后面的代码。
do-while循环是while循环的一种变体,其和while循环的区别如下:while循环会先进行循环条件的判定再执行一遍循环体中的代码;do-while循环则先执行一遍循环体中的代码,再进行循环条件的判定,示例代码如下:
分析while循环和do-while循环的特点可以发现:while循环如果循环条件不成立,则循环体中的代码一次也不会执行;do-while循环不论循环条件是否成立,至少都会执行一遍循环体中的代码。
for循环是一种更加简洁的循环结构,示例代码如下:
for关键字后面的小括号中包含3个表达式,这3个表达式定义了核心的循环逻辑:第1个表达式用来进行循环变量的初始化,这个变量的作用是控制循环的执行次数;第2个表达式是循环的判定条件,如果第2个表达式成立,则会执行循环体中的代码,否则会跳出循环;第3个表达式用来进行循环变量的修改。
其实,除上面提到的3种循环结构外,针对对象数据,JavaScript中还提供了一种for-in遍历结构,其作用是将对象中的所有属性和方法遍历出来,示例代码如下:
这种对对象进行遍历获取对象中数据的方式在实际开发中十分常用。
2.1.8 中断结构
在学习switch-case结构时,我们曾提及break语句,它的作用是跳出switch-case结构,提前进行中断操作。在JavaScript中,常用的中断语句有3种:return、break、continue。
return语句的作用是进行函数返回,在后面学习函数时将会着重介绍函数返回值的相关内容。break语句主要用于switch-case结构和循环结构中,在switch-case结构中,其作用是当匹配成功一个case后,控制程序跳出switch-case结构。在循环结构中,break语句的作用是跳出当前循环,示例代码如下:
上面的while循环判定条件虽然始终为true,但是循环并没有无限执行下去,在循环体内遇到break语句后会直接跳出循环结构。
continue语句的作用是跳过本次循环,需要注意的是,跳过循环并不会跳出循环,示例代码如下:
运行上述代码,通过打印信息可以看到,当i==2时,程序并没有执行到打印语句,这是由于continue语句使程序跳过了continue语句后的循环体中的代码,直接开始进行下一轮循环条件的判定。
2.1.9 异常捕获
代码在执行过程中难免会出现各种各样的异常,因此一款优质的产品往往需要经过层层测试,最大限度地保证用户在使用时不产生问题。在JavaScript中,写错了关键字,使用了作为声明的变量,或调用了不存在的属性和方法等都会产生异常,示例代码如下:
如果产生异常,则代码的运行会停止在产生异常处,并在控制台输出异常信息。例如,上面的代码是一个典型地使用了未声明变量的错误,运行上面的代码后,控制台将输出如下信息:
有时一个应用程序可能有多个功能,作为产品开发者,我们并不想因为某个功能的异常而使应用程序的所有功能都无法使用,这时可以使用try-catch结构对异常进行捕获,示例代码如下:
运行上面的代码,虽然有异常产生,但是并没有影响程序的继续运行。
对于try-catch结构,try后面的代码块中需要放入可能产生异常的代码,如果没有异常产生,则try-catch结构并没有任何作用,如果try代码块中有异常产生,则程序会将异常进行捕获,并将异常对象传入catch代码块中。在catch代码块中,我们可以对异常问题进行处理,catch代码块执行完成后,程序不会受到影响,而是继续向后执行。
在try-catch结构的最后,还可以追加一个finally代码块,这个代码块会在try-catch结构执行完成之后执行。无论是否产生异常,finally代码块中的代码都会被执行,示例代码如下:
除某些错误的操作会产生系统异常外,在必要的时候,开发者也可以手动抛出自定义的异常终止程序,示例代码如下:
自定义异常通常用于功能性函数中,当调用者使用了错误的调用方式或调用者传递的参数不合规时,为了避免后续产生更加严重的错误,可以通过抛出异常提前终止程序,并对调用者起到警告作用。
2.1.10 使用函数
函数这个概念在数学中也有,一个函数通常描述了一种计算模式,数学中的函数有3个要素:定义域、值域和映射关系。编程中的函数与数学中的函数概念十分类似,其也有3个要素,分别为参数、函数体和返回值。
函数的实质是一段可复用的代码块,通过函数名,可以直接对函数进行调用,示例代码如下:
如上述代码所示,使用function关键字定义函数时并不会执行内部的代码,当对函数进行调用时内部的代码才会执行,使用函数名加小括号的方式可以直接调用函数。
上面示例的函数非常简单,其没有显式地定义参数和返回值,很多时候,函数的执行需要依赖外部传递的参数,在函数定义时,小括号中可以定义参数列表,示例代码如下:
有时候,函数需要将执行的结果返回外部,这时就需要使用return语句,示例代码如下:
在JavaScript中,函数其实也是一种对象,这也表示可以将一个函数赋值给某个变量,示例代码如下:
上面定义的函数没有名字,这种函数也被称为匿名函数,将这个函数赋值给func变量,后面可以直接通过func变量调用函数。
ECMAScript6中新增了箭头函数的语法,之所以称为箭头函数,是因为在定义时使用了符号“=>”,示例代码如下:
从定义格式来看,箭头函数和普通函数并没有太大的区别,都是由参数、函数体和返回值组成的,但是箭头函数有其特殊的性质,其中,this的指向在函数声明时就已经固定,而普通函数中this的指向则在函数调用时才能确定,关于函数中this变量的内容,后面介绍对象的部分会详细讲解。
箭头函数也可以省略参数列表的小括号,并且如果函数体只有一行代码,那么函数体的大括号也可以省略,示例代码如下:
2.1.11 使用对象
JavaScript中的数据其实都是对象,对象实际上就是属性与方法的包装。属性用来存储数据,方法用来描述行为,在开发中,对象也用来模拟实际应用中的事务。例如,一个教学系统软件中通常会有许多教师的信息,每个教师都可以是一个对象,示例代码如下:
使用点语法可以进行对象属性和方法的访问,也可以使用中括号的方式进行访问,示例代码如下:
①注意:
中括号中应是字符串类型的属性或方法名。
从上面的示例代码可以看到,在teaching方法中使用了this关键字,在普通函数中,这里的this关键字就是指调用此方法的对象本身,需要注意的是,这个对象并不一定始终是teacher1对象,示例代码如下:
上面的代码创建了两个教师对象,并将teacher1的teaching方法赋值给teacher2,这时当teacher2调用teaching方法时,其中的this就不再指teacher1对象,而是指teacher2对象,如果使用箭头函数定义teaching方法,则其中的this会始终指向当前词法作用域中的this对象。
2.1.12 定义类
类既可以理解为对象模板,也可以理解为对象工厂,即使用类可以方便生成对象,2.1.11节创建了两个教师对象,使用类来构造它们将更加方便,ECMAScript6中提供了class关键字帮助开发者快速定义类,示例代码如下:
在使用class关键字定义类时,类名一般采用首字母大写的命名方式。在定义类时,开发者需要实现类中的constructor方法,这个方法被称为构造方法,用来进行对象的构造,类中还可以定义一些其他的自定义方法。在使用类创建对象时,使用new关键字加类名的方式即可调用构造方法进行创建。
在JavaScript中,类也支持继承。继承是面向对象编程语言的基础特性,使用继承,子类可以直接使用父类中定义的对象属性和方法,继承能够使代码的复用性和程序的结构性更好。例如,教师也是人类,人类都会发起“问好”这个行为,我们可以再定义一个People类,让Teacher类继承它,示例代码如下:
2.1.13 解构赋值
解构赋值也是ECMAScript6的一种新特性,通常会使用点语法获取对象中属性的值,示例代码如下:
如果对象中属性很多,这种方法会非常麻烦,并且增加许多逻辑类型的冗余代码,使用解构赋值可以一次性解析对象进行属性取值,示例代码如下:
在进行对象的解构赋值时,左侧大括号中声明的变量名字只要和对象的属性名一致,即可直接完成解析与赋值。需要注意的是,如果声明的变量名在对象中并没有属性可以对应,则会被赋值为undefined,示例代码如下:
如果变量名与对象中的属性名不一致,也可以额外指定变量名,示例代码如下:
解构赋值也支持对象的嵌套,示例代码如下:
同样,对于数组的取值也可以使用解构赋值,语法上将大括号修改成中括号即可,示例代码如下:
2.1.14 Proxy代理对象
Proxy代理对象是ECMAScript6中的高级特性,可以对某个对象进行代理,之后可以拦截此对象的某些行为,使其实现额外的操作,最常用的场景是拦截对象属性的赋值和取值操作,控制对象属性值的有效性,示例代码如下:
上面的代码首先创建了一个teacher对象,之后使用Proxy代理对象对其进行了代理,代理中重写了get方法和set方法。get方法在对象属性取值的时候会被调用,set方法在对象属性赋值的时候会被调用。需要注意的是,直接对teacher对象属性的操作需要使用newTeacher对象,这样才能触发代理方法。
2.1.15 Promise承诺对象
使用Promise可以实现JavaScript异步编程。我们前面编写的所有代码都是同步进行的,也就是说,代码的执行是从上到下、从前到后的。在实际应用开发中,这种完全同步的编程方式往往具有很大的局限性,如大量数据的处理、网络信息的请求等耗时行为通常需要使用异步的方式进行处理,这样可以保证用户的界面交互不会被阻塞。下面的代码演示了Promise承诺对象的简单用法:
运行上面的代码,控制台的打印信息如下:
从打印信息可以看出,耗时任务首先被执行,但是并没有阻塞程序的继续运行,当程序运行结束后,会执行then方法中设置的回调。then方法有两个参数:第1个参数指定Promise任务执行完成后的回调,第2个参数指定Promise任务执行失败的回调。在构造Promise承诺对象时,其参数为函数对象,这个函数对象中有两个参数,第1个参数为成功回调,第2个参数为失败回调,开发者可以根据Promise中的逻辑执行情况确定执行成功或失败回调。