跳转至

第10章 JavaScript

第 10 章

JavaScript

JavaScript 是前端开发中最重要的编程语言,经过近三十年的发展,JavaScript 已经成为世界上最流行的编程语言之一。前面已经介绍了 HTML 和 CSS 的相关知识,实际的应用需要三者共同开发,其中 HTML 负责结构,CSS 负责表现,而 JavaScript 负责最重要的行为。本章主要介绍 JavaScript 的基本语法,包括变量、函数、数组和对象等的使用,以及 DOM 模型、事件驱动的讲解,并且提供了丰富的案例,讲练结合。

10.1 JavaScript 简介

1995 年,Netscape 公司的 Brendan Eich 设计并实现了 JavaScript,最初它被命名为 LiveScript。由于 Netscape 与 Sun 公司合作,并希望与 Java 关联,因此将其名称更改为 JavaScript。这个改名并不表示 JavaScript 和 Java 之间有直接的关联,它们是两种不同的编程语言,只是名称上存在一些相似之处。

10.1.1 什么是 JavaScript

JavaScript 是一种直译式脚本语言,是一种动态类型、弱类型、解释型的、基于对象的脚本语言。

脚本语言是指可以嵌入在其他编程语言当执行的开发语言。JavaScript 也是一种广泛用于客户端 Web 开发的脚本语言,其解释器被称为 JavaScript 引擎(后续简称 JS 引擎),是浏览器的一部分。最早是在 HTML (标准通用标记语言下的一个应用)网页上使用,用来给 HTML 网页添加动态功能。随着 JavaScript 的发展,现在可以使用它做更多的事情,如读写 HTML 元素、在数据被提交到服务器之前验证数据等。JavaScript 同样可以适用于服务器端的编程。JavaScript 的具体特点如下。

(1) 动态类型。

JavaScript 能够动态地修改对象的属性值类型,在编译时是不知道变量的类型的,只有在运行的时候才能确定变量类型,也就是说当程序执行的时候,数据类型才会确定。

(2) 弱类型。

JavaScript 在运行时可能会做隐式类型转换。例如,“1+"2"” 在 JavaScript 中会将整型 1 转换成字符串 “1”,然后再与字符串 “2” 进行拼接,最后得到的结果为字符串 “12”。这里 JavaScript 做了隐式类型转换,因此 JavaScript 具有弱类型的特性。

(3)解释型的脚本语言。

JavaScript 是一种解释型的脚本语言,解释型语言在运行过程中逐行进行解释,不需要被编译为机器码执行。另一种相对应的是编译型语言,它在运行中为先编译后执行,比如 C、C++、Java 等。

(4) 基于对象。

JavaScript 是一种基于对象的脚本语言,它不仅可以创建对象,还能使用现有的对象。但是面向对象的三大特性中,JavaScript 能够实现封装,可以模拟继承,不支持多态。

(5) 事件驱动。

JavaScript 是一种采用事件驱动的脚本语言,它不需要经过 Web 服务器就可以对用户的输入做出响应。

(6) 跨平台性。

JavaScript 脚本语言不依赖于操作系统,仅需要浏览器的支持。因此一个 JavaScript 脚本在编写后可以带到任意机器上使用,前提是机器上的浏览器支持 JavaScript 脚本语言。目前 JavaScript 已被大多数的浏览器所支持。

值得注意的是,JavaScript 的语法与 Java 类似。除此之外,这两门编程语言之间没有任何关系。

JavaScript 由三部分构成,ECMAScript、DOM 和 BOM,如图 10-1 所示。根据宿主(浏览器)的不同,具体的表现形式也不尽相同。

50216571062b8c3e803ffc097ff6cafdb7ef0a31ae84506d66a9effa6590dcc5.jpg

事实上,浏览器是 JavaScript 最早的宿主环境,也是目前最常见的运行环境。换句话说,运行在浏览器上的 JavaScript 可以调用浏览器所提供的 API。所谓宿主环境就是运行 JavaScript 的平台,负责对 JavaScript 进行解析编译,以实现代码的运行。

随着互联网的普及,在 2010 年诞生的 Node.js,成为 JavaScript 的另一个宿主环境。从此 JavaScript 不仅可以在浏览器上运行,还可以在 Node.js 上运行。与浏览器相同,运行在 Node.js 上的 JavaScript 也可以调用 Node.js 提供的 API。

BOM 提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。通过 BOM,开发人员可以进行浏览器定位和导航、获取浏览器和屏幕信息、操作窗口的历史记录、获取地理定位、进行本地存储,以及 Cookie 操作等。

当创建好一个页面并加载到浏览器时,DOM 就悄然而生,它会把网页文档转换为一个文档对象,通过 DOM,我们可以获取页面中的元素,操作元素的内容、属性、样式,也可以创建、插入、删除节点,页面中的各种特效效果都需要通过 DOM 来实现。

10.1.2 应用场景

JavaScript 在生活中的应用场景随处可见,只要是与互联网有关的,几乎都使用了 JavaScript。比如开发前端页面特效时,应用在页面中的轮播图、下拉菜单等各种效果以及用户名格式校验、手机号码合法性校验等功能,再比如数据交互时,我们会发送 AJAX(Asynchronous Javascript And XML,Web 数据交互方式)请求将数据传递给后端。

轮播图是 JavaScript 中的经典案例,通过 JavaScript 实现了单击左右按钮切换图片、图片自动轮播等功能。以尚硅谷官网首页的轮播图为例,如图 10-2 所示。

82ee9b1b87e45db1a55b19fb85e6c2a0e6fa0520d72fd32e926259bd132e2473.jpg

表单验证举例来说就是登录注册信息。不管在网站上还是在各大 App 上,这都是必须具备的功能。当用户输入一些信息时,可以通过 JavaScript 对表单信息进行格式校验,根据结果进行页面上的相应处理。

这里我们以谷粒学苑的登录页面为例,如图 10-3 所示。

3171d57e288dee797b05893e927f5852fec6d899786d50d097852893e2a4fe8a.jpg

10.2 HelloWorld 案例

下面编写一个 HelloWorld 入门案例,演示 JavaScript 代码的简单应用。HelloWorld 案例的流程如图 10-4

f44785933629d56ef2752cc8c2092e72118c7eb33b485239272f06e86ab2105a.jpg

所示,通过 HTML 代码编写一个 SayHello 按钮,编写 JavaScript 代码实现当用户通过鼠标单击 SayHello 按钮时,弹出警告框并显示 “hello world” 字符串。

示例代码如下。

运行代码查看页面效果,如图 10-5 所示。

f0ea46ece5c5f39828570039fe092b1f003ee0d04a72d94c98c6b7e9a174ff16.jpg

然后单击 “SayHello” 按钮,弹出警告框并输出 “hello world” 字符串,如图 10-6 所示。

3f3bd051c477c3179cefac2498431c3692fb0cd7c885e8f1724e3e81f42dd08a.jpg

10.3 基本语法

了解了什么是 JavaScript,接下来继续讲解 JavaScript 的基本语法,包括 JavaScript 代码在 HTML 文档中的嵌入方法,以及变量、函数、数组、对象、json 等的使用。

10.3.1 代码嵌入方式

本书所讲解的是运行在浏览器上的 JavaScript 代码,需要嵌入到 HTML 中执行。而常见的编写位置有三种,本节将为读者依次展开介绍。

(1)使用 DOM 事件将 JavaScript 代码写在标签的内部,示例代码如下。

这里只需明白怎么将 JavaScript 代码放在标签上,代码的具体含义在后面的学习中会逐渐理解。需要注意的是,“console.log ()” 是用来在浏览器控制台输出内容的,方便调试 JavaScript 代码,在浏览器中单击 F12 键打开开发者工具便可以看到输出结果。相比 alert () 弹窗看到的内容更全面,接下来代码中都采用控制台输出方式。

