第5章
CHAPTER 5




微课视频5




JavaScript核心知识








图51JavaScript的代码示例


JavaScript是一种脚本语言,它的正式名称为ECMAScript[5]。
JavaScript的基本代码示例如图51所示。作为一种脚本语言,JavaScript具有“即插即用”的特点,它的变量不需要事先声明就可以在需要的时候直接使用。

JavaScript的版本发展如图52所示。目前最常用的版本为ECMAScript 6。ECMAScript 6 相对于之前的版本变化较大,而ECMAScript 6 之后的版本则变动较小。所以,掌握ECMAScript 6版本的JavaScript是学好JavaScript的一项必要技能。


图52JavaScript的版本演进



JavaScript的知识点比较庞杂,本章将重点详细讲解JavaScript中的基本语法、运行环境和核心概念。
本章在首先介绍JavaScript基本语法的基础上,介绍JavaScript在浏览器中的使用方法和关键知识点; 然后介绍JavaScript中的面向对象编程和函数式编程的基本方法。所有内容都将结合实例进行示范讲解。本章应重点掌握以下要点: 
(1) JavaScript的基本语法; 
(2) JavaScript的面向对象编程; 
(3) JavaScript的函数式编程。
5.1JavaScript基本语法
JavaScript具有脚本语言的优点与缺点。其优点是: 入门简单; 其缺点是: 执行速度相对稍慢。
作为JavaScript和其他语言的对比,图53给出了JavaScript和Java语言的区别说明。


图53JavaScript和Java的不同点


5.1.1变量和数据类型
变量是计算机内存中一个有名字的用来存储值的存储空间。通过设置变量这种方式,就可以有效地获取某个值。在JavaScript程序中,变量的名字也被称为标识符。标识符的命名规则为: 必须由字


图54JavaScript变量声明示例

母、下画线(_)、美元符号($)和数字组成,并且只能以字母、下画线(_)和美元符号($)开始。JavaScript是一种区分大小写的计算机语言,所以大写字母和小写字母在JavaScript中表示不同的字符。JavaScript中共有3种声明变量的方式: var、let和const,如图54所示。

虽然JavaScript有3种声明变量的方式,但在使用时通常优先使用const,如果是需要改变值的变量通常优先使用let。var作为ES6之前的变量声明方式通常不再建议使用。
数据类型是指变量所存储值的类型。对于静态类型的编程语言(例如Java),在声明变量的时候就要指定该变量的数据类型。JavaScript作为一种动态类型的编程语言,不需要在声明变量的时候指定数据类型。JavaScript中变量的数据类型会随着变量所存储值的数据类型的变化而动态变化。JavaScript中有9种数据类型,包括7种基本数据类型和2种索引数据类型,如图55所示。


图55JavaScript中数据类型示例



JavaScript中的基本数据类型是指非对象并且没有对象方法的数据类型,其中null是一种特殊的基本数据类型。JavaScript中的索引数据类型是对象型的数据类型,包括Object、Array、Function、Set、WeakSet、Map和WeakMap,其中Function是一种特殊的索引数据类型。JavaScript中的数据类型通常可以通过typeof操作符来判定,只是要注意typeof在处理null时会给出值object,如图56所示。


图56JavaScript数据类型的验证示例


5.1.2操作符和控制语句
操作符是可以对变量进行某种操作的标识符。JavaScript中主要的操作符包括赋值操作符、逻辑操作符、数学运算操作符、比较操作符、分组操作符、void操作符、位运算操作符、in操作符、管道操作符、逗号操作符、delete操作符、new操作符等,如图57所示。


图57JavaScript中主要的操作符


控制语句分为赋值语句、条件语句和循环语句。
赋值语句是通过赋值操作符对一个变量赋值。JavaScript在进行赋值操作时将赋值操作符右边的值赋给操作符左边的变量,如图58所示。


图58JavaScript中的赋值语句示例


JavaScript中的条件语句分为由关键字if构成的条件语句和由条件操作符构成的条件语句,这两种实现方式如图59所示。


图59JavaScript中的条件语句示例


JavaScript中的循环语句有多种代码实现方式,基本可以分为由操作符构成的循环语句和由函数构成的循环语句两大类。
由操作符构成的循环语句是指由for、for…in、for…of、while和do while构成的循环语句。图510给出了由操作符构成的循环语句,实现打印1~10的10个数字的不同方式。


由函数构成的循环语句是指由Array的函数forEach、map、filter、reduce、every、some、find等具有循环功能的函数构成的循环实现方式语句。由函数构成的循环语句是函数式编程中经常使用的一种循环语句实现方法。图511给出了由函数构成的循环语句,实现打印1~10的10个数字的不同方式。


图510由操作符构成的循环语句的不同实现方式




图511由函数构成的循环语句的不同实现方式



