第2章 基本构成

本章主要内容

JavaScript变量

数据类型

JavaScript运算符

JavaScript流程控制

每个编程语言都有自己一套完整的基础语法,当然这些语法之间会有一些类似,如果读者之前有了解过其他语言,将很容易上手。无论哪门语言,都会涉及变量、函数、对象等最基础的知识。

我们在编程过程中更多的是对不同类型的数据在不同的条件下进行不同的操作。每个应用中都会涉及各种类型的数据以及对数据的操作。

本章将介绍JavaScript中有关变量的基础语法以及数据类型、常见运算符和流程控制。

2.1 JavaScript变量

在应用编程中,需要使用变量作为值的符号,而且操作最多的就是变量。变量是整个JavaScript语法最基础的概念。

2.1.1 变量的概念

在代数计算中,通常会使用一个字母来保存值,如字母x上保存一个数值2,字母y上保存一个数值3,之后通过表达式z=x+y,可以计算出z的值为5。

在JavaScript 中,这些字母称为变量。读者可以将变量理解为就是一个用来存储数据的容器。JavaScript 中的变量可以保存和引用任意类型的数据。变量在内存中的存储如图2-1所示。

图2-1

在JavaScript 中,变量除了可以用单个的字母来命名,也可以用一些更语义化的名称来命名,如num、sum等。这和它的松散类型关系密切。这里读者需要牢记JavaScript 中的命名规范:

1)严格区分字母的大小写。JavaScript 是区分大小写的语言,即变量名、函数名、关键字都必须采取一致的大小写形式。例如,变量名“num”“Num”“NUM”是3 个不同的变量名;函数名也类似;关键字“var”必须写成“var”,而不能写成“Var”或者“VAR”。

2)变量名必须以字母、下画线、$符号作为开始,后面可以跟任意的字母、数字、下画线、$符号。

3)不能使用关键字或保留字命名。关键字就是JavaScript 中用来执行特定操作的代码,如var。保留字是JavaScript预留的用于执行特定操作的代码,如int。

4)JavaScript 有自己的命名习惯,例如,驼峰命名法:getElementById;首字母大写法:Object()。

5)命名尽量要有实际意义,这样代码的可读性比较高。

在JavaScript中,常常用到标识符(即一个名字)对变量和函数进行命名。在JavaScript中规定了一些标识符专门为自己所用,即关键字,见表2-1。

表2-1

除了关键字以外,和其他语言一样,JavaScript 也保留了一些标识符在后续的版本中可能会用到,称为保留字,见表2-2。

表2-2

2.1.2 变量的声明和赋值

在ES6之前,变量的声明一般使用var来完成,在ES6中新增了使用let来声明变量的方式。

1.var声明变量

使用var声明和赋值变量有以下4种形式:

1)声明的同时赋值:

说明:在上述示例中通过var定义一个变量url,并同时通过赋值运算符给它赋值。

2)先声明后赋值:

说明:在上述示例中通过var先声明一个变量url,声明后可以在之后使用时再对它进行赋值操作。在上述示例中存在变量覆盖问题,详见2.1.3节。

3)一次声明多个变量,同时赋值:

说明:上述示例和第1种方式声明类似,只不过此处同时声明了多个变量并同时进行赋值,需要特别注意的是,多个变量间要用逗号隔开。

注意:

1)此处有的值上加了引号,有的没加,加了引号的声明是字符串类型的数据,数字17是数值类型的数据。

2)一次性声明多个变量必须要用逗号隔开。

3)一次声明多个变量,然后赋值:

说明:同第3种声明方式,需要注意多个变量间用逗号隔开。

2.Iet声明变量

let是ES6 新增的命令,用法类似于var,但是它所声明的变量只在let 命令所在的代码块内有效。let方式与var方式的区别主要体现在作用域上,这里暂且不做讨论。

不像var存在变量提升现象,即变量可以在声明之前使用,值为undefined。为纠正这种奇怪的现象,let命令改变了语法行为,即变量一定要先声明后使用,否则会报错。