运行这段代码后,页面会出现两个按钮,如图 10-7 所示。

a0b47ddab07be54b50bcd6caa70b4aab898d20320130b0b2ad9db8d1b2d2faef.jpg

当鼠标单击第一个按钮 “单击按钮” 可以触发 JavaScript 代码的执行,当鼠标移入第二个按钮 “鼠标移入按钮” 会触发对应的 JavaScript 代码的执行。像 onclick 与 onMouseenter 都是 DOM 事件,更多的 DOM 事件将在 10.8 节详细讲解。

另外使用此方式,HTML 与 JavaScript 代码没有做到分离,而是混在一起,导致代码可读性很差,因此这种方式并不推荐使用。

(2) 通过在 HTML 中内嵌 标签,将 JavaScript 代码写在 标签对,并且将 标签对放在了其他标签的后面。

运行代码查看页面效果,如图 10-8 所示。

93b4a6bad15294b7c9ddaea818564eaa3fb03c8a27d4f607c63630d967f752e2.jpg

其实 标签对放在 HTML 文件的任意位置都可以运行,但是我们仍然建议写在其他标签的后面。这是因为 JavaScript 代码的执行会阻塞 HTML 标签的加载,所以更建议开发人员将 JavaScript 代码写在其他标签的后面。等其他标签加载完毕,再执行 JavaScript 代码。除此之外,JavaScript 写在后面还有助于获取到元素,这一点在后面学习 DOM 的时候还会讲到,这里只做了解即可。

如果 标签写在了 HTML 标签前面,则功能会失效,可以通过 window.onload 去处理,代码如下。

还要注意的是,在 HTML4.01 标准中, 标签对即可。

(3)同样需要在 HTML 中内嵌 标签,但不是把 JavaScript 代码写在 标签对中,而是将 JavaScript 代码写在一个单独的 js 文件中。然后在 标签中通过 src 属性指定 js 文件的地址。

例如,在和文件同级别的目录下新建一个名为 “index.js” 的文件,将第一种方式 中的代码写在 “index.js” 文件中,然后再通过 标签的 src 属性引入。在 HTML 文件中可以这样写,示例代码如下。

这种写法可以实现 HTML 与 JavaScript 的分离,写法类似于使用 CSS 的 标签。只不过这里仍然使用 标签对。至于 标签对的位置仍然建议与第二种方式相同,位于 中其他标签的后面。

10.3.2 声明和使用变量

JavaScript 与 Java 语法类似,接下来分别介绍 JavaScript 代码中的变量、函数、对象和数组等的声明和使用。

JavaScript 程序中,使用变量之前需先声明。ES6 之前的版本,通过关键字 var 定义变量,示例代码如下。 var name;

代码中定义了一个名为 name 的变量,可以用它存储 JavaScript 支持的任意类型值。例如,给 name 赋值为字符串 “atguigu”。

事实上,变量的声明和赋值有以下三种方式。

上面这段代码采用的定义方式就是 “先声明,后赋值”。其实,“声明的同时进行赋值” 与 “先声明,后赋值” 的本质是一样的,区别是将赋值和变量声明写在一起了,请看下面的代码。

运行代码后,控制台输出字符串 atguigu,与 “先声明,后赋值” 的效果相同。

“一次声明多个变量” 也是在开发中常用的一种变量声明的方式。通常有两种使用情况,声明多个变量但是不赋值和声明多个变量并分别赋值。下面将对这两种情况进行分别演示。

声明多个变量不赋值是通过一个 var 关键字对多个变量进行声明,变量间使用逗号 “,” 进行分割,示例代码如下。

运行代码后,控制台输出 f=30、g=30。

声明多个变量并分别赋值是通过一个 var 关键字为多个变量声明赋值,变量间使用逗号 “,” 将变量隔开,示例代码如下。

运行代码后,控制台输出 d=10、e=20。

运行代码后,控制台输出 “undefined”。undefind 在英语中的意思为 “不明确的,未下定义的”,它是 JavaScript 中一个特殊的值。当变量仅仅被 var 定义,但是没有被赋值时,它的默认值是 undefined。

10.3.3 数据类型

前面提到,JavaScript 是动态类型编程语言,这意味着编程时无须指定变量的类型,JavaScript 引擎会自动识别数据的类型。但 JavaScript 是动态类型,并不意味着 JavaScript 没有类型。在 JavaScript 当中,数据类型主要分为两大类,基本数据类型(简单数据类型)和对象数据类型(复杂数据类型)。ES5 中基本数据类型有 5 种,number、string、boolean、undefined 和 null。复杂数据类型也被称作 Object 对象,包含数组、函数和对象。

JavaScript 提供了一个 typeof 运算符,用来检测任意变量的数据类型,示例代码如下。

在上述代码中,使用 typeof 关键字检测值 666 的类型,控制台输出结果为 “number”,因此值 666 的类型被称为 “数字类型” 或 “数值类型”。使用 typeof 关键字检测值 “atguigu” 的类型,控制台输出结果为 “string”,因此值 “atguigu” 的类型被称为 “字符串类型”。

使用 typeof 检测数据类型,如表 10-1 所示。需要注意的是,返回的都是数据类型名的小写字符串形式。

表 10-1 任意值经过 typeof 后的返回值

数据检测后
undefinedundefined
true 或 falseboolean
任意字符串string
任意数字或者 NaNnumber
任意对象(非函数)或者 nullobject
任意函数function

下面分别演示 5 种基本数据类型的输出结果。

1. number 类型

任何数字都是 number 类型,无论它是整数还是小数、正数还是负数、较大数还是较小数,都属于 number 类型。这个规定很重要,这是因为一些其他的编程语言如 Java、C++ 等,它们将数字区别为多种类型,比如整数被称为 int, 而小数被称为 float、double 等。注意,在 JavaScript 中,所有的数值都只有一种类型,即 number 类型,示例代码如下。

代码中我们用 typeof 分别测试了数字的各种情况,有整数、有小数、有较大数、有较小数以及负数,返回结果均为 “number”。

NaN 也是 JavaScript 中的一个特殊数值,一切数学运算如果难以产生普通数值结果,那么结果为 NaN。NaN 是英语 “not a number” 的缩写,它的英语原意为 “不是一个数”。有趣的是,虽然 NaN 表示 “不是一个数”,但它本身却是一个 number 类型值,示例代码如下。

运行代码后,控制台输出 NaN 和 number,这证明了 NaN 是一个 number 类型的值。

2. string 类型

字符串由零个或多个字符组成。字符包括字母、数字、标点符号和空格,将字符包含在单引号或者双引号内就是字符串,示例代码如下。

代码中用 typeof 检测了四个字符串的类型值,其中前两个用单引号包裹,后面两个用双引号包裹。需要特别讲解的是第四行代码,“1234” 看上去是数字,但因为嵌套了双引号的缘故变为字符串,所以使用 typeof 检测返回的结果是 “string”。

当把字符串赋值给某个变量时,在后续使用中需要注意使用变量不加引号,因为变量里保存的是字符串,而变量无论是定义还是使用,它依旧是变量,所以不必加上引号。

代码中我们给变量命名为 “str”,它是 string 的词头,是程序员对临时字符串常见的命名习惯。使用 typeof 判断变量 str 不用加引号,运行代码后输出 “string”。

在实际开发中,建议无论是使用单引号或者是双引号都应在开发中保持一致,一些公司会限制程序员统一使用某种引号。

3. boolean 类型

在 JavaScript 中,boolean 类型只有两个值:true 和 false,分别表示 “真” 和 “假”。“布尔类型” 得名于 19 世纪英国数学家乔治・布尔,他是符号逻辑学的开创者,示例代码如下。