5.1.3JavaScript程序示例
本节展示如何使用JavaScript中的基本语法来完成一个程序实例。
任务: 对于给定的一个由数字组成的数组,找出其中为奇数的数字,生成一个新的数组,将这个新的数组升序排列,将排列好的数组的每一项乘以2,生成一个新的数组返回。
程序实现: 实现该功能的JavaScript代码如图512所示。


图512JavaScript实现代码示意图


5.2JavaScript的面向对象编程
JavaScript语言是基于对象的,而不是面向对象的。JavaScript中有对象的概念,例如,对象数据类型具有对数据和方法封装的功能,但是没有继承的概念。从定义的角度来看,JavaScript中有两种类型的对象: 一种是JavaScript语言本身提供的内置对象,例如Date对象或浏览器及Node运行环境提供的对象; 另一种是用户根据需要自己创建的对象。

面向对象编程是指采用创建类和对象的方法来思考问题、解决问题的一种编程方法。JavaScript中提供了class关键字来模拟定义传统面向对象编程中的类的概念。这种通过class关键字声明的类本质上是一种基于原型的实现。图513给出了JavaScript中定义类的基本语法。当使用class关键字创建好一个类后,可以用new关键字来从该类派生出新的对象,对象将继承类的所有属性和方法。


图513JavaScript中类的定义方法



5.2.1JavaScript面向对象编程的概念和原则

JavaScript面向对象编程的概念是基于面向对象编程(OOP)的理论,用JavaScript模拟对象、创建对象,使用对象来编程的一种方法。OOP的基本概念是在程序中使用对象来模拟现实世界中的所有物体。对象可以包含属性和方法,属性通常用来存储数据。
下面用一个简单的例子讲解面向对象编程的基本方法。
假设创建了Person这个类,如图513所示。现在要从此类创建多个不同的Person,如图514所示。图514中的Person1和Person2都将具有Person这个类中的sayHi方法。这种从Person类创建Person1和Person2对象的过程叫作Person类的对象实例化。



图514创建Person对象实例



更多的情况下,我们不需要从Person类创建实例,因为这样会导致所有的实例对象都大同小异。我们可能需要创建更个性化的实例,例如教师和学生。我们可以采用从类创建子类的方法来实现这个要求。每个子类都可以具有不同的个性化的属性和方法。同时,每个子类又可以具有共同父类的所有属性和方法。如图515所示。图515中的Teacher和Student都将具有Person这个类中的name和age属性及sayHi方法。但是Teacher和Student又都具有不同的额外属性和方法。


图515由父类创建子类代码示意图



创建好子类Teacher和Student后,可以从Teacher和Student这两个子类创建不同的对象实例,这些对象实例除了有共同父类Person的方法外还具有各自Teacher和Student子类的独特属性和方法,如图516所示。



图516由子类创建实例方法示意图


5.2.2JavaScript面向对象编程的程序示例
本节展示如何使用JavaScript中的面向对象的方法来完成一个程序实例。
任务: 对于给定的一个由数字组成的数组,找出其中为奇数的数字,生成一个新的数组,将这个新的数组升序排列,将排列好的数组的每一项乘以2,生成一个新的数组返回。

程序实现: 实现该功能的JavaScript代码如图517~图519所示。


图517定义父类





图518定义子类




图519实例化并获得结果


5.3JavaScript的函数式编程
函数式编程是和面向对象编程并列的另一种编程范式。函数式编程的主要概念是不改变原始数据(immutability)和纯函数(pure function)。通过编写不产生任何副作用的纯函数,并且让数据在纯函数间流动,使函数式编程更加有利于系统的可维护性。JavaScript中函数即是变量的特点使JavaScript天生具有可使用函数式编程的便利性。
5.3.1JavaScript函数式编程的概念和原则
为了更好地了解函数式编程,需要首先了解纯函数的概念。纯函数有两个特点: 一个是给定相同的输入,该函数总是给出相同的输出; 二是该函数没有副作用,即不会改变输入的原始数据。图520给出一个计算圆的面积的纯函数的例子。


图520计算圆面积的纯函数示意图


图520中的PI是一个变量,如果PI的值发生了改变,那么该函数calculateArea就不再是一个纯函数了。为了避免这种情况发生,可以将图520中的纯函数修改为鲁棒性更强的纯函数,如图521所示。


图521鲁棒性更强的计算圆面积的纯函数示意图


通过以上例子应该能够很好地理解纯函数的概念。下面来看看函数式编程的另一个重要方面——不改变原始数据性,即不变性。
如果数据具有不可变性,则说明数据被创建好后将不再改变。但是程序的运行将不可避免地改变某些数据,所以我们能做的就是在需要改变数据的时候不断创建新的数据,而让新的数据和原来的数据具有相同的值。这就是函数式编程中实现原始数据不变性的原则。