示例:

说明:在上述示例中,通过关键字var 声明的变量foo 可以在声明之前进行访问,只不过结果为undefined,undefined是个数据类型,并不是报错。而通过let声明的变量bar在声明之前进行访问会报错,并非变量未定义。

3.const方式

可以使用关键字const 声明一个只读的常量。常量标识符的命名规则和变量相同。此外需要注意,const必须在声明的同时赋值,并且不能重新声明和赋值。

示例1:

说明:以上示例表明常量不可以通过赋值来改变原来的值,也不可以在脚本运行时重新声明,它必须初始化为某个值。

示例2:

说明:以上示例表明const 一旦声明变量,就必须立即初始化,不能留到以后赋值,否则会报错。

2.1.3 声明变量的其他注意事项

1)变量在没有被赋值的情况下会被自动赋值为undefined,undefined 也是一种数据类型,不是错误。

2)不使用var 或者let 声明的变量,如果直接赋值,不会报错,这个变量会被当作window对象的属性存在,拥有全局的作用域,本书不推荐这样的写法。

3)如何覆盖已有变量?变量在声明之后是可以重新赋值的,使用var 的方式可以重新声明和赋值,使用let则只能重新赋值,不能重新声明。

示例:

说明:上述示例表明,通过关键字var 声明的变量可以重新声明和赋值,通过let 声明的变量只能重新赋值不能重新声明,那么在写程序的过程中,let就能很好地帮助开发人员避免一些不必要的错误。

2.2 数据类型

在JavaScript 变量中,我们会将一些值保存在变量中以备后续使用,那么在编程过程中,经常需要对值进行操作,而能够表示并操作值的类型称为数据类型。本节将重点介绍JavaScript中的数据类型。

JavaScript的数据类型大致可以分为初始类型和引用类型。

1.初始类型

1)Number:数值,如10或3.1415。

2)String:字符串,如UEK。

3)Boolean:布尔值,只有true 和false。

4)undefined:变量未定义时的属性。

5)null:空。

6)ES6中新增的Symbol类型:一种数据类型,它的实例是唯一且不可变的。

2.引用类型

1)对象。

2)数组。

3)函数。

通过这几种数据类型,我们就可以在应用程序中执行和实现不同的功能。接下来将对每个类型进行详细讲解。

2.2.1 typeof操作符

鉴于JavaScript是松散型的弱类型语言,需要通过一种手段来检测已有变量的数据类型,而typeof操作符正好可以帮助我们检测,通常返回的结果是一个字符串,可能的情况如下。

1)number:数值。

2)string:字符串值。

3)boolean:布尔值。

4)undefined:未定义。

5)object:(对象)或者null。

6)function:函数。

2.2.2 初始类型

初始类型在内存中是在栈区保存的,因为一个初始类型的数据在内存中所占的长度总是固定的。

1.Number(数值)

和其他编程语言不同,在JavaScript 中不区分整型和浮点型数值,即数值类型包括整型和浮点型,支持二进制(0b)、八进制(0o)、十进制、十六进制(0x)。用科学计数法来表示,还包括一些特殊的值:Number.MAX_VALUE(最大值);Number.MIN_VALUE(最小值)。

示例:

ES6中规定:二进制使用0b开头,八进制不再使用0开头,而是使用0o开头,十六进制依然使用0x开头。

示例:

2.String(字符串)

用单双引号来说明,它所包围的值都可以是字符串。

单双引号的用法:效率是一样的;只能成对出现,不能相互交叉使用;可以相互嵌套。

还包括一些特殊的字符,具体见表2-3。

表2-3

在ES6中还新增了模板字符串。模板字符串使用反引号(``)来代替普通字符串中的双引号和单引号。之所以称为模板字符串,是因为模板字符串中的插值特性。

在模板字符串中可以方便地引入变量:

说明:在上述示例中,${year}、${month}和${date}称为模板占位符,这样就可以很方便、很优雅地将JavaScript的值插入到字符串中。