这段代码将布尔值 true 和 false 分别存入变量 a 和 b 中,然后使用 typeof 进行类型检测,输出结果都是 “boolean”。需要注意的是,布尔值 true 和 false 是区分大小写的,因此不要书写为 True 和 False。

布尔值在实际开发中经常使用,比如 “关系运算” 的结果就是布尔值。

这段代码使用了大于运算符比较两组数的大小,JavaScript 中的大于运算符和数学中的大于运算符一样,都是用来比较符号两边的数字。当不等式成立时返回结果为 true,反之返回 false。

4. undefined 类型

前面学习变量相关知识时,我们提到一个变量如果只声明而没有赋值,则它的默认值是 undefined。

在 JavaScript 中,没有值的变量其值是 undefined,类型也是 undefined,示例代码如下。

在这段代码中,第一个输出的是变量 und 的值为 “undefined”,第二个输出的是变量 und 的类型为 “undefined”。代码中只是声明了变量,但是没有对它进行初始化。这个例子和下面的例子是等价的。

这段代码在初始化的时候将值设置为 undefined,但在日常开发中我们一般不这么做,因为未声明的变量其默认值就是 undefined。实际上我们可以在变量使用完成后,将变量赋值为 undefined 来清空变量数据。

5. null 类型

JavaScript 还有一个特殊值 null,其英语原意为 “空、无效”,顾名思义,null 在 JavaScript 中表示 “空” “设为无效”。null 属于基本数据类型,该类型只有一个值为 null,示例代码如下。

在这段代码中,第一个输出的是变量 a 的值为 null,第二个输出的是变量 a 的类型为 “object”。

大家可能会有疑惑,之前的基本数据类型中案例不是返回的都是它的类型吗?为什么使用 typeof 检测 null 会返回 “object” 呢?其实这被程序员认为是 JavaScript 的一个不能修正的小 “bug”,它和 number、string、boolean、undefined 一样,属于基本类型值,但是一定要记住,使用 typeof 检测 null 的结果是 “object”。

null 这个值的用法,即一般不再需要某个对象、函数或事件监听时,就将它设置为 null 即可。常见的数学运算、关系运算、逻辑运算的计算结果不会产生 null 值。

另外,在 ES6 之前没有特定的关键字来定义存储常量(值不能改变)数据,使用 var 关键字并不能定义真正的常量,因为使用 var 关键字定义的变量都是可以被修改的。为此 ES6 做出了改进,提供了 const 关键字来定义常量,也就是说使用 const 定义的常量是不能被修改的,一般用来保存不用改变的数据,示例代码如下。

使用 var 定义的 username1 的值被更改为 “尚硅谷”,使用 const 定义的 username2 会报错,报错信息为 “TypeError: Assignment to constant variable”,即不能给常量重新赋值。

如果你在实际开发中定义变量时,不确定这个变量在后期使用时是否需要修改,这种情况下建议使用 const 关键字定义。当在使用时发现需要更改该值时,可以再将声明该值的关键字更改为 var。

在编程的过程中,变量和常量的命名虽然没有很高的技术含量,但对于个人编码或者在一个团队的开发中是相当重要的。良好的书写规范可以让你的 JavaScript 代码更上一个台阶,也更有利于团队的再次开发和阅读代码。

如果随意命名,在小项目中看起来可能没什么影响,但是在大型项目中,当多人协作代码维护时,弊端就会显现出来,增加了理解代码的时间,也增加了代码维护的难度,很可能会造成很难发现的 bug。因此变量的命名规范在日常开发中是至关重要的。

在制定变量的名称时,必须遵守 “JavaScript 标识符命名规范”。所谓 “标识符” 是指变量名、函数名、类名等 “名字”。

JavaScript 标识符命名规范如下。

根据 JavaScript 标识符命名规范,例如,下面的变量命名都是合法的。

这五个变量的命名都是合法的。一定要记住,变量名中能够含有的 “符号” 只能是下画线 “_” 和美元符号 “$”,其他的一切符号都是非法的。变量名可以只有一个符号,比如上面最后一个例子 “_” 单独作为一个变量名,它没有违反标识符的命名规则是合法的。

例如,下面的变量命名都是非法的。

“JavaScript 标识符命名规范” 中不允许变量的名字和关键字及保留字同名。所谓 “关键字”,前文已经讲解过,像 “var” 这样的单词,在 JavaScript 内部本身就具有特殊的功能,它们被称为 “关键字”。所谓 “保留字” 是指当前 JavaScript 版本还没有将它们设置为关键字,但是可预见的将来 JavaScript 可能会发展相关的功能,从而它们有机会成为关键字,现阶段它们被予以 “保留”。JavaScript 中常见关键字和保留字如表 10-2 所示。

表 10-2 JavaScript 中常见关键字和保留字

关键字
breakcasecatchcontinuedefault
deletedodebuggerelsefinally
functionfalseforifin
instanceofnewnullreturnswitch
thistypeofthrowtruetry
varvoidwithwhileconst
classexportextendsimportstatic
superthrow
保留字
abstractbooleanbytechardouble
enumfinalfloatgotointerface
intimplementslongnativepackage
protectedprivatepublicsynchronizedshort
transientvolatile

在编程中,不仅要保证变量命名合法,而且要注意变量命名必须清晰、简明,做到 “见名知意”。试想,如果代码中的变量都用 a、b、c 等简单字母表示,那么其他程序员查看代码时,不能马上知晓这个变量的真实含义。如果变量名是 “chinaGoldMedalsNumber” 呢?你会立即猜到它表示的含义 “中国金牌数”,这就是 “见名知意”,尽管它有点长。

10.3.4 运算符和表达式

JavaScript 中的运算符和 Java 的运算符类似,按照功能可以分为算术运算符、赋值运算符、关系运算符、逻辑运算符、条件运算符等。

表达式 1?表达式 2:表达式 3

表达式可以是一个变量或数据,也可以是变量或数据与运算符的组合。JavaScript 解释器会计算表达式的结果并返回这个结果值。简单地说,一个表达式总会返回一个数据,任何需要数据的地方都可以使用表达式。

另外,简单介绍一下 Java 代码中没有的运算符,===(全等于)和!==(不全等于),全等于和不全等于除了要求数据是否相同,也会验证数据的格式是否相同,示例代码如下。

10.4 函数

函数是具有某种特定功能代码块的封装体。详细地说,函数就是封装了一些功能代码,当需要的时候可以多次调用函数,它的作用就是实现了代码的复用。

对于定义函数,常见的有两种方式,分别是函数声明、函数表达式。下面将对这两种定义方式依次展开介绍。

10.4.1 函数声明

函数声明是在 JavaScript 中使用 function 关键字来进行函数定义,语法格式如下。

function 在英文中是 “功能、函数” 的意思,表示其内部封装了一个功能。function 关键字后面是函数的名称,比如 function total () {},它的函数名称就是 total,也常叫作 total 函数。函数的名称需要符合标识符命名规范,只能由字母、数字、下画线、美元符号组成,不能以数字开头等,具体规则见 10.3.3 节。

函数名后的圆括号用来书写函数的参数,也叫作形式参数(简称形参)。形参相当于函数中定义的局部变量,需要在调用函数时传入参数的具体数据。形参的个数可多可少,一个函数可以有多个形参,也可以没有形参。圆括号中可以使用逗号来分隔形参,比如 “(a,b,c,d)”。但是需要注意的是,无论圆括号中是否存在参数,都要书写圆括号。

需要注意的是,与 Java 代码相比,JavaScript 函数中参数的声明无须表明其类型,且对于形参的个数更加随意,传入参数的个数和声明的参数个数不相等也是可以的,例如,声明三个参数,可以传入 2 个或者 4 个都是可以的,但为了代码的可读性和维护性,建议实参个数和形参个数保持一致。