同样,以图521计算圆的面积的纯函数为例。如果原始数据是以数组的形式保存的,而函数的参数同样是数组的形式,那么如图521所示的纯函数将变为如图522所示的形式。


图522不具有不变性的纯函数示意图


为了使纯函数具有不变性,可以将如图522所示的纯函数改为如图523所示的纯函数。


图523具有不变性的纯函数示意图


5.3.2JavaScript函数式编程的程序示例
本节展示如何使用JavaScript中的面向对象的方法来完成一个程序实例。

任务: 对于给定的一个由数字组成的数组,找出其中为奇数的数字,生成一个新的数组,将这个新的数组升序排列,将排列好的数组的每一项乘以2,生成一个新的数组返回。

程序实现: 实现该功能的JavaScript代码分别如图524~图526所示。


图524实现获取奇数功能的纯函数




图525实现排序功能的纯函数





图526实现对数字做乘法功能的纯函数及结果



5.4ES6基础知识
ES6的正式名称为ECMAScript 2015(ES 2015)。与上一个版本ES5相比,ES6增加了很多特性。由于这些特性相较之前的版本有较大变化,因此本节将详细讲解ES6中增加的主要特性。
本节将要讲解的ES6中的主要特性为: 函数的默认参数、字符串模板、多行字符串、解构赋值、对象表达式、箭头函数、Promise、let和const、类、模块、可计算的对象属性、for…of语句、getters和setters。
5.4.1ES6的主要特性
1.  函数的默认参数

在ES6中,我们可以在声明定义函数时给出函数参数的默认值,而在ES5中我们只能在函数体中通过编程实现,如图527所示。


图527ES6和ES5中函数参数默认值的程序示例


2. 字符串模板
在ES6中,我们可以用反引号来创建字符串,在反引号中可以通过${}来获取变量的值,而在ES5中只能通过字符串的拼接实现相同的功能,如图528所示。


图528ES6和ES5中字符串模板的程序示例


3. 多行字符串
在ES6中,我们可以用反引号来创建多行字符串,而在ES5中只能通过对字符串的拼接实现相同的功能,如图529所示。


图529ES6和ES5中多行字符串的程序示例


4. 解构赋值
在ES6中,我们可以对数组或者对象进行解构,并将数组的元素的值赋给具有同样结构的变量数组,或者将对象的属性值赋给具有同样结构和名称的变量对象。这种解构赋值在ES5中只能通过编程来实现,如图530所示。

5. 对象表达式
在ES6中,当对象的属性名称和表示属性值的变量名称相同时,可以采用只写属性名称的简写方式。而在ES5中不存在这种简写方式,如图531所示。


6. 箭头函数
ES6中引入了函数表达式的新的写法——箭头函数。箭头函数除了可以使函数表达式的写法更加简洁之外,还可以使函数中出现的this有固定的指代——调用该函数的上下文对象。这有别于非箭头函数中this在不同使用环境下具有不同指代对象的问题。ES6中的箭头函数和ES5中的普通函数的写法的程序示例如图532所示。



图530ES6和ES5中解构赋值的程序示例




图531ES6和ES5中对象表达式的程序示例





图532ES6和ES5中箭头函数的程序示例


7. Promises
Promises是用来完成异步功能的对象,通过使用该对象的返回值的不同方法,可以获取Promises处理的值。ES6中提供了原生的Promises对象。而在ES5中只能通过复杂的编程或者借用其他的库的方法实现相同的功能。由于ES5实现代码的复杂性,这里只是给出ES6代码的实现,如图533所示。


图533ES6中Promise的程序示例


8. 块级作用域的变量声明关键字
ES6中引入了块级作用域的变量声明关键字let和const。所谓块级作用域,就是由花括号包围的区域。所以,let和const与ES5中的var的主要区别就是: var的作用域是由最近的函数体决定的,而let和const的作用域是由最近的花括号和var相比决定的。另外,let和const还具有只能声明一次、不具有变量提升的特点,const可以用来声明常量。在学习曲线方面,let和const比var更加简单。很多时候需要运行程序后才能知道var带来的准确含义,而let和const在没有运行的时候就能够让学习者了解不同变量的作用范围。ES6和ES5中不同变量声明关键字的使用程序示例如图534和图535所示。


图534ES6和ES5中块级作用域实现的程序示例1




图535ES6和ES5中块级作用域实现的程序示例2


虽然const是定义一个常量,但是如果该常量是对象,那么const保存的就是该对象的索引。用const定义对象的问题如图536所示,解决方案如图537所示。


图536使用const声明对象的程序示例




图537使用const声明对象时解决方案的程序示例