在模板字符串中可以进行变量运算,示例如下:

说明:模板占位符可以是任何JavaScript 表达式,故函数调用以及四则运算都是合法的。

3.booIean(布尔值)

只有两个特殊的值true和false,用来表示真或假。

4.undefined(未定义)

当一个变量声明了但未被赋值时就会被默认赋值为undefined。此外,在一些其他地方,如对象未被赋值的属性、函数没有定义的返回值等都会被自动赋值为undefined。

5.nuII(空对象)

空类型,表示一个占位符或清空一个对象,通常用于清空对象。

对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。

6.SymboI

Symbol 类型是一种特殊的、不可变的数据类型,表示唯一的值,可以保证不会与其他属性名产生冲突。

Symbol函数的参数只是表示对当前Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

示例:

说明:每一个Symbol值都是不相等的,所以Symbol值可以作为标识符。

属性名的遍历:

1)Symbol.for()。

有时,我们希望重新使用同一个Symbol 值,Symbol.for 方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol 值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,而后者不会。

示例:

2)Symbol.keyFor()。

Symbol.keyFor方法用来获取Symbol注册表中与某个Symbol关联的键。

示例:

注意:使用Symbol作为属性名时,该属性将不会被for...in遍历,不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。

2.2.3 引用类型

引用类型在内存中存储在堆区,当访问一个引用类型时,只是在访问它的应用地址。关于引用类型的详细介绍,参见第5章。

2.3 JavaScript运算符

在JavaScript 程序中,不仅要定义不同数据类型的变量,还要对这些变量进行操作。操作时采用JavaScript 本身提供的运算符。本节主要描述了JavaScript 的运算符,包括算术、关系、赋值、逻辑、一元、三元、特殊运算符等。

在介绍运算符之前,我们先来了解一个常见的概念——表达式。

表达式是一组可以计算出一个数值的有效的代码的集合。简单地说,任何即将赋值或已经赋值的都是一个表达式。除了表达式以外的都是运算符。

JavaScript中有如下类型的运算符:

1)算术运算符。

2)关系运算符。

3)赋值运算符。

4)逻辑运算符。

5)一元运算符。

6)三元运算符。

7)特殊运算符。

2.3.1 算术运算符

算术运算符取数值作为其操作数,并且返回一个数值。标准的算术运算符就是加、减、乘、除。

算术运算符包括“+”“-”“*”“**”“/”“%”“i++”“++i”“i--”“--i”。

加号(+)运算符和代数中的用法一样,用来进行数值的求和运算,在JavaScript 中还有一个用法就是进行字符串的拼接。

1)用于数值的求和运算:

2)用于字符串的拼接:

说明:任何的数据类型和字符串相加都是等于相加以后的字符串。

减法(-)、乘法(*)和除法(/)跟代数中是一样的运算方式,需要注意的是,不仅是数值,其他类型的数据也可以进行这些运算。

说明:上述示例中,操作数a 和b 都是标准数值的字符串类型,当进行算术运算时,会发生隐式的转换:先将操作数转换为数值类型再进行操作;如果转换不成功,即得不到一个数值结果,则会得到一个NaN(not a number)。有关数据类型的转换详见3.2.2节。

取余(%)用来取某个数的余数,或用于取一段范围的值。

一般不用于小数,因为结果不确定(不精确)。

i++是先运行,再自增。++i是先自增,再运行。

示例:

i--是先运行,再自减。--i是先自减,再运行。用法同自增。

指数运算符(**)是ES6中新增的一个运算符操作,即可以进行指数运算。

示例:

2.3.2 关系运算符(或比较运算符)

关系运算符会比较它的操作数并返回一个基于表达式是否为真的boolean类型的值。操作数可以是任何类型的数据。遇到字符串类型的数据做比较时,会使用Unicode 值。具体见表2-4。

表2-4

注意,=>不是运算符,是箭头函数的标记符号。

== 和===的区别:

1)== 对数值比较是否相等。

2)===对数值比较是否相等,并且比较数据类型是否相同。