花括号中用来书写功能性代码,也被叫作函数体。通常函数体内需要书写 return 关键字来返回值,简单地说,return 关键字后面的值,最终要返回给函数调用表达式。与 Java 代码不同,即便函数有返回值,也无须指定返回值类型。

代码中定义了函数 total,函数体内计算了形参 a 和形参 b 的和,最后使用 return 将结果返回。调用函数后返回结果为 “3”。注意,函数调用的结果就是函数体内 return 的结果值。

如果 return 关键字后面不写值,就会返回默认值 “undefined”。JavaScript 也允许函数体内没有 return 关键字,此时返回的同样是函数默认的返回值 “undefined”。

将函数体内 return 关键字的使用情况总结为以下三种情况。

需要注意的是:当函数使用了 return 语句后,这个函数在执行完 return 语句之后停止并立即退出,也就是说 return 后面的所有代码都不会再执行。在实际开发中推荐的做法是:要么让函数始终都返回一个值,否则就不要写返回值。

10.4.2 函数表达式

使用函数表达式方式,不再是在 function 关键字后定义函数名,而是通过 var 关键字来声明函数名称,其余部分与函数声明定义的语法相同。

将函数声明式定义改为函数表达式定义,示例代码如下。

其实不管使用哪种方式定义函数,本质上都是定义了一个变量,然后将函数数据赋值给这个变量。需要注意的是,函数在定义时是不会自动执行的,只有在调用函数的时候才会执行函数。

10.4.3 函数调用

了解了如何定义函数后,下面分别通过函数声明和函数表达式两种不同的方式定义两个函数,来演示几种不同的函数调用情况。

综上所述,在 JavaScript 中,对于函数要传递的参数,其个数和数据类型都是比较随意的。如果传入的实参少于形参,缺少的实参值为 undefined;如果传入的实参多于形参,结果只传入对应的实参个数,并不会影响函数的正常调用。

10.5 对象

在 JavaScript 中,有各种不同类型的对象。比如数字对象、布尔值对象、字符串对象、时间对象、函数对象、数组对象等,它们都继承于 Object 类型。

JavaScript 将对象分为三类:内置对象、宿主对象和自定义对象。内置对象是由 ES 标准中定义的对象,如 Object、Math、Date、String、Array、Number、Boolean、Function 等;宿主对象是 JavaScript 的运行环境提供的对象,主要指由浏览器提供的对象,如 BOM、DOM、console、document 等;自定义对象可以理解为自己创建的类,可以通过 new 关键字创建出来对象实例进行应用。

本节主要介绍自定义对象,也就是开发中需要开发人员自己创建的对象。JavaScript 语言提供了两种创建对象的方式:new object () 和对象字面量。虽然创建对象的方法很多,看上去语法差异也很大,但实际上它们都是大同小异的。下面分别介绍这两种创建对象的方式。

10.5.1 通过 new 关键字创建对象

在 JavaScript 中,可以使用 new 关键字和 Object 构造函数来显式地创建实例对象,语法格式如下。new Object ();

语法中通过 new 来执行 Object 函数,此时 Object 称为构造函数,此时会返回对应的实例对象。这样解释可能有些晦涩,通过如下代码来具体讲解。

这段代码通过 “new Object ()” 产生并返回一个实例对象,使用 obj 来接收这个实例对象,后面我们就可以通过 obj 来访问该实例对象。输出的结果如图 10-9 所示。

从图 10-9 中可以看到,对象中只有一个 “[Prototype]]” 属性,没有其他属性,该属性涉及了对象底层知识,这里暂且不做讲解。

3dc3811ecb4cf09ff507f726b3b2df30927cc0fd69ba6dc9df05214422374578.jpg

向对象内部增加属性,可以使用中括号 “[]” 操作符或者点 “.” 操作符。例如,给创建的 obj 对象添加 name 和 age 属性并赋值如下。

代码通过点 “.” 操作符向 obj 对象添加了 name 属性,其值为 “尚硅谷”。通过中括号 “[]” 操作符向 obj 对象添加了 age 属性,其值为 9。在控制台输出此时的 obj 对象,如图 10-10 所示。

4465b79659647a572c401d2c154ff1b2a7627da8fe020903017c4aee0dce700b.jpg

需要特别注意的是:一个对象中不能存在两个同名的属性,如果出现同名属性,后声明的属性会覆盖同名属性的值。例如,在 obj 对象中再次添加 age 属性。

在实例对象 obj 中,重复声明了两次 age 属性。根据前面所述,后声明的属性会覆盖同名属性的值,因此 obj 对象中的属性 age 对应的值是 6。运行代码,查看控制台,输出 obj 对象,如图 10-11 所示。

我们除了为对象设置属性和属性值,还可以为对象设置函数,示例代码如下。

运行代码,再次查看控制台,如图 10-12 所示。

726377623e35137fa9a3668e2ae4254268ec99347b52e2ebe1815efaf6ba2b9f.jpg

29377c705abe371d5cb1c7594af52e3129391f3522a3054656354777c2dd734a.jpg

10.5.2 对象字面量

JavaScript 中可以使用对象字面量快速定义对象,这是实际开发中最常用的方式,也是最高效、最简便的方式。语法格式如下。

{属性名 1:属性值 1,属性名 n:属性值}

在对象字面量中,属性名与属性值之间通过冒号进行分隔。属性名是一个字符串,一般省略引号。属性值可以是任意类型数据,当它是字符串时,引号是不能省略的。属性与属性之间通过逗号进行分隔,最后一个属性末尾一般不加逗号,但语法上是允许的。

如果属性值是对象,则可以设计嵌套结构的对象,示例代码如下。

在这段代码中,使用字面量定义了对象 obj,obj 对象内部存在两层嵌套。尽管属性 family 是一个实例对象,但它也是作为属性嵌套在实例对象 obj 内。属性 sister 同理,也是作为属性嵌套在实例对象 family 中。

运行代码后,查看控制台,如图 10-13 所示。

值得一提的是,如果当前的属性值是一个函数,则把当前属性称作方法。也就是说,方法是特殊的属性,示例代码如下。

034ff560e627ccb1c2246515a7bf2f1dc0a6e893cb910b2e645cab64918cc9fc.jpg

实例对象 obj 中 eat 属性的属性值是一个函数,此时可以将 eat 属性叫作 eat 方法。

实例对象内也可以不包含任何属性,也被称作空对象。示例代码如下。

10.5.3 this 关键字

this 关键字包括两种情况。如果出现在函数外面,this 关键字指向 window 对象,代表当前浏览器窗口。如果出现在函数内部,this 关键字指向调用函数的对象。

(1) this 关键字出现在函数外面。

(2) this 关键字出现在函数内部。

//2. 创建对象

//3. 调用函数

10.5.4 对象的使用

无论对象是通过哪种方式创建的,关于对象的使用都是一样的。例如,以之前创建的 obj 对象为例,分别演示如何调用对象的属性,以及对象的函数,示例代码如下。

10.6 数组

除了函数和对象,本节学习的数组也属于对象数据类型。JavaScript 中的数组与其他语言的数组大相径庭,以 Java 语言为例,Java 中的数组只能存储相同类型的数据,而 JavaScript 中的数组可以存储任意类型的数据。

数组是一系列有序数据的集合,数组中的每个数据都称作元素。元素可以是 JS 支持的任意类型。数组示例代码如下。

这行代码定义了一个数组 arr,它的内部分别存储了三个数据:“1”“atguigu”“true”。

数组也是对象,可以使用 typeof 和 instanceof 进行检测。

但是数组是一种特别的对象,它拥有自己的特性。数组中每个元素都有一个下标(也被称作索引),从 0 开始依次递增。每定义一个数组,在该数组中都会有一个默认属性为 length,它代表着数组中元素的个数,也被称作数组的长度,示例代码如下。