9. 类的实现
在ES6中,我们可以用关键字class来创建类,虽然JavaScript是一种基于原型的编程语言,但是关键字class和其他相关关键字给JavaScript提供了模拟面向对象的编程语法。而在ES5中只能通过编程的方式来模拟实现面向对象的编程范式。图538给出了ES6中类的定义与使用以及ES5中对类的定义与使用的示例。图539给出了ES6中子类的定义与使用以及ES5中对子类的定义与使用的示列。


图538ES6和ES5中类的实现的程序示例




图539ES6和ES5中子类的实现的程序示例


需要注意的是,有些类的特性,例如类中的私有方法,还处于实验阶段,暂时无法在浏览器中使用。遇到这种情况,使用babel进行转义是通常使用的一种方法。同理,其他暂时在浏览器中不能够使用的新特性往往也可以通过babel转义来实现。使用babel来实现类中的私有属性的程序示例如图540所示。使用babel的package.json文件和命令如图541所示。使用npm install命令安装依赖包后在命令行进行文件转义的命令如图542所示。



图540ES6和ES5中子类的实现的程序示例





图541使用babel的package.json文件示例





图542使用babel的命令示例


10. 模块的实现
开发大型复杂程序的时候往往需要将程序的功能分别用多个不同的JavaScript文件实现,这些不同的JavaScript文件就构成了JavaScript程序的模块。不同模块间的程序联合工作需要用到import和export关键字。在ES5中只能使用额外的工具来辅助实现该功能。在ES6中将模块变成了JavaScript语言本身的一个特性。在浏览器中ES6模块的具体使用方法如图543~图545所示。



图543ES6中模块在浏览器的使用的程序示例1



图544ES6中模块在浏览器的使用的程序示例2



图545ES6中模块在浏览器的使用的程序示例3


11. 可计算的对象属性
在ES6中,可以使用表达式作为对象属性。这时需要使用[]来计算表达式的值。图546给出了通过表达式来计算对象属性的程序示例。


图546ES6中表达式作为对象属性的程序示例3


12. for…of语句

ES6中提供的for…of语句可以用来代替for…in语句和数组的forEach方法。for…of语句可以用来遍历各种数据结构,例如,数组、字符串、Maps、Sets等。在某些情况下,for…of比for…in和forEach具有更多的优点。图547和图548给出了for…of遍历部分不同数据结构的程序示例。


图547ES6中for…of语句的程序示例1





图548ES6中for…of语句的程序示例2


13. Getters 和 Setters

Getters和Setters可以让我们在定义对象的属性时增加额外的数据处理功能。Getters和Setters既可以用在类的声明中,也可以用在对象的声明中。图549给出了在类中使用Getters和Setters的程序示例。图550给出了在对象中使用Getters和Setters的程序示例。


图549在类中使用Getters和Setters的程序示例




图550在对象中使用Getters和Setters的程序示例


5.4.2ES6程序示例

本节展示如何使用ES6的知识来完成一个程序示例。
任务: 请对比ES5和ES6在实现(模拟实现)类的静态方法(属性)、实例方法(属性)、私有方法(属性)的区别。
程序实现: 静态方法(属性)的代码如图551所示,实例方法(属性)的代码如图552所示,私有方法(属性)的代码如图553所示。


图551ES5和ES6静态方法和属性的代码实现图





图552ES5和ES6实例方法和属性的代码实现图




图553ES5和ES6私有方法和属性的代码实现图

本章小结
JavaScript是Web开发的核心支柱。本章重点讲解了JavaScript中的基本语法、如何使用JavaScript进行面向对象编程以及如何使用JavaScript进行函数式编程。本章还介绍了ES6中的各种主要新特性,包括函数的默认参数、字符串模板、多行字符串、解构赋值、改进的对象表达式、箭头函数、Promise、let与const、类、模块、可计算的对象属性、for…of语句、Getters和Setters。在实例部分,完成了ES5和ES6在实现类的各种不同特性方面的代码实现。有了本章的这些基础,读者就可以在Web上用JavaScript进行编程,或者进行其他需要进行JavaScript编程的工作。
本章主要介绍了如下内容: 
(1) JavaScript的基本语法。
(2) JavaScript中面向对象编程的概念和编程范式。
(3) JavaScript中函数式编程的概念和编程范式。
(4) ES6中各种主要的新特性的基本概念和用法。
(5) ES5和ES6在实现类的各种不同特性方面的代码实现。
(6) 用JavaScript解决实际问题的方法。
思考题
(1) JavaScript中有哪些数据类型?
(2) JavaScript中的操作符“==”和“===”有什么区别?
(3) 请简述JavaScript中的函数式编程和面向对象编程的基本概念。
(4) 请解释ES6中字符串模版的使用方法。
(5) 请解释ES6中箭头函数和ES5中函数的异同。
(6) 请对比ES5和ES6在实现(模拟实现)类的静态方法(属性)、实例方法(属性)、私有方法(属性)的区别。