示例:

注意:

1)如果两个操作数都是字符串,则比较两个字符串对应的字符编码值,直到比较出大小。

2)如果一个数值和布尔值进行比较,则会把布尔值转换为数值再进行比较,true为1,false为0。

3)当一个是字符串,另一个是数值时,则把字符串尝试转换成数值类型,然后进行比较,如果转换不成功,则返回false。

等性运算特例,见表2-5。

表2-5

2.3.3 赋值运算符

赋值运算符旨在将它右边操作数的值赋值给左边的操作数,见表2-6。

表2-6

示例:

说明:如上述示例中的x+=y的加法赋值运算,就相当于计算x=x+y。

2.3.4 逻辑运算符

逻辑运算符分为3 部分:逻辑与“&&”、逻辑或“||”、逻辑非“!”,常用于布尔值之间。当用于非布尔值时,返回的是一个特定的操作数的值,即返回的值可能是非布尔值。

逻辑运算符的运算一旦得到结果,运算立即停止,最终结果取决于运算停止的表达式。

1. 逻辑与

关于逻辑与运算,下面分两种情况进行介绍。

情况1:运算数为布尔类型的值,具体见表2-7(1)。

表2-7(1)

情况2:运算数为其他类型的值。

示例:

总结见表2-7(2)。

表2-7(2)

2. 逻辑或

关于逻辑或运算也分两种情况进行讨论。

情况1:运算数为布尔类型的值,具体见表2-8(1)。

表2-8(1)

情况2:运算数为其他类型的值。

示例:

总结见表2-8(2)。

表2-8(2)

3. 逻辑非

! 运算符,即执行取反操作,假的变成真的,真的变成假的,该运算符返回的一定是布尔值。

示例:

小结:

逻辑运算符可以对任何类型的数据进行运算,但是在运算后,可以转换为对应的布尔值,具体见表2-9。

表2-9

能转换为false的值有undefined、null、0、NaN、空字符串。

短路原则:

1)如果第一个运算数决定了结果,则不再计算第二个运算数。

2)对于逻辑与运算来说,如果第一个运算数是false,那么无论第二个运算数的值是什么,结果都不可能为true,所以将不会计算第二个运算数。

3)对于逻辑或运算来说,如果第一个运算数是true,那么无论第二个运算数的值是什么,结果都不可能为false,所以将不会计算第二个运算数。

示例:

2.3.5 一元运算符

1.typeof运算符

typeof运算符可以返回任意一个数据的数据类型,示例如下:

2.自增/自减运算符

所谓自增/自减运算符就是,数值进行加1或减1的操作,示例如下:

第2行代码对num进行了加1操作,实质上等价于:

3.new运算符

new 运算符的作用是创建一个对象实例,详见第4章。

4.deIete运算符

delete 运算符用来删除一个对象或一个对象的属性,详见第4章。

示例:

说明:上述示例表明,如果delete操作成功,则变量会变成undefined。但是如果变量是通过关键字声明的,则删除不成功。

5.+(正)和-(负)运算符

示例:

2.3.6 三元运算符

三元运算符是JavaScript 中唯一需要3 个操作数的运算符,运算结果根据给定条件在两个值中取其一为变量赋值。

格式:

如果条件为真,则结果为值1,否则为值2,示例如下:

说明:当age 大于等于25 时,将“老师”赋值给position,当age 小于25时,将“学生”赋值给position。

示例:

说明:三元运算符可以等同于if…else流程控制结构。有关流程控制将在下节中详细介绍。

2.3.7 特殊运算符

1. 逗号运算符

逗号运算符常用在变量声明中,用来一次性声明多个变量或for 循环,在每次循环时对多个变量进行更新。

示例:

2. 小括号运算符

小括号运算符在运算时可以确定计算一个表达式的顺序,即改变运算的优先级。

示例:

2.4 JavaScript流程控制