下面介绍如何创建数组,以及对于数组元素的增删改查等操作。

10.6.1 创建数组

数组的创建方式与对象数据类型的创建相似,包括两种方式:一种是使用数组字面量创建;另一种是使用 Array 构造函数创建。

以数组字面量的方式创建数组是直接使用中括号来包裹数据,中括号内以逗号间隔元素,而且数组字面量创建方式是使用频率较高的。示例代码如下。

在这段代码中,使用数组字面量的方式定义了三个数组:arr1、arr2、arr3。arr1 中包含了三个元素:

“1”“atguigu”,“3”;arr2 是一个空数组,内部没有元素;arr3 中只有一个元素 “3”。运行代码后,查看控制台,如图 10-14 所示。

另一种使用 Array 构造函数创建数组,它是创建数组的本质。其实字面量创建数组的方式的底层原理就是使用该方法进行创建的,示例代码如下。

使用构造函数创建数组可以省略 new 关键字,与使用构造函数的方式结果相同。需要注意的是,当参数为一个数字时,该参数代表着数组的长度。需要特别注意的是,使用构造函数创建数组可以省略 new 关键字,与使用 new 的方式结果相同,示例代码如下。

这段代码使用 Array () 的方式定义了 arr1、arr2、arr3、arr4 四个数组。arr1 中包含了三个元素:“1”“2”“3”;arr2 中只有一个元素:“atguigu”;arr3 中传递了一个数字,此时会返回一个长度为 3 的数组对象。arr4 定义了一个空数组,相当于 “const arr4=[]”。运行代码后,查看控制台,如图 10-15 所示。

b22101f473de66366154becf24b5363c009d47790bf30fa7239bd68cef3d5e36.jpg

1fb5ad7b7854c194042ae9a96cc33eeca1f55e78b3c34d43e15da5b55734bbe8.jpg

10.6.2 添加元素

创建完数组后,我们就可以对其元素进行增删改查操作了。我们可以通过索引读取数组中对应位置的元素,同样也可以通过索引添加新元素,示例代码如下。

// 创建数组

这段代码定义了一个空数组 arr,使用下标的方式向数组中添加了元素 “1” 和 “atguigu”。此时数组中 arr [0] 的值为 “1”,arr [1] 的值为 “atguigu”。运行代码后,查看控制台,如图 10-16 所示。

上述案例主要演示了向空数组中增加元素的情况,当数组内部有值时,可以通过 length 属性向数组的末尾增加元素,示例代码如下。

这段代码定义了数组 arr,里面包含三个元素:“1”“2”“3”。通过 “arr [5]” 的方式将 1000 赋值给 arr 数组中下标为 5 的元素,再通过 “arr.length” 得到 arr 数组的末尾(arr 数组中下标为 6),然后赋值为 1000。此时输出数组 arr,如图 10-17 所示。

714a0e5f111d426dd18b65b3bd724a66a37418d838fbc1092a6de4af7690d09e.jpg

0e533cee4e70701663301a2074c977b2af76dc5e170747b9c4258233658a595d.jpg

另外,添加元素还可以借助 push () 方法和 unshift () 方法。

push () 方法用于实现在数组末尾添加一个或多个元素,其返回值为添加元素后的新数组的长度,语法格式如下。

其中,item 分别表示添加到数组的元素。

示例代码如下。

unshift () 方法可以向数组第一位新增一个或多个元素,语法格式如下。

示例代码如下。

10.6.3 遍历数组

访问数组每个元素的过程叫作遍历。数组遍历有 for、for...in、forEach、for...of 四种方法。下面介绍两种常见的遍历数组的方式,即 for 循环语句和 for...in 循环语句。

使用 for 循环语句来遍历数组,是利用数组的 length 属性来控制 for 循环的执行次数,示例代码如下。

这段代码数组的第一个下标 “0” 作为起始循环值,在循环体内依次对数组中的每项值依次遍历。运行代码后,查看控制台,如图 10-18 所示。

59304cb42728c8e1cd793f111b61ed7c8548f588b644655bfd39003a1810847f.jpg

for...in 循环是一种特殊类型的循环,它是普通 for 循环的变体,其语法格式如下。

使用该方法需要传入两个参数,其中变量 variable 表示数组的下标,object 表示需要遍历的对象,在这里指的是数组。

示例代码如下。

运行结果同普通 for 循环结果。因为 for 语句需要配合 length 属性和数组下标来实现,所以执行效率没有 for...in 语句高。另外,for...in 语句会跳过空元素。对于超长数组来说,建议使用 for...in 语句进行迭代。

10.6.4 更新元素

通过前面的学习可以得知,可以使用下标更新对应的元素。如果想要更新多个元素要怎么操作呢?

请思考这个问题:将数组 [1,3,5] 每个元素值都增加 10,如何实现?

此时可以通过循环语句的方式对数组中每个元素进行遍历,再进行更新操作,如下代码所示。

这段代码封装了函数 addTen,函数内部遍历了形参 arr,对每项元素加 10 并返回。从而实现了数组内元素的更新。运行代码后,查看控制台,如图 10-19 所示。

1cc927f38def61add86dbf2f733dce61a38a5971b8e1f49d2ad7db2917afe6d8.jpg

10.6.5 删除数组元素

删除数组元素其实与数组添加元素的方式类似,可以在数组的头部、中间和尾部进行操作。

对数组尾部进行操作可以利用数组的属性 length 来实现,示例代码如下。

这段代码通过操作 length 属性,在数组的末尾进行了三次删除。因为在前面的操作中删除了 arr [4],所以最后输出 arr [4] 返回的结果为 undefined。运行代码后,查看控制台,如图 10-20 所示。

删除数组头部元素与在头部添加元素原理相同,都是利用 for 循环来实现,只不过删除头部元素是将数组中的整体元素前移,再删除一个元素,示例代码如下。

这段代码使用 for 循环,将数组中索引大于 0 的元素全部向前移动一位。通过 arr.length-- 删除 arr 数组的末尾,实现将数组的头部第一个数删除。运行代码后查看控制台,如图 10-21 所示。

c5d5b3673f079a0c4acf379bba1e18cc156826670986b628bdb31061540cb2c5.jpg

1745a150eb98e16bba84ea491ebfb24cc486ac1fb270f70da13a119b452e6de0.jpg

对于删除数组元素,JavaScript 也提供了三种简洁的方式:使用 pop 方法删除数组最后一个元素、使用 shift 方法删除数组的第一个元素和 splice 方法删除数组中指定位置元素。下面将分别讲解这三种方法的使用。

pop 方法可以在数组末尾删除一个元素,该方法是没有参数的,其方法的返回值为删除的元素,这个方法会影响原数组,示例代码如下。

这段代码使用 pop 方法将 arr 数组的最后一位删除,返回结果为删除的数组元素 “4”。运行代码后,查看控制台,如图 10-22 所示。

需要注意的是,如果操作数组为空数组,则返回结果为 undefined。

此时数组 arr 为空数组,运行代码后查看控制台,如图 10-23 所示。

dd9b9eee774d0068b5f6de80cde41cd2480ec7349bc4e4d871e96840d688656e.jpg

4a7bce6062247c35b031dd68372742a9cbd1b8b6274f9b5ac6f663ee11e863d7.jpg

shift 方法可以在数组头部删除一个元素,该方法是没有参数的,其方法返回值为返回删除的元素,同

样这个方法也会影响原数组,示例代码如下。

运行代码后查看控制台,如图 10-24 所示。

2c1695b22ba16cb0681ea257dec2dc2382b2f4f9a4b70f15444c1d0670bb9ccd.jpg

另外,如果操作数组为空数组,shift 方法与 pop 方法相同,返回结果也为 undefined。

splice 方法可以删除数组中指定位置的元素,该方法有两个参数,第一个是索引位置,第二个是删除数据个数。示例代码如下。

10.7 JSON

10.7.1 JSON 简介

JSON 全称为 JavaScript Object Notation,翻译为 JavaScript 对象表示法,是一种轻量级的数据交换格式,并不属于一种编程语言。它是基于 JavaScript 的一个子集,易于人的编写和阅读,也易于机器解析。JSON 采用完全独立于语言的文本格式,并且凭借其简洁和清晰的层次结构,使得 JSON 成为理想的数据交换语言。

JSON 主要由对象和数组两种结构组成,具体如下。

- 对象,使用键值对的无序集合表示,以 “{}” 开始,同时以 “}” 结束,键值对之间以 “:” 相隔,不同的键值对之间以 “,” 相隔。值得注意的是,这里的对象可以被称为记录、结构、字典、哈希表、有键列表或关联数组等。JSON 对象的语法格式如下所示。

- 数组,使用值的有序列表表示,以 “[” 开始,同时以 “]” 结束。JOSN 数组的语法格式如下所示。[value, value, ..., value]

其中,键值对中 “key” 的类型固定是字符串,“value” 的类型可以是基本数据类型,也可以为引用类型,如 JSON 对象或 JSON 数组。以上两种数据结构都是常见的数据结构,事实上大部分计算机语言都能够以某种形式支持它们。这使得 JSON 这种数据格式,能够轻松地在基于这些数据结构的编程语言之间进行交换。示例代码如下,JSON 语法定义了一个 sites 对象,该对象是包含 3 条网站信息(对象)的数组。

JSON 之所以受欢迎,主要是因为它仍然使用 JavaScript 语法来描述数据对象,并没有改变开发人员的使用习惯,这使得开发人员更容易接受。由于这种相似性,JavaScript 程序无须解析器,便可以直接用 JSON 数据来生成原生的 JavaScript 对象。

JSON 主要有以下特性,正是这些特性使它成为理想的数据交换语言。

JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。目前常见的动态编程语言(PHP、JSP、.NET)都支持 JSON。

JSON 是存储和交换文本信息的一种语法,它与 XML 具有相同的特性,是一种数据存储格式,却比 XML 更小、更快、更易于人编写和阅读、更易于生成和解析。

类似于 XML 的特性,具体如下。

相比 XML 的不同之处,具体如下。

例如,创建 JSONObject 对象,借助 JavaScript 代码输出,示例代码如下。

运行代码查看页面效果,如图 10-25 所示,成功输出 weibo 和 github 对应的值。

da56c37d5e75789b076630d463e5dfbe82542b6f00283672cb6526bd759b23b3.jpg

10.7.2 JSON 格式应用

在开发中涉及跨平台数据传输时,首选的数据类型一定是 JSON 格式。因为 JSON 格式中 value 部分还可以继续使用 JSON 对象或 JSON 数组,所以 JSON 格式是可以多层嵌套的,不论多么复杂的数据类型都可以表达。

例如,创建简单的 JSON 对象,示例代码如下。

运行代码后查看控制台,如图 10-26 所示。

创建复杂的 JSON 对象,对象中包含对象属性,示例代码如下。

运行代码后查看控制台,如图 10-27 所示。

58c9b8d5689c21538885a22e281114c790fb9d194ae688a4034ae9c2b2917376.jpg

ec574634ddfa1cf394fd51ff7d064ee13cee8def1bc48f17346c984c56d3ed2d.jpg

创建简单的 JSON 数组,示例代码如下。

运行代码后查看控制台,如图 10-28 所示。

3c023f365472f87677ae81d76c28814f07c91b5d4c2dd3f477970bebd26f60d5.jpg

创建复杂数组(对象数组),示例代码如下。

运行代码后查看控制台,如图 10-29 所示。

创建复杂对象(数组对象),示例代码如下。

运行代码后查看控制台,如图 10-30 所示。

07e3bc2be3fe315cfcace9e89c1823c70faf9bfbfc13ca82e1bbafcc554a3a59.jpg

e27cc4afd334036408a38046c217319b64c3e727003cc0fb8d6c80a954ece8ce.jpg

10.7.3 JSON 对象和 JSON 字符串互转

JSON 格式在语法上与创建 JavaScript 对象代码是相同的。由于它们很相似,所以 JavaScript 程序可以很容易地将 JSON 数据转换为 JavaScript 对象。

值得注意的是,JSON 对象是可以通过对象。属性的方式获取到属性值,但是 JSON 字符串是办不到的,因为 JSON 字符串的数据类型是 String。

(1) JSON 对象转 JSON 字符串。

运行代码后查看控制台,如图 10-31 所示。

6384f268876de3e62ac61d8c2ae1a93bfa5232d6e8c04fe56a324c68c3615589.jpg

(2) JSON 字符串转 JSON 对象。

运行代码后查看控制台,如图 10-32 所示。

9a66816e77fd9dc98442d02bd63851144805cfd7c3ef31c5834facc050e79272.jpg

10.8 DOM 操作

DOM 是 Document Object Model 的缩写,翻译为文档对象模型,简单来说就是将 HTML 文档抽象成模型,再封装成对象,目的是方便程序操作。这是一种非常常用的编程思想,将现实世界的事物抽象成模型,这样就很容易使用对象来量化描述现实事物,从而把生活中的问题转化成一个程序问题,最终实现用应用软件来协助解决现实问题。而模型就是连通现实世界和代码世界的桥梁。

DOM 是 W3C(World Wide Web Consortium)制订的一套技术规范,用来描述 JavaScript 脚本如何与 HTML 进行交互的 Web 标准。W3C 文档对象模型(DOM)是中立于平台和语言的接口,它允许程序动态地访问、更新文档的内容、结构和样式。W3C DOM 标准被分为三个不同的部分:Core DOM 代表所有文档类型的标准模型;XML DOM 代表 XML 文档的标准模型;HTML DOM 代表 HTML 文档的标准模型,

如图 10-33 所示。

DOM 的历史可以追溯至 1990 年后期微软与 Netscape 的 “浏览器大战”,双方为了在 JavaScript 与 JScript 之间一决生死,于是大规模赋予浏览器的强大功能。

在加载 HTML 页面时,Web 浏览器生成一个树形结构,用来表示页面内部结构。DOM 将这种树形结构理解为由节点组成的 DOM 树。DOM 规定了一系列标准接口,允许开发人员通过标准方式访问文档结构、操作网页内容、控制样式和行为等。

18ba3f28c5aa410fce678e91fde6acf803a91378ae86aef04926b9867b1aae69.jpg

10.8.1 DOM 树

浏览器把 HTML 文档从服务器下载后,就开始按照从上到下的顺序读取 HTML 标签。每个标签都会被封装成一个对象。

而第一个读取到的肯定是根标签 html,然后是它的子标签 head,再然后是 head 标签里的子标签等,以此类推直到遍历完所有的标签。因此从 html 标签开始,整个文档中的所有标签都会根据它们之间的父子关系被放到一个树形结构的对象中,如图 10-34 所示。

3a634840d3582bea2b4f0a2c4072a9d964f7b087e9ccc690e64988238f3b4027.jpg

树形结构包括父子关系,如图 10-35 所示,以及先辈与后代的关系,如图 10-36 所示。

d834abdb83e8718ad1e9772d7e5386cb90ebdcc97880f4574d62bd6447deaf73.jpg

604e48a66afade467d8cd1ec01c2b49a716f87ff61d2a0e872c235343259db8e.jpg

其中这个包含了所有标签对象的整个树形结构对象,就是 JavaScript 中的一个可以直接使用的内置对象 ——Document 对象。例如,下面的标签结构。

浏览器解析后得到的结果,如图 10-37 所示。