HTML 中存在默认的文档流顺序,才能让代码在页面中对应的位置正确地呈现。同样地,JavaScript 中程序代码的执行也有一定的顺序,但是通常情况下需要让程序代码按照我们指定的方式去执行,为此JavaScript提供了一套灵活的语句集——流程控制。

流程控制是每一个高级语言的核心语法,也是程序之所以能称之为程序、程序之所以能有思想并按照我们的意愿去执行的必要条件。本书中写的每一个功能、每一个应用都有流程控制的语法。其中,if…else 是管控逻辑的,循环是用来帮助我们程序化处理大量流程和数据的。

2.4.1 名词解释

1.流程

流程就是程序代码的执行顺序,一条一条语句地执行。

2.流程控制

通过规定的语句(在JavaScript 中,任何一条表达式都可以看作一条语句,每条语句间用分号隔开)让程序代码有条件地按照一定的方式执行。

3.3种流程控制

(1)顺序结构

按照书写顺序来执行,一行行地解释执行,顺序结构是程序中最基本的流程结构。

如果想让程序代码在某种条件下执行,就必须用到接下来的选择结构。

(2)选择结构(分支结构、条件结构)

根据给定的条件有选择地执行相应的语句。

(3)循环结构

在给定的条件满足的情况下,反复地执行同一段代码。

2.4.2 选择结构

在选择结构中,会根据if 语句指定的条件执行特定的语句,返回对应的结果。if 语句是一种较为基本的控制语句,这种语句有两种形式,一种是:

在这种形式中,当条件或值是真值时,后面只能紧跟着一条执行语句。当执行语句有多条时,可以将多条语句合并成一条,具体形式如下:

示例:

1.分支结构

(1)单路分支

当判断一个逻辑条件为真时,用if语句执行一个语句,否则不做任何处理。

示例:

若条件不成立或值为假,则不执行内部代码。可理解为:跳过。

示例:

说明:在上述示例中,通过方法prompt()输入随机的成绩并保存在变量cords 中,通过条件判断,若获取的成绩比60大,则弹出“及格”,否则不做任何处理。

(2)双路分支

相比较于单路分支,当判断一个逻辑条件为真时,多路分支用if语句执行一条语句;当条件或值为假时,使用else从句来执行这个语句。

必定执行,且只能执行其中的一段代码。

示例:

说明:在上述示例中,将输入的成绩与60 进行比较,当成绩大于60 时,弹出“及格”,后面的代码将不再执行。当成绩小于60 时,先判断是否大于60,若该条件不满足,则继续执行else 语句,最终弹出“不及格”。若读者不清楚代码到底是如何执行的,可以打开火狐浏览器的FireBug工具或者谷歌浏览器的开发者工具进入Sources一栏,给代码添加断点进行调试。

(3)多路分支

当需要判断多个条件或值时,可以采用多路分支。多路分支是组合语句通过else if来测试连续的多个条件或值的判断。

第1种形式:

第2种形式:

示例:

(4)嵌套分支

if语句还可以相互嵌套来使用,语法格式如下:

示例:

说明:在多路分支示例的基础上,这里实现嵌套分支。在成绩大于等于90 的条件下,再来判断它是否等于100并弹出“满分”。

下面通过分支结构来实现一个有关成绩录入的示例,代码如下:

注意:条件满足的情况不可重复,以免造成不可预期的结果,示例代码如下:

2.条件结构

在if语句中,可以通过else if创建多条分支,以处理不同的情况,但是这么多分支如果都依赖于同一个表达式的值,则重复地使用多条if语句会很麻烦。而这种情况,switch语句可以很好地处理。在switch 语句中允许一个程序请求一个表达式的值并且尝试去匹配表达式的值到一个case 标签。如果匹配成功,则这个程序执行相关的语句。switch 语句的语法格式如下:

注意:break 语句与每一个case 语句相关联,用来保证匹配的语句在执行完成后可以跳出switch 并继续执行switch 后面的语句。如果不写break,则程序会从匹配的语句开始执行,并继续执行下一条语句,直到全部执行完。

示例:

说明:在上述示例中,通过条件结构实现了星期几的输出。需要注意的是,通过prompt()输入的数据都是字符串类型的,所以每个表达式的值都必须是字符串类型的。有兴趣的读者也可以参考第3章,将得到的成绩转换成数值类型的,再进行匹配。

总结:

当判断某种范围时最好用if 语句,当判断单个值时用switch 语句。例如,分支结构示例中成绩的判断,若使用switch语句实现,那么就要写100种情况,比较麻烦,不适合。

2.4.3 循环结构

循环结构是一系列反复执行直到找到符合特定条件的命令。对于比较简单的循环情况,完全可以用if语句来完成,示例如下:

说明:如上述示例,当a 的值比7 大,甚至超过100 时,再用if 语句来完成就比较麻烦。为了更方便地实现循环,JavaScript中专门提供了用来实现循环的for、while和do while语句。另外,可以在循环语句中使用break和continue语句来中断或跳出循环。

示例:

1.for

for语句会反复循环直到一个特定的条件计算为假。同时for语句对循环做出了简化,即每次开始循环之前都会初始化变量,每次执行循环之前都会检测变量的值,最后变量做自增或自减操作。语法示例如下:

说明:在上述示例中,通过for循环在页面中输出金字塔,输出结果如图2-2所示。

图2-2

2.whiIe

while 语句是一个基本循环语句,在执行循环之前,会先检测条件得到的结果是否为真值,如果为真就会执行循环体,否则跳过循环体执行程序中的下一条语句。语法示例如下:

说明:在上述示例中,当条件表达式a < 5的值为false时,循环体内的语句停止执行,会跳出循环体执行之后的语句。故此处a的值最终为6。

3.do whiIe

do while 语句和while 语句非常相似,不同的是,它的循环体每次至少会执行一次,主要原因是由于它检测条件得到的结果是否为真值是在循环的尾部执行。语法如下:

do while语句第一次执行时会在条件判断之前先执行一次,然后再进行条件判断。在每次语句执行完毕时都会执行条件判断。若条件表达式为false,则跳出循环继续执行下面的语句。

示例:

说明:在上述示例中,do while循环至少执行一次,然后重复执行直到a不再小于5。

小结:

(1)do while 和while的区别

1)while:当条件满足时,执行循环体,当不满足的时候退出循环,先判断后执行。

2)do while:先最少执行一次,再进行条件判断,如果条件满足则继续执行,如果不满足则退出循环。

(2)for和while的区别

1)for:一般用于循环次数已指定的情况。

2)while:根据条件的真假来循环,当为真时进行循环,为假时则退出循环。

(3)break和continue

在循环结构中,当条件满足需求时需要跳出循环或者从一个位置跳到另一个位置。在JavaScript中能实现跳转的语句有break和continue。

1)break:跳出并终止当前循环,并继续执行后面的语句。

示例:

说明:在上述示例中,循环输出i,直到i的值为3跳出循环。

2)continue:跳出并终止当前循环,和break 不同的是,如果下个值还满足,则继续执行下一次循环体,而不是终止整个循环。

示例:

说明:在上述示例中,当i 的值为3 时,输出当前的值并跳出循环,但它只是跳出了当前循环,并没有跳出整个循环。

小结:

1)break经常出现在switch语句和循环语句中。

2)在switch语句中,当条件满足时,break用于退出switch语句。

3)在循环中,可以在任何地方通过break来提前退出当前循环。

4)continue在循环中,会跳出并终止当前循环,转而继续执行下一次循环。

5)continue只能出现在循环体内,所以最好还是用适当的语句代替continue。

4. 标签语句

标签语句可以提供一种使开发人员在同一程序的另一处能找到它的标识,并用break 或者continue来说明是中断整个循环,还是跳出当前循环继续执行。

语法格式如下:

注意:标签名只可以作用于break 或continue。

示例:

说明:如上述示例中,out标签定义了一个for循环,当第一层循环i = 0的时候,会循环1次第二层循环,此时当j=3的时候,用break语句中断out标签定义的for循环。