684de9cce7118e34960848bc0bfb762c0b2fd797bd6e60e94b68e9d10ac625b2.jpg

整个文档中的一切都可以看作 Node 节点。各个组成部分的具体类型可以看作 Node 类型的子类。HTML 文档与 Node 节点之间的对应关系如表 10-3 所示。

表 10-3 HTML 文档与 Node 节点之间的对应关系

组成部分节点类型具体类型
整个文档文档节点Document
HTML 标签元素节点Element
HTML 标签内的文本文本节点Text
HTML 标签内的属性属性节点Attribute

严格来说,JavaScript 并不支持真正意义上的继承,这里我们借用 Java 中的继承概念,从逻辑上来帮助我们理解各个类型之间的关系。

10.8.2 查询操作

由于实际开发时,基本上都是使用 JavaScript 的各种框架来操作,而框架中的操作方式和我们现在看到的原生操作完全不同,所以下面展示的 API 仅供参考,不做要求。

(1)在整个文档范围内查询元素节点,如表 10-4 所示。

表 10-4 在整个文档范围内查询元素节点

功能具体方法返回值
根据id值查询document.getElementById("id值")一个具体的元素节点
根据标签名查询document.getElementsByTagName("标签名")元素节点数组
根据name属性值查询document.getElementsByClassName("name值")元素节点数组
根据类名查询document.getElementsByClassName("类名")元素节点数组

(2)在具体元素节点范围内查找子节点,如表 10-5 所示。

表 10-5 在具体元素节点范围内查找子节点

功能具体方法返回值
查找子标签element.children子标签数组
查找第一个子标签element.firstElementChild标签对象
查找最后一个子标签element.lastElementChild节点对象

(3)查找指定元素节点的父节点,如表 10-6 所示。

表 10-6 查找指定元素节点的父节点

功能具体方法返回值
查找指定元素节点的父标签element.parentElement标签对象

(4)查找指定元素节点的兄弟节点,如表 10-7 所示。

表 10-7 查找指定元素节点的兄弟节点

功能具体方法返回值
查找前一个兄弟标签node.previousElementSibling标签对象
查找后一个兄弟标签node.nextElementSibling标签对象

使用 DOM 操作查找元素,示例代码如下。

运行代码查看页面效果,如图 10-38 所示。

b718457cc67755faecdce92fe2183cae367d2cd473bde0c10de227cc7a70ee20.jpg

10.8.3 元素属性与标签体操作

关于元素属性操作和标签文本值的操作具体如下。

(1)关于属性的操作,如表 10-8 所示。

表 10-8 属性操作

需求操作方式
读取属性值元素对象.属性名
修改属性值元素对象.属性名=新的属性值

(2) 关于标签体的操作,如表 10-9 所示。

表 10-9 标签体操作

需求操作方式
获取标签体的文本内容element.innerHTML
获取标签体的内容element.innerHTML
设置标签体的文本内容element.innerHTML=新的文本值
设置标签体的内容element.innerHTML=新值

使用 DOM 操作属性和标签体,示例代码如下。

运行代码查看页面效果,如图 10-39 所示。

93c36e9c8bcb1561c0b897dde0f7944e1407270f7ba4fb44118d0f39ff708b4c.jpg

10.8.4 增删改操作

使用 DOM 还可以对文档内容进行增删改操作,下面介绍一些常用的操作,如表 10-10 所示。

表 10-10 DOM 增删改操作

功能具体方法
创建元素节点并返回,但不会自动添加到文档中document.createElement("标签名")
将 ele 添加到 element 所有子节点的后面element.appendChild(ele)
将 newEle 插入到 targetEle 前面parentEle.insertBefore(newEle,targetEle)
用新节点替换原有的旧子节点parentEle.replaceChild(newEle, oldEle)
删除某个标签element.remove()

例如,创建一个城市列表,示例代码如下。

运行代码查看页面效果,如图 10-40 所示。

在城市列表的最后添加一个子标签 “长沙”,示例代码如下。

运行代码查看页面效果,如图 10-41 所示。

ba8c5f3c315e9e898f4b794de2cfd6aa482eee1ebd367f8aa44384f0d3503520.jpg

d9372b4b07297323b0350404cf72d9eb5d7713a9441a8e065fe1ad912a0c81d3.jpg

还可以在城市列表中的 “深圳” 之前添加一个子标签 “长沙”,示例代码如下。

运行代码查看页面效果,如图 10-42 所示。

在城市列表中添加一个子标签 “长沙” 替换深圳,示例代码如下。

运行代码查看页面效果,如图 10-43 所示。

c74862efce10d2d2a95ef3f15867eba3d96b7ccfc9d222127b614035a55bdcf0.jpg

6b1cbbc52a70bb5a0fdca50dc9a634a079eb5c728f2a736d526d458d897cd821.jpg

在城市列表中删除子标签 “深圳”,示例代码如下。

运行代码查看页面效果,如图 10-44 所示。

c97e320671abf936684bbd81f0a0ee33745283fc3efd644164eac0b7d688246b.jpg

最后,清除城市列表中的所有城市,保留城市列表标签

    ,示例代码如下。

    运行代码后,发现页面没有任何城市列表信息。

    10.9 事件驱动

    10.9.1 事件简介

    HTML 事件是发生在 HTML 元素上的 “事情”, 是浏览器或用户做的某些事情。事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数执行。常见事件如表 10-11 所示。

    表 10-11 常见事件

    事件说明事件发生的时间
    onclick鼠标单击事件当用户单击某个对象时触发事件
    ondblclick鼠标双击事件当用户双击某个对象时触发事件
    onchange改变事件域的内容发生改变时触发事件
    onblur失去焦点事件元素失去焦点时触发事件
    onfocus获得焦点事件元素获得焦点时触发事件
    onload加载完成事件文档加载完成后触发事件,并且能够为该事件注册事件处理函数
    onsubmit表单提交事件单击“确认”按钮或者表单被提交时触发事件
    onkeydown某个键盘按键被按下时触发事件
    onkeyup某个键盘按键被松开时触发事件
    onmousedown鼠标按钮被按下时触发事件
    onmouseup鼠标按钮被松开时触发事件
    onmouseout鼠标从某元素移开时触发事件
    onmouseover鼠标移到某元素之上时触发事件
    onmousemove鼠标被移动时触发事件

    事件绑定的方式分为普通函数方式和匿名函数方式两种,普通函数方式通过设置标签属性值的方式实现,匿名函数方式借助 function 函数实现。

    - 普通函数方式

    <标签 事件属性 ="js 代码,调用函数"></ 标签>

    - 匿名函数方式

    标签对象。事件属性 = function () {

    下面介绍几种常见事件的应用。

    10.9.2 单击事件

    创建两个 button 按钮,分别使用普通函数方式和匿名函数方式给按钮绑定单击事件,实现每单击一次按钮就会弹出警告框。

    运行代码查看页面效果,如图 10-45 所示。

    506e14262151083fdf77fa46d9a1d00576b80f04b9d204c946be78346ef60c90.jpg

    单击 “按钮 1”,查看控制台,如图 10-46 所示。

    单击 “按钮 2”,查看控制台,如图 10-47 所示。

    b063c6bf352fbac86e6c7bca81dd76823d3f45347cbc92223ba10d2fe44ea5d1.jpg

    fde6b6ca65d30ee9c26454b86c8b79d031d8d0d63e3402f71712344279b6de22.jpg

    10.9.3 焦点事件

    焦点事件包括获得焦点事件和失去焦点事件。下面分别演示这两者,创建输入框,给输入框设置获得和失去焦点事件。

    运行代码查看页面效果,如图 10-48 所示。

    8417baaa85f6652acbae4dec7f9827c0baad57a6f1f4f473a2f8c78cecd999e0.jpg

    当光标在输入框里面时触发获得焦点事件,如图 10-49 所示,输入 “123”,查看控制台。

    当光标从输入框移开时触发失去焦点事件,如图 10-50 所示,查看控制台。

    91ac0c999d19c68695931c12a744ffdda530dc946df6742e7ee134890fd84db8.jpg

    2b59e4507d6ca47f5cff0e8fbe86d833b3b3b4a3b9947d210dd1b81fe09fb5ab.jpg

    10.9.4 内容改变事件

    创建下拉框并为其设置 onchange 事件。当下拉框里的城市信息发生改变时便会触发该事件。

    运行代码查看页面效果,如图 10-51 所示。

    92109edbe5b96f2d7b5934a9895d038716b47d0213556bee298411dee16597b5.jpg

    当城市从 “北京” 改为 “上海” 时查看控制台,如图 10-52 所示。

    e541a98c2512bfd289aa2c9a7db24462acff6d4889ff1b5d3a7a73576b323f25.jpg

    其他事件和上述介绍事件操作方式一致。

    10.10 正则表达式

    正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符及这些特定字符的组合,组成一个 “规则字符串”,这个 “规则字符串” 用来表达对字符串的一种过滤逻辑。通俗来讲就是正则表达式是用来校验字符串是否满足一定规则的公式。

    正则表达式的作用主要包括模式验证、匹配读取、匹配替换,具体如下。

    10.10.1 基本语法

    定义正则表达式,包括对象形式和直接量形式两种。

    (1) 对象形式。

    // 类似创建数组可以 new Array ()、创建对象可以使用 new Object ()

    \[ \text { var reg } = \text { new RegExp("a"); } \]

    (2) 直接量形式。

    // 类似创建数组时可以使用 []、创建对象可以使用 {}

    \[ \text { var reg } = / a /; \]

    另外,正则表达式本身也是一个字符串,它由两种字符组成:普通字符和元字符。普通字符如大、小写英文字母,数字等。元字符指被系统赋予特殊含义的字符。例如,\(^\) 表示以某个字符串开始,$ 表示以某个字符串结束。

    正则表达式的字符集合包括以下三种不同的语法格式,如表 10-12 所示。

    表 10-12 字符集合的语法格式

    语法格式示例说明
    [字符列表]正则表达式[abc]:表示目标字符串包含abc中的任何一个字符则匹配成功。如目标字符串为plain,判断是否匹配,结果匹配成功,因为plain中的“a”在列表“abc”中目标字符串中任何一个字符出现在字符列表中就算匹配
    [^字符列表]正则表达式[^abc]:表示目标字符串包含abc以外的任何一个字符则匹配成功。如目标字符串为plain,判断是否匹配,结果匹配成功,因为plain中包含“p”“l”“i”n”匹配字符列表中未包含的任意字符
    [字符范围]正则表达式[a-z],匹配所有小写英文字符组成的字符列表正则表达式[A-Z],匹配所有大写英文字符组成的字符列表匹配指定范围内的任意字符

    在正则表达式中被赋予特殊含义的字符,不能被直接当作普通字符使用。常见的元字符如表 10-13 所示。

    表 10-13 常见的元字符

    说明
    .匹配除换行字符以外的任意字符
    \w匹配字母或数字或下画线等价于[a-zA-Z0-9_]
    \W匹配任何非单词字符。等价于[^A-Za-z0-9_
    \s匹配任意的空白符,包括空格、制表符、换页符等。等价于[\f\n\r\t\v]
    \S匹配任何非空白字符。等价于[^\f\n\r\t\v]
    \d匹配数字。等价于[0-9]
    \D匹配一个非数字字符。等价于[^0-9
    \b匹配单词的开始或结束
    ^匹配字符串的开始,但在[]中使用表示取反
    $匹配字符串的结束

    如果要匹配元字符本身,需要对元字符进行转义,转义的方式是在元字符前面加上 “\”,例如 “^\”。还有一些常见的元字符,表示出现的次数,如表 10-14 所示。

    表 10-14 表示出现次数的元字符

    说明
    *出现零次或多次
    +出现一次或多次
    ?出现零次或一次
    {n}出现n次
    {n,}出现n次或多次
    {n,m}出现n到m次

    正则表达式可以用于校验用户名、密码等是否符合要求,常用的正则表达式如表 10-15 所示。

    表 10-15 常用的正则表达式

    正则表达式
    校验用户名/^[a-zA-Z_][a-zA-Z_-0-9]{5,9}$/
    校验密码/^[a-zA-Z0-9_-@\#\&*]{6,12}$/
    校验前后空格/^s+|\s+$/g
    校验电子邮箱/^[a-zA-Z0-9_-]+@([a-zA-Z0-9-]+[\.{1})+[a-zA-Z]+$/

    10.10.2 正则表达式的应用

    下面通过具体场景应用正则表达式,包括模式验证、匹配读取、全文查找等方面。

    (1) 模式验证。

    注意,这里使用正则表达式对象来调用方法。

    (2) 匹配读取。

    注意,这里使用字符串对象来调用方法。

    (3) 替换。

    注意,这里使用字符串对象来调用方法。

    (4) 全文查找。

    正则表达式中 “/g” 表示全文查找。例如,判断目标字符串 “Hello World!” 中大写字母的个数。

    正则表达式中没有使用全局匹配的情况如下。

    借助 /g 使用了全局匹配后,示例代码如下。

    // 目标字符串

    // 使用了全局匹配的正则表达式

    // 获取全部匹配

    // 遍历数组,发现可以获取 “H” 和 “W”

    对比以上两种情况,如果不使用 g 对正则表达式对象进行修饰,则使用正则表达式进行查找时,仅返回第一个匹配;使用 g 后,返回所有匹配。

    (5) 忽略大小写。

    正则表达式中 “/i” 表示忽略大小写。例如,判断目标字符串 “Hello WORLD!” 中包含小写字母 o 的个数,不忽略大小写的示例代码如下。

    // 目标字符串

    // 没有使用忽略大小写的正则表达式

    // 获取全部匹配

    // 遍历数组,仅得到 '。'

    借助 i 忽略大小写后,查找到 o 和 O 两个结果。

    // 目标字符串

    // 使用了忽略大小写的正则表达式

    // 获取全部匹配

    // 遍历数组,得到 '○' 和 '○'

    (6) 元字符的使用。

    例如,判断以下两个字符串 “I love Java” 和 “Java love me” 是否以 Java 开头。

    再例如,判断以下两个字符串 “I love Java” 和 “Java love me” 是否以 Java 结尾。

    (7) 字符集合的使用。

    例如,判断字符串 “123456789” 是否匹配 n 位数字的正则表达式。

    例如,判断字符串 “HelloWorld” 是否匹配含有数字或字母或下画线的 6~16 位的正则表达式。

    10.11 案例:水果库存静态页面功能优化

    下面借助 JavaScript 的相关知识对水果库存静态页面进行优化,功能优化主要包括鼠标悬浮和离开时的操作、单价的更新,以及删除指定行。

    10.11.1 鼠标悬浮效果实现

    对于水果库存静态页面,实现当鼠标悬浮在某一行时,该行字体和背景颜色加深表示特指,与其他行区分开;而当鼠标离开后,该行恢复到原状。

    对静态页面中不同水果的所在行, 标签上绑定 onmouseover 事件和 onMouseout 事件。onmouseover 事件,表示鼠标悬浮时触发的事件;onmouseout 事件,表示鼠标离开时触发的事件。并且为了代码的简洁易懂,我们把 JavaScript 代码单独放到一个文件,命名为 demo01.js,具体实现如下,其中 showBGColor () 函数表示鼠标悬浮时的具体操作,clearBGColor () 函数表示鼠标离开时的具体操作。

    然后在 demo01.html 文件中引入 demo01.js 文件,借助