0% found this document useful (0 votes)
166 views621 pages

零基礎JavaScript從入門到精通

《零基础JavaScript从入门到精通》是一本为初学者设计的编程书籍,涵盖JavaScript的基础知识、核心编程概念及浏览器编程等内容,旨在帮助读者快速掌握这门语言。书中提供丰富的示例、视频教学和在线答疑服务,确保学习者能够有效理解和应用所学知识。该书适合编程初学者、爱好者及希望进入互联网行业的人员。

Uploaded by

yaochiny
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
166 views621 pages

零基礎JavaScript從入門到精通

《零基础JavaScript从入门到精通》是一本为初学者设计的编程书籍,涵盖JavaScript的基础知识、核心编程概念及浏览器编程等内容,旨在帮助读者快速掌握这门语言。书中提供丰富的示例、视频教学和在线答疑服务,确保学习者能够有效理解和应用所学知识。该书适合编程初学者、爱好者及希望进入互联网行业的人员。

Uploaded by

yaochiny
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

零壹快学《零基础JavaScript从入门到精通》编委会

主 编:郑显军
副 主 编:宋宏宇 杨 森
编委会成员:李延杰 蔡宇轩 吕鉴倬 汪心桐
廖 旭 吕 严
前言
历经七十多年的发展,无论是对于国内数以十万计的学习者而言,还是在有着多年培训经验的编者
们看来,学习编程语言,仍存在不小的难度,甚至有不少学习者因编程语言的复杂多变、难度太大而选
择了中途放弃。实际上,只要掌握了其变化规律,即使再晦涩难懂的计算机专业词汇也无法阻挡学习者
们的脚步。对于初学者来说,若有一本能看得懂,甚至可以用于自学的编程入门书是十分难得的。为初
学者提供这样一本书,正是我们编写本套丛书的初衷。
零壹快学以“零基础,一起学”为主旨,针对零基础编程学习者的需求和学习特点,由专业团队量身
打造了本套计算机编程入门教程。本套丛书的作者都从事编程教育和培训工作多年,拥有丰富的一线教
学经验,对于学习者常遇到的问题十分熟悉,在编写过程中针对这些问题花费了大量的时间和精力来加
以阐释,对书中的每个示例反复推敲,加以取舍,按照学习者的接受程度雕琢示例涉及的技术点,力求
成就一套真正适合初学者的编程书籍。
本套丛书涵盖了Java、PHP、Python、JavaScript、HTML、CSS、Linux、iOS、C语言、C++、C#等
计算机语言,同时借助大数据和云计算等技术,为广大编程学习者提供计算机各学科的视频课程、在线
题库、测评系统、互动社区等学习资源。
◆ 课程全面,聚焦实战
本套丛书涵盖多门计算机语言,内容全面、示例丰富、图文并茂,通过通俗易懂的语言讲解相关计
算机语言的特性,以点带面,突出开发技能的培养,既方便学习者了解基础知识点,也能帮助他们快速
掌握开发技能,为编程开发设计积累实战经验。
◆ 专业团队,紧贴前沿
本套丛书作者由一线互联网公司高级工程师、知名高校教师和研究所技术人员等组成,线上线下同
步进行专业讲解及点评分析,为学习者扫除学习障碍。与此同时,团队在内容研发方向上紧跟当前技术
领域热点,及时更新,直击痛点和难点。
◆ 全网覆盖,应用面广
本套丛书已全网覆盖Web、APP和微信小程序等客户端,为广大学习者提供包括计算机编程、人工
智能、大数据、云计算、区块链、计算机等级考试等在内的多门视频课程,配有相关测评系统和技术交
流社区,互动即时性强,可实现在线教育随时随地轻松学。
JavaScript是最流行的Web编程语言之一。它是一种基于原型链和事件驱动的跨平台解释型执行语
言,广泛应用于Web网站开发,嵌套在HTML页面中,能够直接在电脑、电视、手机、平板等终端设备
上运行。
JavaScript语言易学易用,受到浏览器厂商和众多开发者的喜爱,从诞生到现在牢牢占据着Web前端
编程语言的第一位。本书以编程初学者的视角,详细介绍了JavaScript从入门到精通需要掌握的技术知
识,同时特别指出容易出现误解的知识点,希望能够帮助读者快速入门,并掌握JavaScript这门语言的特
性。
本书内容
◆ 基础知识:第1~4章,主要介绍了JavaScript的概况、基础语法和基本语句,帮助读者打好基
础,快速进入JavaScript的学习之旅。
◆ 核心编程:第5~12章,主要介绍JavaScript编程的主要知识点,涵盖运算、流程控制、函数、对
象、数组、字符串、正则表达式、原型链等,帮助读者掌握JavaScript的重点知识和设计理念。
◆ 浏览器编程:第13~17章,主要介绍JavaScript主要运行环境——浏览器的相关知识,涵盖
BOM、DOM、事件处理、表单、AJAX等,帮助读者理解与JavaScript密切相关的浏览器环境,学会处
理实际应用中可能遇到的疑难问题。
◆ 进阶应用:第18~22章,将JavaScript与HTML、CSS完整结合,介绍了HTML5的进阶知识点和
流行框架,帮助读者掌握使用JavaScript构建应用的技能,向更高层次的JavaScript应用开发迈进。
◆ 项目实战:第23章,以完整的项目案例演示JavaScript的实战开发过程,增强读者的实战能力、
分析和解决问题的能力。
本书特点
◆ 由浅入深,全面讲解。本书以编程初学者的视角,循序渐进地讲解JavaScript的语法知识,让初
学者逐步掌握JavaScript的语法规则和编程思想。
◆ 示例丰富,贴近场景。本书提供了丰富的代码示例,而且每段代码后都有解释,便于读者清晰
理解代码的含义。这些示例大多选自工作中的各类场景,力求做到编程场景化,让读者可以感受到真实
的企业编程,提高分析解决问题的能力,增加实战操作经验。
◆ 视频教学,动手操作。本书每一章都配有教学视频,直观展示了JavaScript代码运行的效果,并
配有通俗易懂的解释。
◆ 知识拓展,难度提升。本书在每一章末尾设有“小结”和“知识拓展”。通过在“知识拓展”中列举一
些重要或有一定难度的知识点,为感兴趣的读者提供更多的拓展类学习内容,多维度强化自身的学习,
加深对JavaScript的理解。
◆ 线上问答,及时解惑。本书为确保广大读者的学习能够顺利进行,提供了在线答疑服务,希望
通过这种方式及时解决各位读者在学习JavaScript的过程中遇到的困难和疑惑。
本书配套资源(可扫下方二维码获取)
◆ 大量的代码示例。通过运行这些代码,读者可以进一步巩固所学的知识。
◆ 零壹快学官方视频教程。力求让读者学以致用,知行并进,加强实战能力。
◆ 在线答疑。为读者解惑,帮助读者解决学习中的困难,快速掌握要点难点。
本书适用对象
◆ 编程的初学者、爱好者与自学者
◆ 高等院校和培训学校的师生
◆ 职场新人
◆ 准备进入互联网行业的再就业人群
◆ “菜鸟”程序员
◆ 初、中级程序开发人员

零壹快学微信公众号

《零基础JavaScript从入门到精通》从初学者角度出发,详细讲述了JavaScript编程语言的基础知识
点和应用开发所需要掌握的技能。全书内容通俗易懂,代码示例丰富,步骤清晰,图文并茂,可以使读
者轻松掌握JavaScript的精髓,是一本实用的JavaScript入门书。
编者
2019年7月
目 录
零壹快学《零基础JavaScript从入门到精通》编委会

前言

第1章 走进 JavaScript
1.1 JavaScript简介

1.1.1 JavaScript的起源

1.1.2 JavaScrip的特点

1.1.3 ECMAScript 6

1.1.4 ECMAScript版本发布

1.2 JavaScript开发环境

1.2.1 Sublime Text下载和安装

1.2.2 WebStorm下载和安装

1.3 JavaScript运行环境

1.3.1 浏览器

1.3.2 Node.js

1.4 学好JavaScript的建议

1.5 小结

1.6 知识拓展

1.6.1 常用软件资源

1.6.2 浏览器兼容性

1.6.3 开发者社区

第2章 初次使用JavaScript
2.1 Hello, JavaScript

2.1.1 编写脚本

2.1.2 执行脚本

2.2 编写JavaScript程序
2.2.1 内联JavaScript

2.2.2 外联JavaScript

2.2.3 行内JavaScript

2.3 执行JavaScript程序

2.3.1 代码块

2.3.2 变量提升

2.3.3 输出结果

2.3.4 动态执行

2.3.5 延迟执行

2.4 小结

2.5 知识拓展

2.5.1 JavaScript框架

2.5.2 JavaScript压缩

第3章 JavaScript语法
3.1 语法基础

3.1.1 标识符

3.1.2 直接量

3.1.3 变量

3.1.4 常量

3.1.5 注释

3.1.6 保留字

3.2 数据类型

3.2.1 布尔值

3.2.2 数字

3.2.3 字符串

3.2.4 对象

3.2.5 空值null

3.2.6 未定义undefined

3.2.7 使用typeof获取数据类型
3.3 严格模式

3.3.1 开启严格模式

3.3.2 严格模式的限制

3.4 小结

3.5 知识拓展

3.5.1 未声明与未定义的区别

3.5.2 JavaScript编程风格

3.5.3 JavaScript文档工具

第4章 JavaScript运算符
4.1 运算符

4.2 算术运算符

4.2.1 加减乘除

4.2.2 递增递减

4.2.3 取余取负

4.2.4 幂运算

4.3 比较运算符

4.3.1 相等与全等

4.3.2 类型转换

4.3.3 大小判断

4.4 逻辑运算符

4.5 位运算符

4.5.1 按位逻辑运算

4.5.2 位移运算

4.6 赋值运算符

4.7 对象操作运算符

4.7.1 new运算符

4.7.2 delete运算符

4.7.3 点号运算符

4.7.4 中括号运算符
4.8 其他运算符

4.8.1 条件运算符

4.8.2 逗号运算符

4.8.3 小括号运算符

4.8.4 in运算符

4.8.5 instanceof运算符

4.8.6 void运算符

4.9 运算符优先级

4.10 小结

4.11 知识拓展

4.11.1 检测运算顺序

4.11.2 惰性运算

第5章 JavaScript数值运算
5.1 特殊数值

5.1.1 最大值/最小值

5.1.2 无穷大/极小值

5.1.3 负零

5.1.4 非数字NaN

5.2 进制

5.2.1 二进制

5.2.2 八进制

5.2.3 十进制

5.2.4 十六进制

5.3 内置函数

5.3.1 parseInt

5.3.2 parseFloat

5.4 小结

5.5 知识拓展

5.5.1 进制转换
5.5.2 浮点数误差

5.5.3 三十六进制

第6章 JavaScript流程控制
6.1 基本语句

6.1.1 声明语句

6.1.2 表达式语句

6.1.3 复合语句

6.1.4 空语句

6.2 条件控制

6.2.1 if语句

6.2.2 switch语句

6.2.3 if和switch对比

6.2.4 条件嵌套

6.3 循环控制

6.3.1 for/for…in语句

6.3.2 while/do…while循环语句

6.3.3 for和while对比

6.3.4 循环语句嵌套

6.4 跳转控制

6.4.1 标签语句

6.4.2 break语句

6.4.3 continue语句

6.5 异常控制

6.5.1 try…catch…finally语句

6.5.2 throw语句

6.6 小结

6.7 知识拓展

6.7.1 避免使用死循环

6.7.2 避免使用标签语句
第7章 JavaScript函数
7.1 函数定义

7.1.1 function 关键字

7.1.2 使用 Function() 构造函数

7.1.3 匿名函数

7.2 函数属性

7.2.1 name属性

7.2.2 length属性

7.3 函数参数

7.3.1 形参和实参

7.3.2 传值和传引用

7.3.3 arguments 对象

7.4 函数调用

7.4.1 简单调用

7.4.2 方法调用

7.5 函数返回值

7.6 嵌套函数

7.7 变量作用域

7.7.1 全局作用域

7.7.2 函数作用域

7.7.3 变量优先级

7.8 闭包函数

7.9 this对象

7.10 小结

7.11 知识拓展

7.11.1 动态参数

7.11.2 递归函数

第8章 JavaScript对象
8.1 对象介绍

8.2 创建对象

8.2.1 使用花括号创建对象

8.2.2 使用Object()创建对象

8.2.3 使用Object.create()创建对象

8.3 对象属性

8.3.1 定义属性

8.3.2 配置属性

8.3.3 赋值属性

8.3.4 删除属性

8.4 对象操作

8.4.1 访问对象

8.4.2 枚举对象

8.4.3 克隆对象

8.4.4 销毁对象

8.5 内置对象

8.5.1 Date日期对象

8.5.2 Math数学对象

8.6 小结

8.7 知识拓展

8.7.1 原始值

8.7.2 冻结对象

第9章 JavaScript数组
9.1 数组介绍

9.2 数组定义

9.2.1 使用中括号定义数组

9.2.2 使用Array()构造函数

9.2.3 二维数组

9.3 数组属性
9.3.1 length属性

9.3.2 prototype

9.4 数组操作

9.4.1 新增元素

9.4.2 获取元素

9.4.3 查找元素

9.4.4 删除元素

9.4.5 替换元素

9.4.6 数组迭代

9.4.7 数组排序

9.5 小结

9.6 知识拓展

9.6.1 数组求和

9.6.2 快速交换

第10章 JavaScript字符串
10.1 字符串介绍

10.2 字符串定义

10.2.1 字符串常量

10.2.2 字符串对象

10.3 字符串属性

10.3.1 length属性

10.3.2 prototype属性

10.4 字符串操作

10.4.1 查找和替换

10.4.2 字符串截取

10.4.3 连接和拆分

10.4.4 字符串转义

10.4.5 大小写转换

10.4.6 字符串转HTML函数
10.5 小结

10.6 知识拓展

10.6.1 模板字符串

10.6.2 Unicode转义序列

第11章 JavaScript正则表达式
11.1 正则表达式介绍

11.2 正则表达式定义

11.2.1 使用双斜线定义

11.2.2 使用RegExp()构造函数

11.3 正则表达式语法

11.3.1 基本字符

11.3.2 字符转义

11.3.3 元字符

11.3.4 限定符

11.3.5 定位符

11.3.6 修饰符

11.3.7 分组捕获

11.3.8 优先级

11.3.9 注释

11.4 正则表达式函数

11.4.1 regexp.exec()

11.4.2 regexp.test()

11.4.3 string.match()

11.4.4 string.replace()

11.4.5 string.search()

11.4.6 string.split()

11.5 小结

11.6 知识拓展

11.6.1 预查匹配
11.6.2 贪婪匹配

11.6.3 常用正则表达式

第12章 原型链
12.1 原型链介绍

12.2 原型属性

12.2.1 prototype属性

12.2.2 __proto__属性

12.3 原型扩展

12.3.1 扩展原型

12.3.2 修改原型

12.4 原型继承

12.4.1 私有方法

12.4.2 公有方法

12.4.3 实例方法

12.5 小结

12.6 知识拓展

12.6.1 原型陷阱

12.6.2 继承方式

第13章 BOM浏览器对象模型
13.1 BOM介绍

13.2 document文档对象

13.2.1 对象集合

13.2.2 对象属性

13.2.3 对象方法

13.3 location导航对象

13.3.1 网页网址

13.3.2 网页访问

13.4 history历史对象
13.4.1 网页操作

13.4.2 网页刷新

13.5 navigator浏览器对象

13.5.1 浏览器信息

13.5.2 多语言与本地化

13.6 window窗口对象

13.6.1 全局作用域

13.6.2 对话框

13.6.3 新建窗口

13.6.4 关闭窗口

13.6.5 窗口位置

13.6.6 缩放事件

13.6.7 异步调用

13.7 Notification

13.7.1 请求权限

13.7.2 显示通知

13.8 小结

13.9 知识拓展

13.9.1 防止网页嵌套

13.9.2 BOM跨域限制

第14章 DOM文档对象模型
14.1 DOM介绍

14.2 DOM节点

14.2.1 节点分类

14.2.2 节点对象

14.2.3 节点关系

14.3 节点属性

14.3.1 获取属性

14.3.2 设置属性
14.3.3 删除属性

14.3.4 数据属性

14.4 元素操作

14.4.1 访问元素

14.4.2 遍历元素

14.4.3 新建元素

14.4.4 插入元素

14.4.5 查找元素

14.4.6 复制元素

14.4.7 删除元素

14.4.8 替换元素

14.5 节点字符串

14.5.1 文本节点

14.5.2 插入文本节点

14.5.3 节点HTML

14.5.4 插入HTML

14.6 样式表

14.6.1 获取样式

14.6.2 设置样式

14.6.3 动画效果

14.7 小结

14.8 知识拓展

14.8.1 浏览器重绘

14.8.2 浏览器重排

第15章 事件处理
15.1 事件介绍

15.1.1 什么是事件

15.1.2 事件冒泡

15.1.3 事件捕获
15.2 事件属性

15.2.1 基本属性

15.2.2 鼠标事件属性

15.2.3 键盘事件属性

15.3 事件方法

15.3.1 停止冒泡

15.3.2 停止同级冒泡

15.3.3 阻止默认操作

15.4 页面事件

15.4.1 网页加载

15.4.2 资源加载

15.4.3 网页滑动

15.4.4 网页卸载

15.4.5 标签事件

15.5 键盘事件

15.6 鼠标事件

15.6.1 鼠标点击

15.6.2 鼠标移动

15.6.3 拖拽元素

15.7 触摸事件

15.7.1 触摸开始

15.7.2 触摸移动

15.7.3 触摸结束

15.7.4 触摸取消

15.8 事件模拟

15.8.1 创建事件

15.8.2 触发事件

15.9 小结

15.10 知识拓展
15.10.1 事件委托

15.10.2 自定义事件

第16章 表单对象
16.1 表单元素

16.1.1 表单对象

16.1.2 控件列表

16.1.3 控件属性

16.2 表单事件

16.2.1 获取焦点

16.2.2 失去焦点

16.2.3 内容修改

16.2.4 提交事件

16.3 表单提交

16.3.1 使用GET提交

16.3.2 使用POST提交

16.3.3 文件上传

16.4 小结

16.5 知识拓展

16.5.1 表单序列化

16.5.2 无刷新提交

第17章 AJAX异步通信
17.1 AJAX介绍

17.1.1 异步提交

17.1.2 XML语言

17.1.3 XMLHttpRequest对象

17.1.4 NGINX服务器配置

17.2 XML数据

17.2.1 XML文档
17.2.2 XML解析

17.3 JSON数据

17.3.1 JSON文档

17.3.2 JSON解析

17.3.3 JSON与XML对比

17.4 AJAX应用

17.4.1 XMLHttpRequest初始化

17.4.2 设置readystatechange

17.4.3 设置HTTP请求头信息

17.4.4 发送请求

17.4.5 获取HTTP响应头信息

17.4.6 获取响应文本

17.4.7 获取JSON数据

17.4.8 获取XML数据

17.4.9 获取二进制数据

17.5 小结

17.6 知识拓展

17.6.1 请求进度条

17.6.2 跨域请求

第18章 CSS网页特效
18.1 CSS操作

18.1.1 样式对象

18.1.2 计算样式

18.1.3 设置样式

18.2 CSS大小

18.2.1 CSS高宽

18.2.2 元素高宽

18.2.3 视窗大小

18.3 CSS动画
18.3.1 淡入淡出

18.3.2 滑入滑出

18.4 小结

18.5 知识拓展

第19章 数据存储
19.1 Cookie

19.1.1 Cookie获取

19.1.2 Cookie设置

19.1.3 Cookie删除

19.1.4 Cookie安全

19.2 Session

19.3 WebStorage

19.3.1 localStorage对象

19.3.2 sessionStorage对象

19.3.3 Cookie与Storage对比

19.4 小结

第20章 多媒体
20.1 文件

20.1.1 Blob对象

20.1.2 File对象

20.1.3 读取文件

20.2 画板

20.2.1 canvas标签

20.2.2 canvas绘图

20.3 音频

20.3.1 audio标签

20.3.2 audio属性

20.4 视频
20.4.1 video标签

20.4.2 video属性

20.5 小结

第21章 多线程
21.1 浏览器线程

21.2 WebWorker

21.2.1 创建线程

21.2.2 线程通信

21.2.3 结束线程

21.3 线程安全

21.4 小结

21.5 知识拓展

第22章 jQuery
22.1 jQuery介绍

22.1.1 元素集合

22.1.2 链式调用

22.2 选择器

22.2.1 CSS选择器

22.2.2 表单选择器

22.2.3 元素筛选

22.2.4 子元素筛选

22.2.5 内容筛选

22.2.6 可见性筛选

22.3 DOM

22.3.1 新建节点

22.3.2 拷贝节点

22.3.3 插入节点

22.3.4 移除节点
22.3.5 替换节点

22.3.6 遍历节点

22.4 属性操作

22.4.1 获取属性

22.4.2 设置属性

22.4.3 移除属性

22.5 样式表

22.5.1 获取样式

22.5.2 设置样式

22.5.3 单位

22.5.4 尺寸

22.5.5 位置

22.6 事件

22.6.1 绑定与解绑

22.6.2 事件模拟

22.6.3 事件对象

22.6.4 文档初始化

22.7 数据

22.7.1 获取数据

22.7.2 设置数据

22.7.3 移除数据

22.8 动画

22.8.1 显示隐藏

22.8.2 滑动动画

22.8.3 淡入淡出

22.8.4 动画队列

22.8.5 清除动画

22.8.6 自定义动画

22.9 AJAX
22.9.1 发送请求

22.9.2 全局事件

22.10 小结

22.11 知识拓展

22.11.1 框架兼容

22.11.2 插件开发

第23章 项目实战
23.1 项目分析

23.2 技术选型

23.3 个税计算器

23.3.1 网页结构

23.3.2 税率表

23.3.3 五险一金

23.3.4 纳税计算

23.4 小结
第1章 走进 JavaScript
在互联网上,JavaScript用于实现五彩纷呈的网页,是网页编程的主流语言。它不需要编译,简单、
易学易用,直接嵌入在网页中,让网页具有与用户交互的能力。本章将介绍JavaScript的起源和特点、如
何配置开发环境,并提供学好JavaScript的建议,让读者能快速了解这门语言。

1.1 JavaScript简介
JavaScript是Web编程中一种常见的解释执行的脚本类编程语言。它主要是嵌入HTML代码中,由浏
览器或WebView逐行解释执行。它通过响应各种事件,如键盘按键、鼠标拖拽、手指触摸等,来与外界
交互,从而实现各种功能。
JavaScript主要在浏览器上运行,因此它具有跨平台的特性,但同时也受到浏览器的限制。因为各个
浏览器厂商根据语言标准实现的JavaScript运行环境并不完全相同,所以在特定情况下,开发人员需要根
据不同浏览器的差异编写不同的代码,以实现相同的功能。
随着技术发展,JavaScript不仅可以在前端浏览器运行,也可以在后端服务器上运行。目前流行的
JavaScript后端运行环境Node.js支持使用JavaScript连接数据库、提供Restful接口。在Node.js出现之前,
这些功能只能依赖Java、PHP等后端语言实现。JavaScript也能够与APP结合,通过APP提供的接口丰富
APP的功能,比如轻应用、微信小程序、支付宝小程序等。
JavaScript应用广泛,涉及网页端程序、服务器端程序、手机APP开发等。本书将讲解JavaScript的语
法基础,并主要呈现网页上的JavaScript应用。在讲解的过程中,本书会提供相应的简单易用的
JavaScript代码,让读者体会到编程的乐趣。在熟练掌握该语言基础之后,相信读者也能够很容易地进行
网页编程。
万丈高楼平地起,让我们开始JavaScript精彩的学习之旅吧!

1.1.1 JavaScript的起源

早期的万维网网页功能简单,大部分功能依赖后端语言实现。例如:表单验证需要将数据传输到服
务器,等待服务器返回验证结果。当网速较慢时,在点击提交表单按钮、等待服务器处理数据的期间,
网页没有任何有用的提示,因此用户的体验很差。
1995年,太阳计算机系统有限公司(Sun Microsystems,简称Sun公司)发布了Java和HotJava浏览
器,率先推出能嵌套在网页上并跟随网络传输的Applet网页小程序。用户访问网页时,浏览器下载并运
行Applet小程序,实现简单的编程交互。这种实现方式比较笨重,网景通信公司(Netscape)开始考虑
开发一种浏览器直接支持运行的脚本语言。同年,网景通信公司的布兰登·艾奇(Brendan Eich)开发设
计了LiveScript,并嵌入到当年准备发布的网景导航者(Netscape Navigator)浏览器上。随后,网景通信
公司与Sun公司成立了一个开发联盟,关系由竞争变为合作。在发布前,网景希望搭上媒体热炒Java的
顺风车,让这门新语言看起来像Java,所以临时将LiveScript改名为JavaScript。
JavaScript 1.0版本发布之后大受欢迎,获得了巨大成功,网景公司随即在Netscape Navigator 3中发
布了JavaScript 1.1版本。
作为竞争对手,微软紧随其后,在Internet Explorer 3中发布了一个JavaScript的克隆版本——
JScript。微软发布JScript,既可以避免与网景的潜在纠纷,如商标、专利等,又可以在Windows操作系
统上捆绑销售浏览器。
提示
JavaScript与Java没有任何关系,所属领域也不同;JavaScript得到了浏览器厂商的支持,发展良好,
而Java Applet已难觅踪影。
除了JavaScript、JScript外,早期还有一种浏览器嵌入式脚本语言ScriptEase,这三者没有统一的语
言标准。随着不同版本的发布,语言标准混乱的问题日益明显,JavaScript的规范化被提上日程。
1997年,JavaScript 1.1草案被提交给了欧洲计算机制造商协会(ECMA)。同年,ECMA-262标准
发布,命名了全新的脚本语言ECMAScript,全称European Computer Manufacturers Association Script。
ECMAScript和JavaScript的区别是,前者是一套语言标准,后者是该标准的实现形式。

1.1.2 JavaScrip的特点

JavaScript作为一门解释执行的脚本语言,具有如下几个特点。
1.简单性
JavaScript语法简单,流程控制与大部分语言类似,变量没有严格的类型区分,函数没有返回类型,
易于学习、理解和上手。
2.解释性
JavaScript源代码不需要经过编译,可以直接在浏览器中解释执行。在解释执行前,浏览器需要进行
预处理,包括语法检测、运行环境设置、变量提升等。
3.动态性
JavaScript以原型为模板创建实例,实例和原型之间具有一种松散的连接关系。JavaScript也可以先
创建实例后修改原型,修改之后会影响所有实例。
JavaScript在运行过程中有一个context(上下文)概念,类似于其他语言的this,可以在程序运行中
修改。
JavaScript还可以在运行中创建可执行的代码,并提供给浏览器运行。后续章节将会介绍动态特性的
细节。
4.跨平台性
JavaScript通过浏览器解释执行,只要有浏览器,均可运行,与操作系统无关,也与硬件设备无关。
5.安全性
JavaScript通过事件与外界实现交互,不能主动读取计算机上的文件,也不能任意修改服务器上的数
据。这也避免了泄露用户隐私和数据丢失的风险。
6.事件驱动
JavaScript主要通过响应外界(通常指用户和浏览器)事件来执行某种操作。例如,用户使用鼠标点
击图像,网页会显示大图;按下回车键,提交表单。这种触发JavaScript程序执行的行为称为事件。响应
事件、执行操作的过程称为事件驱动。
在实际应用中,会有非常多的事件拼装组合,以完成复杂的操作。
7.基于对象
JavaScript是一种基于原型链的面向对象编程语言。在JavaScript的世界中,除了代表空值的null和
undefined,一切皆为对象。数字是对象,函数是对象,数组是对象,集合是对象。
8.原型链继承
在面向对象的编程语言中,有类和衍生类、类和实例的概念。在JavaScript中,没有类的概念,一切
都是基于原型链。后续章节将会介绍原型链继承特性的细节。
1.1.3 ECMAScript 6

2015年6月,ECMAScript 6发布。作为JavaScript的下一代标准,它引入了大量新特性,尤其支持模
块化开发,夯实JavaScript开发大型企业应用的基础。
ECMAScript 6,简称ES6,是一个具有里程碑意义的版本。它在ES5的基础上做了大幅改进,同时
每年都会有新的特性加入,比如随后发布的ECMAScript 2016和ECMAScript 2017。因此,有时候ES6也
代表最新的JavaScript语言标准,直到下一个版本ES7发布。

1.1.4 ECMAScript版本发布
ECMA的第39号技术专家委员会,即TC39委员会,全称Technical Committee 39,是推动JavaScript
发展和审核ES语言标准的委员会,由各个主流浏览器厂商的代表构成。它接收所有人的提案,并参照以
下流程修改语言标准。
1.Stage 0:Strawman
任何成员和贡献者都可以向委员会提出草案。
2.Stage 1:Proposal
指定委员会成员作为草案带头人,负责描述解决的问题和实现该草案的条件,提供示例,指出潜在
问题,最终定义清晰的草案。
3.Stage 2:Draft
尽可能完善所有事项,形成第一个版本,该版本与最终版本不会有太大差异。
4.Stage 3:Candidate
进入候选阶段,等待获得用户真实反馈。此时的草案是完整的,一般只要不出现重大问题,都会通
过。
5.Stage 4:Finished
定稿阶段,通过测试委员会验收,准备发布到年度的规范中。
根据该流程,可以让JavaScript改善自身存在的问题,也能持续吸收其他语言的特性,保持旺盛的生
命力。在TC39官方网站https://tc39.github.io/ecma262/可以查看最新的语言标准。

1.2 JavaScript开发环境
JavaScript源代码文件以“.js”作为文件后缀名,任何支持文本编辑的软件都可以打开和修改源代码。
本书代码使用Sublime Text试用版和WebStorm试用版编写和管理,下面简单介绍这两款软件的安装和使
用。

1.2.1 Sublime Text下载和安装

Sublime Text是一款非常优秀的代码编辑器,虽然是收费软件,但可以无限期试用。
1.Sublime Text安装
首次安装并使用Sublime Text,参考如下步骤:
(1)打开Sublime Text官方网站 https://www.sublimetext.com/,找到DOWNLOAD按钮,点击下载最
新版本;
(2)安装完成之后,使用项目管理将“F:\code\”加入到Project(项目)中,如下图所示。

图1.2.1 Sublime Text添加项目文件夹

2.Sublime Text汉化
使用中文操作界面能够更方便用户使用Sublime Text这款优秀的软件。Sublime Text的汉化只需要安
装中文插件,不需要安装中文汉化包,参考如下步骤:
(1)打开Sublime Text,找到Preferences菜单,点击Package Control;直接在编辑器界面按下
Ctrl+Shift+P,也可以进入下一步,如下图所示。

图1.2.2 打开Package Control

(2)在弹出的界面中输入in(“Install”的前两个字母),找到Install Package,按下回车,如下图所
示。

图1.2.3 搜索Install Package

(3)稍等10秒钟左右(根据网速快慢决定),在弹出界面输入localized,找到LocalizedMenu插
件,按下回车,如下图所示。
图1.2.4 搜索localized插件

(4)再稍等10秒钟左右,插件安装完成,点击Preferences菜单,出现Languages选项,如下两图所
示。

图1.2.5 查看语言选项

图1.2.6 选择中文语言项

(5)选择中文或简体中文之后,Sublime Text完成汉化,如下图所示。

图1.2.7 Sublime Text完成汉化

完成汉化之后,即可开始编写JavaScript代码。Sublime Text有很多复杂功能,不仅仅是一款代码编
辑器。读者可以前往它的官方网站学习更多丰富的功能或者查阅Sublime使用技巧文档,本节不做深入
介绍。
1.2.2 WebStorm下载和安装

WebStorm是一款极具人气的JavaScript集成开发环境,提供30天试用期,安装使用参考如下步骤:
1.打开WebStorm官方网站https://www.jetbrains.com/webstorm/,找到DOWNLOAD按钮,点击下载最
新版本;
2.安装完成之后,使用WebStorm打开目录,将“F:\code\”导入到项目中,如下图所示。

图1.2.8 WebStorm导入项目

WebStorm没有提供直接可用的语言包或汉化插件,目前由开发者自行开发汉化包,并分享到
GitHub上,读者可以在GitHub上搜索WebStorm Chinese Language Pack(中文语言包)并安装。

1.3 JavaScript运行环境
运行环境是指执行码在目标机器上运行的环境。执行码包括需要编译的中间字节码和不需要编译即
可执行的源代码。JavaScript源代码不需要编译,可以被运行环境直接解释执行。
在Web网站开发中,根据职能将程序分为前端和后端,前端负责网页展示、响应用户操作、提交表
单等,后端负责文件处理、数据库读写、结果输出等。从1995年到现在,不同的运行环境中,JavaScript
源代码在前后端都能正常执行:
◇ 在前端,JavaScript通过浏览器解释执行;
◇ 在后端,JavaScript通过Node.js解释执行。

1.3.1 浏览器

目前的PC端浏览器主要有Chrome、Firefox、IE、Safari、Edge、Opera,以及国内的搜狗高速浏览
器、猎豹安全浏览器、遨游浏览器等。国内的PC端浏览器基本都集成了IE内核Trident和Chrome内核
Webkit,称为双核浏览器,并根据国内网民的上网习惯做了改进。手机端浏览器主要有QQ浏览器、UC
浏览器、360浏览器、搜狗浏览器、猎豹浏览器,以及特殊的微信WebView。WebView同样能够解释执
行JavaScript,但没有完整的浏览器功能。
本书主要使用Chrome浏览器演示JavaScript源代码的运行结果。
Chrome原生提供的开发者工具——DevTools,可以帮助工程师高效调试JavaScript程序,并进行页
面优化、网络优化、性能优化等。

1.3.2 Node.js
在很长一段时间里,开发者只能使用浏览器运行JavaScript。2009年5月,瑞恩·达尔(Ryan Dahl)
基于Chrome V8引擎,封装了大量文件处理、数据处理、网络处理以及其他相关的接口,为JavaScript提
供了一个服务器端的运行环境。
JavaScript自身基于事件驱动,Node.js在此基础上形成了非阻塞I/O的运行模式,能够更高效地处理
并发出请求。Node.js把JavaScript从前端浏览器带入到了后端,扩大了JavaScript的使用场景,增加了
JavaScript的开发岗位需求。

1.4 学好JavaScript的建议
1.熟练掌握基本理念
JavaScript是一门比较活跃的语言,每年TC39委员会都会收到各地开发者提出的改进意见,这有助
于弥补JavaScript自身的缺点和吸纳其他语言的优点,保持强大的生命力,但其本身的基础变化不会特别
大。
读者应熟练掌握JavaScript的基本理念,确保自己既可以快速掌握使用JavaScript,也可以及时了解
和使用JavaScript语言的新特性。
2.勤于练习、善于利用调试工具
JavaScript程序不需要编译,只需要使用文本编辑器和浏览器就能构建和运行。因此,读者在了解到
新知识后,要尝试投入编码实现,以加深记忆。
此外,读者要善于利用Chrome和其他浏览器提供的开发者工具,它们既能帮助开发者调试跟踪程
序运行过程,也可以获取程序运行状态,帮助优化和提高网页性能。
3.多做总结、概括
读者在学习完本书的每一个章节之后,应尝试总结本章知识要点,划出需要重点掌握的地方。
4.尝试答疑
笔者建议每位读者加入一些在线JavaScript讨论组、网站、QQ群,并尝试回答网友提出的问题。虽
然这种方式不够系统,但可以有效检验自己对技能的掌握程度。
5.适当学习一些英文单词
良好的英语阅读能力对于编程也很重要,这是深入软件编程之后的更高要求。相比国内用户而言,
国外用户讨论技术问题更加活跃,导致一部分问题的描述和解答只会出现在https://github.com或
https://stackoverflow.com等技术社区,这类网站都是采用英文讨论JavaScript编程技术,此时读者要有能
力和耐心去阅读这种纯英文网站。

1.5 小结
本章介绍了JavaScript的起源、应用场景,推荐了两款JavaScript开发工具,让读者对JavaScript有基
本的了解,为后续章节的学习打下基础。

1.6 知识拓展
1.6.1 常用软件资源

1.软件开发工具
软件开发工具一般代指集成开发环境,英文全称Integrated Development Environment,简称IDE,集
合了代码编辑、自动提示、代码分析、代码重构、软件编译、运行调试等功能;部分IDE还可以与代码
管理工具结合,支持代码管理、程序发布。
常见的JavaScript IDE有Atom、HBuilder、Komodo Edit、Netbeans、Sublime Text、PHPStorm、
WebStorm。
在学习JavaScript的初期,不推荐读者使用功能强大的IDE,因为可能会出现过多干扰,比如下图这
个示例,初衷是输出computer.price,但是WebStorm这款IDE给出的补齐提示是propertyIsEnumerable,这
是一个极少使用的函数。另外一点,在学习的初期,使用IDE提示会导致记忆不深刻,离开IDE就不知
道如何编写代码。再比如函数document.querySelectorAll()和encodeURIComponent(),需要全部输入多次
才能掌握,因此不建议读者一味地依赖IDE自动提示。

图1.6.1 违反初衷的IDE提示

本书主要使用Sublime Text编写代码,使用Chrome浏览器进行演示。
提示
笔者建议,读者在学习JavaScript的初期使用Sublime Text编辑器,在能够熟练书写系统自带的复杂
函数之后,再考虑使用其他补全功能强大的IDE。
2.代码管理工具
在软件开发中,常见多人协作、多人编写同一个文件的情况,这时需要选择合适的代码管理工具。
代码管理工具可以方便跟踪文件变化、人员协作与管理,甚至形成社区,营造工程师文化。这里的人员
不仅包括软件工程师,还包括设计人员、策划人员、运营人员等。
(1)常见的代码管理工具:
◇ SVN
SVN是一款版本控制系统,全称Subversion,它能够方便地查看代码历史记录、合并代码、创建分
支、合并分支,实现多人协作开发。SVN需要使用独立的SVN服务器,所有开发者将代码提交到服务
器,并从服务器下载代码,因此在离线时无法保存文件的版本记录。
◇ GIT
GIT是一款分布式版本控制系统,除了提供基本的版本控制功能(这一点与SVN类似),还支持分
布式,允许在离线时提交文件,并在联网时与服务器同步数据,同时配套了完整的Web管理后台,支持
GIT成员对文件、代码片段进行讨论。
(2)常见的代码管理平台:
◇ 全球领先的软件开发平台Git Hub(https://github.com/);
◇ 阿里云提供的GIT代码管理工具(https://code.aliyun.com/);
◇ 基于GIT和SVN的代码托管和协作开发平台GITEE(https://gitee.com/)。

1.6.2 浏览器兼容性
在1.1.2 JavaScript特点一节提到过JavaScript具有跨平台特性,仅在浏览器上运行,与操作系统无
关。不过因为不同浏览器厂商的实现不同,也会导致JavaScript在浏览器上存在一些兼容性问题,这类问
题涉及具体的JavaScript应用,建议读者在学习完本书之后再深入研究。本节只做简单介绍,不做深入学
习。浏览器兼容性有关问题如下:
1.事件兼容性
(1)事件绑定addEventListener()与attachEvent(),对应事件解绑removeEventListener()与
detachEvent();
(2)事件对象,IE下Event是全局对象,Chrome、Firefox下Event是函数参数;
(3)事件冒泡stopPropagation()与cancleBubble;
(4)事件阻止preventDefault()与returnValue;
(5)事件名称不同,比如鼠标滚轮事件,Chrome、IE下是mousewheel,Firefox下是
DOMMouseScroll。
2.AJAX兼容性
(1)http请求对象构建方式,new XMLHttpRequest()与new
ActiveXObject("Microsoft.XMLHTTP");
(2)http请求进度事件progress;
(3)IE针对未知的responseType会抛出错误。
3.样式对象
(1)IE的呈现原理导致runtimeStyle与currentStyle不能完全同步;绝大部分情况下,只需要关心
currentStyle;
(2)className对象操作,IE不支持操作classList。
4.JavaScript不同版本
(1)最新函数支持,比如DOM选择器;
(2)最新语言特性支持,比如变量作用域。
遇到兼容性问题时,我们一般可以通过查询官方文档或者搜索开发者论坛解决。

1.6.3 开发者社区

国内的一些社区和网站:
1.SegmentFault,开发者问答社区
https://segmentfault.com/t/javascript
2.CSDN,国内著名的技术论坛
https://bbs.csdn.net/forums/JavaScript/
3.w3school在线教程
http://www.w3school.com.cn/js/index.asp
4.Web技术开发文档
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
5.GitHub JavaScript专题
https://github.com/topics/javascript
另外,Can I Use网站(https://caniuse.com/)专注为HTML 5、CSS 3、JavaScript提供使用指南。
第2章 初次使用JavaScript
在浏览器中执行的JavaScript,不能脱离HTML网页独立运行。本章将介绍在Web网页中引入
JavaScript的三种方式,以及与JavaScript运行相关的基本概念和结果输出形式。接下来我们从最简单
的“Hello,JavaScript”开始。

2.1 Hello, JavaScript


Web网页的HTML文件是JavaScript代码的载体,开发者通过在HTML文档中使用<script>标签嵌入
JavaScript代码。一个HTML文档可以包含多个<script>...</script>标签,一个<script>标签能够包含多段
JavaScript代码。
接下来通过一个简单的例子来了解如何在HTML网页中嵌入JavaScript代码,并让浏览器运行。

2.1.1 编写脚本

1.编写第一个JavaScript程序
首先,使用WebStorm以项目形式打开“F:\code”目录,并在第2章的第一个小节新建2.1.1.html文件。
然后,在2.1.1.html文件中嵌入一段JavaScript代码,并且在网页中显示“Hello, JavaScript”。
(1)打开WebStorm,在02→01目录,点击右键,在弹出菜单依次选择New→HTML File,如图
2.1.1所示。
(2)在弹出的对话框输入2.1.1,并选择HTML 5 file,如下图所示。

图2.1.1 选择新建HTML File

图2.1.2 新建HTML 5 file

(3)在新建的2.1.1.html文件中编写代码,如下所示。
动手写2.1.1
在网页文件2.1.1.html中有几点需要注意:
◇ 首行代码使用DOCTYPE将文档类型声明为HTML,这是HTML 5新增的文档类型声明方式。
◇ <meta charset="UTF-8">标签表示网页编码为UTF-8。为了让浏览器正确显示字符,网页文件需
要以UTF-8格式存储。
◇ <script type="text/javascript">标签表示嵌入脚本,type="text/javascript"表示嵌入的是JavaScript脚
本。
使用浏览器打开2.1.1.html,将在浏览器显示一段文字,如图2.1.3所示。
<script>标签缺少type字符串时,浏览器会自动将标签内的代码当作JavaScript解析并处理。<script>
标签的type属性是其他值时,浏览器不会将其作为JavaScript脚本执行,也不会显示标签内的文字。例
如,网页中出现代码<script type="text/plain">Hello, JavaScript</script>时,其既不会被浏览器显示,也不
会被作为JavaScript脚本执行。

图2.1.3 显示“Hello, JavaScript”

2.JavaScript启用提醒
有两种情况会导致网页中的JavaScript脚本不会执行:(1)浏览器不支持JavaScript;(2)用户禁
用某个或全部网站的JavaScript。
第一种情况出现在过渡时期,某些早期的浏览器或现在的手持设备不支持运行脚本。第二种情况可
能是出于安全考虑,用户设置浏览器禁用脚本;此时使用<noscript>标签,提醒用户需要支持和启用
JavaScript。
下面通过动手写2.1.2展示浏览器不支持脚本的提醒。在目录“F:\code\02\01”中新建文件2.1.2.html,
编写代码如下所示。
动手写2.1.2
动手写2.1.2与动手写2.1.1有两点区别:
◇ <script>标签内部多了<!-- -->。这是HTML注释的书写方式,用于确保在早期不支持脚本的浏览
器上不会显示这段JavaScript代码。浏览器不论是否启用JavaScript脚本,它都不影响脚本运行和网页的
显示效果。
◇ 增加了<noscript>标签。<noscript>标签的字符串既可以是普通文本,也可以是HTML标签,并且
能够设置显示样式。
使用浏览器打开2.1.2.html,如果浏览器支持JavaScript,则显示JavaScript已启用的提示,如下图所
示。

图2.1.4 浏览器已开启JavaScript

如果浏览器不支持JavaScript,则提醒开启浏览器支持,如下图所示。

图2.1.5 浏览器不支持JavaScript或未开启JavaScript

提示
在Chrome浏览器内打开网址“chrome://settings/content/javascript”,关闭JavaScript,再执行
2.1.2.html,查看上图提示。
3.<script>标签闭合
浏览器采用自上而下的方式对HTML代码进行解析,当遇到<script>开始标签时认为开始一段新的脚
本,遇到</script>结束标签时认为本段脚本结束。因此,在<script>标签内嵌入脚本时,不要在其他任何
地方出现<script>或</script>,除非本段代码确实已经结束。
在网页内运行的主要脚本是JavaScript。当开始标签<script>没有包含type="text/javascript"属性时,
该脚本被浏览器默认当作JavaScript脚本。
通过动手写2.1.3展示在代码中错误地使用了<script>、</script>字符串,让浏览器误认为脚本结束的
情况。在目录“F:\code\02\01”中新建文件2.1.3.html,编写代码如下所示。
动手写2.1.3

执行2.1.3.html时,既不会在网页中输出“<h1>Hello, <script>JavaScript</h1>”,也不会输
出“<h1>Hello, JavaScript</script></h1>”,如下图所示。

图2.1.6 没有正常闭合的脚本标签
网页中显示“Hello, ');”,是因为脚本没有被浏览器正确识别,导致出现错误的信息。浏览器读取到
第一个位于单引号里的<script>标签时,认为新起一段脚本,同时将“<h1>Hello,”输出到网页;读取到第
二个位于单引号里的</script>标签时,认为本段脚本结束,将“</h1>');”作为普通HTML字符串解析,闭
合标签“</h1>”会被丢弃,只留下“');”,最终在网页上显示“Hello, ');”。总体来说,脚本没有被正确识
别,执行失败。
4.浏览器开发者工具
图2.1.7使用了Chrome浏览器的开发者工具,该工具可以查看网页元素、网络资源状态、调试代码
错误、分析网页性能等,使用该工具辅助开发能够显著提高开发效率。打开开发者工具,在网页任意地
方单击右键弹出右键菜单,选择“检查”元素,即可出现如图2.1.6所示的代码结构。检查元素步骤如下图
所示。

图2.1.7 使用Chrome的“检查”元素功能

提示
在网页中,按下F12、Ctrl+Shift+C、Ctrl+Shift+I等快捷键,都可以调出开发者工具。按下
Ctrl+Shift+C,再移动鼠标,可直接检查鼠标指向的标签元素。
5.<script>转义闭合
如果想在<script>标签内部嵌套代码,就一定要使用<script>和</script>,有两种方式可以解决:一
是将<script>拆分成两段字符串,例如“<scr”和“ipt>”;二是使用反斜线(\)对字符串进行转义。
转义是指让浏览器将两个字符(反斜线与之后紧邻的字符)当作一个普通的字符去处理,这样能够
避免出现歧义字符,同时也可以赋予这两个字符一些特殊的含义。
新建文件2.1.4.html,在2.1.3.html的基础上使用反斜线对<script>和</script>进行转义,让字符串正常
输出,编写代码如下所示。
动手写2.1.4
执行2.1.4.html,网页中会输出两段“Hello, JavaScript”,如下图所示。

图2.1.8 使用反斜线转义script标签

JavaScript转义字符应用广泛,除了在字符串中使用,还可以在正则表达式中使用,相关内容将在第
10章JavaScript字符串和第11章JavaScript正则表达式为大家讲解。编程语言中经常使用反斜线(\)作为
转义字符,例如C、C++、Go、Java、Lua、PHP、Python等。

2.1.2 执行脚本

浏览器是从上到下解析HTML,也是从上到下解释执行JavaScript。使用多段<script>在网页中引入
多段JavaScript代码,浏览器在解析HTML时,按照从上到下的顺序,每出现一个<script>标签,就立刻
执行该标签内的代码。
新建文件2.1.5.html,引入多段脚本,在网页中输出多段字符串,展示脚本执行顺序,编写代码如下
所示。
动手写2.1.5
执行2.1.5.html,依次在网页输出“执行第一段脚本”“执行第二段脚本”“执行第三段脚本”,如下图所
示。
提示
浏览器采用单线程解析和执行JavaScript,如果某段<script>标签中的代码执行需要耗费较长时间,
那么网页将出现白屏、失去响应,阻塞浏览器渲染下一段HTML,直到代码执行完毕。

2.2 编写JavaScript程序
除了在<script>标签内嵌入脚本代码,我们还可以为<script>标签指定一个src属性,该属性用于指定
外部脚本文件的路径位置。
浏览器有三种方式执行JavaScript:
◇ 直接在<script>标签内编写脚本,这种方式称为内联JavaScript。
◇ 通过<script src="">属性引入外部脚本,这种方式称为外联JavaScript。
◇ 将JavaScript脚本写入标签属性中,通过事件触发执行,这种方式称为行内JavaScript。
除了这三种方式之外,还可以使用eval、setTimeout等在JavaScript运行过程中创建可执行代码。

2.2.1 内联JavaScript

在2.2.1.html中使用两个<script>标签,嵌入两段内联JavaScript。在第一个<script>标签中定义一个函
数getMaxNumber,在第二个<script>标签中调用该函数,获得列表的最大值并输出到网页上,如下所
示。
动手写2.2.1
执行2.2.1.html,在网页上输出数组的最大值,如下图所示。
图2.2.1 使用内联脚本获取最大值

2.2.2 外联JavaScript

JavaScript可以完成非常复杂的交互,如在线邮箱、在线文档编辑、在线视频点播等网站应用。若将
全部JavaScript代码放置在<script>中,代码量多且结构复杂,会给代码的管理和维护造成明显的负担,
显然不太合适。因此,在实现一些复杂功能时,我们通常使用<script>标签的src属性引入外部JavaScript
文件。
使用外部JavaScript示例:
1.编写2.2.2.js,JavaScript源代码文件通常采用缩写“js”作为后缀名;
2.编写2.2.2.html,使用src属性引入当前目录下的2.2.2.js;
3.执行2.2.2.html,查看外联JavaScript运行流程。
在2.2.2.js中定义函数sayHello()输出欢迎语句,并立即调用sayHello(),如下所示。

2.2.2.html包含三个<script>标签,依次引入2.2.2.js、输出第二段文本、调用sayHello()函数输出第三
段文本,如下所示。
动手写2.2.2
执行2.2.2.html,依次在网页中输出提示语句,如下图所示。

图2.2.2 使用外联脚本输出欢迎语句

在第一个<script>标签中,通过src属性引入2.2.2.js,浏览器发起HTTP请求获取2.2.2.js的代码内容,
获取之后立刻执行其中的代码,向网页输出“你好,第一段外联脚本”。
在第二个<script>标签中,向网页输出“第一段内联脚本”,说明第二个<script>标签中的代码需要等
待浏览器下载并执行2.2.2.js之后才执行,这就是外联JavaScript阻塞网页渲染。
在第三个<script>标签中,调用2.2.2.js定义的函数“sayHello()”,向网页输出“你好, 第二段内联脚
本”。
提示
通过src属性引入外联JavaScript的<script>标签不能再包含JavaScript代码。即使包含,浏览器也会忽
略内联代码,即外联脚本优先级高于内联脚本。
根据动手写2.2.1和动手写2.2.2,对比内联JavaScript和外联JavaScript的优缺点,如下表所示。
表2.2.1 内联脚本和外联脚本的优缺点
在实际应用中,可以根据内联JavaScript和外联JavaScript的优缺点采用合适的实现方式,两者没有
绝对超越对方的优势,可以同时出现。

2.2.3 行内JavaScript

行内JavaScript与前两种方式不同,只需要将JavaScript代码写入HTML标签的属性中,不需要使用单
独的<script>标签。目前行内JavaScript有两种使用方式。
1.使用事件调用
在网页中,使用事件属性为标签绑定事件代码,其中事件属性是表示事件的属性,事件代码就是
JavaScript代码。事件属性有onclick(点击事件)、oncontextmenu(显示邮件菜单)、onload(网页加载
完成)、onmousemove(鼠标移动)等,本书将在第15章详细介绍事件的相关知识。
2.2.3.html包含一个按钮,鼠标点击按钮显示警告对话框,如下所示。
动手写2.2.3

执行2.2.3.html,点击按钮显示对话框,如下图所示。

图2.2.3 HTML事件属性触发执行JavaScript代码

2.使用超链接href调用
浏览器支持在地址栏输入“javascript:alert(1)”,直接调用当前网页内的JavaScript函数和方法。利用这
个特性,可以将超链接的href属性设置为“javascript:alert(1)”,让用户在点击链接时触发程序执行。
2.2.4.html包含一个<a>标签,点击之后显示警告对话框,如下所示。
动手写2.2.4

执行2.2.4.html,点击“点我呀”超链接(<a>标签)显示对话框,如下图所示。

图2.2.4 使用超链接href属性触发调用JavaScript

2.3 执行JavaScript程序
浏览器虽然不对JavaScript进行编译,但会在执行前进行一些操作,例如词法分析、语法分析、语义
分析、环境设置,以及特有的变量提升(hoist)。

2.3.1 代码块

1.代码块独立运行
HTML通过<script>标签引入JavaScript代码,每一个<script>标签就是一个代码块。每一段代码独立
执行,前面的代码不能立即访问后面代码声明的变量,但后面的代码可以立即访问前面代码声明的变
量。
2.3.1.html包含三段代码,每段代码声明一个新的变量,并在本段代码访问前一段代码声明的变量,
如下所示。
动手写2.3.1
执行2.3.1.html,预期向网页中输出四段文字,但实际只输出了三段文字。按下F12打开浏览器开发
者工具,我们可以在Console控制台看到一行错误提示,如下图所示。
图2.3.1 JavaScript代码块

2.3.1.html有三个代码块,每一个代码块都可以随时访问当前代码块以及之前的代码块中的变量和函
数。因为之后的代码块要等待当前代码块执行,所以在第二个<script>标签中立刻访问第三个<script>中
的变量block3会引发一个错误“Uncaught ReferenceError:block3 is not defined”,在“JavaScript代码块2”之
后只输出了“2. 一二”,没有出现“3. 一二三”。
提示
从上例的运行结果可以看出,本段代码块出现执行错误(Runtime Error)时,下一段代码块没有受
到影响,仍然可以正常执行。
2.测试语法错误
动手写2.3.1展示了一段代码块运行时出现错误并不影响其他代码块的执行,接下来通过一个示例测
试代码块发生语法错误是否会影响其他代码块的执行。
动手写2.3.2与动手写2.3.1相比,在第二个<script>标签内增加语法错误,如下所示。
动手写2.3.2
执行2.3.2.html,预期向网页中输出四段文字,但实际只输出了两段文字。按下F12打开浏览器开发
者工具,我们可以在控制台看到两行错误提示,如下图所示。
图2.3.2 JavaScript代码块语法错误

2.3.2.html有三个代码块,第二个<script>标签结尾出现未闭合的“1/”,导致浏览器解析本段代码时出
现语法错误“Uncaught SyntaxError: Unexpected end of input”,即该<script>标签有一行代码没有被执行。
第三个<script>标签正常输出block3,紧接着输出block1、block2时抛出错误“Uncaught ReferenceError:
block2 is not defined”,也从侧面说明第二个<script>标签有一行代码没有被执行。
提示
从上例的运行结果可以看出,本段代码块出现语法错误(Syntax Error)时本段代码不会执行,下一
段代码块同样没有受到影响,仍然可以正常执行。

2.3.2 变量提升

JavaScript变量提升(hoist)是指将变量和函数声明都提升到作用域的最顶部。它最直观的表现是允
许函数先使用后声明。
2.3.3.html中,先调用函数sayHello(),再声明sayHello()函数,如下所示。
动手写2.3.3

执行2.3.3.html,向网页中输出“Hello, JavaScript”,如下图所示。
图2.3.3 JavaScript变量提升

JavaScript允许函数先使用再声明是其一大特点,大部分编程语言都要求函数先声明再使用。变量提
升还涉及复杂的预处理流程,相关内容将在第7章详细介绍。

2.3.3 输出结果

浏览器上有多种方式将结果输出,支持:显示警告框、输出到网页、输出到控制台、使用HTTP请
求传输到服务器。方法如下所示:
1.使用alert()弹出警告框显示结果;
2.使用document.write()、document.writeln()将结果写到HTML网页中;
3.使用innerHTML、innerText、nodeValue、textContent等属性将结果写入到节点中;
4.使用console.log()将结果写入到浏览器的控制台;
5.使用fetch()、XMLHttpRequest()将结果发送到服务器。
2.3.4.html演示多种结果输出方式,如下所示。
动手写2.3.4

执行2.3.4.html,使用多种方式输出结果,如下图所示。
图2.3.4 JavaScript多种方式输出结果

提示
对元素设置innerHTML和innerText时,虽然等号右侧的值都一样,但是innerText会对右侧的字符串
进行转义,这种转义称为HTML转义,将在后续章节详细介绍。

2.3.4 动态执行

在第1章里介绍JavaScript特点时,有一项动态性,表示可以在脚本运行过程中创建代码,由浏览器
继续执行。
有三种方式可以实现JavaScript的动态执行,如下所示:
◇ 使用document.write()将JavaScript代码写入新输出的<script>标签中,浏览器在执行完本段代码之
后,立即解析新生成的<script>标签并执行。
◇ 使用eval()、setTimeout()、setInterval()等函数执行JavaScript代码。
◇ 使用new Function()创建函数对象并执行。
1.使用document.write()动态创建JavaScript代码
2.3.5.html中,输出一个新的<script>标签,在新标签内修改元素的innerHTML属性,如下所示。
动手写2.3.5

执行2.3.5.html,第一段<script>标签输出一个新的<script>标签,在新标签中设置标签为<div
id="output">的innerHTML,如下图所示。
图2.3.5 使用document.write()动态创建JavaScript代码

按下快捷键F12打开Chrome开发者工具,在Elements面板查看网页元素,能够看到在第一段<script>
中通过document.write()向网页中写入新的<script>标签。浏览器在执行完本段代码之后立即解析并执行
新<script>标签内的代码,从而实现动态创建代码并执行。
2.使用eval()动态执行JavaScript代码
2.3.6.html中使用eval()执行一段文本,如下所示。
动手写2.3.6

执行2.3.6.html,浏览器将eval()函数中的字符串当作代码立即运行,但不会像2.3.5.html那样生成新
的<script>标签,如下图所示。

图2.3.6 使用eval()动态执行JavaScript代码

打开Chrome开发者工具,在Elements面板查看网页元素,看到调用eval()函数并没有生成新的
<script>标签。document.write()和eval()都是动态创建代码,区别是前者需要创建新的<script>标签,后者
不需要,并且eval()中的代码是立刻执行,不会等待本段代码执行完毕。
3.使用new Function()创建函数对象
如果需要重复将eval()中的文本当作JavaScript代码语句执行,可以使用Function()函数根据这段文本
创建新的函数,新创建的函数可以在任意地方被调用。
2.3.7.html使用Function()函数根据2.3.5.html和2.3.6.html中的文本创建函数对象,如下所示。
动手写2.3.7
执行2.3.7.html,输出函数对应的可执行字符串,如下图所示。

图2.3.7 使用new Function()创建函数对象

提示
使用变量fn将new Function()创建的函数对象保存下来,让它能在任何位置被调用,这就是基本的代
码复用。

2.3.5 延迟执行

在2.2.2外联JavaScript一节中,我们提到浏览器通过src引入外联脚本时会阻塞网页渲染,但是使用
<script>标签的defer属性,能够告知浏览器本段脚本不需要阻塞网页渲染,可以闲时下载、延迟执行。
2.3.8.html引入外联脚本2.3.8.js,并使用defer属性,如下所示。
动手写2.3.8
执行2.3.8.html,按照代码顺序,应该依次弹出对话框“外联脚本 2.3.8.js”“内联脚本 1”“内联脚本
2”,但使用defer属性之后,依次弹出对话框“内联脚本 1”“内联脚本 2”“外联脚本 2.3.8.js”,说明2.3.8.js
被延迟到其他<script>标签之后执行。
<script>也支持设置async属性,表示允许脚本异步加载。感兴趣的读者可以在学习完本书之后对这
两个属性进行测试,探索两者的区别。
在下载大型脚本时,defer属性的延迟执行和async属性的异步下载功能对于提高网页首屏的渲染速
度非常实用。
提示
为了降低下载和执行外联JavaScript时对网页渲染的阻塞,除了设置defer属性,另一个做法是将外
联脚本放置在页面最底部,即结束标签</body>之前。

2.4 小结
本章主要对初次使用JavaScript做了简单介绍,包括JavaScript代码与HTML的关系、如何管理多段
JavaScript代码、JavaScript结果输出,以及何时执行JavaScript,为深入学习JavaScript语法、JavaScript应
用奠定基础。

2.5 知识拓展
2.5.1 JavaScript框架
代码框架是一种程序实现方式的约定,例如MVC、MVVM,软件的代码实现需要按照这种约定来
实现。前端网页应用的变化比较多,也比较松散,而且JavaScript官方没有推出指定的框架,因此催生出
一批JavaScript框架,例如AngularJS、React、Vue等。
另外有一些JavaScript常用代码虽然不能称为框架,但它们封装了常见操作的、提供丰富功能的函
数,可以称为JavaScript库,例如jQuery、three.js、Zepto.js等。
还有一部分针对特定领域实现的JavaScript函数库,例如CryptoJS、Echarts、Raphael.JS、FastClick、
iScroll等。这些函数库拥有足够多的使用者,有大量实践应用,并且开源免费。开发者可以在项目中使
用这些函数库,减少重复的开发工作。
2.5.1.html使用<script>标签引入JavaScript框架,如下所示。
动手写2.5.1

执行2.5.1.html,使用jQuery修改<textarea>标签的内容,如下图所示。

图2.5.1 通过脚本标签引入JavaScript框架

jQuery是一款广泛应用的JavaScript框架,将在第22章详细介绍。

2.5.2 JavaScript压缩

在本章的示例中,我们看到浏览器会直接下载JavaScript源代码进行解析和执行。当源代码文件较大
时,对文件进行压缩既可以减小文件体积、缩短下载时间,又可以增加混淆、提高代码阅读难度,在一
定程度上实现保密。
2.5.2.html使用两段<script>标签展示普通函数代码经压缩后的函数代码,如下所示。
动手写2.5.2
执行2.5.2.html,输出两个函数的调用结果到网页,如下图所示。
图2.5.2 JavaScript压缩示例

提示
压缩的基本原理是删除多余的字符,目前成熟的、免费使用的JavaScript压缩、混淆工具都会保证压
缩前后的代码逻辑是一致的。对读者而言,研究代码压缩能够帮助自己进一步掌握JavaScript的基本原
理。
第3章 JavaScript语法
JavaScript参考了C语言的语法,形成了具有函数式编程和原型链编程的语法风格。本章将从
JavaScript的变量、常量、数据类型开始,介绍精彩的JavaScript编程世界。

3.1 语法基础
JavaScript语法规定了如何正确编写JavaScript程序,包括词法和句法。
JavaScript支持完全采用函数式编程,也支持以面向对象的方式编程。在函数式编程方面,它与C语
言相似;而在面向对象编程方面,它则与Lua的元表相似。
JavaScript完全采用Unicode编码,Unicode支持以2个字节(扩展的Unicode支持最多6个字节)来表
示一个字符,因此在JavaScript代码中可以使用中文命名变量,也不需要处理字符串的字节宽度。
支持中文变量和函数是JavaScript语法的一个特性,但从兼容性考虑,笔者建议不要模仿和使用,因
为在不同操作系统之间同步和显示代码时如果没有选择正确的文件编码,会导致操作失败、文件损坏。
3.1.1.html使用英文单词和中文分别命名变量(var)和函数(function),它们都能正常运行,如下
所示。
动手写3.1.1

执行3.1.1.html,调用函数“getBookName()”“获取书名()”并在网页中输出本书的名字,如下图所示。
图3.1.1 命名变量和函数

虽然3.1.1.html可以正常运行,但是不建议使用,请使用标准的标识符命名变量和函数。接下来介绍
标识符以及如何正确使用标识符。

3.1.1 标识符

JavaScript标识符是一个名称,可以用来表示变量、函数、参数、属性,也可以用作定义循环和跳转
位置的标签。
1.命名格式
命名标识符需要满足以下三个条件:
(1)第一个字符必须是字母、下划线、美元符号、2个字节以上的Unicode;
(2)从第二个字母开始,任意位置可以是数字、字母、下划线、美元符号、2个字节以上的
Unicode;
(3)JavaScript使用一些单词作为保留字,例如var、function,这部分保留字具有特殊含义,不能
作为标识符,保留字列表详见3.1.6一节。
JavaScript支持与HTML混合编写,但需要注意JavaScript严格区分大小写。
3.1.2.html定义两个变量并对变量赋值,最后判断变量是否相等,如下所示。
动手写3.1.2

执行3.1.2.html,在网页中输出变量bookName和bookname的值,最后显示的false表示这两个变量不
相等,如下图所示。

图3.1.2 定义变量并区分大小写

2.命名标准
标识符允许包含多种字符,但随意命名会让代码显得混乱、不够严谨,例如在上面3.1.2.html中使用
了两种命名风格bookName和bookname,第一种是从第二个单词开始首字母大写,第二种是全部采用小
写。
实践中有多种标识符书写风格:
(1)驼峰式命名(camelCase),例如“var bookName = '\u96f6\u58f9\u5b66\u5802'”;
(2)首字母大写命名(StudlyCaps),例如“function BookSeller() {}”;
(3)下划线命名(UnderScoreCase),例如“var book_name = '\u96f6\u58f9\u5b66\u5802'”。
在HTML中还有一种常见命名方式为短横线命名(kebab-case),例如<div data-target= "#new">。由
此可见,命名方式多种多样。除了命名风格,还需要规定何时使用$、_等特殊字符。统一团队编码风
格,有助于降低团队成员之间的沟通成本。
提示
JavaScript没有规定必须使用哪一种命名标准,但是从JavaScript自带的变量如localStorage、函数如
parseInt()可以看出,JavaScript的原始风格是驼峰式命名。

3.1.2 直接量
JavaScript直接量就是值,比如数字7、字符串“name”、布尔值true、正则表达式/name/、数字初始化
[]、对象初始化{},以及表示空值的null和表示未定义的undefined。
直接量经常用于变量的初始化和字符串拼接。
3.1.3.html将直接量赋值给变量,实现变量初始化,如下所示。
动手写3.1.3

执行3.1.3.html,在网页中输出变量的初始值,按下F12打开浏览器控制台,还可以看到变量的详细
信息,如下图所示。
图3.1.3 JavaScript直接量

3.1.3 变量

变量可以理解为“变量=标识符+值”,标识符名称不变,值可以任意变化。JavaScript是弱类型语言,
即不需要声明变量的数据类型,变量在这一刻是数字,下一刻又可以是字符串。
1.声明一个变量,并赋予不同的值
3.1.4.html定义变量variable,依次赋予不同类型的值,如下所示。
动手写3.1.4

执行3.1.4.html,向网页中输出同一个变量在不同时刻的值,如下图所示。
图3.1.4 JavaScript变量

对象和数字一样,是JavaScript的一种数据类型,将在下一节详细介绍。变量的值为对象时,输出到
网页会变成字符串“[object Object]”,不能和数字一样保留字面量。如果按下F12打开浏览器控制台,我
们可以看到变量variable的详细信息。
2.声明多个变量
JavaScript支持一次声明多个变量,并同时进行赋值。
3.1.5.html使用var声明多个变量,如下所示。
动手写3.1.5

执行3.1.5.html,将变量输出到网页中,如下图所示。

图3.1.5 声明多个变量

虽然在一行内声明多个变量可以减少代码行数,但是一次只声明一个变量,代码会比较清晰。
提示
JavaScript支持使用var关键字重复声明变量,而且重复声明变量不会覆盖变量的值,但是在编码时
应该知晓变量何时声明,尽可能减少重复声明。

3.1.4 常量
常量是一种特殊类型的变量,即其对应的值不能改变。除了复杂数据类型,简单的直接量(或称字
面量)也是常量,如数字7、字符串“name”、布尔值、null、undefined。
ES5增加了常量的支持,也就是在变量声明之后不改变变量的类型和值。通过使用变量,即使
JavaScript是弱类型语言,也能够显著提升程序的性能。
3.1.6.html使用最新的const关键字声明常量,如下所示。
动手写3.1.6

执行3.1.6.html,输出常量到网页,如下图所示。

图3.1.6 声明常量

常量不允许修改,不管新值与旧值是否相等;常量也不允许重复声明。
3.1.6.html中出现了两处错误:第一处是修改常量firstConst的值,第二处是重复声明常量
secondConst。这两处错误都会被浏览器输出到控制台,因此我们可以在浏览器控制台看到以下错误信
息:“Uncaught TypeError: Assignment to constant variable”和“Uncaught SyntaxError: Identifier 'secondConst'
has already been declared”。
3.1.5 注释

JavaScript注释不会被执行,通常使用注释提高代码可读性以及临时备忘。
JavaScript支持两种注释:
1.单行注释,以“//”开头;
2.多行注释,以“/*”开头,以“*/”结尾。
2.5.2小节讲解了JavaScript压缩,因为注释对于代码运行来说是无效信息,所以在压缩过程中都会被
删除。JavaScript有大量可以免费使用的开源代码,为了保护作者的版权信息,会在多行注释的基础上扩
展一种开源注释。开源注释是在多行注释的基础上增加一个感叹号,压缩工具会特意保留这段注释。开
源注释以“/*!”开头,以“*/”结尾。它并不是新的语法结构,只是注释的一种使用方式。
3.1.7.html展示了多种注释的使用方式,如下所示。
动手写3.1.7

3.1.6 保留字
ECMA-262定义了一套具有特殊用途的关键字,例如用于声明变量的var、声明函数的function、实
现流程控制的if和for、一些执行特定操作(如删除属性、导出变量),以及一部分目前虽不会使用但将
来可能作为关键字的英文单词。这些特殊的英文单词被统一称为保留字,不能用作标识符的名称。
JavaScript语言发展至今已经积累了60多个保留字,如下表所示,在命名标识符时不要与之同名。
表3.1.1 JavaScript保留字
在3.1.4.html中,变量variable可以被重新声明、重新赋值。函数是一种特殊类型的变量,也可以重
新声明、重新赋值。因此在定义变量时,需要避免与JavaScript全局函数重名而导致歧义或产生bug。

3.2 数据类型
JavaScript是一种弱类型语言,具有六种数据类型,ES6又新增一种symbol类型。
◇ 基本数据类型:boolean、null、number、string、symbol、undefined。
◇ 复杂数据类型:object。
其中null表示空值,symbol表示唯一标识,undefined表示变量存在但它的值没有定义。数组是一种
特殊的对象类型,因此没有单独列出。
根据是否可以作为数据集合将数据类型分为简单数据类型和引用数据类型。
◇ 简单数据类型:boolean、null、number、string、symbol、undefined。
◇ 引用数据类型:array、object、function。
数组(array)和函数(function)都属于复杂数据类型。

3.2.1 布尔值

布尔类型boolean只有两个值:true和false。true表示真,false表示假,没有中间值。true和false均为
小写,没有其他写法。True是一个普通的标识符,不代表真值,与true没有关系。
布尔类型可以直接使用true和false表示,也可以通过构造函数new Boolean()获取。布尔类型的变量
常在判断语句if…else中使用,判断语句会在第6章进行介绍。
3.2.1.html使用保留字和构造函数new Boolean()创建布尔类型数据,如下所示。
动手写3.2.1
执行3.2.1.html,输出变量zhen1、zhen2的值,并判断zhen1是否为true,在网页上输出提示“变量
zhen1 表示真”,如下图所示。

图3.2.1 JavaScript的布尔类型

布尔类型数据经常作为开关使用,可以将true看作开关打开、灯亮,将false看作开关断开、灯灭。
布尔数据可以很容易地和数值进行转换,在运算表达式“true+1+false”中,将true转换为1,将false转换为
0,因此整个表达式的值为2。布尔数据转换为字符串时,true转换为字符串"true",false转换为字符
串"false",与所见字符一致。

3.2.2 数字
JavaScript的数字类型只有一种,即number类型,它不区分整型、长整型、浮点型,采用64位浮点
格式表示数字,最大值支持“1.7976931348623157e+308”,最小值支持“5e-324”。使用数字时,不需要特
殊指定数字是整型还是浮点型,JavaScript会自动判断数字类型,例如1、2.3、4.56等。因此在JavaScript
中,1、1.0、1.00都用1表示,若要对三者进行区分,则需要将它们转换为字符串。
提示
数字中出现的e表示科学计数法,e+2表示10的2次方100,e+3表示10的3次方1000。3.14e3表示3.14
乘以10的3次方等于3140。大写E与小写e含义一致。
3.14e+2表示3.14乘以10的2次方,314e-2表示314除以10的2次方,表示乘法时可以省略加号。e不能
单独使用,必须与数字一起使用,前后必须有数字。
3.2.2.html演示了数字的使用方式,并区别1和1.00,如下所示。
动手写3.2.2
执行3.2.2.html,输出数字到网页中,其中1和1.00输出到网页时都一样,只有使用字符串时两者才
会有区别,如下图所示。

图3.2.2 JavaScript的数字类型

数字前出现减号或负号(-),表示负数;加号(+)表示正数,可以省略。在第4章运算符一节会
讲解加号作为一元运算符时不能省略的内容。
JavaScript的小数与CSS中的小数相似,当数字是大于0且小于1的小数时,可以省略前面的0,即0.1
与.1等价,都可以正常使用。

3.2.3 字符串
JavaScript字符串不区分单个字符与多个字符,统一称为字符串,支持使用单引号、双引号以及new
String()创建字符串。
字符串以Unicode编码,英文字符和中文字符的宽度都是1。
使用单引号创建字符串时,若字符串中出现单引号,需要使用反斜线进行转义。同样地,使用双引
号创建字符串时,若字符串中出现双引号,也需要使用反斜线进行转义。
3.2.3.html分别使用单引号、双引号和构造函数new String()创建字符串,并使用加号(+)连接两个
字符串,如下所示。
动手写3.2.3
执行3.2.3.html,在网页中输出字符串,并展示转义字符的输出结果,发现\'和\"分别只作为一个字
符'和"输出,如下图所示。

图3.2.3 JavaScript的字符串类型

3.2.4 对象

JavaScript对象是一种复杂数据类型,能够将多种数据类型集中在一起,实现数据集合。对象使用花
括号({})表示,采用形如{key:value}的结构。对象内部有多个key:value键值对时,使用逗号分隔。
3.2.4.html使用对象组织布尔值、数字、字符串、对象等多种数据类型,如下所示。
动手写3.2.4
执行3.2.4.html,在网页中输出对象序列化之后的字符串,并在控制台输出对象,点击前面的小三角
即可以展开对象,如下图所示。

图3.2.4 JavaScript的对象类型

对象可以组织任意数据类型,在后续章节中还能看到使用对象组织函数、数组等更加复杂的数据结
构。
当对象的最后一个键值对末尾出现逗号(通称Trailing commas,即尾后逗号)时,例如 {key1:1,
key2: 2,},旧版本浏览器将无法识别,会出现语法错误。新版本浏览器能够正常识别尾后逗号,但是从
兼容性考虑,建议避免使用尾后逗号。
3.2.5 空值null

null是一个保留字,表示空值,它的值没有类型,有时会被称为空对象。
对象{}不包含任何key:value键值对时,也会被称为空对象,但是与null含义不同,null更倾向于表示
什么都没有。
数字0表示没有计数,但它仍然是一个数值。空字符串""表示空的字符串,不包含任何字符,但它
仍然是一个字符串。null与它们相比,表示什么都没有。
3.2.5.html使用null进行操作,如下所示。
动手写3.2.5

执行3.2.5.html,null与数字进行计算时转换为0,与字符串连接时转换为"null",如下图所示。

图3.2.5 JavaScript的数据类型null

JavaScript不能主动进行垃圾回收。JavaScript引擎会根据变量的引用数判断该变量是否仍然可用,
如果引用数为0,则有概率回收该变量,不会百分之百回收。设置变量的值为null,可以释放变量的值所
占用的内存空间。

3.2.6 未定义undefined

未定义的值undefined表示变量的值未定义、属性的值未定义或属性不存在。undefined不是常量,也
不是保留字,在语法上允许修改,但目前浏览器为了避免产生歧义,会阻止修改undefined。
3.2.6.html声明变量但不进行赋值,使用undefined与数字和字符进行操作,最后展示null和undefined
的区别,如下所示。
动手写3.2.6
执行3.2.6.html,输出运算结果到网页,如下图所示。

图3.2.6 JavaScript的数据类型undefined

JavaScript不区分变量类型,判断变量相等有两种运算符号:
◇ 两个等号(==),a==b表示两者的值相等,或经过类型转换之后两者的值相等,不限制a、b的
数据类型,例如数字1和字符串"1"相等,表达式1=="1"为true。
◇ 三个等号(===),a===b表示两者的值相等、类型相同,例如数字1和字符串"1"不全等,
1==="1"为false。
第4章运算符会对相等和全等做进一步介绍。
null和undefined的区别:
◇ null==undefined为true,其他任何值与这两个值进行相等比较均为false。
◇ null===undefined为false,null和undefined与其他任何值进行全等比较都为false。
◇ null是保留字,undefined是全局变量,浏览器控制不能修改undefined的值。
◇ null转换为数字等于0,undefined转换为数字等于NaN,NaN全称Not a Number,它属于number类
型,不过是一个不能表示的数值。

3.2.7 使用typeof获取数据类型
JavaScript虽然是弱类型语言,没有显示设置变量的类型,但变量的值仍然会区分数据类型。使用关
键字typeof,可以获取紧邻的操作数的数据类型。例如,typeof 1.2输出结果为number,表示1.2是数字类
型。typeof也可以作为函数调用,typeof 1.2 与typeof(1.2) 输出结果一致。
在实际应用中,typeof常常用于检测参数的数据类型,如下表所示。
表3.2.1 typeof检测数据类型
使用typeof得到的返回值有:boolean、function、number、object、string、undefined、symbol。
typeof null返回值是object,这是早期JavaScript设计时遗留下的问题,即使到现在也无法通过typeof判断
null类型。
typeof还有一个特性是检测不存在的变量。如果变量a没有使用var关键字声明但直接使用,会抛出
类似错误“Uncaught ReferenceError: a is not defined”。在这种情况下,typeof a的返回值是"undefined",说
明a没有定义,不能直接使用。利用typeof可以避免使用未定义的变量导致程序执行出错。
3.2.7.html使用typeof检测各种类型的数据,如下所示。
动手写3.2.7

执行3.2.7.html,输出各个数据的类型,如下图所示。
图3.2.7 使用typeof获取数据类型

关键字typeof在两种情况下会返回undefined:变量不存在和变量的值未定义。在这两种情况下声明
变量都不会出现错误。

3.3 严格模式
严格模式(Strict Mode)是指JavaScript在限制性更强的条件下运行,可以让IDE增强查错,检测可
能存在的bug。ECMAScript 5引入严格模式执行语句“use strict”,指令语句向浏览器发送信息。JavaScript
引擎会忽略不识别的指令语句。

3.3.1 开启严格模式

通过指令语句 'use strict' 开启严格模式。严格模式只支持在某个范围内开启,比如在整个当前


<script>标签、在某个函数内部等。
3.3.1.html在第一段<script>标签中直接对不存在的变量赋值,浏览器允许代码执行,但是严格模式
不允许有这样的行为,因此在第二段<script>标签中开启严格模式后,对不存在的变量赋值,会抛出错
误,如下所示。
动手写3.3.1

执行3.3.1.html,只有第一段<script>向网页成功输出了文本,第二段<script>出现语法错误,打开浏
览器控制台可以看到错误信息“Uncaught ReferenceError: var 2 is not defined”,如下图所示。

图3.3.1 开启严格模式

JavaScript允许变量未经声明直接赋值,此时的变量会作为全局对象的属性存在。虽然浏览器支持这
样赋值,但这不是一种规范的编程方式,而且容易引起变量污染。在多个<script>标签中修改全局变
量,会让代码跟踪变得复杂,因此建议限制全局变量数量或者不使用。
提示
目前PC端浏览器IE10+、Chrome、Firefox、Safari都支持严格模式,手机端浏览器也都基本支持严
格模式。

3.3.2 严格模式的限制

严格模式增加了一些限制,读者在学习完全部的JavaScript内容之后,再来理解这些限制会更加深
刻,此处只简单列举严格模式的限制:
1.变量必须先声明后使用;
2.不允许参数重名;
3.不允许属性重名;
4.不允许使用arguments作为标识符;
5.不允许使用delete操作符删除变量;
6.不允许使用delete操作符删除函数;
7.不允许删除不能删除的属性,调用delete直接报错而不是返回false;
8.不允许使用八进制;
9.不允许使用转义序列;
10.不允许修改只读属性;
11.不允许this指向全局对象;
12.不允许在函数内部调用function.arguments、function.caller、arguments.callee;
13.增加了与类相关的几个关键字;
14.函数必须声明在作用域顶部,不能在控制语句内使用function声明具名函数;
15.不允许使用with动态绑定,必须使用静态绑定;
16.增加eval安全作用域。
静态绑定是指属性和方法在预编译阶段决定所属对象,而不是在运行时决定。在JavaScript中声明对
象之后,可以增加、删除、改变对象的属性和函数,这些属性和函数是在对象创建之后绑定在一起的,
属于运行时绑定。在对象创建时就明确它拥有的属性和函数,属于静态绑定。
对于初学者,笔者建议默认增加严格模式指令,结合浏览器控制台查看提示信息,这样做更加容易
理解JavaScript。

3.4 小结
本章主要对JavaScript语法知识做了详细介绍,包括标识符、变量和常量、数据类型,以及对严格模
式做了讲解。这些知识是学习和使用JavaScript编程的基本要素,熟练掌握它们也有助于对后续章节的学
习和使用。

3.5 知识拓展
3.5.1 未声明与未定义的区别

1.变量未声明与未定义
使用一个变量前,必须先使用var关键字声明,因为访问一个不存在的变量会触发错误,比如在
2.3.1.html中访问未声明的变量block3时会抛出错误“Uncaught ReferenceError: block3 is not defined”。
在实际应用中,一个网页包含多达几十甚至上百个外部JavaScript文件,不同文件维护各自的变量。
在本段<script>标签中,如果对其他JavaScript代码有依赖,在使用它们提供的变量前通过typeof检测变量
是否为undefined,可以避免直接访问报错,导致程序异常结束。
3.5.1.html分别使用了未声明的变量和未定义的变量,如下所示。
动手写3.5.1
typeof检测到未声明的标识符和未定义的变量时均返回undefined。第一个<script>的if判断语句都为
true;未声明的标识符不能直接访问,第二个<script>标签抛出错误“Uncaught ReferenceError:
undeclaredVariable is not defined”;第三个<script>标签中if判断语句为true。
执行3.5.1.html,看到网页输出的提示,打开浏览器控制台,则能看到访问未声明变量
undeclaredVariable时抛出的错误,如下图所示。

图3.5.1 使用typeof检测未声明和未定义的变量

使用typeof检测一个外部变量是否为undefined,而不是直接判断该变量的值为undefined,这样可以
避免抛出“Uncaught ReferenceError”错误。
2.属性未声明与未定义
检测对象的属性不存在或者属性的值是undefined时,不管是使用typeof判断还是直接与undefined比
较,都可以正常执行。
3.5.2.html分别通过typeof关键字和undefined判断对象的属性是否存在,如下所示。
动手写3.5.2
执行3.5.2.html,输出检测结果到网页,如下图所示。

图3.5.2 检测对象的属性是否存在

默认情况下,所有对象都继承了hasOwnProperty()函数,该函数用于判断当前对象是否拥有指定属
性,而不管属性的值是什么类型。

3.5.2 JavaScript编程风格
JavaScript代码支持使用制表符Tab和空格缩进,每行之间的缩进不要求对齐,这点与Python不同。
JavaScript代码行尾不强制要求添加分号。
代码要尽量清晰、易读、易懂,避免出现意外错误。在此基础上配置代码风格,通过IDE或Lint工
具对代码进行格式化。
打开WebStorm,依次点击File->Settings->Editor->Code Style->JavaScript,配置代码风格。配置完成
之后导出配置,分享给团队成员使用,如下图所示。
图3.5.3 使用WebStorm配置JavaScript代码风格

JavaScript是一门松散的语言,这一点从行尾不强制要求添加分号就可以看出。除了约定代码风格,
还需要约定如何使用语言特性:
1.采用驼峰式命名、首字母大写命名,还是下划线命名;
2.语句末尾是否添加分号;
3.使用全等还是相等;
4.何时使用连续赋值;
5.如何使用全局变量;
6.函数声明位置;
7.其他有差异的特性。
避免在项目中使用不同风格的代码,要选择适合自己和团队的编码风格,确保在项目中使用统一的
代码规范。

3.5.3 JavaScript文档工具

自动化文档工具将根据代码注释自动生成文档接口、调用示例,以及进行代码分析,提出改进意
见。
JavaScript文档工具有JSDoc、JSDuck、YUIDoc,这些工具可以免费使用,其中JSDoc的可扩展性
强,支持深度定制。欢迎大家尝试使用这些工具,可以减少专门为代码编写文档的时间。
JSDoc官方网站:http://usejsdoc.org/
JSDouk官方网站:https://github.com/rwhogg/node-jsduck
YUIDoc官方网站:http://yui.github.io/yuidoc/
第4章 JavaScript运算符
在各种运算表达式中使用的符号称为运算符。JavaScript的运算符包括各种标点符号和部分保留字,
几乎每一行代码都会涉及运算符。本章将介绍运算符的种类和使用方法,以及运算表达式的相关知识。

4.1 运算符
执行各种运算操作的符号称为运算符,也称为操作符。JavaScript的运算符除了大部分是标点符号之
外,还有部分是使用关键字表示的运算符,如new、delete、instanceof、typeof、void。
运算表达式是指参与运算的操作数和运算符组成的语句。其中,根据操作数的个数,将运算符分为
一元运算符、二元运算符、三元运算符。操作数可以是变量、常量、直接量、函数返回值、语句返回
值。根据运算符的功能,将运算符分为算术运算符、比较运算符、逻辑运算符、位运算符、对象操作运
算符、条件运算符等。
运算符的一元、二元、三元也可以称为目,因此又可以称为一目运算符(单目运算符)、二目运算
符、三目运算符。
1.一元运算符
一元运算符是指只需要一个操作数的运算符,比如递增(++)、递减(--)、数值转换(+)、逻
辑取反(!)、按位取反(~)等。
JavaScript支持的一元运算符如下表所示。表4.1.1、4.1.2和4.1.3中,运算顺序为1表示从左到右运
算,运算顺序为2表示从右到左运算。
表4.1.1 JavaScript一元运算符

(续上表)
2.二元运算符
二元运算符是指需要两个操作数的运算符,它是JavaScript中最常见的运算符,比如加减乘除、位
移、大小比较等。
JavaScript支持的二元运算符如下表所示。
表4.1.2 JavaScript二元运算符

(续上表)
3.三元运算符
三元运算符是指需要三个操作数的运算符,JavaScript中唯一的三元运算符是条件运算符(?: )。
JavaScript支持的三元运算符如下表所示。
表4.1.3 JavaScript三元运算符

有C++开发经验的读者应该知道,C++支持运算符重载,即运算符根据不同的操作数类型执行不同
运算。这一点上,JavaScript的运算符与C++不同,它不支持运算符重载。

4.2 算术运算符
算术运算符用于实现算术运算,包括加法(+)、减法(-)、乘法(*)、除法(/)、递增
(++)、递减(--)、取余数(%)、取负数(-)、取幂(**)。

4.2.1 加减乘除
4.2.1.html演示算术四则运算,如下所示。
动手写4.2.1

执行4.2.1.html,将运算结果输出到网页,如下图所示。

图4.2.1 加减乘除

JavaScript数字类型number不区分int和float,若表达式运算结果为浮点数则自动转换为浮点数,运算
结果为整数则自动转换为整数。以下两条运算结果展示浮点数与整数的自动转换:
◇ 2/3结果为0.6666666666666666,而不是0。
◇ 2*1.5结果为3。
有时候需要获得表达式的整数结果,可以使用函数parseInt()强制将结果转换为整数。parseInt()也可
以将数字字符串转换为整数。负数用负号(-)表示,正数用加号(+)表示,默认为正数时,省略加
号。
4.2.2.html转换浮点数为整数,如下所示。
动手写4.2.2

执行4.2.2.html,输出转换结果到网页,如下图所示。
图4.2.2 强制转换为整数

除数为0时,正数取商,如 2 / 0 ,得到正无穷大,在JavaScript中用Infinity表示;负数取商,比如 -1
/ 0 ,得到负无穷大,用-Infinity表示。
4.2.3.html将除数设置为0,获取无穷大,如下所示。
动手写4.2.3

执行4.2.3.html,输出正负无穷大到网页,如下图所示。

图4.2.3 除数为0时

4.2.2 递增递减

递增(++)和递减(--)都属于快捷运算,分别用于将变量自身的值加1和减1。递增和递减可以位
于操作数左侧和右侧。
递增和递减位于操作数两侧时,表达式的返回值不同:
1.运算符位于操作数右侧时,修改自身值,返回修改前的值,比如a++将a的值加1,但返回a原来的
值;
2.运算符位于操作数左侧时,修改自身值,返回修改后的值,比如++a将a的值加1,并返回a最新的
值。
4.2.4.html使用递增和递减对总数进行修改,如下所示。
动手写4.2.4
执行4.2.4.html,输出表达式的值到网页,如下图所示。

图4.2.4 递增和递减

递增和递减只能用于变量、对象属性(包括数组元素),不能用于直接量、函数返回值和表达式返
回值。
4.2.5.html使用递增修改对象属性的值,如下所示。
动手写4.2.5

执行4.2.5.html,修改属性的值时正常输出,修改函数返回值时抛出错误,打开浏览器控制台能看到
错误信息,如下图所示。
图4.2.5 递增和递减使用限制

递增和递减只能用于标识符,不能用于直接量、表达式返回值、函数返回值,非法使用会被识别成
缺少对应操作数,抛出错误“Uncaught ReferenceError: Invalid left-hand side expression in postfix
operation”。

4.2.3 取余取负

1.取余运算
取余运算也称为取模运算,用于获取余数。
4.2.6.html使用取模运算符(%)获取表达式的余数,如下所示。
动手写4.2.6

执行4.2.6.html,输出余数到网页,如下图所示。

图4.2.6 取余运算

在JavaScript的取余运算中,如果有操作数为负数,最终余数等于移除商值之后与0的差值,即余数
=被除数-parseInt(被除数 / 除数)。
◇ 3%-5=3-(3 / -5),等于3。
◇ -3%-5=-3-(-3 / -5),等于-3。
其他语言中取余数和取模数可能会有一点差异,但在JavaScript中两者没有差别。两者仅在商值为负
数时有差异,即商值无论是向0方向靠近,还是向负无穷大方向靠近,同样应用上述计算公式。
2.取负运算
取负运算是指交换操作数的正负符号,结果等于0减去操作数。
4.2.7.html对正负数进行取负运算,如下所示。
动手写4.2.7

执行4.2.7.html,输出运算结果到网页,如下图所示。

图4.2.7 取负运算

代码“-positiveNumber”和“0 - positiveNumber”虽然最终结果一致,但两者含义不同,前者是取操作
数负数,后者是两个操作数相减。

4.2.4 幂运算

幂运算是指获取操作数的n次方。
4.2.8.html获取2的3次幂,如下所示。
动手写4.2.8
执行4.2.8.html,输出结果到网页,在第二个和第五个代码块幂运算和取负运算产生歧义,触发语法
错误,打开浏览器控制台可以看到错误提示,如下图所示。

图4.2.8 幂运算

幂运算的底数与一元运算符组合使用时会引发歧义。ES2016中规定,底数不能与一元运算符(+/-/
~/!/delete/void/typeof)组合使用。从4.2.8.html看出,有歧义的运算会导致抛出错误“Uncaught
SyntaxError:Unexpected token **”。在上述例子中,可以使用括号()将表达式-x** n中的-x包围起来,明
确作为一个整体,用于消除歧义。
提示
使用()将表达式的一部分包围起来,不论这部分是有歧义还是容易让阅读者产生困扰,都有助于阅
读者和浏览器将这部分当作统一的整体。
4.3 比较运算符
比较运算符又称为关系运算符,用于判断两个操作数的大小关系、是否相等。比较运算符总是返回
布尔值true或false。

4.3.1 相等与全等

JavaScript是弱类型语言,支持两种判断方式——相等(==)和全等(===)。相等是指两个操作数
的类型相同,值相等,或者类型不同但经过一定转换规则之后的值相等;全等是指两个操作数的类型相
同,值相等。
相等和全等的使用方式如下:
1.表达式a==b 返回true,表示a、b两个操作数的值相等;
2.表达式a!=b返回true,表示a、b两个操作数的值不相等;
3.表达式a===b返回true,表示a、b两个操作数的值相等,类型相同;
4.表达式a!==b返回true,表示a、b两个操作数的值不相等或类型不同。
4.3.1.html使用相等和全等运算符比较相似的字符串和数字,如下所示。
动手写4.3.1

执行4.3.1.html,输出比较结果到网页,如下图所示。

图4.3.1 相等判断
leftVariable和rightVariable都是数字1时,两者相等且全等;leftVariable是数字1,rightVariable是字符
串1时,两者相等但不全等。

4.3.2 类型转换

JavaScript支持比较两个类型不一致的操作数。当两个操作数类型不一致,要判断两者是否相等时,
需要按照以下规则对数字、字符串、布尔值、对象、null、undefined进行转换之后再判断:
1.null与除了undefined之外的任意值均不相等(!=),undefined与除了null之外的任意值均不相等。
2.null===null为true,与其他值进行全等比较均为false。
3.undefined===undefined为true,与其他值进行全等比较均为false。
4.非数字NaN与任意值均不相等(!=),NaN不等于NaN为true,NaN参与比较运算的结果均为
false。
5.比较数字和字符串时,字符串会转换为数值再进行比较,比如'1'==1 为true,'1.00'==1 也为true。
6.比较布尔值和非布尔值时,将true转换为1,false转换为0,再应用其他规则。比如,true=='1.00'
先将true转换为1、'1.00'转换为1再进行比较,因此为true;false=='000' 将false转换为0、'000'转换为0再进
行比较,因此也为true。
7.比较对象与数字、字符串时,尝试用该对象的原始值(Primitive Value)进行比较,即尝试调用对
象的valueOf()和toString()获取原始值,再进行比较。
8.比较两个对象时,仅比较两个操作数的引用是否相同。
NaN是JavaScript的一个特殊数值常量,表示非数字;typeof NaN返回“number”。
此处的对象包括数组、函数和其他内置、自定义对象。
4.3.2.html对数字、字符串、布尔值进行比较,如下所示。
动手写4.3.2
执行4.3.2.html,输出比较结果到网页,如下图所示。
图4.3.2 类型转换

比较对象和原始值(数字、字符串)稍微有点复杂,有一个函数调用过程。一般情况下,JavaScript
的对象从关系链的最顶层继承了toString()和valueOf()两个方法,为对象设置这两个方法,可以覆盖默认
的toString()、valueOf()。
4.3.3.html为对象设置toString()、valueOf()方法,并在函数执行时输出提示语句,以便查看对象在执
行比较运算时的函数调用过程,如下所示。
动手写4.3.3
执行4.3.3.html,输出比较结果、函数调用过程到网页,如下图所示。

图4.3.3 比较对象与原始值

在获取原始值的过程中,函数valueOf()先执行,因此看到先输出valueOf函数的提示语句,再执行
toString()。如果这两个函数中有一个的返回值是原始值(数字、字符串),则使用该值进行比较。
提示
调用对象的valueOf()和toString()获取原始值(数字、字符串),如果转换失败(返回值不是数字或
字符串),会报错。其他情况调用这两个方法,不会报错。

4.3.3 大小判断

JavaScript的大小判断包括数值大小判断、字符大小判断。比较字符大小,其实是比较字符的
Unicode码。比较大小的运算符包括:小于(<)、大于(>)、小于等于(<=)、大于等于(>=)。
4.3.4.html使用比较运算符对数字、字符串进行比较,如下所示。
动手写4.3.4
执行4.3.4.html,输出比较结果到网页,如下图所示。

图4.3.4 大小判断

字符串a与数字1比较,参考4.3.2类型转换一节中的规则“比较数字和字符串时,字符串会转换为数
值再进行比较”,字符串a转换为数值(转换公式+a)得到结果NaN,因此'a'>1、'a'<1、'a'==1的结果都为
false。
字符串a与字符串1比较,实际上是比较两者的Unicode码,最终结果为true。
4.3.5.html对字符串进行比较,如下所示。
动手写4.3.5

执行4.3.5.html,输出比较结果到网页,如下图所示。
图4.3.5 字符大小判断

比较字符串时按照顺序依次比较相同位置字符串的Unicode码,直到得出比较结果。

4.4 逻辑运算符
逻辑运算是指根据操作数的真值判断表达式的真值,包括逻辑与(&&)、逻辑或(||)、逻辑非
(!)。
进行逻辑运算需要知道操作数的布尔值,因为JavaScript是弱类型语言,所以在进行布尔运算时要根
据一定的规则将操作数转换为布尔值。转换为布尔值时等于false的非布尔值包括:
◇ null;
◇ undefined;
◇ 0;
◇ 非数字NaN;
◇ 空字符串""。
其他非布尔值转换为布尔值均为true。
逻辑表达式的含义:
◇ 逻辑与表达式a&&b,当a、b对应的布尔值都为真时,结果为真,否则为假。
◇ 逻辑或表达式a||b,当a、b中至少有一个的布尔值为真时,结果为真,否则为假。
◇ 逻辑非表达式!a,当a的布尔值为假时,结果为真,否则为假。
1.逻辑与运算
4.4.1.html获取操作数的逻辑与运算结果,运算符&&两侧的表达式全部为真时整个表达式才为真,
如下所示。
动手写4.4.1

执行4.4.1.html,输出结果到网页,如下图所示。
图4.4.1 逻辑与运算

提示
虽然逻辑运算会将操作数转换为布尔值,但逻辑与和逻辑或表达式返回的是操作数的值,而不是表
达式对应的布尔值,这一点与其他语言可能不同。
2.逻辑或运算
4.4.2.html获取操作数的逻辑或运算结果,运算符||两侧的表达式只要有一项为真,整个表达式就为
真,如下所示。
动手写4.4.2

执行4.4.2.html,输出结果到网页,如下图所示。

图4.4.2 逻辑或运算

3.逻辑非运算
4.4.3.html获取操作数的逻辑非运算结果,如下所示。
动手写4.4.3

执行4.4.3.html,输出结果到网页,如下图所示。
图4.4.3 逻辑非运算

4.连续逻辑运算
逻辑运算和算术运算一样,允许多个操作数一起参与计算。
4.4.4.html同时对多个操作数进行逻辑运算,如下所示。
动手写4.4.4

执行4.4.4.html,查看浏览器类型,如下图所示。

图4.4.4 连续逻辑运算

逻辑与和逻辑或都支持连续运算。
5.获取操作数布尔值
使用函数Boolean()返回操作数的布尔值,也可以对该操作数进行两次逻辑取反获取对应的布尔值。
4.4.5.html分别使用Boolean()函数和两次逻辑非运算获取操作数布尔值,如下所示。
动手写4.4.5

执行4.4.5.html,输出操作数的布尔值到网页,如下图所示。
图4.4.5 使用连续取非运算获取操作数布尔值

提示
!0与true全等,且比true少两个字符;!1与false全等且少三个字符。执行JavaScript压缩时常常利用此
特性减少字符数量。

4.5 位运算符
位运算是将操作数当作32位的0和1组成的比特序列进行运算。位运算符包括按位与(&)、按位或
(|)、按位异或(^)、按位非(~)、左移(<<)、右移(>>)、无符号右移(>>>)。
计算机存储的数据序列就是比特序列,位运算具有天然的速度优势。

4.5.1 按位逻辑运算

按位逻辑运算有四种 :
1.按位与(&),对比操作数每个比特位,当对应比特位均为1时,新结果中该比特位是1,否则为
0;
2.按位或(|),对比操作数每个比特位,当对应比特位至少有一个为1时,新结果中该比特位是1,
否则为0;
3.按位异或(^),对比操作数每个比特位,当对应比特位只有一个为1时,新结果中该比特位是
1,否则为0;
4.按位非(~),是一元运算符,将操作数比特位取反,0变1,1变0。
4.5.1.html对采用二进制表示的操作数进行按位逻辑运算,如下所示。
动手写4.5.1

执行4.5.1.html,输出运算结果到网页,如下图所示。
图4.5.1 按位逻辑运算

数字前加上0b表示二进制,在后面的5.2进制一节会详细讲解。调用数字的toString()方法,并传递参
数2,表示获取该数字的二进制序列。

4.5.2 位移运算

位移运算符有三种:
1.左移(<<),将操作数按位向左移动,右侧空白用0填充;
2.右移(>>),将操作数按位向右移动,丢弃移出的比特位;
3.无符号右移(>>>),将操作数按位向右移动,丢弃移出的比特位,左侧空白用0填充;因为比特
位序列总长度为32,所以位移时右操作数不能大于32。
4.5.2.html对二进制表示的数字进行位移运算,如下所示。
动手写4.5.2

执行4.5.2.html,输出运算结果到网页,如下图所示。

图4.5.2 位移运算

左移相当于乘以2,右移相当于除以2。
有兴趣的读者可以在学习完流程控制之后,尝试使用for循环执行100万次计算检测位移运算与乘除
运算的速度。
4.6 赋值运算符
赋值运算符是将运算符右侧操作数的值赋给左侧操作数。若右侧操作数是简单数据类型,赋值运算
进行值拷贝;若右侧操作数是复杂数据类型,赋值运算进行引用拷贝,两侧操作数指向相同的数据对
象。
常见的赋值运算符是等号(=),还有一些与等号一起使用实现赋值功能的组合运算符,如+=、-
=、*=、/=、%=、&=、|=、^=、<<=、>>=、>>>=。
组合使用是一种快捷方式,比如 a += b 等价于 a = a +b。
4.6.1.html使用赋值运算符和组合运算符对变量进行赋值,如下所示。
动手写4.6.1

4.7 对象操作运算符
对象操作运算符是指与对象操作相关的运算符,包括对象创建(new)、属性访问(点号.和中括号
[])、属性删除(delete)。
对象操作运算符适用于除了null和undefined之外的所有值。

4.7.1 new运算符
new运算符用于创建新对象。new运算符调用构造器函数默认返回实例,如果函数执行中途返回,
必须返回引用数据类型才能生效。
4.7.1.html使用new运算符创建实例,如下所示。
动手写4.7.1
执行4.7.1.html,输出实例到网页,如下图所示。

图4.7.1 new运算符创建对象

在JavaScript中,使用new调用函数,则该函数作为构造函数运行;直接调用函数时,则作为普通函
数运行。若在构造函数Course()中没有明确使用return语句,则使用new调用时默认返回Course的实例;
若在构造函数Course()中使用了return语句,并且返回了引用数据,则使用new调用时,返回值不是实
例,而是return语句返回的引用数据对象。
4.7.2.html使用new调用构造函数和不使用new调用函数,两种情况如下所示。
动手写4.7.2
执行4.7.2.html,输出函数返回值到网页,如下图所示。

图4.7.2 new运算符返回其他对象

◇ 对比构造器CourseA和构造器CourseB,CourseB返回一个普通对象,控制台显示CourseA返回的
是CourseA实例,而CourseB返回的是普通对象。
◇ 对比构造器CourseA和构造器CourseC,CourseC返回简单数据类型1,JavaScript规定new运算不能
返回简单数据类型,因此CourseC返回值被替换为CourseC实例。
◇ 构造器CourseD返回函数console.log,函数属于引用数据类型,JavaScript允许这样返回。
new运算符调用构造函数时,若构造函数支持不传参数,也可以不使用小括号,如newObject的返回
值是不包含任何属性的空白对象{}。

4.7.2 delete运算符
delete用于删除对象属性、数组元素,但不能删除变量。在浏览器全局作用域下,delete可以删除给
window新增的属性。
1.删除普通属性
4.7.3.html使用delete删除对象的属性,如下所示。
动手写4.7.3

执行4.7.3.html,输出删除前后对象的属性值,如下图所示。

图4.7.3 delete运算符

执行delete运算之后,course的name属性被删除。delete可以删除自定义的属性,不能删除从原型链
(见第12章原型链)继承的属性,比如course.constructor。
2.删除继承属性无效
4.7.4.html删除继承的属性,虽然删除运算返回值为true,但实际上并没有删除这类属性,如下所
示。
动手写4.7.4

执行4.7.4.html,删除某些特殊属性返回true,但实际上并没有删除,如下图所示。
图4.7.4 delete不能删除继承的属性

一般情况下,delete删除成功返回true,删除失败返回false。但是上述示例的运行结果说明,根据
delete语句的返回结果判断是否删除成功是不可靠的。在实际应用中基本都会忽略delete的返回值。
3.删除变量无效
delete运算符可以删除对象的属性,但是不能删除使用var声明的变量。
4.7.5.html使用delete删除变量,查看是否删除成功,如下所示。
动手写4.7.5

执行4.7.5.html,输出删除前后变量的值到网页,如下图所示。

图4.7.5 delete不能删除变量

4.删除数组元素
数组是特殊的对象,是一种列表结构,用中括号([])表示一个数组,数组的元素采用从0开始的索
引作为键值,比如数组['a','b','c']的第0个元素是字符串a,第1个元素是b,以此类推。
delete运算符也可以用于删除数组元素,但不会影响其他元素的索引值,不会触发重新排序。
4.7.6.html删除数组第1个元素,如下所示。
动手写4.7.6
执行4.7.6.html,输出删除前后的数组到网页,如下图所示。
图4.7.6 delete删除数组元素

JavaScript中数组(Array)的索引从0开始,将在第9章 JavaScript数组详细介绍。
删除数组array的第1个元素之后,数组并没有更新所有元素的索引,第1个数组元素缺失,占了一个
空位。调用数组的hasOwnProperty(1)函数,发现缺少该索引。

4.7.3 点号运算符

点号属于存取运算符,用于访问对象的属性和方法。点号右侧紧跟对象的属性名或方法名。
null表示空值,undefined表示未定义的值,因此除了null和undefined不能使用点号运算符之外,其他
任何类型的数据都支持点号运算符。
4.7.7.html使用存取运算符获取对象的属性,调用数字的toString()方法将数字转换为字符串,如下所
示。
动手写4.7.7

执行4.7.7.html,输出结果到网页,如下图所示。

图4.7.7 点号运算符

除了标识符可以通过点号访问属性和方法外,直接量也可以使用点号运算符。直接量比较特殊,与
标识符不同,在进行点号运算时需要遵循以下规则:
◇ 字符串和布尔值直接量可以直接使用点号运算符。
◇ 正整数和0也可以直接使用点号操作符,但因为小数点会引起歧义,所以需要使用两个点号。
◇ 负数直接量因为有负号,所以会被当作一元运算符取负,优先级低于点号操作符,需要使用括
号()确认其是一个独立的数字。
◇ 小数直接量因为使用点号操作符会出现歧义,所以需要使用括号()确认其是一个独立的数字。
◇ 因为无法定位空值null和未定义undefined的原型链,所以这两个数据对象不支持点号操作符,也
不支持接下来介绍的中括号([])操作符。
4.7.8.html使用直接量执行存取运算,如下所示。
动手写4.7.8

执行4.7.8.html,输出结果到网页,结果显示使用点号运算符出现歧义时和对null、undefined执行点
号运算时都会触发错误,打开浏览器控制台查看错误提示,如下图所示。
图4.7.8 直接量使用点号运算符

4.7.4 中括号运算符

中括号运算符([])也属于存取运算符,可以实现点号运算符不支持的功能。
4.7.9.html使用中括号执行存取运算,获取对象的属性值,如下所示。
动手写4.7.9

执行4.7.9.html,输出结果到网页,如下图所示。
图4.7.9 中括号运算符

点号运算符右侧必须是合法标识符,因此在表达式course.key中key就是属性的名称,但在表达式
course[key]中变量key的值才是属性的名称,两者返回结果不一致。表达式course.1中,1不是合法的标识
符,因此触发语法错误“Uncaught SyntaxError: missing ) after argument list”,正确的使用方式是course['1']
。对象的key可以是任意值,最终转换为字符串。因为window转换为字符串是"[object Window]",所以
course[window]和course["[object Window]"]是同一个值。
中括号运算符可以实现对象属性的动态访问,也可以访问属性名不是合法标识符的属性。
数组是一种特殊的对象,其索引全是数字,因此访问数组元素必须使用中括号运算符。
4.7.10.html使用中括号获取数组的元素,如下所示。
动手写4.7.10

执行4.7.10.html,输出数组元素到网页,如下图所示。

图4.7.10 数组使用中括号运算符

4.8 其他运算符
4.8.1 条件运算符
条件运算符(?: )是JavaScript中唯一的三元运算符,使用方式形如 a ? b : c ,其中a为真时,执行表
达式b并返回b的结果,否则执行表达式c并返回c的结果。
4.8.1.html使用条件运算符获取两个数的最大值,如下所示。
动手写4.8.1

执行4.8.1.html,输出最大值到网页,如下图所示。

图4.8.1 条件运算符

4.8.2 逗号运算符

逗号运算符用于连接多个运算表达式,并返回最后一个表达式的值。
4.8.2.html使用逗号连接多个表达式,如下所示。
动手写4.8.2

执行4.8.2.html,输出连续运算结果60到网页。
逗号运算符还用于声明多个变量。除了这两种情形外,在实际开发中很少使用逗号运算符。
JavaScript代码在压缩时,压缩工具为了节省字符,会使用逗号运算符拼接表达式。
4.8.3 小括号运算符

小括号运算符用于函数调用、提高优先级、避免歧义。
4.8.3.html使用小括号运算符提高加减运算优先级,如下所示。
动手写4.8.3

执行4.8.3.html,输出运算结果到网页,如下图所示。

图4.8.2 小括号运算符

4.8.4 in运算符
in运算符用于检测右侧操作数是否包含左侧操作数。右侧操作数必须是引用类型数据,不能是简单
类型数据。
4.8.4.html使用in运算符检测对象是否包含指定属性,如下所示。
动手写4.8.4
执行4.8.4.html,输出检测结果到网页,使用in检测简单类型数据的属性会触发错误,打开浏览器控
制台查看错误提示,如下图所示。
图4.8.3 in运算符

in保留字用于表示包含检测时,右操作数只能是复杂数据类型(对象、数组、函数),不能是数
字、字符串、布尔值等类型的数据。在上例中,打开浏览器控制台可以看到表达式'toString' in 1 触发的
错误“Uncaught TypeError: Cannot use 'in' operator to search for 'toString' in 1”。

4.8.5 instanceof运算符

instanceof运算符用于检测左操作数是否是右操作数的实例。
4.8.5.html检测使用new运算符调用构造函数生成的对象是否为构造函数创建的实例,如下所示。
动手写4.8.5

执行4.8.5.html,输出检测结果到网页,如下图所示。

图4.8.4 instanceof运算符

4.8.6 void运算符
void运算符用于返回一个undefined值。当void与超链接结合使用,形如<a href="javascript: void(0)">
超链接</a>时,用户点击超链接时网页不会跳转。
void运算符也可以和typeof一样,当作函数使用,void(0) 与 void 0 等价。
4.8.6.html使用void占用href属性,阻止网页跳转,如下所示。
动手写4.8.6

执行4.8.6.html,输出结果到网页,如下图所示。

图4.8.5 void运算符

浏览器检测到超链接<a>标签的href属性是javascript:expression时,会判断表达式expression的返回
值,如果返回值是undefined,则不会跳转,而void表达式的返回值正好是undefined。

4.9 运算符优先级
正如在四则运算中,乘除运算的优先级高于加减运算,JavaScript的所有运算符都具有优先级。当表
达式中含有多个运算符时,优先级指明了计算方向。
JavaScript的运算符较多,一般可以根据语义理解运算顺序,按照小括号()、成员访问/函数调用、算
术运算、位运算、比较运算、逻辑运算、赋值运算、返回(展开运算符和逗号)理解优先级。完整的优
先级如下表所示。
表4.9.1 运算符优先级
当表达式较为复杂时,使用小括号()既能提高括号内表达式的优先级,也能让表达式更易于理解。
因此不需要对运算符优先级强制记忆,当不确定优先级时,可以使用小括号()提高优先级。

4.10 小结
本章重点讲解了JavaScript的常用运算符。JavaScript代码的每一行几乎都会涉及运算符,熟练掌握
运算符是JavaScript编程的基础。JavaScript默认类型转换与别的语言不太一致,需要重点关注不同类型
操作数的比较运算和逻辑运算。深入理解运算符,才能更好地学习后续章节。

4.11 知识拓展
4.11.1 检测运算顺序
前面的4.1运算符一节里提到,参与运算的操作数可以是函数返回值。这里将操作数封装成函数调
用,使用函数返回值来验证运算顺序。
4.11.1.html使用函数返回值作为操作数,在函数中输出文字提示操作数执行顺序,如下所示。
动手写4.11.1

执行4.11.1.html,输出操作数获取顺序到网页,如下图所示。

图4.11.1 验证取幂运算符的运算顺序

将左侧操作数封装成函数leftOperand(value),右操作数封装成rightOperand(value),这两个函数输出
各自的运行标识并原样返回value,通过观察标识出现的顺序来确认操作数运算顺序。这种采用函数回调
确认执行顺序在第15章事件处理中会有较多的应用。

4.11.2 惰性运算

惰性运算主要是指在运算表达式中,只需要计算部分操作数就可以获取结果并返回,之后的操作数
不再执行计算。
JavaScript的惰性运算主要体现在逻辑与(&&)和逻辑或(||)。
4.11.2.html同样使用函数返回值作为操作数,原样返回操作数,同时输出操作数提示,如下所示。
动手写4.11.2
执行4.11.2.html,输出操作数的获取顺序到网页,如下图所示。

图4.11.2 惰性运算的逻辑与和逻辑或

在4.11.2.html中,使用惰性运算获取表达式的值:
◇ 表达式0 && 1 ,第一个操作数0不为真,直接返回结果,不需要继续判断第二个操作数1,因此
控制台只输出“左操作数 0”。
◇ 表达式1 || 0,第一个操作数1为真,直接返回结果,不需要继续判断第二个操作数0,因此控制台
只输出“左操作数1”。
◇ 表达式1 && 2 ,仅根据第一个操作数1为真不能判断整个表达式为真,继续判断第二个操作数
2,因此控制台输出“左操作数 1”“右操作数 2”。
◇ 表达式0 || 1 ,仅根据第一个操作数0为假不能判断整个表达式为假,继续判断第二个操作数1,
因此控制台输出“左操作数 0”“右操作数 1”。
惰性运算可以减少不必要的计算,提高代码性能,但是在语义上惰性运算不够明显,因此进行逻辑
运算时,注意避免因惰性运算而产生错误。
第5章 JavaScript数值运算
JavaScript的数值具有一定的特色,比如带符号的0、内置的进制转换、不能精确表示某些浮点数
等。本章专门介绍数值运算的相关知识。

5.1 特殊数值
JavaScript的数值运算主要是为了实现数学相关运算,除了基本的算术运算、位移运算外,
JavaScript还定义了无穷大、无穷小,提供了Number和Math对象实现各种数学运算以及各种进制转换。

5.1.1 最大值/最小值

JavaScript采用64位浮点格式表示数字,最小值支持“5e-324”,最大值支持“1.7976931
348623157e+308”。和大多数语言一样,JavaScript的整数有最大值限制,若需要进行超过该值的数值运
算,则需要进行特殊处理。JavaScript的最大安全整数是9007199254740991,也就是253-1,默认情况下超
过该值的计算都是不可靠的。
5.1.1.html查看数值相关常量,如下所示。
动手写5.1.1

执行5.1.1.html,输出结果到网页,如下图所示。
图5.1.1 Number相关常量

Number对象定义了数值基础数据,并提供了数字转换函数。与数学计算相关的函数由Math对象提
供。
JavaScript支持的浮点数最小精度接近2.2204460492503130808472633361816E-16,或者 2-52,由此可
知JavaScript浮点数运算可能会出现误差,这一点与JavaScript采用的数字表示方式IEEE 754标准有关。
Number提供的函数与全局函数实现同样的功能。下表中的全局判断函数(isFinite、isNaN)接收的
参数a如果不是数字,就将其转换为数字之后再进行判断,而Number提供的函数则要求参数a必须是数
字,若不是数字则返回false。全局解析函数(parseFloat、parseInt)与Number提供的函数完全一致,如
下表所示。
表5.1.1 Number成员函数

(续上表)

提示
JavaScript在安全整数范围内进行运算能够获得正确结果,一旦超过安全整数,计算结果会不准确,
比如“9007199254740991+1”“9007199254740991+2”这两个表达式的结果都是9007199254740992。
5.1.2.html使用Number提供的函数和全局函数判断参数是否为指定类型,并将字符串解析为对应的
数字,如下所示。
动手写5.1.2

执行5.1.2.html,输出判断结果和解析结果到网页,如下图所示。
图5.1.2 Number成员函数

运行结果显示:
◇ 解析函数Number.parseInt与parseInt完全相等,Number.parseFloat与parseFloat完全相等。
◇ 判断函数Number.isFinite仅支持以数字为参数,isFinite支持以数字和字符串为参数。
◇ 判断函数Number.isNaN与isNaN,不一致而且差异较大。
◇ parseInt和parseFloat尽可能解析字符串,一元运算符加号(+)转换操作数为数字时,一旦操作数
不是合法的数字或字符串形式的数字,直接返回NaN。

5.1.2 无穷大/极小值

JavaScript使用Infinity表示无穷大,无穷大也能参与运算。JavaScript使用Number.EPSILON表示浮点
数精度,浮点数计算出现变化时,其变化必然大于等于Number.EPSILON。
极小值是JavaScript能表示的最接近0的值,所有小于极小值Number.MIN_VALUE的溢出值
(underflow values)会被转换为0。
5.1.3.html使用无穷大进行数学运算,如下所示。
动手写5.1.3
执行5.1.3.html,输出运算结果到网页,如下图所示。

图5.1.3 无穷大Infinity参与运算

无穷大参与运算时遵循以下规则:
◇ Infinity进行普通的加、减、乘、除(右侧操作数为正数)仍然等于Infinity。
◇ Infinity与Infinity相加、相乘的结果均为Infinity,相减、相除的结果均为NaN。
◇ Infinity与0相乘的结果是NaN,这一点不符合定理0与任何数相乘等于0。
◇ Infinity与Number.POSITIVE_INFINITY等价。
JavaScript支持极小值参与运算。
5.1.4.html使用极小值进行数学运算,如下所示。
动手写5.1.4
执行5.1.4.html,输出运算结果到网页,如下图所示。

图5.1.4 极小值Number.MIN_VALUE参与运算

极小值与整数相加时结果等于该整数,极小值会被忽略。

5.1.3 负零

JavaScript的数字从负数向零无限靠近时会出现负零,即带符号的-0。一般情况下,-0与0没有任何
区别,只在计算无穷大时有差异。
5.1.5.html使用负零进行数学运算,如下所示。
动手写5.1.5
执行5.1.5.html,输出运算结果到网页,如下图所示。

图5.1.5 负零

5.1.4 非数字NaN

在数学运算和数字转换过程中,如果无法得到正确的数值结果,比如除法运算中被除数和除数都为
零,格式错误的字符串转换为数字,这些情况下用NaN表示。NaN是Not a Number的缩写,在IEEE 754
标准中定义为非数值,是一种特殊值。使用typeof检测NaN的数据类型,得到的仍然是number。
5.1.6.html使用NaN进行运算的结果仍然为NaN,如下所示。
动手写5.1.6
执行5.1.6.html,输出结果到网页,如下图所示。

图5.1.6 非数字NaN

提示
NaN不等于任何值,即使是NaN也不等于NaN,即表达式“NaN===NaN”为false。判断一个操作数是
否为NaN,需要使用Number.isNaN(),而不是全局函数isNaN()。

5.2 进制
进制是进位计数制的简称,表示个位数达到一定数值时十位加一。比如,我们在生活中经常使用的
十进制是逢十进一,计算机二进制是逢二进一,还有一种应用得比较多的十六进制是逢十六进一。

5.2.1 二进制

二进制是计算机科学计数中广泛使用的一种计数方式,所有数字由0和1组成。JavaScript支持直接在
代码中书写二进制数字,只需要在数字前加上0b,即数字0和字母b,字母b不区分大小写。
5.2.1.html使用0b表示二进制数字,如下所示。
动手写5.2.1

执行5.2.1html,输出二进制数字,如下图所示。
图5.2.1 二进制

在数字前加上0b,JavaScript引擎会自动识别为二进制数据。二进制只有0和1,若0b后紧跟的数字串
出现大于1的数字,JavaScript引擎不支持这样的写法,会抛出错误“Uncaught SyntaxError: Invalid or
unexpected token”。获取数字的二进制序列,只需要使用该数字的toString(radix)方法,并将参数radix设
置为2。

5.2.2 八进制

八进制是一种以8为基数的计数方式。八进制数字串中只能出现0、1、2、3、4、5、6、7,若出现
任何其他字符串,都会出现类似于动手写5.2.1中的错误。在数字前加上0o表示八进制数字,即数字0和
字母o,字母o不区分大小写。
一个八进制数字正好可以用3位二进制数字表示,因为3位二进制数字的最大值0b111正好等于7。
5.2.2.html使用0o表示八进制数字,如下所示。
动手写5.2.2

执行5.2.2.html,输出八进制数字,如下图所示。
图5.2.2 八进制

提示
八进制英文单词Octal对应的缩写是OCT或O,因此在数字前使用0o表示八进制数字。

5.2.3 十进制

十进制从0到9计数,符合现实生活的使用场景。JavaScript的数字默认是十进制,不需要使用特殊标
识。
5.2.3.html使用没有任何特殊标识的数字进行计算,如下所示。
动手写5.2.3

执行5.2.3.html,a和b相加超过10,十位进一,个位余下6,因此结果是16,由两个数字组成,如下
图所示。

图5.2.3 十进制

5.2.4 十六进制
十六进制是计算机中常用的一种数据标识方法,一个字节具有8个比特位,拆分成4+4,刚好可以用
两个十六进制数字表示。Windows操作系统下的WinHex打开二进制文件时,正是用十六进制显示数据
的。在数字前加上0x表示十六进制数字,即数字0和字母x,字母x不区分大小写。
十六进制在十进制的基础上补充ABCDEF(不区分大小写),加上数字0到9,总共16个字符,用于
表示十六进制数字。十六进制与十进制的对应关系:0到9对应十进制的0到9,A到F对应十进制的10到
15。
5.2.4.html使用十六进制进行计算,如下所示。
动手写5.2.4
执行5.2.4.html,输出结果到网页,如下图所示。

图5.2.4 十六进制

十六进制的英文单词是hexadecimal,缩写是hex,在数字前使用0x表示十六进制数字。十六进制引
入的A、B、C、D、E、F不区分大小写。在字符串中也可以使用十六进制,\x紧跟该字符串ASCII对应
的十六进制即表示该字符,但要求必须是小写\x。
提示
虽然JavaScript区分大小写,但在进制表示方面,也可以使用OB、0O、0X等大写前缀来标识各种进
制,如0B10、0O10、0X10。

5.3 内置函数
JavaScript数字和字符串经常互相转换,通常使用parseInt(string, radix = 10)和parseFloat(string) 函数
将字符串转换成数字。若已知字符串是合法的数字,可直接使用一元运算符加号(+)获取操作数数
值。
5.3.1 parseInt

parseInt(string, radix)函数尽最大可能转换字符串为整数。
1.parseInt解析字符串
5.3.1.html使用parseInt解析字符串,如下所示。
动手写5.3.1

执行5.3.1.html,输出转换结果到网页,如下图所示。

图5.3.1 parseInt转换字符串为数字

2.parseInt解析对象
调用parseInt(a)时,如果参数a是对象,会依次调用a的toString()、valueOf()尝试获取a的原始值;如
果返回值都不是字符串或数字,则触发错误“Uncaught TypeError: Cannot convert object to primitive
value”。一元运算符加号(+)则先调用操作数的valueOf(),再调用toString()获取原始值。
5.3.2.html使用parseInt和一元运算符加号(+)将对象解析为数字,并展示获取原始值的过程,如下
所示。
动手写5.3.2
执行5.3.2.html,输出结果到网页,如下图所示。

图5.3.2 parseInt转换对象为数字

3.parseInt根据二进制解析
parseInt(a,radix)支持根据第二个参数radix指定字符串a使用的进制,并进行解析。
5.3.3.html使用parseInt解析用二进制和十六进制表示的数字字符串,如下所示。
动手写5.3.3
执行5.3.3.html,输出二进制和十六进制的解析结果到网页,如下图所示。

图5.3.3 parseInt根据进制转换数字

5.3.2 parseFloat

parseFloat(string)尽最大可能转换字符串为浮点数,使用方式与parseInt一样,但是parseFloat不支持
根据进制进行转换。
5.3.4.html使用parseFloat将字符串解析为浮点数,如下所示。
动手写5.3.4

执行5.3.4.html,输出结果到网页,如下图所示。

图5.3.4 parseFloat转换字符串为小数

调用parseFloat(a)时,如果参数a不是字符串或数字,转换流程与parseInt一致。
5.4 小结
本章重点讲解了JavaScript数值运算的基础知识,着重讲解了Number对象。虽然这一部分在软件开
发过程中相对较少使用,但是可以帮助读者深入理解数值运算设计、数值转换。

5.5 知识拓展
5.5.1 进制转换

本章5.2一节提到JavaScript支持二进制、八进制、十进制和十六进制,在数据传递过程中需要对这
几种进制进行转换。因为JavaScript默认使用十进制表示数字,当使用其他进制表示数字时,需要使用特
殊符号(0b、0o、0x)或字符串表示数字。
5.5.1.html使用toString、parseInt在不同进制之间转换,如下所示。
动手写5.5.1

执行5.5.1.html,输出转换结果到网页,如下图所示。

图5.5.1 进制转换

使用数字的原型方法toString(radix)和parseInt(string, radix)函数实现各种进制之间的转换。

5.5.2 浮点数误差

JavaScript采用IEEE二进制浮点数算术标准(简称IEEE 754标准)表示浮点数,导致某些浮点数计
算会出现误差。
5.5.2.html使用浮点数进行加法和乘法运算,如下所示。
动手写5.5.2
执行5.5.2.html,输出运算结果到网页,如下图所示。
图5.5.2 浮点数误差

表达式0.1 + 0.2出乎意料地不等于0.3,这是因为64个比特位(bit)无法存储完整的无限循环小数。
当然,绝大部分浮点数计算仍是可信的。
凡是采用IEEE 754 Floating-point浮点数编码方案的语言都有这个问题,并不是JavaScript独有。比
如,在Java中使用“System.out.println(.1 + .2);”输出的是0.30000000000000004,使
用“System.out.println(.1F + .2F);”才能正常输出0.3。0.1 + 0.2这个问题非常有名,为此有人专门建立了一
个兴趣网站http://0.30000000000000004.com/,该网站罗列了在处理0.1 + 0.2和使用浮点数形式表示0.3时
会出现精度问题的语言。
JavaScript没有原生提供解决方案来解决这个问题,其他采用该标准的语言虽然也会出现此问题,但
有的语言原生提供了解决方案。开发者可以使用JavaScript库math.js解决浮点数计算的精度问题,该函数
库功能强大且稳定。

5.5.3 三十六进制

JavaScript除了直接支持二进制、八进制、十进制、十六进制表示数据外,还支持使用函数转换为2
~36进制。三十六进制由数字0~9和字母a~z组成。
5.5.3.html将数字35使用17~36进制表示,如下所示。
动手写5.5.3

执行5.5.3.html,输出转换结果到网页,如下图所示。

图5.5.3 三十六进制

使用三十六进制表示最大整数9007199254740991是2gosa7pa2gv,可以将字符数由16个缩减到11
个。若引入大写字母,扩展到10个数字+26个小写字母+26个大写字母,即62进制,可以缩短更多字符。
第6章 JavaScript流程控制
在编程语言中,流程控制是指控制代码执行的顺序。一般情况下,代码按照文件内容从上到下、从
左到右的顺序执行。流程控制允许代码按照条件执行,比如根据用户不同的按键执行不同的代码;它也
支持循环执行某一段代码,比如显示重新发送验证码的倒计时。流程控制语句控制了整个程序运行的步
骤,包括顺序控制、条件控制、循环控制、跳转控制。本章将对这几种控制语句进行详细介绍。

6.1 基本语句
语句是一个可执行的命令,多条语句组成一个程序。按照功能将语句分为声明语句、表达式语句、
控制语句。控制语句之后若跟随多条相关语句,需要使用花括号({})将这些相关的语句包含在一起作
为复合语句。

6.1.1 声明语句

声明语句包括使用var关键字的变量声明语句和使用function关键字的函数声明语句。
6.1.1.html使用var、function关键字声明变量和函数,如下所示。
动手写6.1.1

声明语句遵循以下规则:
◇ 使用var关键字声明变量时,语句结尾需要加上分号(;)。
◇ 使用function关键字声明函数时,结尾不需要加分号(;)。
◇ 使用function关键字声明函数后立刻赋值给变量,则该语句是赋值语句,结尾需要加上分号。
语句中缺少分号时,JavaScript引擎不会报错,而是自动猜测并在结尾加上分号。在某些有歧义的
return语句中,自动猜测默认分号位置会与预期分号位置不一样,因此建议按照上述规则在语句末尾增
加分号。
6.1.2 表达式语句

表达式是JavaScript的短语,语句是JavaScript的一条完整的句子。表达式语句包括了赋值、删除和
函数调用。
6.1.2.html包含了各种表达式语句,如下所示。
动手写6.1.2

6.1.3 复合语句
复合语句又称为块语句,它将多条具有相关性的语句联合在一起。复合语句必须使用花括号({})
包围。
6.1.3.html使用花括号将多条表达式语句组织成复合语句,如下所示。
动手写6.1.3
执行6.1.3.html,输出结果到网页,如下图所示。

图6.1.1 复合语句

上面这段代码,最外面的花括号加与不加对运行结果没有影响。通常情况下,复合语句与控制流程
语句组合使用,将相关联的语句组合在一起;如果不是与控制语句一起使用,最外层的花括号不影响运
行结果,实际编程中一般不会按照上述示例使用。
声明语句和表达式语句需要使用分号作为结尾符号,复合语句不需要使用分号结尾,但要求花括号
({})成对出现。复合语句嵌套复合语句时,被嵌套的复合语句被当作一个语句。
传统JavaScript没有块作用域,因此使用复合语句不会产生新的作用域。ES6新增了块作用域,并增
加let作为变量声明关键字。
6.1.4.html使用let在语句块内声明变量,并在语句块外部访问该变量,如下所示。
动手写6.1.4
执行6.1.4.html,输出结果到网页,如下图所示。

图6.1.2 复合语句作用域

使用var声明的变量two,在复合语句外也能使用,但使用let声明的变量three则不行。

6.1.4 空语句

JavaScript使用分号(;)作为单条语句的结束符,空语句只包含一个分号,不包含其他代码。空语
句不会执行任何动作,可以作为占位符。
6.1.5.html使用分号独占一行表示空语句,如下所示。
动手写6.1.5

执行6.1.5.html,多余的分号表示空语句,不影响程序运行结果,如下图所示。
图6.1.3 空语句

空语句不执行任何代码,但可以在某些语句结构中占位,让代码符合语法规则,接下来要讲解的循
环控制语句就经常使用空语句作为占位符。

6.2 条件控制
条件控制语句是指根据条件表达式执行不同的语句:如果满足什么条件,就做什么事情。比如交通
红绿灯,如果是绿灯则前进,如果是红灯则等待,如果是黄灯则注意即将切换成红灯。JavaScript的条件
判断语句分为两种:if语句和switch语句。
◇ if条件语句包括if、if…else、if…else if…else。
◇ switch语句只有switch…case…default。

6.2.1 if语句

1.if语句
if语句是最基本的分支语句,语法格式如下:

语法格式说明:
◇ expression指条件表达式语句。
◇ statement指单条表达式语句。
◇ statements指复合语句。
if语句执行流程如下图所示。
图6.2.1 if语句执行流程

6.2.1.html根据两个变量的大小关系,使用if条件语句输出对应提示,如下所示。
动手写6.2.1

执行6.2.1.html,输出结果到网页,如下图所示。

图6.2.2 if语句

if语句中的表达式布尔值为true,执行语句console.log('b 大于 a');,执行完成之后继续执行
console.log(a, b); 。
2.if…else语句
在最基本的if语句基础上扩展else从句,当条件表达式的布尔值为false时执行else从句对应的语句,
语法格式如下:
语法格式说明:
◇ 表达式expression布尔值为true时,执行语句firstStatement(s)。
◇ 表达式expression布尔值为false时,执行语句secondStatement(s)。
if…else语句执行流程如下图所示。

图6.2.3 if…else语句执行流程

6.2.2.html根据表达式真值,使用if…else语句输出不同提示,如下所示。
动手写6.2.2

执行6.2.2.html,输出结果到网页,如下图所示。
图6.2.4 if…else语句

if语句中的表达式布尔值为true,执行语句 console.log('现代浏览器');,否则执行语句 console.log('推


荐试用 Chrome 浏览器');,执行完成之后继续执行 console.log('继续执行其他语句');。
3.if…else if…else语句
if…else语句是典型的二路条件分支语句,引入else if以支持更多分支结构。if…else if…else语法格式
如下:

语法格式说明:
◇ 先判断firstExpression,若表达式为true,执行firstStatement(s)。
◇ 再判断secondExpression,若表达式为true,执行secondStatement(s)。
◇ 全部条件表达式都为false,执行thirdStatement(s)。
◇ 支持加入多个else if条件语句。
if…else if…else语句执行流程如下图所示。
图6.2.5 if…else if…else语句执行流程

提示
在某些编程语言中,elseif是关键字,但是需要注意JavaScript的else if中间有空格,没有关键字
elseif,因此声明语句“var else if = 1”符合语法规则。
6.2.3.html使用if…else if…else实现根据多种条件执行不同语句,如下所示。
动手写6.2.3

执行6.2.3.html,输出结果到网页,如下图所示。
图6.2.6 if…else if…else语句

上述代码执行步骤如下:
◇ 依次判断表达式 isChrome 、 isFirefox、 isEdge的布尔值是否为true,如果为true,则执行紧跟该
表达式之后的复合语句,其他表达式对应的复合语句不执行。
◇ 一旦遇到布尔值为true的表达式,则停止判断。
◇ 如果所有表达式的布尔值都为false,则执行else对应的复合语句。
◇ 最后执行其他语句console.log('继续执行其他语句');。
if语句的其他限制:
◇ if语句是必选项,else if和else语句是可选项。
◇ if条件语句之后的复合语句如果是一条语句,就可以省略花括号。

6.2.2 switch语句

switch语句是更加易读的多路分支语句,通过判断一个条件表达式的值来执行不同的分支语句。
switch支持依次执行两个紧邻的分支语句。
switch语法格式如下:

语法格式说明:
◇ switch表达式expression的值匹配不同的case从句,执行case对应的语句。
◇ break从句用于结束switch语句,当执行某个case对应的语句时,如果遇到break语句,则结束
switch语句,否则依次执行之后相邻的case,直到遇到break语句。
◇ default语句类似if语句中的else分支,如果expression与所有常量表达式不相等,执行default从
句。
switch语句执行流程如下图所示。

图6.2.7 switch语句执行流程

从流程图可以看出break语句增加了switch分支语句的复杂性,导致相邻的case语句常量表达式判断
失效,一般建议在case语句内都使用break语句。default语句作为补充语句,类似于else,可以不使用。
6.2.4.html使用switch语句根据同一变量不同的值输出不同提示,如下所示。
动手写6.2.4
执行6.2.4.html,输出结果到网页,如下图所示。

图6.2.8 switch语句

函数showGrade(score)判断分数所属成绩等级:
◇ 分别输入分数100到70,其中100、90、70分段正常输出。
◇ 因为80分级别没有使用break,导致输出“成绩 80 分,一般”。
◇ 因为字符串100与数字100不全等,导致输出“其他成绩 100”。
6.2.3 if和switch对比

if语句称为if条件分支语句,switch语句称为多路分支语句。从语义和使用上两者有明显的区别:
1.if语句使用条件表达式,case语句使用常量表达式;
2.if语句可以使用多个条件表达式,switch语句只能使用一个表达式;
3.if条件表达式结果转换为布尔值,case语句常量表达式与switch表达式的数值和类型需要一致;
4.if语句不支持依次执行多个分支,switch语句在case中不使用break时支持依次执行多个分支;
5.if条件表达式复杂时可读性变差,switch结构清晰,适合分支较多的情形;
6.if条件表达式均为常量且分支较多时,性能不如switch。
6.2.5.html使用if和switch语句分别实现成绩等级判断,如下所示。
动手写6.2.5
执行6.2.5.html,输出结果到网页,如下图所示。
图6.2.9 if和switch对比

上述示例中,判断条件较多且类似,但switch语句比if语句更加清晰。实际应用中的判断条件更加
复杂,应该根据两者的区别选择恰当的语句。

6.2.4 条件嵌套

在if语句的复合语句中增加if语句或switch语句是条件控制语句的嵌套使用。在实际使用中往往会有
多层嵌套。if语句可以嵌套switch语句,switch语句也可以嵌套if语句。
if语句嵌套语法格式如下:

switch语句也支持嵌套使用,但是一般多层switch语句嵌套的可读性非常差。如果一定要使用switch
嵌套,建议将case语句封装成函数调用。
6.2.6.html使用if语句进行多层嵌套,根据不同浏览器的版本显示升级提示信息,如下所示。
动手写6.2.6
执行6.2.6.html,输出结果到网页,如下图所示。

图6.2.10 条件语句嵌套

函数getBrowserName()和getBrowserVersion()属于伪代码,没有实现功能。动手写6.2.6进行了两层判
断,即先获取浏览器名称,再根据浏览器不同版本执行相应代码。
提示
流程嵌套在代码中经常出现,除了if、switch语句,本章后续章节介绍的for、while、try语句都支持
互相嵌套。
6.3 循环控制
循环逻辑是程序开发中的常见操作,比如计算整数1到100的和数。JavaScript的循环控制语句分为两
种:for语句和while语句。
◇ for循环语句包括for、for…in。
◇ while循环语句包括while、do…while。

6.3.1 for/for…in语句

1.for语句
for语句是使用简单、方便的循环语句,语法格式如下:

语法格式说明:
◇ initExpression是循环初始语句,用来设置循环初始变量、初始环境等,在这个语句中可以声明变
量;
◇ testExpression是循环判断语句,如果testExpression布尔值为true,则执行本次循环,否则退出循
环,包括第一次循环也会判断;
◇ stepExpression是循环步长语句,每次循环结束时执行该语句,因此第一次循环初始时不执行。
6.3.1.html使用for循环根据步长输出数字,如下所示。
动手写6.3.1

执行6.3.1.html,输出结果到网页,如下图所示。
图6.3.1 for循环语句

当testExpression为false时,循环结束,因此第一个for循环输出0、1、2、3、4、5、6,第二个循环
输出5、15、25。
6.3.2.html使用for循环语句计算1到100的总和,如下所示。
动手写6.3.2

执行6.3.2.html,输出结果到网页,如下图所示。

图6.3.2 for循环计算1到100的和数

判断表达式i <= 100 确保i等于100时执行循环。


2.for…in语句
for语句可以用于循环数组获取数组元素,for…in是for语句的一种特殊形式,语法格式如下:

语法格式说明:
◇ for…in只支持一个表达式,JavaScript引擎在每次循环时自动设置objectProp为对象的属性值。
◇ object可以是任意数据类型,主要用于循环对象、数组。
6.3.3.html使用for…in循环输出数组的元素和对象的属性,如下所示。
动手写6.3.3

执行6.3.3.html,输出结果到网页,如下图所示。

图6.3.3 for…in循环语句枚举数组元素和对象属性

在循环体内使用object[objectProp]获取属性对应的value。
提示
for…in语句用于枚举对象的所有属性,如果对象的属性是不可枚举的,则使用for…in进行枚举时不
会出现该属性。
在第12章原型链会介绍for…in语句存在的隐患。

6.3.2 while/do…while循环语句
1.while循环
while循环语句用于持续测试一个表达式,不会主动修改表达式内的变量,语法格式如下:
语法格式说明:
◇ 每次循环开始前,判断testExpression,如果表达式布尔值为true,则执行循环,否则退出循环。
6.3.4.html使用while语句循环输出数组元素,如下所示。
动手写6.3.4

执行6.3.4.html,输出结果到网页,如下图所示。

图6.3.4 while循环语句

因为JavaScript引擎不修改while循环使用的变量,所以需要显示修改表达式使用的变量,比如
index++; 在循环结束后将索引index自增1,若不修改变量,可能会导致死循环,直到达到浏览器的安全
限制触发浏览器结束该网页进程,即网页crash。
while循环也可以用来模拟for循环,语法格式如下:

2.do…while循环
while循环在循环开始前执行testExpression,属于前测试循环语句;do…while在每次循环后执行
testExpression,属于后测试循环语句。已知初始循环必然为true时,使用do…while可以减少一次判断。
语法格式如下:

语法格式说明:
◇ do…while的testExpression含义与while循环中的完全一致,唯一区别是先执行statements,再执行
testExpression。
6.3.5.html使用do…while循环语句计算1到100的总和,如下所示。
动手写6.3.5

执行6.3.5.html,输出结果到网页,如下图所示。

图6.3.5 do…while循环语句

提示
do…while循环语句先执行一次statements,再根据testExpression表达式判断是否再次执行
statements,因此必然会执行一次statements,即使第一次判断testExpression就为false。一般推荐使用
while语句,因为和do…while相比,while语句代码更易于理解。

6.3.3 for和while对比

for和while循环在应用中都很常见,一般来说,for强制分开初始语句和步长语句,更适合循环次数
已知的情形,而while适合循环次数未知的情形。
新手在使用while循环时常忘记更改变量值,导致无限循环,更容易出现bug。
两者在功能上基本是等价的,可以实现相互转换。

6.3.4 循环语句嵌套
和条件控制语句一样,循环控制语句也支持嵌套,常见的是两层循环嵌套,但嵌套最好不要超过三
层。
6.3.6.html使用for语句嵌套,计算两个数组元素的交集,如下所示。
动手写6.3.6
执行6.3.6.html,输出结果到网页,如下图所示。

图6.3.6 for循环语句嵌套获取数组交集

6.4 跳转控制
跳转控制语句能够跳过后续代码块,直接执行指定位置的代码,但不能单独使用,需要与switch分
支语句、for和while循环语句结合使用,比如结束switch条件判断、结束while循环、继续执行下一次循
环等。在6.2.2 switch语句一节已经提到break语句,本节将介绍另外两种跳转语句:标签语句和continue
语句。

6.4.1 标签语句

标签语句是指在一行代码的起始位置设置标识符,为本行代码设置标签名,一般用于循环嵌套语句
中,让循环语句中的break和continue关键字能够跳转到指定位置,语法格式如下:

语法格式说明:
◇ 标签label用于给这段statements设置标签名。
标签名称必须是合法的标识符。标签和变量属于不同的名称体系,不互相干扰,可以重名;但为了
提高可读性,应尽量避免与变量重名。
设置标签不会对statements造成影响。
6.4.1.html为for循环语句设置标签名,如下所示。
动手写6.4.1

执行6.4.1.html,输出结果到网页,如下图所示。

图6.4.1 标签语句

6.4.1.html定义了两个标签firstLabel和secondLabel,在第二个循环中使用“break firstLabel;”终止了循
环。
6.4.2 break语句

break语句用于结束分支语句或循环语句和终止整个循环,语法格式如下:

语法格式说明:
◇ break有两种使用形式,默认不加标签名,结束紧邻的switch语句或循环语句。
◇ break跟随标签名,表示终止执行之后跳转的位置。
本节主要介绍在循环语句中使用break语句。
6.4.2.html使用break语句结束for循环和while循环,如下所示。
动手写6.4.2

执行6.4.2.html,输出结果到网页,如下图所示。
图6.4.2 break语句终止整个循环

break语句之后的语句不会执行,控制台只输出“before break”,结合标签语句break可以终止任意嵌
套循环。

6.4.3 continue语句

continue语句用于终止本次循环,继续执行下一次循环,语法格式如下:

语法格式说明:
◇ continue有两种使用形式,默认不加标签名,结束紧邻的循环语句。
◇ continue跟随标签名,表示终止本次循环之后跳转的位置。
6.4.3.html中,当变量i为奇数时,使用continue跳过本次循环,如下所示。
动手写6.4.3

执行6.4.3.html,输出结果到网页,如下图所示。
图6.4.3 continue语句终止本次循环

上例使用continue语句跳过奇数。continue只能用于循环语句for和while中,在其他地方使用会触发
语法错误“Uncaught SyntaxError: Illegal continue statement: no surrounding iteration statement”。
6.4.4.html使用break、continue结合标签语句跳出两层和三层循环,如下所示。
动手写6.4.4

执行6.4.4.html,输出结果到网页,如下图所示。
图6.4.4 continue语句跳转到指定标签

continue紧跟标签名跳转到指定位置,标签名必须是标识循环语句,不能跳转到其他位置的标签;
break语句跳出循环时,同样只能跳出所在循环或外层嵌套循环,不能跳转到任意标签位置,否则触发
语法错误“Uncaught SyntaxError: Undefined label 'loopThird'”。
提示
continue跳出外层循环会使代码的可读性变得很差,一般情况下不建议使用。通过下一章对函数的
学习,我们可以将复杂的语句封装成函数,让代码更加简洁,可读性更强。

6.5 异常控制
异常处理是代码安全执行的保障,用于处理代码执行过程中发生的意外和异常状况。异常状况包括
访问不存在的变量、调用不存在的函数、使用不符合类型要求的数据、运行环境不符合要求等。在没有
异常处理的情况下,当发生意外或错误时,代码会停止执行;而使用了异常处理之后,我们就可以根据
发生的错误进行相应的补救操作。
在ES3之前,JavaScript没有异常处理,一旦出现错误,JavaScript代码就会停止执行,整个代码非常
不健壮,导致数据不完整,而且会产生难以跟踪的错误信息。JavaScript参考了其他高级语言,引入了
try…catch…finally语句处理代码的运行异常。

6.5.1 try…catch…finally语句
try…catch…finally是JavaScript中唯一的异常处理语句,语法格式如下:

语法格式说明:
◇ try用于尝试运行代码firstStatements。
◇ catch用于捕获代码firstStatements中出现的异常,如果出现异常,则执行secondStatements,catch
是必选结构。
◇ finally表示最终一定会执行的代码,执行try或catch对应的代码之后执行thirdStatements,finally是
可选结构。
6.5.1.html使用两个<script>标签,第一个<script>标签使用in运算符判断null包含的属性触发异常,第
二个<script>在原来的基础上使用try…catch…finally语句捕获运行时的错误,如下所示。
动手写6.5.1

执行6.5.1.html,输出结果到网页,打开浏览器控制台查看提示的异常信息,如下图所示。
图6.5.1 try…catch…finally语句捕获运行时错误

使用存在性检测运算符in检测null是否包含指定属性时,JavaScript会触发类型错误“Uncaught
TypeError: Cannot use 'in' operator to search for 'name' in null”。第一个<script>标签没有使用try…catch…
finally捕获异常,导致引擎直接抛出错误,终止进程。第二个<script>标签捕获了异常,保证程序继续往
下执行。
finally作为可选项,一般较少使用,可以改变语法结构实现:

6.5.2.html使用try语句捕获6.5.1.html中发生的异常错误,如下所示。
动手写6.5.2
执行6.5.2.html,输出结果到网页,打开浏览器控制台,发现没有输出异常信息,因为该异常已经被
try语句捕获,不会再输出到控制台,如下图所示。

图6.5.2 try…catch异常控制语句

使用finally是从语义上与try…catch统一;作为一个统一的整体,即使不使用finally也可以达到同样
的效果。
提示
JavaScript是弱类型语言,没有指定变量类型,因此只能有一个catch语句。多个catch语句会触发语
法错误“Uncaught SyntaxError: Unexpected token catch”。

6.5.2 throw语句

除了使用try…catch…finally捕获异常,JavaScript也支持使用throw语句抛出异常。一般情况下,检
测变量发现不符合要求(不符合指定类型、不符合指定范围、不包含指定属性等)时可以选择抛出异
常。
语法格式如下:

语法格式说明:
◇ Error是JavaScript错误对象,包含异常信息。
◇ throw语句抛出Error实例。
6.5.3.html使用throw语句抛出异常,如下所示。
动手写6.5.3
执行6.5.3.html,输出结果到网页,throw语句抛出的异常没有被try语句捕获,因此打开浏览器控制
台可以看到提示的异常信息,如下图所示。

图6.5.3 throw语句抛出异常

函数getSum(a, b)检测参数类型,如果参数的数据类型不是number,则抛出错误。

6.6 小结
本章讲解了JavaScript的各种语句结构。语句是代码的基本要素,包括简单语句、条件语句、循环语
句、跳转语句、异常语句。熟练掌握这些语句,就可以轻松处理项目中复杂的业务逻辑。

6.7 知识拓展
6.7.1 避免使用死循环
在循环语句中,无法依赖自身判断条件终止的循环称为死循环,又称无限循环。死循环并不一定是
坏事,所以IDE基本不对疑似的死循环语句预警。
6.7.1.html使用for、while实现JavaScript死循环,如下所示。
动手写6.7.1
执行6.7.1.html,会出现死循环,造成浏览器卡死。
意外出现死循环的原因,可能是for循环的stepExpression表达式的递增/递减方向设置错误、while循
环忘记修改变量值等。
提示
除了意外出现的死循环,在JavaScript开发中不建议开发者使用死循环。死循环会持续消耗内存直到
达到浏览器限制,最终导致网页崩溃。

6.7.2 避免使用标签语句

标签语句和跳转语句一起使用增强了JavaScript代码的灵活性,同时也降低了代码的可读性,给代码
维护造成困难。
在实际开发中,随意大量地使用标签语句,虽然可以实现功能,但容易产生可读性极差的代码,导
致项目维护成本变高。我们可以采取函数封装、使用if…else控制语句来替换标签语句。
第7章 JavaScript函数
如果一段具有特定功能的代码块要被重复执行,我们可以将它们封装成一个整体,并赋予一个名
字,这就是函数。这个名字就是函数名,这个整体就是函数体,最后在每个地方调用这个名字,就可以
执行一整段代码。在JavaScript中,函数包含了绝大多数代码,本章将详细介绍函数的相关知识。

7.1 函数定义
函数是由一段可以重复执行的代码封装而成的逻辑单元。JavaScript函数接收一个或多个输入数据,
返回一个输出数据。使用函数能够优化JavaScript代码的性能,提高代码的复用率和编程效率。
JavaScript支持以下两种定义函数的方式:
◇ 使用function关键字声明函数。
◇ 使用Function()构造函数创建函数。

7.1.1 function 关键字

JavaScript的function关键字用于声明具名函数和匿名函数,具名函数可以直接使用,匿名函数需要
赋值给变量或属性才能使用,语法格式如下:

语法格式说明:
◇ functionName是函数名,与变量名一样必须是合法的标识符,不能与变量名重名。
◇ 从arg0到argN是参数列表,参数与变量一样必须是合法的标识符;参数名如果与函数内部的变量
名重名,会被变量覆盖。
◇ statements是函数体,用于实现函数的功能;statements可以包含1到N个return语句,最终只有一
个生效,如果没有return语句,则默认返回undefined。
7.1.1.html将问好语句封装成函数sayHello(),向不同的人打招呼,如下所示。
动手写7.1.1
执行7.1.1.html,输出结果到网页,如下图所示。

图7.1.1 函数声明语句

函数可以在代码顶部、尾部声明,也可以在控制语句内声明,还可以在函数内声明函数。函数声明
语句是静态语句,声明时不会立刻执行,因此一般在顶部、尾部和函数内部声明函数,很少在控制语句
内声明函数。
7.1.2.html在不同位置声明函数,如下所示。
动手写7.1.2
使用IE9浏览器执行7.1.2.html,输出结果到网页,如下图所示。

图7.1.2 函数声明位置(IE9)

函数init() 内部声明函数 innerFunction() ,在该函数内可以正常调用。


虽然代码if ('X' > 'Y') 不成立,但上述代码在IE9版本中可以正确运行。动手写7.1.2在控制语句中声
明具名函数,会造成歧义,而且大部分新版的浏览器已不支持提升在控制语句中声明的函数。
使用Chrome浏览器执行7.1.2.html,在浏览器控制台可以看到函数不存在的提示,如下图所示。

图7.1.3 函数声明位置(Chrome)

在Chrome浏览器中,因为if (1 > 2) 不成立,所以函数sayHello()没有声明,调用该函数触发错


误“Uncaught TypeError: sayHello is not a function”,因此不要在控制语句中声明具名函数。
7.1.3匿名函数一节将介绍在控制语句中声明匿名函数。
JavaScript允许重复声明函数,重名函数不会根据参数进行重载,而是覆盖之前声明的函数。
7.1.3.html声明同名函数,如下所示。
动手写7.1.3

执行7.1.3.html,后续声明的函数将覆盖之前声明的函数,在IE和Chrome浏览器中结果都是一样
的,如下图所示。

图7.1.4 重复声明函数

在一个<script>标签内,最后出现的重名函数被提升到顶部,同时覆盖其他同名的函数。
7.1.4.html在不同<script>标签内声明同名函数,如下所示。
动手写7.1.4
执行7.1.4.html,输出结果到网页,如下图所示。

图7.1.5 在多个标签重复声明函数

在每个标签内运行时,重名函数不会立刻被覆盖,但最终在运行其他标签的代码时,会覆盖已经声
明的同名函数。浏览器端的JavaScript对分包分模块还不够成熟,声明函数时需注意已有的全局函数,不
要覆盖。
同样地,引入其他JavaScript文件,也可能覆盖已声明的函数,常见的做法是将所有函数封装到一个
匿名函数中执行。7.1.3一节将详细介绍匿名函数。

7.1.2 使用 Function() 构造函数


使用function关键字声明函数属于静态代码,声明之后函数体不能改变。JavaScript支持使用
Function()构造函数动态创建函数。
7.1.5.html使用Function构造器创建函数,如下所示。
动手写7.1.5
执行7.1.5.html,执行动态创建的函数,输出结果到网页,如下图所示。

图7.1.6 使用Function()构造函数创建函数

Function()接收多个参数名,前N-1个参数作为返回函数的参数名,最后一个参数作为函数体。函数
参数数量不宜过多;若函数参数较多,可以使用对象组织参数,将多个参数合并成一个。
提示
构造函数Function()通过字符串创建函数,代码可读性比较差,一般不会刻意编写函数字符串再去
调用Function()函数。Function()可以将一段字符串编译成函数,提升函数字符串的运行效率,常常在
JavaScript模板引擎中使用。

7.1.3 匿名函数
匿名函数也叫闭包函数。相对于具名函数,匿名函数没有名称,需要复制给变量或作为参数传递,
或者作为立即调用函数表达式。匿名函数的语法格式与普通函数一样,只是缺少函数名。匿名函数不能
直接调用,需要赋值给变量或作为参数传递给其他函数。因此,在赋值语句、函数调用语句中声明的函
数,即使function关键字后面有名称,也是匿名函数。
匿名函数应用范围广泛,具有很强的灵活性。
7.1.6.html声明匿名函数并赋值给变量,如下所示。
动手写7.1.6
执行7.1.6.html,输出结果到网页,如下图所示。
图7.1.7 匿名函数使用场景

◇ 声明匿名函数(函数直接量)直接赋值给customFunction(),该函数除了没有名称,其他地方与
具名函数一致,可以重复使用。
◇ 对象object属性支持任意数据类型,属性greet是一个函数,让该对象具有执行能力,不再是单纯
的数据结构。
◇ 因为JavaScript之前只支持函数作用域和全局作用域,不支持块作用域,所以使用立即调用函数
表达式让函数一次声明,一次使用,避免污染外部作用域的变量。
◇ 通过控制结构将不同匿名函数复制给callback,callback可以作为函数直接调用,也可以作为参数
传递给其他函数。
提示
对象object使用函数作为属性greet之后,具备了可执行能力,这在JavaScript编程中很常见。

7.2 函数属性
函数作为一种可执行的代码,具有特定的属性。

7.2.1 name属性

函数的name属性是函数的名称,是function关键字紧跟的标识符。匿名函数function之后没有标识
符,因此匿名函数的name为空字符串。
7.2.1.html查看函数的name属性,如下所示。
动手写7.2.1
使用IE浏览器和Chrome浏览器执行7.2.1.html,两个浏览器运行结果不一致,如下两图所示。

图 7.2.1 函数属性name(IE11)

图 7.2.2 函数属性name(Chrome)
Chrome浏览器在函数表达式赋值时设置匿名函数的name属性为左侧标识符的名称,让匿名函数的
name属性不为空,IE11则把所有函数的name属性都设置为undefined,因此不能使用name区分匿名函数
和具名函数。

7.2.2 length属性

函数的length属性是声明函数时使用的参数的数量。
7.2.2.html查看函数的length属性,如下所示。
动手写7.2.2

执行7.2.2.html,输出结果到网页,如下两图所示。

图 7.2.3 函数属性length(IE9)

图 7.2.4 函数属性length(Chrome)

IE浏览器系统函数如console.log()的length属性为0,而Chrome的console.log()的length属性为1,两个
浏览器的parsetInt()函数的length属性均为2。对于这类系统自带的函数,不同浏览器给函数设置的length
属性不一致,因此不能通过length属性来判断系统函数接收的参数的数量。对于用户自定义函数
sayHello()、anonymousHi(),两个浏览器设置的length属性一致。
JavaScript函数调用时支持传入的参数数量与定义时的参数数量不一致,因此不能根据length属性检
测函数的参数数量。

7.3 函数参数
函数参数是变量的抽象,定义函数时将相似变量归类为一个参数对象,调用函数时再传入具体的变
量。

7.3.1 形参和实参

函数参数分为两种:形参和实参。声明函数时使用的参数称为形参,调用函数时使用的参数称为实
参。
参数语法格式如下:

语法格式说明:
◇ 函数定义时,expectArg0和expectArg1是形参,形参必须是合法的标识符。
◇ 函数调用时,actualArg0和actualArg1是实参,实参可以是标识符、直接量、表达式、函数返回
值。
7.3.1.html使用不同实参调用函数显示最大值,如下所示。
动手写7.3.1
执行7.3.1.html,输出结果到网页,如下图所示。

图7.3.1 形参和实参

每次函数调用进行实参与形参对应,如果传递的实参数量多于形参,JavaScript引擎仍然会传递实
参,但可能会被函数丢弃。
形参的作用域属于函数内部,如果与函数内部变量重名,内部变量会覆盖形参的值。

7.3.2 传值和传引用

JavaScript参数传递分为两种:传值和传引用。传值表示在函数内部改变参数的值,不会影响实参的
值;传引用表示在函数内部修改形参的成员,会影响实参的值。
不管是哪种传递,在函数内部直接对形参赋值,都不会影响实参的值。
1.值传递
使用简单数据类型的实参调用函数时,在函数内部修改形参的值,实参的值不会随着修改。简单数
据类型包括布尔值、数字、字符串、null、undefined。
7.3.2.html以值传递的方式调用函数,如下所示。
动手写7.3.2
执行7.3.2.html,输出结果到网页,如下图所示。

图7.3.2 函数调用传值

传值调用在函数内部修改形参的值,不会影响到实参。
2.引用传递
使用复杂数据类型的实参调用函数时,在函数内部修改形参的属性,实参的属性也会随之改变。复
杂数据类型包括对象、数组、函数。
7.3.3.html以引用传递的方式调用函数,如下所示。
动手写7.3.3

执行7.3.3.html,输出结果到网页,如下图所示。
图7.3.3 函数调用传引用修改形参属性

传引用时在函数内部修改形参的属性会同步修改实参的属性,但是在函数内部为形参重新赋值时,
实参的值不会随之改变。
7.3.4.html在函数内为形参重新赋值,如下所示。
动手写7.3.4

执行7.3.4.html,输出结果到网页,如下图所示。

图7.3.4 引用调用,对形参重新赋值,不影响实参

传引用时在函数内部直接对形参赋值,不是修改形参属性,不会影响实参。
函数调用传值还是传引用根据实参的数据类型决定,与函数声明无关。如果实参是简单数据类型
(布尔值、数字、字符串、null、undefined),则采用传值调用;如果实参是引用数据类型(对象、数
组、函数),则采用传引用调用。

7.3.3 arguments 对象

JavaScript函数内的特殊变量arguments是函数调用时所有参数的集合;通过arguments对象可以获取
任意数量的参数,即使没有形参与之对应。
arguments对象是一种类似数组的数据结构,但是没有数组操作的方法。
7.3.5.html在函数内部使用arguments,让函数支持任意数量的参数调用,如下所示。
动手写7.3.5
执行7.3.5.html,输出arguments到网页,如下图所示。

图7.3.5 arguments对象

使用arguments提高了函数编程的灵活性和容错性。arguments虽然不是关键字,但为了避免冲突,
声明变量时不要使用arguments作为变量名。
7.3.6.html在普通模式和严格模式下对arguments重新赋值,如下所示。
动手写7.3.6
执行7.3.6.html,在普通模式下能成功修改arguments,而在严格模式下修改arguments会触发错误;
打开浏览器控制台查看异常信息,如下图所示。

图7.3.6 覆盖arguments对象

arguments在两种模式下的区别是:
◇ 在普通模式下,浏览器没有保护arguments,修改arguments属性会影响形参的值,对arguments赋
值会覆盖该标识符的值,但不影响形参。
◇ 在严格模式下,不允许修改arguments标识符,对arguments进行修改会触发语法错误“Uncaught
SyntaxError: Unexpected eval or arguments in strict mode”。
7.4 函数调用
函数调用分为简单调用和方法调用。

7.4.1 简单调用

简单调用是指用类似C语言的形式,直接使用函数名进行调用,形如functionName()。
7.4.1.html直接通过函数名调用函数,如下所示。
动手写7.4.1

执行7.4.1.html,输出结果到网页,如下图所示。
图7.4.1 函数简单调用

JavaScript函数调用时有一个特殊对象this,this表示代码运行环境的上下文。在函数简单调用中,
this一般会指向全局对象,浏览器中的全局对象是window对象。
JavaScript的this对象与其他语言不一样,在代码运行时可以修改,而不是函数声明之后不可修改,
这也是JavaScript动态性的一大特点。

7.4.2 方法调用

方法调用是指函数作为对象的属性值,通过对象获取属性值的方式调用,形如
object.functionName(),这种形式的函数也被称为方法或成员函数,因此叫作方法调用。
7.4.2.html以访问对象属性的方式进行函数调用,如下所示。
动手写7.4.2

执行7.4.2.html,输出结果到网页,如下图所示。
图7.4.2 函数作为方法调用

简单调用与方法调用有很大不同:
◇ 采用object.greet()形式调用函数是方法调用,这点在面向对象编程语言中很常见,此时函数内部
的this指向当前对象object。
◇ 使用greet()形式调用函数是简单调用,虽然greet和object.greet表示同一个函数,但在运行时,内
部的this完全不同,前者的this指向全局对象window,后者的this指向当前对象object。

7.5 函数返回值
函数作为一个可以接收输入数据的可执行逻辑单元,同样可以向外部返回数据。JavaScript返回语句
的语法格式如下:

语法格式说明:
◇ return是JavaScript关键字,表示返回函数运行的结果,return语句之后的所有代码都不会被执行。
◇ 一个函数可以有多个return语句,通常放在不同的逻辑分支中,但最终只有一个生效。
因为JavaScript是弱类型语言,所以函数返回数据的类型不固定。
7.5.1.html使用return语句返回函数运行结果,如下所示。
动手写7.5.1

执行7.5.1.html,输出结果到网页,因为return语句之后的代码不会执行,所以没有输出“log after
return”,如下图所示。
图7.5.1 函数返回值

JavaScript不要求函数固定返回数据的类型,因此函数内的每个return语句都可以返回任意类型的数
据。但是,为了保障程序的稳定性,在开发中会约定函数返回类型,并且不能随意修改。
7.5.2.html在函数不同位置返回不同类型的数据,如下所示。
动手写7.5.2

执行7.5.2.html,输出不同的运行结果到网页,如下图所示。
图7.5.2 多个return语句

提示
JavaScript函数支持多个return语句,不同return语句不需要统一数据类型,但是这样会造成程序不可
控。在实际开发中,请一定要统一返回数据的类型。

7.6 嵌套函数
函数嵌套是指在一个函数内调用另一个函数,或者在一个函数内继续声明函数。与控制结构嵌套类
似,JavaScript支持函数嵌套调用和函数嵌套定义。
7.6.1.html在函数内调用函数和声明函数,如下所示。
动手写7.6.1

执行7.6.1.html,输出函数嵌套调用结果到网页,如下图所示。

图7.6.1 嵌套函数

函数introduceFriends()在函数domLoaded()内部定义,仅可以在domLoaded()函数内部使用,外部无
法直接访问introduceFriends()函数。
嵌套函数是将函数内部可以重复使用的代码或相对独立的代码封装成一个逻辑单元,避免干扰其他
元素。

7.7 变量作用域
变量作用域是指在代码中能够访问该变量的范围。如果函数内部声明的变量在函数内任何位置都可
以获取和设置变量的值,这个函数就是该变量所在的作用域。在<script>标签内而不是函数内声明的变
量,在代码任意位置都可以访问该变量,这是全局作用域。
根据作用域的范围,变量作用域分为函数作用域和全局作用域。在这两个作用域的变量,分别称为
局部变量和全局变量。局部变量只能在函数内部访问,全局变量可以在代码任意位置访问。
全局作用域只有一个,在浏览器中全局作用域内的this是window对象;函数作用域随函数创建而生
成,每声明一个函数,就生成一个函数作用域。

7.7.1 全局作用域

在<script>标签最顶部声明的变量是全局变量。在任意位置直接赋值给某个没有声明的标识符,该
标识符将作为全局对象window的属性而存在;赋值之后在任意位置都可以访问该属性。全局属性必须
先赋值再使用,直接访问未赋值的全局属性会被当作访问未声明的变量,触发类似错误“Uncaught
ReferenceError: a is not defined”。
全局属性可以使用delete操作符删除,但全局变量不可以删除。
7.7.1.html声明全局变量和设置全局属性,如下所示。
动手写7.7.1

执行7.7.1.html,设置全局属性之后可以正常访问,删除全局属性之后触发错误,打开浏览器控制台
可以看到提示信息,如下图所示。
图7.7.1 全局变量

全局变量globalVariable可以在当前作用域下访问,也可以在函数内部访问,这也让全局变量可以在
任意位置被修改,使代码的稳定性变差。全局属性globalProp被删除之后再次访问会触发错
误“ReferenceError: globalProp is not defined”。
提示
因为全局属性在任意位置都可以被获取和修改,所以代码越来越复杂之后会导致全局变量失控。因
此,在实际开发中应该尽量减少对全局变量的依赖,更多地使用局部变量,缩小访问变量的范围,以增
强代码稳定性。

7.7.2 函数作用域

函数内的作用域称为函数作用域。在函数内声明的变量称为局部变量。局部变量不能被外部直接访
问,因此不会出现异常修改,这种机制有效保证了函数的正常运行。
7.6一节介绍了嵌套函数,嵌套函数中的内部函数可以访问外部函数定义的变量,这一点与其他语
言稍有不同,其他语言的部分语句不支持内部函数直接访问外部变量。
7.7.2.html使用多层嵌套函数,并在每层函数声明局部变量,查看能够访问该变量的范围,如下所
示。
动手写7.7.2
执行7.7.2.html,输出结果到网页,如下图所示。

图7.7.2 局部变量
在嵌套的内部函数可以任意访问外部变量,但外部函数不能直接访问内部函数的变量。

7.7.3 变量优先级
在函数内部访问变量标识符时,优先访问局部变量。因此,当局部变量与外部变量重名时,外部变
量将不能被访问,即使这个内部变量的值是undefined。
7.7.3.html在多层嵌套函数内部声明变量,覆盖外部同名的变量,如下所示。
动手写7.7.3

执行7.7.3.html,输出结果到网页,如下图所示。
图7.7.3 变量优先级

内部变量优先级高于外部变量,依次查询外部嵌套函数,最后查询全局变量和全局属性;如果最终
没有找到对应的标识符,则触发错误“ReferenceError: second is not defined”。

7.8 闭包函数
闭包函数可以简单理解为匿名函数。在闭包函数作用域内可以访问外部变量,将闭包函数直接返
回,或者复制给对象属性,使得外部函数通过这个返回值间接访问闭包函数内部定义的变量和方法。
7.8.1.html在函数counterUp()内部使用匿名函数作为返回值,并在匿名函数内修改函数counterUp()的
变量counter,如下所示。
动手写7.8.1

执行7.8.1.html,输出结果到网页,如下图所示。
图7.8.1 使用闭包函数访问内部变量,完成计数

正常情况下,在函数counterUp()外部,不能修改局部变量counter,但是与counter位于相同作用域的
匿名函数能够访问它,函数counterUp()返回该闭包函数,该闭包函数在外部执行时访问局部变量
counter。通过将函数的返回值设置为匿名函数的形式,实现了在函数外部访问内部变量。
7.8.2.html将匿名函数作为对象的属性,同样可以实现在函数内部访问局部变量,如下所示。
动手写7.8.2

执行7.8.2.html,输出结果到网页,如下图所示。
图7.8.2 闭包函数赋值给对象

将闭包函数赋值给对象,让对象在函数Counter()外部通过成员函数countUp()和getValue()设置和获
取内部变量counter。

7.9 this对象
JavaScript本质上是基于原型链的面向对象编程语言。面向对象的编程语言有一个特色,它们的函数
在运行时,内部有一个调用对象,用this表示。JavaScript的this关键字用来指向当前函数的调用对象。当
函数进行简单调用时,this一般指向当前网页的window对象;当函数进行方法调用时,this指向拥有该函
数的对象。
this表示当前函数运行的上下文环境,也就是调用该函数的对象。
1.this指向window
直接通过函数名调用函数时,浏览器会为函数绑定默认的this对象,即全局对象window。在这种情
况下,因为this指向的对象都是同一个,所以基本不会操作this对象。
7.9.1.html在简单函数调用中访问this对象,如下所示。
动手写7.9.1

执行7.9.1.html,将this转换为字符串之后输出到网页,如下图所示。

图7.9.1 this 指向 window

调用函数getMaxNumber()时,this是全局对象window,在网页中显示this转换成字符串之后是"
[object Window]"。
2.this指向调用对象
在函数运行时,面向对象编程语言的this指向的是当前封装的类的实例。在JavaScript中,主要有两
种方式让this指向当前实例:
(1)使用new调用构造函数,this指向当前对象;
(2)使用对象调用函数,this指向当前对象。
7.9.2.html使用new操作符调用构造函数,并在构造函数中访问this对象,如下所示。
动手写7.9.2

执行7.9.2.html,将this转换为字符串之后输出到网页,如下图所示。

图7.9.2 this 指向当前对象

7.9.3.html调用对象的函数属性,如下所示。
动手写7.9.3
执行7.9.3.html,将this转换为字符串之后输出到网页,如下图所示。

图7.9.3 this指向调用对象

大部分情况下,只需要使用这两种形式访问this对象;少数情况下,可以通过特定方式修改this对
象。
3.修改函数的调用对象
大部分的编程语言,在函数声明之后,this就不能修改了,但是JavaScript的this用法很灵活,允许通
过多种方式修改函数运行时的this对象。主要有三种方式修改this对象:
(1)调用函数的apply()函数,传递参数数组,并修改this对象;
(2)调用函数的call()函数,传递指定参数,并修改this对象;
(3)将函数赋值给对象的属性,使用对象调用该函数。
在JavaScript中,所有函数都具有apply()、call()函数,调用这两个函数都可以实现函数调用;最后一
种方式实际就是给对象设置函数属性。本节将介绍使用apply()、call()函数两种形式调用函数。
7.9.4.html调用函数的apply()函数,传入任意数量参数并修改this对象,如下所示。
动手写7.9.4

执行7.9.4.html,将this转换为字符串之后输出到网页,如下图所示。

图7.9.4 使用apply()函数修改 this

7.9.5.html调用函数的call()函数,传入指定参数并修改this对象,如下所示。
动手写7.9.5

执行7.9.5.html,将this转换为字符串之后输出到网页,如下图所示。
图7.9.5 使用call()函数修改this对象

apply()和call()函数达到的效果都一样,区别是前者可以传递任意数量的参数,后者必须传递指定数
量的参数,因此在开发过程中会更多地使用call()修改this对象。
提示
在JavaScript中,函数运行时this指向的对象就是函数声明时所属的对象,但是可以通过apply()、
call()等方式修改,因此this属于运行时绑定,这也是JavaScript动态性的一种表现。

7.10 小结
本章详细介绍了函数的创建和使用方式,并对变量的作用域进行了深入介绍,同时介绍了JavaScript
闭包函数这种特殊用法。通过闭包函数可以让外部作用域访问内部作用域变量。JavaScript函数可以在任
意位置定义和使用,在开发过程中需要合理规划JavaScript函数的结构。

7.11 知识拓展
7.11.1 动态参数

JavaScript函数内部支持通过arguments获得参数集合,在函数调用时同样支持动态传入参数。
7.11.1.html获取函数内arguments对象,获取任意数量的参数,如下所示。
动手写7.11.1

执行7.11.1.html,输出结果到网页,如下图所示。
图7.11.1 使用函数apply()方法动态传参

7.11.2 递归函数

递归函数是指函数直接调用自身或者通过其他函数间接持续调用自身的函数。递归函数必须满足两
个条件,缺一不可:
1.函数调用自身的语句;
2.结束调用自身的条件。
函数若不能调用自身,不能称为递归函数。若没有结束调用自身的条件,调用该函数会陷入无限循
环。
7.11.2.html使用递归函数计算斐波那契数列,如下所示。
动手写7.11.2

执行7.11.2.html,输出结果到网页,如下图所示。
图7.11.2 使用递归函数求阶乘

每次进行函数调用时,JavaScript引擎会进行一次入栈操作,将函数调用前的场景暂存,函数调用完
成之后,还原场景。递归函数不断调用自身,因此必须有合适的结束条件,否则入栈过多会触发错
误“Uncaught RangeError: Maximum call stack size exceeded”。这是浏览器的一种保护措施,避免低效的
JavaScript代码导致浏览器崩溃。
第8章 JavaScript对象
对象是JavaScript重要的引用型数据。它将多种类型的数据集中在一起,形成一个键值对形式的无序
集合。对象包含的键都是字符串,包含的值可以是任意数据类型。

8.1 对象介绍
JavaScript对象是最常用的引用类型数据。对象包含多种类型的数据,通过属性访问对象的数据成
员。对象既是对现实世界的抽象,又是根据抽象定义的实例。对象的属性值支持任意数据类型,但最终
被转换为字符串。
8.1.1.html定义一个代表数学老师的对象,并且拥有开始讲课startLecture方法,如下所示。
动手写8.1.1

执行8.1.1.html,输出数学老师的所有属性到网页,最后执行startLecture,输出“开始讲课”提示,如
下图所示。
图8.1.1 对象介绍

JavaScript的对象有以下三个特点:
◇ 对象可以组织任意类型的数据。
◇ 对象属性值是字符串,为对象设置非字符串的属性,该属性名称会被转换为字符串。
◇ 对象不能进行排序,但使用for循环获取对象属性的顺序与设置属性的顺序一致。
虽然JavaScript语法没有要求对象属性的格式,但大部分情况下对象的属性名称是合法的标识符,这
样方便获取和使用存取操作符点号(.)所设置和获取的对象属性值。
1.转换属性名称为字符串
对象的属性都是字符串,为对象设置非字符串的属性时,该属性名称会被转换为字符串。
8.1.2.html将一个对象设置为另一个对象的属性名,如下所示。
动手写8.1.2
执行8.1.2.html,输出结果到网页,如下图所示。

图8.1.2 对象属性名自动转换为字符串

设置和获取对象成员时,如果属性值不是字符串,依次尝试调用属性值的toString()和valueOf(),直
到其中一个函数返回原始值;如果无法获取原始值,则触发错误“Uncaught TypeError: Cannot convert
object to primitive value”。
2.环形对象
对象包含的数据可以是任意数据,甚至包含自身,包含自身的对象也被称为环形对象。JavaScript的
对象能够通过函数JSON.stringify()序列化成字符串,使用JSON.parse()又可以将字符串反序列化成对象,
而获取环形对象的属性会造成无限循环,因此无法将环形对象序列化成字符串。
8.1.3.html将对象设置为自身的属性值,形成环形对象,如下所示。
动手写8.1.3
执行8.1.3.html,打开浏览器控制台,查看环形对象的结构,使用JSON.stringify()序列化环形对象时
触发错误“Uncaught TypeError: Converting circular structure to JSON”,如下图所示。

图8.1.3 递归对象

提示
环形对象是一种无限循环结构,使用时要特别小心,避免出现不合理的递归访问,造成网页崩溃。

8.2 创建对象
创建对象有四种方式,分别是使用花括号({})、Object()函数、Object.create()函数、自定义构造
函数。

8.2.1 使用花括号创建对象
使用花括号({})创建对象直接量是最简洁、最常用的对象创建方式。
8.2.1.html使用花括号创建对象,如下所示。
动手写8.2.1
执行8.2.1.html,输出结果到网页,如下图所示。

图8.2.1 使用花括号创建对象

注意声明对象courseJava时最后一个属性isEffective: 'Yes', 末尾有一个英文逗号(,)。标准的对象声


明方式是花括号结尾前不能有逗号。目前大部分浏览器都支持花括号结尾前的逗号可有可无,但早期的
浏览器不支持,而且部分压缩工具也不支持这个可选的逗号。
在IE7下执行8.2.1.html,输出结果到网页,如下图所示。

图8.2.2 使用花括号创建对象(IE7)

提示
早期的浏览器不支持花括号结尾前的逗号,该逗号会直接触发语法错误。因此,笔者建议对象的最
后一个元素不要使用逗号。

8.2.2 使用Object()创建对象

JavaScript为对象提供了包含数字Number()、字符串String()等一系列的构造函数Object(),支持使用
Object()创建对象。语法如下:

参数说明:
◇ value:可选参数,表示将该参数对象化,比如Object(1)的结果是数字1的对象表示形式,它的值
仍然是1;缺少该参数时,返回值是一个空对象{}。
函数的某个参数是可选时,在文档中可以使用中括号([])将参数包围起来,比如 Object([value]),
表示参数value是可选的。
8.2.2.html使用Object()创建对象,如下所示。
动手写8.2.2

执行8.2.2.html,输出结果到网页,如下图所示。
图8.2.3 使用Object()创建对象

使用Object()和new Object()创建对象,效果一样。
使用Object()创建对象与使用花括号创建对象的区别是:
◇ 如果参数是null或undefined,Object()返回空对象;
◇ 如果参数是其他类型,Object()将它转换为该数据类型对应的对象实例。
8.2.3.html使用Object()将参数对象化,如下所示。
动手写8.2.3

执行8.2.3.html,输出结果到网页,如下图所示。

图8.2.4 使用Object()将参数对象化

Object(arg0)函数用于将参数arg0对象化。如果参数arg0是对象、数组、函数,则直接返回参数
arg0。
8.2.3 使用Object.create()创建对象

JavaScript是基于原型链的面向对象编程语言,使用Object.create()方法创建一个新的对象时,可以指
定新对象的原型链。语法如下:

参数说明:
◇ proto:必选参数,表示以该参数为原型(模板)创建新对象,返回的新对象能够访问该参数的
所有属性和方法。
8.2.4.html使用Object.create()创建对象,如下所示。
动手写8.2.4

执行8.2.4.html,输出结果到网页,如下图所示。

图8.2.5 使用Object.create()以参数对象为原型创建对象

原型对象必须是对象、数组、函数或特殊数据null,简单数据类型不能作为原型。使用简单类型数
据作为参数时,会触发错误“Uncaught TypeError: Object prototype may only be an Object or null:
undefined”。

8.3 对象属性
对象属性除了支持简单的设置和获取,还能配置属性的特性。属性的特性称为描述符,包括数据描
述符和存取描述符。数据描述符是指属性是否可配置、可枚举、可写。存储描述符是指getter、setter函
数,可在属性读取时控制。

8.3.1 定义属性

定义属性有两种方式:
1.{属性名:属性值},这种方式用于声明对象时直接指定属性;
2.对象.属性名 =属性值,使用这种方式定义属性时,如果属性不存在,则新建属性并赋值。
8.3.1.html使用对象直接量定义属性,如下所示。
动手写8.3.1

执行8.3.1.html,输出结果到网页,如下图所示。

图8.3.1 直接声明属性
JavaScript支持为对象的属性设置详细的属性描述符,属性描述符包括属性的数据属性和访问的访问
器属性,使用函数Object的defineProperty()和defineProperties()为属性设置描述符。
定义单个属性语法如下:

参数说明:
◇ obj:必选参数,表示即将拥有新属性的对象。
◇ prop:必选参数,表示新属性的名称。
◇ descriptor:必选参数,表示属性描述符,详见后面的8.3.2一节。
定义多个属性语法如下:

参数说明:
◇ obj:必选参数,表示即将拥有新属性的对象。
◇ props:必选参数,表示新属性和描述符的配置对象,该对象的属性名即为新属性名,属性值即
为descriptor对象。
8.3.2.html使用defineProperty()定义属性,如下所示。
动手写8.3.2

执行8.3.2.html,输出结果到网页,如下图所示。
图8.3.2 使用Object.defineProperty定义属性

8.3.3.html使用defineProperties()定义多个属性,如下所示。
动手写8.3.3

执行8.3.3.html,输出结果到网页,如下图所示。

图8.3.3 使用Object.defineProperties定义多个属性
8.3.2 配置属性

配置属性是指设置属性的描述符;属性描述符包括数据属性和访问器属性。数据属性是指属性是否
可写、可枚举、可配置;访问器属性是指属性的getter和setter函数。
使用defineProperty()定义属性配置的描述符如下表所示。
表8.3.1 属性描述符

1.可写描述符
8.3.4.html先将属性设置为不可写,再将属性设置为可写,查看属性值是否修改成功,如下所示。
动手写8.3.4

执行8.3.4.html,输出结果到网页,如下图所示。
图8.3.4 配置属性数据描述符

先定义属性name的writable属性为false,表示该属性可读不可写。再次直接设置属性值,设置失
败。修改writable属性为true,设置属性值成功。
2.枚举描述符
8.3.5.html定义两个属性,其中一个可以枚举,另一个不可以枚举,使用for循环枚举对象的属性列
表,如下所示。
动手写8.3.5

执行8.3.5.html,输出结果到网页,如下图所示。

图8.3.5 配置属性迭代

在一些特定环境中设置属性enumerable为false,既能存放属性,又能避免属性被其他函数访问。
3.getter和setter
如果定义了属性的getter描述符,则获取属性的值时会调用getter函数;同样地,如果定义了属性的
setter描述符,则设置属性的值时会调用setter函数。
8.3.6.html设置属性的getter和setter函数,如下所示。
动手写8.3.6
执行8.3.6.html,输出结果到网页,如下图所示。

图8.3.6 属性存取操作符

定义对象abert的name和age属性后,设置属性age时检测属性值是否为合理返回的数字,不合理则不
设置。
提示
描述符writable或value和get或set不能同时出现,否则触发错误“Uncaught TypeError: Invalid property
descriptor. Cannot both specify accessors and a value or writable attribute”,即不能同时指定这对描述符。

8.3.3 赋值属性
使用存取运算符点号和中括号操作对象属性:
1.对象属性是合法标识符时,可以直接使用“对象.属性名”形式获取属性;
2.对象属性是变量或非法标识符时,需要使用“对象[属性名]”形式获取属性。
8.3.7.html使用等号为属性赋值,如下所示。
动手写8.3.7
执行8.3.7.html,输出结果到网页,如下图所示。

图8.3.7 属性赋值

8.3.4 删除属性
对象属性和数组元素一样支持设置和删除。使用delete运算符可以删除对象属性;删除之后,对象
将不再拥有这个属性,使用JSON.stringify()序列化对象时会发现没有该属性。
8.3.8.html使用delete运算符删除属性,如下所示。
动手写8.3.8
执行8.3.8.html,输出结果到网页,如下图所示。

图8.3.8 属性删除

使用delete成功删除属性后返回true,但这个返回值并不可靠。如果将属性的描述符configurable设置
为false,则该属性不能被删除。

8.4 对象操作
对象常见操作包括访问对象、枚举对象、克隆对象、销毁对象,但是因为JavaScript不支持开发者执
行垃圾回收,所以一般不需要执行销毁对象。

8.4.1 访问对象

JavaScript对象是引用数据类型,访问一个对象时不论是传参还是直接赋值,都是访问该对象的引
用。
8.4.1.html使用对象作为参数传递给函数,并在函数中修改对象的属性,如下所示。
动手写8.4.1
执行8.4.1.html,输出结果到网页,如下图所示。

图8.4.1 访问对象

8.4.1.html将对象赋值给另一个变量时,不会创建一个新对象,而是两个变量表示同一个对象实例。
参数传递时,实参和形参都指向同一个对象实例,在函数内修改对象参数,直接影响实参。对于简单数
据类型,赋值和参数是值拷贝,因此两个变量指向不同的实例,修改各自的值时两者互不影响。
提示
当对象作为函数参数时,按引用传递。

8.4.2 枚举对象

对象是一个无序的数据集合,JavaScript确保使用for…in枚举对象的属性列表时与属性设置的顺序一
致。
8.4.2.html使用for…in语句枚举对象属性,如下所示。
动手写8.4.2
执行8.4.2.html,输出结果到网页,如下图所示。

图8.4.2 使用 for…in 枚举对象

使用for…in枚举对象有如下几个特性:
◇ 以花括号定义的属性可以被枚举。
◇ 使用Object.defineProperty()设置属性enumerable描述符为false,该属性不能被枚举。
◇ 修改对象的原型链直接新增属性,该属性也会被枚举。
◇ 使用Object.defineProperty()为原型链增加enumerable描述符为false的属性,该原型链属性不会被
枚举。
8.4.3.html使用for…in枚举属性并使用hasOwnProperty()方法过滤原型链属性,如下所示。
动手写8.4.3
执行8.4.3.html,输出属性列表到网页,并打开浏览器控制台查看对象的数据结构,如下图所示。

图8.4.3 枚举对象过滤原型属性

使用Object.prototype.hasOwnProperty()判断对象是否直接拥有该属性,而不是继承自原型链,避免
枚举时意外处理对象原型链上的属性。

8.4.3 克隆对象

对象数据属于引用型数据,不能直接通过赋值拷贝一个新对象。为了避免修改原始对象的数据,需
要先将对象数据序列化成字符串,再将字符串反序列化成对象结构数据。
8.4.4.html使用JSON的stringify()和parse()实现克隆对象,如下所示。
动手写8.4.4
执行8.4.4.html,输出结果到网页,如下图所示。

图8.4.4 克隆对象

将对象序列化再反序列化之后创建的对象与原有对象已无联系,它是一个结构和数据完全一致的新
对象。

8.4.4 销毁对象

JavaScript的垃圾回收机制会自动回收没有被继续引用的数据元素。如果明确变量不再使用,可以将
其设置为null,但基本不需要这样做。

8.5 内置对象
JavaScript提供了一些内部对象,也叫内置对象。这些内部对象提供了常用的基本功能,可以直接使
用,比较常用的有Array、Date、Math、RegExp、String。
Array数组对象将在第9章JavaScript数组中详细介绍,String字符串将在第10章JavaScript字符串中详
细介绍,RegExp正则表达式将在第11章JavaScript正则表达式中详细介绍。
本节介绍Date日期对象和Math数学对象。
8.5.1 Date日期对象

JavaScript的Date对象用来处理日期和时间的相关操作。Date对象以世界标准时间1970年1月1日0时0
分0秒起的毫秒数进行相关操作。
Date函数语法格式如下:

注意时间戳一定要以毫秒为单位。
8.5.1.html使用Date对象获取日期,如下所示。
动手写8.5.1

执行8.5.1.html,输出结果到网页,如下图所示。

图8.5.1 创建Date对象

Date对象支持的日期维度很长,时间戳为负数时即可支持1970年1月1日往前的日期。
Date对象提供了静态方法和成员方法,成员方法getter和setter成对出现。
Date静态方法如下表所示。
表8.5.1 Date静态方法
Date成员方法(date = new Date(),表示Date实例)getter系列如下表所示。
表8.5.2 Date成员方法getter系列

Date成员方法(date = new Date(),表示Date实例)setter系列如下表所示。


表8.5.3 Date成员方法setter系列

8.5.2.html使用Date获取详细的日期和时间信息,如下所示。
动手写8.5.2
执行8.5.2.html,输出自定义格式的日期信息到网页,如下图所示。

图8.5.2 Date方法

Date函数依赖本机电脑的时间和时区,如果本机的时间与实际时间不一致,那通过Date获得的日期
就不准确。为了保证日期准确,前端日期仅用于展示,服务器端的时间用于校验。

8.5.2 Math数学对象

JavaScript的Math对象与Number对象不同,Math主要提供大量数学常量和数学函数。Math与Date不
同,Math是对象,Date是函数。因此直接使用“Math.属性”形式获取常量和调用方法。
Math数学常量如下表所示。
表8.5.4 Math常量
表中所列常量的真实值都是不可循环小数,因此表中的值都是近似值,而非精确值。
Math函数如下表所示。
表8.5.5 Math函数列表

(续上表)
8.5.3.html使用Math.random()函数获取随机数,并对随机数取整,如下所示。
动手写8.5.3

执行8.5.3.html,输出结果到网页,如下图所示。
图8.5.3 Math常用方法

函数parseInt()获取整数会直接丢弃小数部分,Math.round()以四舍五入方式获取整数。
提示
随机函数Math.round()不需要设置随机种子,在循环中也能获取理想的随机数。

8.6 小结
本章详细介绍了对象定义、对象属性配置、对象操作,以及两个常用内置对象Date和Math,并引入
了部分原型链的相关知识。对象的相关知识需要熟练掌握,常用内置对象可以在实际使用中熟悉。

8.7 知识拓展
8.7.1 原始值

ECMAScript定义的变量支持两种数据类型:原始值和引用值。原始值是存放在栈(stack)的简单
数据,即直接存放在变量访问的位置。引用值(对象)存放在堆(heap)中,通过变量所在位置存放的
指针指向对象的真实位置。因此,进行变量赋值时,两个引用数据的变量实际指向的是同一个数据。引
用值的大小会任意改变,占用的内存空间也需要支持随意变化,如果把引用值直接存放在栈中,会降低
变量查询速度。因此,在栈中存放对象在堆中的地址,在堆中实际存放对象内容,堆数据内容改变不会
对栈中的变量性能造成影响。
在JavaScript中,原始值有数字、字符串、布尔值、null、undefined共五种;引用值有对象、数组、
函数,这三种都可以归纳为对象。
原始值和引用值的存储示例如下图所示。
图8.7.1 原始值与引用值的存储

8.7.2 冻结对象

使用Object.defineProperty()定义属性,可以控制属性不被删除和修改。ECMAScript 6新增函数
Object.freeze()支持将对象整体冻结,使之既不能增删属性,也不能配置属性描述符,让对象变成一个不
可变对象。语法如下:

参数说明:
◇ obj:必选参数,表示要被冻结的对象。
8.7.1.html使用Object.freeze()冻结对象,并在冻结之后修改对象的属性,查看是否修改成功,如下所
示。
动手写8.7.1

执行8.7.1.html,输出冻结前后的结果到网页,如下图所示。
图8.7.2 冻结对象

将对象冻结之后,一些写操作和配置操作都会被拒绝;在严格模式下,设置冻结对象的属性值会触
发错误“Uncaught TypeError: Cannot assign to read only property 'name' of object”。
Object.seal()函数也可以用于冻结对象,但它着重于密封对象,与Object.freeze()的区别是密封之后的
对象不能新增属性、不能配置已有属性,但是原有属性在密封之前可写,在密封之后仍然可写。
第9章 JavaScript数组
数组是除了对象之外最常用的引用类型数据,可以对大量同类数据进行存储、查找、排序、新增和
删除,使用数组可以提高开发效率。JavaScript的数组比较松散,它和对象一样可以存放不同类型的数
据。本章将详细介绍数组的基本概念和使用方法。

9.1 数组介绍
数组是一组数据的有序集合,数组中的每个值是数组元素,元素对应编码是数组下标。以“数组[下
标] ”形式访问数组元素。
数组更像是格式化的列表对象。与对象不同,数组元素可以进行排序,数组下标也是有序的,下标
从0开始。
使用数组可以很方便地存放同类型的数据,而使用数字下标即可访问数组元素。
9.1.1.html使用数组存放字符串,如下所示。
动手写9.1.1

执行9.1.1.html,输出结果到网页,如下图所示。

图9.1.1 使用数组存放编程语言列表

提示
JavaScript数组的下标是从0开始的,即array[0]表示数组的第一个元素。访问不存在的下标和访问不
存在的对象属性一样,都返回undefined。
数组可以存放任意类型数据,每个元素的数据类型互不干扰。
9.1.2.html使用数组存放布尔值、数字、字符串等数据,如下所示。
动手写9.1.2

执行9.1.2.html,将数组序列化后输出到网页,如下图所示。

图9.1.2 使用数组存放各类数据

9.2 数组定义
数组定义有两种形式:
◇ 使用中括号([])直接声明并设置每个元素的值。
◇ 使用Array()函数创建更详细的数组对象。

9.2.1 使用中括号定义数组

中括号([])前有变量时作为存取运算符获取对象属性,前面没有标识符或表达式时作为数组声明
表达式。
9.2.1.html使用中括号([])声明数组直接量,如下所示。
动手写9.2.1

执行9.2.1.html,输出结果到网页,如下图所示。

图9.2.1 使用中括号定义数组

JavaScript数组索引从0开始,使用中括号声明数组时,每个元素用逗号隔开,数组长度即为声明时
元素的数量。使用索引设置数组元素,设置之后,数组长度等于最大索引加1;不存在的索引会标识为
empty,效果等同于使用delete删除该索引。
提示
JavaScript数组长度可以任意变化,不需要在声明时指定。

9.2.2 使用Array()构造函数

JavaScript提供数组构造函数Array(),它支持传入多个数组元素,有两种调用形式。语法如下:

参数说明:
◇ element:可选参数,表示数组元素,自动根据element数量设置数组长度。
◇ length:可选参数,表示数组长度,创建长度为length,但所有元素为空的数组。
使用Array()和new Array()创建数组,返回结果一致。
使用Array()和中括号([])创建数组,对比如下:
◇ Array()与[]效果一致。
◇ Array(元素1,元素2)与[元素1, 元素2]效果一致。
◇ Array(元素)与[元素]效果一致。
◇ Array(数组长度)与[元素]效果不一致。
当调用Array()函数创建数组,有且只有一个参数,并且参数是自然数时,新数组以该参数作为数组
长度,比如上述示例中的Array(2)返回一个长度为2的一维数组。
9.2.2.html使用Array()函数创建数组,如下所示。
动手写9.2.2

执行9.2.2.html,输出结果到网页,如下图所示。

图9.2.2 使用Array()构造函数创建数组

提示
数组new Array(2)转换为字符串显示到网页时是“[null,null]”,这是因为序列化函数JSON.stringify()使
用null表示不存在的元素,与new Array(2)对应的字符串形式是“[,,]”,即数组的长度为2,但是没有这两
个元素。

9.2.3 二维数组
数组的每个元素都是一个新的数组,即由数组组成的数组,称为二维数组。若二维数组的元素都是
数组,则称为多维数组。
9.2.3.html使用两层花括号声明二维数组,如下所示。
动手写9.2.3
执行9.2.3.html,打开浏览器控制台查看二维数组的结构,如下图所示。

图9.2.3 二维数组

虽然数组不要求每个元素的数据类型一致,但在实际使用中一般都需要统一数据类型。二维数组则
需要每个子数组对应的索引统一数据类型和含义。

9.3 数组属性
数组是有序的数据集合,数组实例具有length属性,length表示数组元素的总数。数组函数Array()具
有prototype属性,prototype表示数组的原型,所有数组实例从该原型继承数组相关的操作函数。

9.3.1 length属性

length表示数组元素数量,是数组实例的属性,是一个32位的无符号自然数,并且大于数组的最大
下标,一般等于数组的最大下标加1。
9.3.1.html查看数组的length属性,如下所示。
动手写9.3.1

执行9.3.1.html,输出数组长度到网页,如下图所示。

图9.3.1 数组length属性
数组的length属性允许直接修改,增大数组的length属性,数组会出现空白元素;减小数组的length
属性,自动删除下标大于等于length的元素。
9.3.2.html修改数组length属性,如下所示。
动手写9.3.2

执行9.3.2.html,输出结果到网页,如下图所示。

图9.3.2 修改数组length属性

修改数组length属性会有以下影响:
◇ 增大length属性,只会修改length的值,不会填充元素。
◇ 减小length属性,自动删除下标大于等于length的元素。
◇ 使用for…in语句枚举数组会跳过不存在的数组索引。
在实际场景中,不限制数组的length属性为只读属性,但一般约定将length作为只读属性,不直接修
改数组的length属性。

9.3.2 prototype
Array.prototype是数组的原型对象,数组实例从这个原型对象继承方法。JavaScript提供了数组的一
部分操作,但没有完全覆盖数组使用场景,有时需要为数组添加额外的方法。比如,在Array.prototype
上定义数组排除重复元素的方法unique,则所有数组实例都可以使用这个方法。
9.3.3.html使用Object.defineProperty为数组定义原型方法,如下所示。
动手写9.3.3
执行9.3.3.html,输出结果到网页,如下图所示。

图9.3.3 定义数组原型方法

在动手写8.4.3中,以Object.prototype.name = 'protoName'; 形式直接修改原型属性,会在for…in语句


中枚举到意外的原型属性。因此,修改数组原型方法时,正确的方式是使用Object.defineProperty()定义
原型属性。

9.4 数组操作
JavaScript原生提供了近三十个函数用于操作数组和数组元素,覆盖了常见的数组操作函数。本节介
绍数组操作函数,如新增、获取、查找、删除、替换、迭代和排序。合理使用这部分函数,不仅可以明
显提高程序开发效率,还可以提高我们处理复杂逻辑的能力。

9.4.1 新增元素
新增元素根据所在的位置分为三种方式,分别是在数组尾部、首部、中间增加元素。
1.在数组尾部新增元素
9.4.1.html使用push()函数在数组尾部新增元素,如下所示。
动手写9.4.1
执行9.4.1.html,输出结果到网页,如下图所示。

图9.4.1 在数组尾部新增元素

使用push()函数新增元素时,数组长度自动加1,并将新元素设置为尾部元素;push()函数接收多个
参数时,自动在尾部追加多个元素。
2.在数组首部新增元素
9.4.2.html使用unshift()函数在数组首部新增元素,如下所示。
动手写9.4.2

执行9.4.2.html,输出结果到网页,如下图所示。
图9.4.2 在数组首部新增元素

使用unshift()方法添加首部元素,与push()一样,在接收多个参数时,依次在首部新增多个元素。
3.在数组中间新增元素
JavaScript支持在数组中间的某个位置新增元素,并自动调整其他元素的索引。
9.4.3.html使用splice()函数在数组指定位置插入新元素,如下所示。
动手写9.4.3

执行9.4.3.html,输出结果到网页,如下图所示。

图9.4.3 在数组指定索引新增元素

使用splice()插入新元素时,后续元素的下标依次更新;splice()函数接收的第二个参数大于0时,则
从该索引开始删除指定数量的元素后,再插入新元素。

9.4.2 获取元素

数组是对象的子集,虽然数组的索引是数字,但最终获取元素时,是将索引转换成字符串获取对象
属性值。
9.4.4.html使用for…in枚举数组元素,并通过typeof查看数组索引的数据类型,如下所示。
动手写9.4.4
执行9.4.4.html,输出结果到网页,如下图所示。

图9.4.4 获取元素

因为数字和字符串直接量不是合法标识符,所以只能通过中括号([])访问数组元素。

9.4.3 查找元素

查找元素在数组中的位置,有三种方式:
◇ 从前往后查找。数组的indexOf(value)从0开始向后查找value在数组中的位置,如果存在,返回
value对应的索引,如果不存在,返回-1。
◇ 从后往前查找。数组的lastIndexOf(value)从数组最后一个元素开始向前查找value在数组中的位
置,如果存在,返回value对应的索引,如果不存在,返回-1。
◇ 按条件查找数组元素。数组的find(callback)从前往后依次调用callback()函数,当callback()函数返
回true时,find()函数返回该元素,没有找到满足条件的元素时,返回undefined。
1.indexOf(value)
使用indexOf()函数查找元素按照以下规则:
◇ 如果value存在于数组中,返回与value全等的元素的索引。
◇ 索引从0开始,如果存在,返回-1。
◇ indexOf(value)使用全等(===)进行比较。
9.4.5.html使用indexOf(value)函数从前往后查找value在数组中的位置,如下所示。
动手写9.4.5
执行9.4.5.html,输出结果到网页,如下图所示。

图9.4.5 indexOf(value)查找元素值是否存在于数组中

2.lastIndexOf(value)
lastIndexOf()的查找标准和indexOf()的一样,不过前者是从后往前查找。
9.4.6.html使用lastIndexOf(value) 从后往前查找value在数组中的位置,如下所示。
动手写9.4.6

执行9.4.6.html,输出结果到网页,如下图所示。

图9.4.6 lastIndexOf(value)查找元素值是否存在于数组中

indexOf()和lastIndexOf()是根据值查找,find(callback)和findIndex(callback)支持按照条件查找。
提示
使用indexOf(value)和lastIndexOf(value)查找数组元素时,按照全等(===)比较数组元素与value。
3.find(callback)
find()函数接收callback参数,callback参数是一个回调函数。find()函数从前往后依次使用数组元素
调用callback回调,直到callback返回true时停止调用。callback()接收三个参数,格式如下:
callback()参数说明如下:
◇ element:数组元素。
◇ elementIndex:数组元素对应的索引。
◇ array:数组本身。
可以将callback回调当作数组元素的测试函数,如果callback返回true,表示该元素通过测试,此时
find()函数停止调用callback并返回该元素;如果所有元素调用callback,都没有返回true,也就意味着没
有元素通过测试,find()函数返回undefined。
9.4.7.html使用find()函数查找元素索引为基数的元素,如下所示。
动手写9.4.7

执行9.4.7.html,输出结果到网页,如下图所示。

图9.4.7 find(callback)按条件查找元素

数组还提供了一个与find()类似的findIndex()函数。不同的是,某个元素调用callback通过测试时,
findIndex()返回该元素的索引,如果所有元素都没有通过测试,findIndex()返回-1。

9.4.4 删除元素
删除数组元素并不是简单地调用delete运算符,元素删除之后,需要确保索引是连续的。使用
splice()删除指定下标的元素,后续元素的下标将自动更新。
9.4.8.html使用splice()删除数组元素,如下所示。
动手写9.4.8

执行9.4.8.html,输出结果到网页,如下图所示。

图9.4.8 删除元素

使用splice(deleteStart, deleteCount)方法删除元素之后,数组的索引仍然是连续的;使用delete操作符
删除元素,只是删除索引和索引对应的值,并没有修正其他元素的索引。

9.4.5 替换元素

使用splice()函数在删除元素时新增元素,即可实现替换元素。替换元素的语法格式如下:

splice()函数的参数说明如下:
◇ startIndex:必选参数,从指定位置开始删除元素,如果超出了数组的长度,则从数组末尾开始
添加元素;如果是负值,则表示从数组末位开始的第几位(从-1计数)开始删除元素。
◇ deleteCount:可选参数,表示删除多少个元素。
◇ newElement1:可选参数,新增的第一个元素。
◇ newElement2:可选参数,新增的第二个元素。
使用splice()函数,如果指定了newElement,则添加新元素;如果指定了多个newElement,则添加多
个新元素;如果没有newElement参数,则不会新增元素。splice()以删除元素再新增元素的形式实现替换
元素。
9.4.9.html调用splice()替换新元素,如下所示。
动手写9.4.9
执行9.4.9.html,输出结果到网页,如下图所示。

图9.4.9 替换元素

提示
splice()先删除deleteCount个元素,再新增元素,因此不要求deleteCount与newElement的数量一致,
替换之后的数组长度可以不同。

9.4.6 数组迭代

循环获取数组元素进行运算是很常见的操作,数组具有length属性,因此往往会使用for i++形式对
数组进行迭代。
1.for i++迭代数组
9.4.10.html使用for i++语句遍历数组元素,如下所示。
动手写9.4.10

执行9.4.10.html,输出结果到网页,如下图所示。
图9.4.10 使用for循环迭代数组

2.for…in迭代数组
使用typeof检测数组,返回"object",表明数组也属于对象,可以使用for…in语句枚举数组元素。
9.4.11.html使用for…in语句遍历数组元素,如下所示。
动手写9.4.11

执行9.4.11.html,输出结果到网页,如下图所示。
图9.4.11 使用for…in迭代数组

提示
如果数组的某个索引没有元素,for…in语句会自动跳过该索引,for i++则不会跳过。
3.forEach迭代数组
数组的forEach()方法是接收一个回调函数作为参数,使用每个元素执行一次该函数,语法如下:

参数说明:
◇ callback:必选参数,表示为每个元素执行的回调函数,该函数接收三个参数,形式如
callback(element, index, array),element表示当前元素,index表示元素对应的索引,array表示当前数组。
◇ thisArg:可选参数,表示执行callback时绑定的this对象。
9.4.12.html使用forEach()迭代数组,如下所示。
动手写9.4.12

执行9.4.12.html,输出结果到网页,如下图所示。

图9.4.12 使用forEach()迭代数组

9.4.7 数组排序
JavaScript提供了基于In-place算法的排序方法sort(callback),callback接收两个参数,格式如下:

参数a、b表示数组的两个元素,sort()将按照一定顺序使用两个数组元素作为参数调用callback回
调,再根据callback的返回值(-1、0、1)调整元素a、b在数组中的位置。
callback(a,b)的返回值含义如下:
◇ -1(小于0的数),表示a位于b的前面,如果不是这个顺序,会交换a、b的位置。
◇ 1(大于0的数),表示a位于b的后面,如果不是这个顺序,会交换a、b的位置。
◇ 0,表示a、b的位置不需要交换。
调用sort()函数但没有传递callback参数时,根据数字大小或字符串的Unicode编码对元素排序。
9.4.13.html使用sort()对数组排序,如下所示。
动手写9.4.13

执行9.4.13.html,输出结果到网页,如下图所示。
图9.4.13 使用sort()对数组排序

排序稳定性是指数组每次排序的结果都一样。JavaScript的sort()函数不要求排序是稳定的,sort()的
稳定性取决于callback():
◇ 如果callback(a,b)是稳定的,即a、b比较结果稳定,则sort()是稳定的。
◇ 如果callback(a,b)不是稳定的,即a、b比较结果会变动,则sort()是不稳定的。
提示
如果sort()函数使用的比较函数callback()是稳定的,那么在排序前后,数组内多个相等的元素相互之
间的相对位置不会改变。

9.5 小结
本章介绍了常用的数组对象,以及数组对象的新增、查找、替换、排序等常见操作。合理使用数
组,比如采用某种方式使二维数组降到一维数组,可以明显改善JavaScript的性能。

9.6 知识拓展
9.6.1 数组求和

数组没有原生提供求和函数,Math也没有提供sum()函数。利用数组的reduce()函数进行累加,可以
获取数组元素之和。
9.6.1.html使用reduce()函数实现元素累加,如下所示。
动手写9.6.1
执行9.6.1.html,获得list的和是359。
reduce(callback, initialValue) 是累加器函数, callback(accumulator, currentValue, currentIndex, list) 接
收四个函数,第一个函数表示回调累计的返回值,即上一次callback调用的返回值,或者是初始值
(initialValue)。
累加器用于遍历元素操作时非常实用。
reduceRight(callback, initialValue) 也是一个累加器函数,与reduce()函数的使用方式一致,区别在于
reduceRight()函数从数组末尾往前累加。

9.6.2 快速交换

在算术运算中,有一种操作是交换两个操作数,这可以利用数组轻松实现。9.6.2.html使用数组对两
个变量进行快速交换,如下所示。
动手写9.6.2

数组结构还有很多巧妙应用,比如在数组首部增加元素、在尾部取出元素模拟消息队列。更多使用
技巧可以在实践中探索。
第10章 JavaScript字符串
字符串是由数字、字母、下划线以及其他符号组成的一串字符,在编程语言中经常使用。JavaScript
的字符串既可以是一个字符,也可以是多个字符,还可以是没有包含任何字符的空字符串。本章将介绍
字符串的相关概念和常用的字符串操作函数。

10.1 字符串介绍
字符串是由字符组成的序列。在JavaScript中不区分字符和字符串,统一使用字符串表示。字符串使
用成对的单引号或双引号包围起来,比如'a'、"abc"、"01"都是字符串。
10.1.1.html使用单引号和双引号声明字符串直接量,如下所示。
动手写10.1.1

执行10.1.1.html,输出多个字符串到网页,如下图所示。

图10.1.1 字符串介绍

10.2 字符串定义
定义字符串有两种方式:单引号和双引号。
使用单引号或双引号声明字符串时两者并没有差别,但各自作为字符串的开始和结尾符号时必须成
对出现。如果字符串中间出现同样的单引号或双引号,需要使用反斜线(\)进行转义。

10.2.1 字符串常量

使用引号定义字符串时引号需要成对出现;若字符串中有相同的引号,需使用反斜线(\)对引号
进行转义。字符串转义是指使用反斜线(\)和紧跟的字符组成一个具有特殊意义的字符。
10.2.1.html使用反斜线转义字符串中出现的引号,如下所示。
动手写10.2.1
执行10.2.1.html,输出结果到网页,如下图所示。

图10.2.1 字符串定义和转义

提示
字符串和数字一样,属于简单类型的数据。使用引号声明字符串之后,该字符串不可改变;如果使
用各种方式修改该字符串,将返回新的字符串。
字符串的每个字符都有一个索引。和数组元素的索引类似,字符索引表示该字符在整个字符串中的
位置;索引从0开始,使用中括号([])可以访问指定位置的字符。
10.2.2.html使用存取运算符访问指定位置的字符,如下所示。
动手写10.2.2

执行10.2.2.html,输出结果到网页,如下图所示。
图10.2.2 字符串直接量

10.2.2 字符串对象

使用new操作符调用字符串构造函数String()将返回字符串对象。通过String()函数可以将参数字符串
化,返回字符串直接量。
10.2.3.html使用String()函数创建字符串,如下所示。
动手写10.2.3

执行10.2.3.html,输出结果到网页,如下图所示。
图10.2.3 字符串对象

全局对象String提供了两种方法,根据Unicode编码创建字符,语法如下:

这两个函数都可以根据Unicode编码创建字符串,并且支持接收多个参数,区别是
String.fromCharCode(code) 只能处理16bit(最大值0xFFFF)以内的低位编码,String.fromCodePoint(code)
能够处理高位编码(16~21bit之间的Unicode编码)。
10.2.4.html根据Unicode编码创建字符串,如下所示。
动手写10.2.4

执行10.2.4.html,输出结果到网页,如下图所示。
图10.2.4 Unicode编码转换为字符串

提示
String.fromCharCode()函数更加常用,String.fromCodePoint()是ES6为了处理5位和6位Unicode编码提
出的函数,最新的IE浏览器还不支持该方法。

10.3 字符串属性
字符串是字符的不可变序列,具有length属性。构造函数String()具有prototype属性,每个字符串都
继承String.prototype上的方法。

10.3.1 length属性

length属性表示该字符串中的字符总数,而不是字节总数。
10.3.1.html查看字符串的length属性,如下所示。
动手写10.3.1

执行10.3.1.html,输出结果到网页,如下图所示。

图10.3.1 字符串的length属性
10.3.2 prototype属性

String.prototype是字符串的原型对象,字符串从这个原型对象继承方法。与Array.prototype一样,可
以给String.prototype增加方法。
10.3.2.html给字符串原型定义size()方法,获取字符串中的字符总数,如下所示。
动手写10.3.2

执行10.3.2.html,输出结果到网页,如下图所示。

图10.3.2 定义字符串原型方法

使用Object.defineProperty()为字符串原型定义size()方法,用于获取字符串长度。直接以
String.prototype = function() {} 形式为原型链添加方法,使用for…in枚举原生数据类型,会出现意外的原
型方法,因此不要直接对原型方法赋值。
提示
字符串是一个可枚举的列表,可以使用for…in语句枚举字符串中的每个字符。

10.4 字符串操作
JavaScript原生提供了近四十个函数用于操作字符串。本节将介绍字符串的常见操作,如查找、替
换、截取、连接、拆分。因为JavaScript早期一般在浏览器环境运行,所以提供了十二个字符串转HTML
标签的函数,但这些函数已不推荐使用,详见10.4.6小节。
10.4.1 查找和替换

JavaScript提供了三种在字符串中查找子串的方法,这三种方法与在数组中查找元素是类似的,包括
indexOf()、lastIndexOf()、includes()。
1.indexOf()方法
indexOf()用于从前往后搜索子串在字符串中首次出现的位置,有则返回第一个字符的下标,没有则
返回-1,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ substr:必选参数,表示要在字符串中查找的子字符串。
◇ startIndex:可选参数,表示从指定位置开始搜索,没有则从首字符开始搜索。
10.4.1.html使用indexOf()在字符串“JavaScript”中搜索字符,如下所示。
动手写10.4.1

执行10.4.1.html,输出结果到网页,如下图所示。

图10.4.1 使用indexOf()查找子字符串

2.lastIndexOf()方法
lastIndexOf()函数与indexOf()函数类似,区别是lastIndexOf()从后往前搜索子字符串,有则返回第一
个字符的下标,没有则返回-1,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ substr:必选参数,表示要在字符串中查找的子字符串。
◇ startIndex:可选参数,表示从指定位置开始搜索,没有则从最后一个字符开始搜索。
10.4.2.html使用lastIndexOf()从后往前搜索字符,如下所示。
动手写10.4.2

执行10.4.2.html,输出结果到网页,如下图所示。

图10.4.2 使用lastIndexOf()查找子字符串

3.includes()方法
includes()函数同样用于查找子字符串,但与indexOf()、lastIndexOf()的不同点在于如果查找到子字
符串,includes()函数返回true,没有则返回false,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ substr:必选参数,表示要在字符串中查找的子字符串。
◇ startIndex:可选参数,表示从指定位置开始搜索,没有则从首字符开始搜索。
10.4.3.html使用includes()函数判断子字符串是否存在,并与indexOf()、lastIndexOf()对比,如下所
示。
动手写10.4.3

执行10.4.3.html,输出结果到网页,如下图所示。
图10.4.3 使用includes()判断是否包含指定子字符串

4.replace()函数
查找字符串常常和替换字符串一起出现。使用replace(substr, newSubstr)将子字符串替换为新的子字
符串,只有substr存在于字符串中时,才进行替换并返回新的字符串,否则返回原字符串的拷贝,语法
如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ substr:必选参数,表示待替换的子字符串;该参数也可以是一个正则表达式,表示根据匹配模
式查找子字符串,正则表达式将在第11章介绍。
◇ newSubstr:必选参数,表示新的子字符串;该参数也可以是一个函数,callback接收参数substr,
replace(substr, callback)使用callback(substr)的返回值作为newSubstr。
10.4.4.html使用replace()函数替换子字符串,如下所示。
动手写10.4.4

执行10.4.4.html,输出结果到网页,如下图所示。
图10.4.4 使用replace()替换字符串

10.4.2 字符串截取

1.slice()方法
slice()是指在指定起始位置到结束位置之间截取字符串,注意只包括起始位置的字符,不包括结束
位置的字符,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ startIndex:必选参数,表示截取字符串的起始位置,注意下标0表示第一个字符;如果是负数,
则从倒数第startIndex个字符开始截取,比如-1表示倒数第一个,-2表示倒数第二个。
◇ endIndex:可选参数,表示截取字符串的结束位置;如果是负数,表示截取到倒数第endIndex个
字符(不含该字符);如果缺少该参数,则截取到最后一个字符。
10.4.5.html使用slice()截取字符串,如下所示。
动手写10.4.5

执行10.4.5.html,输出结果到网页,如下图所示。

图10.4.5 使用slice()截取字符串

2.substr()方法
substr()是指从指定位置截取指定长度的子字符串,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ startIndex:必选参数,表示截取字符串的起始位置,注意下标0表示第一个字符;如果是负数,
则从倒数第startIndex个字符开始截取。
◇ length:可选参数,表示截取的字符数量;如果缺少该参数,则截取到最后一个字符。
10.4.6.html使用substr()截取字符串,如下所示。
动手写10.4.6

执行10.4.6.html,输出结果到网页,如下图所示。

图10.4.6 使用substr()截取字符串

3.substring()方法
substring()是指在指定起始位置到结束位置之间截取字符串,注意只包括起始位置的字符,不包括
结束位置的字符,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ startIndex:必选参数,表示截取字符串的起始位置,如果startIndex或endIndex不大于0,则将其
当作0。
◇ endIndex:可选参数,表示截取字符串的结束位置;如果startIndex等于endIndex,则返回空字符
串;如果startIndex大于endIndex,则交换这两个参数之后再截取;如果缺少该参数,则截取到最后一个
字符。
10.4.7.html使用substring()截取字符串,如下所示。
动手写10.4.7
执行10.4.7.html,输出结果到网页,如下图所示。

图10.4.7 使用substring()截取字符串

提示
slice()、substring()这两个函数在截取字符串时都不包括参数endIndex表示的字符。
4.trim()方法
当字符串首尾包含一些空白符号时,可以使用trim()函数删除首尾的空白符号,包括空格、制表
符、换行符、终止符。比如,在注册账号的网页上,用户输入用户名时全部使用了空格,此时使用
trim()将首尾空格删除后发现用户没有输入任何有效字符,在用户提交注册表单时提示用户输入正确的
用户名信息。
trim()函数不接收任何参数,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。10.4.8.html使用trim()删除字符串首尾的空白符号,
如下所示。
动手写10.4.8

执行10.4.8.html,输出结果到网页,如下图所示。

图10.4.8 使用trim()删除字符串首尾的空格

提示
JavaScript字符串的trim()函数只能删除指定的空白符号,如果需要同时删除其他符号,可以使用
replace()函数将其他字符替换为空字符。

10.4.3 连接和拆分

1.加号和数组join()方法
使用加号运算符(+)将两个字符串拼接成新的字符串,也可以使用数组的join([separator])函数将所
有数组元素用分隔符(separator)拼接成一个字符串,分隔符默认为英文逗号(,)。
10.4.9.html使用加号(+)和数组join()方法连接字符串,如下所示。
动手写10.4.9

执行10.4.9.html,输出结果到网页,如下图所示。

图10.4.9 字符串拼接

2.split()方法
与拼接相反的是拆分,split()函数将字符串拆分成字符串数组,语法如下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
◇ separator:可选参数,表示拆分字符串时使用的分隔符;如果separator是空字符串,则将字符串
全部分割成单个字符;如果缺少该参数或者字符串中不存在该分隔符,则将整个字符作为数组的一个元
素。
◇ limit:可选参数,表示最多拆分成多少个子字符串;如果拆分后数组的长度大于limit,则下标大
于limit的元素全部丢弃,不返回剩余的字符串;如果缺少该参数,则按照分隔符全部拆分。
10.4.10.html使用split()截取新字符串,如下所示。
动手写10.4.10
执行10.4.10.html,输出结果到网页,如下图所示。

图10.4.10 字符串拆分

10.4.4 字符串转义

JavaScript提供了三对函数,用于转义HTTP请求的参数,执行编码和解码。编码是指按照一定规则
将原始字符串生成更容易在网络中传输的字符串;解码是指将编码后的字符串解析成原始字符串。编码
函数用于转义字符串,解码函数用于反转义编码后的字符串,如下表所示。
表10.4.1 字符串转义

(续上表)

在实际开发中,使用encodeURIComponent(str)、decodeURIComponent(encodedURI)对参数值进行转
义和反转义较为常见。
10.4.11.html使用编码函数对字符串进行转义,如下所示。
动手写10.4.11
执行10.4.11.html,输出结果到网页,如下图所示。

图10.4.11 字符串转义

提示
全局函数unescape()、decodeURI()、decodeURIComponent()必须解析对应编码函数生成的字符串,
如果传入参数格式不正确,将抛出错误,类似于“Uncaught URIError: URI malformed”。
escape()和unescape()函数已经从最新的Web标准中删除,虽然还有很多历史代码使用了这两个方
法,但是不推荐新应用使用它们。

10.4.5 大小写转换

1.toLowerCase()方法
toLowerCase()将str中的大写字符全部转换为小写字符,返回新的字符串,不接收任何参数,语法如
下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。
2.toUpperCase()方法
toUpperCase()将str中的小写字符全部转换为大写字符,返回新的字符串,不接收任何参数,语法如
下:

参数说明:
◇ str:表示字符串、字符串变量或字符串对象。10.4.12.html使用toLowerCase()、toUpperCase()转
换字符串中的大小写字符,如下所示。
动手写10.4.12

执行10.4.12.html,输出结果到网页,如下图所示。

图10.4.12 字符串大小写转换

10.4.6 字符串转HTML函数

为了更好地与HTML结合,JavaScript提供了十二个字符串直接转HTML标签的函数,如下表所示。
表10.4.2 字符串转HTML函数

目前这部分函数已经从最新的Web标准中删除,在设计网站新功能时,不推荐使用。

10.5 小结
本章介绍了常用的字符串对象,以及字符串的查找、替换、截取、连接、拆分、转义等常见操作。
正则表达式是字符串的规则模式,与字符串组合使用能够提高开发效率。正则表达式的相关知识将在第
11章详细介绍。
10.6 知识拓展
10.6.1 模板字符串

1.声明多行字符串
JavaScript字符串不支持在双引号或单引号之间加入换行,如果使用换行,必须在字符串末尾增加反
斜线。
10.6.1.html使用反斜线跨行声明字符串直接量,如下所示。
动手写10.6.1

执行10.6.1.html,输出结果到网页,第二段<script>标签没有使用反斜线,会导致语法错误,打开浏
览器控制台查看错误提示,如下图所示。

图10.6.1 字符串换行

使用反斜线让跨行字符串拼接在一起,但最终的字符串没有包含换行符,与直观上的代码差异太明
显,而且字符串中间多了一段空白。
ECMAScript 6定义了模板字符串,允许在字符串中间直接插入换行符,且不需要使用转义字符。模
板字符串使用反引号(``)作为字符串边界符。
10.6.2.html使用反引号(``)声明跨行字符串,如下所示。
动手写10.6.2
执行10.6.2.html,输出结果到网页,如下图所示。

图10.6.2 模板字符串换行

模板字符串最终返回的结果能够正常包含换行,更符合代码的直观感受,比使用反斜线连接的字符
串长度多1。
2.变量插值
模板字符串支持声明时使用变量,替换成变量的值后返回新的字符串,语法如下:

表达式${name}表示在本段模板中,使用变量name的值进行替换。
10.6.3.html在模板字符串中使用变量,如下所示。
动手写10.6.3
执行10.6.3.html,输出结果到网页,如下图所示。

图10.6.3 模板字符串变量

模板字符串greet使用了变量name,将${name}替换为变量真实值即得到最新的字符串。模板字符
串减少了字符串拼接,使用更直接,阅读性更强。
模板字符串``紧跟函数名,函数接收到的参数是根据变量符号分割之后的字符串数组、模板中使用
的参数0、参数1、参数2等,使用各个参数与字符串数组交叉拼接,得到模板使用变量转换后的字符
串。
提示
反斜线(\)也可以转义反引号。在模板字符串中使用反引号,需要用反斜线进行转义,比如`\``。

10.6.2 Unicode转义序列

Unicode字符通常使用形如“\u0000”~“\uFFFF”的4字节十六进制表示,而JavaScript支持使用5个字节
编码表示的Unicode字符串,当\u字符后紧跟的编码不是4位时需要使用花括号({})包围起来,最大支
持“\u{FFFFF}”。当编码大于FFFF时,字符串长度大于1。
十六进制数字包括0~9、a~f(不区分大小写)。字符\x紧跟2位十六进制数字表示使用ANSI编码
声明字符,字符\u紧跟4~5位十六进制数字表示使用Unicode编码声明字符。Unicode编码在00到FF段与
ANSI编码重合,所以\x37、\u{37}、\u0037都表示同一个字符。
10.6.4.html使用\x和\u序列声明字符串,如下所示。
动手写10.6.4
执行10.6.4.html,输出结果到网页,如下图所示。

图10.6.4 Unicode转义序列

提示
\x和\u必须紧跟编码,不能使用表达式,比如'\u'+'{61}'、'\x'+'{61}'这两种使用方式都不符合
JavaScript语法标准。
第11章 JavaScript正则表达式
正则表达式(Regular Expression),又称为规则表达式,它是按照一定规则书写的表达式,用于在
字符串中搜索符合该规则的子字符串。正则表达式功能丰富,可以轻松查找和替换子字符串,比如\d表
示字符串中的数字0~9。本章将详细介绍正则表达式的相关知识以及如何与字符串结合使用。

11.1 正则表达式介绍
正则表达式用于查找子字符串,也经常用于验证目标字符串是否符合指定规则,比如使用\d即可验
证用户在网页上输入的文字是否为数字,省去了依次验证数字0~9的烦琐代码,条理更加清晰。
正则表达式是一种通用知识,学习完JavaScript的正则表达式之后,也可以在其他高级编程语言中使
用。但需要注意的是,不同编程语言对正则表达式的支持会稍有不同,使用前仍然需要阅读该语言的正
则表达式规范。
11.1.1.html使用正则表达式查找字符串中的数字,如下所示。
动手写11.1.1

执行11.1.1.html,输出结果到网页,如下图所示。

图11.1.1 使用正则表达式在字符串中查找数字

JavaScript中的正则表达式是用双斜线(//)包围起来的特殊表达式。\d是一种元字符,表示匹配任
意数字,在后面的11.3正则表达式语法一节会详细讲解。

11.2 正则表达式定义
在JavaScript中创建正则表达式有两种形式:
◇ 使用双斜线(//)定义正则表达式直接量。
◇ 使用RegExp()构造函数创建更详细的正则表达式对象。

11.2.1 使用双斜线定义

11.2.1.html使用双斜线(//)声明正则表达式,如下所示。
动手写11.2.1
执行11.2.1.html,输出结果到网页,如下图所示。

图11.2.1 使用双斜线定义正则表达式

提示
使用双斜线定义正则表达式时,两个斜线之间一定要包含字符,否则会被当作注释语句。

11.2.2 使用RegExp()构造函数

11.2.2.html使用构造函数RegExp()创建正则表达式对象,如下所示。
动手写11.2.2

执行11.2.2.html,输出结果到网页,如下图所示。
图11.2.2 使用RegExp()构造函数定义正则表达式

正则表达式属于引用数据类型,是RegExp对象,两个变量必须指向同一个正则表达式才相等,比
如这两个正则表达式/a/、/a/,虽然字符串看起来一样,但两者并不相等。
使用RegExp()构造函数创建正则表达式时,会在首尾自动增加斜线;如果字符串中包含斜线,会被
转义,比如表达式new RegExp('/A/') 返回的结果是/\/A\// 。
正则表达式修饰符,即正则表达式 /abc/i 或new RegExp('\d', 'g') 的i和g,经常用于补充正则表达式的
含义——i表示忽略大小写,g表示匹配所有满足条件的字符串。

11.3 正则表达式语法
正则表达式是一种描述性表达式,包含多个语法元素,如元字符、限定符、定位符、匹配模式、分
组等。

11.3.1 基本字符

所有的字符,包括输出字符和不可输出字符,都属于正则表达式的基本字符,比如abc、123、空
格、制表符、换行符等。
11.3.1.html在正则表达式中使用普通字符,如下所示。
动手写11.3.1

执行11.3.1.html,输出结果到网页,如下图所示。
图11.3.1 正则表达式基本字符

制表符比较特殊,既可以直接显示,也可以使用\t表示,两者在控制台上显示的状态不一样,但使
用效果一致。
因为JavaScript不支持字符串跨行,所以需要使用正则表达式/\n/匹配字符串中的换行符,\n表示换
行符。

11.3.2 字符转义

正则表达式使用一些具有特殊用途的字符,如果要将这部分字符当作普通字符,就需要进行转义。
JavaScript使用反斜线(\)对特殊字符转义,其他语言可能使用不同的转义符号,比如百分号(%)。
11.3.2.html使用反斜线转义正则表达式中的特殊符号,如下所示。
动手写11.3.2

执行11.3.2.html,输出结果到网页,如下图所示。

图11.3.2 正则表达式字符转义

JavaScript的正则表达式特殊字符包括.、*、+、/、\、?、[]、{}、|。这些字符具有特殊含义,与其
他字符组合使用实现特殊匹配。若要将这些字符作为普通字符进行匹配,需要使用反斜线对其进行转
义。
11.3.3 元字符

正则表达式里具有特殊用途的字符称为元字符。元字符按照用途分为分类字符、限定符、定位符。
分类字符与反斜线一起使用,表示一类字符,如下表所示。
表11.3.1 正则表达式分类字符

(续上表)

提示
分类字符\d、\s、\w有对应的反向匹配,分别是\D、\S、\W,比如小写的\d表示匹配任意数字,大
写的\D则表示匹配\d以外的任意字符。
点号不能匹配换行符\n和回车符\r,使用[\d\D]、[\s\S]、[\w\W]等正向匹配和反向匹配组合可以匹配
任意字符,包括换行符和回车符。
11.3.3.html使用元字符查找分类字符,如下所示。
动手写11.3.3
执行11.3.3.html,输出结果到网页,如下图所示。

图11.3.3 正则表达式元字符

使用分类字符能够在字符串中查找同类字符,而不需要指定明确查找的字符,经常用于查找一定范
围内的字符列表。
提示
特殊字符.、*、+、/、\、?、[]、{}、|出现在[]内部时(除了[]\),都表示普通字符。其中{}和]单
独出现在末尾且没有成对出现时,可以不用转义,直接表示普通字符。

11.3.4 限定符

正则表达式限定符,表示限定符前面的字符需要出现指定次数,如下表所示。
表11.3.2 正则表达式限定符

11.3.4.html使用限定符匹配指定数量的子字符串,如下所示。
动手写11.3.4
执行11.3.4.html,输出结果到网页,如下图所示。

图11.3.4 正则表达式限定符

限定符在正则表达式中经常使用,比如匹配手机号时,要求必须是11位的数字字符串,且首位数字
必须为1。
11.3.5.html使用限定符匹配字符串中的手机号,如下所示。
动手写11.3.5
执行11.3.5.html,输出结果到网页,如下图所示。

图11.3.5 使用正则表达式匹配手机号

11.3.5 定位符

正则表达式定位符,也可以称为边界符,包括字符串首尾边界符和单词首尾边界符,如下表所示。
表11.3.3 正则表达式定位符

1.从开始位置匹配
11.3.6.html使用顶角符号(^)匹配字符串开始位置的字母,如下所示。
动手写11.3.6

执行11.3.6.html,输出结果到网页,如下图所示。
图11.3.6 从字符串开始位置匹配

2.从结尾位置匹配
11.3.7.html使用美元符号($)匹配字符串结尾位置的字母,如下所示。
动手写11.3.7

执行11.3.7.html,输出结果到网页,如下图所示。

图11.3.7 从字符串结束位置匹配

3.匹配单词边界
11.3.8.html使用\b匹配单词边界,如下所示。
动手写11.3.8
执行11.3.8.html,输出结果到网页,如下图所示。

图11.3.8 匹配单词边界

提示
首尾定位符一般用于完整限定,单词限定符用于匹配连续字符串。

11.3.6 修饰符

正则表达式修饰符是指在正则表达式双斜线外对表达式的补充标识,比如表达式/ab/i最后的字符i,
表示不区分大小写,因此该正则表达式可以匹配ab、AB、aB、Ab等字符串。如下表所示。
表11.3.4 正则表达式修饰符
修饰符可以组合使用,比如/a/gi匹配全部的字符a和A。修饰符有两种使用方式:
◇ 直接在双斜线定义的正则表达式后添加修饰符。
◇ 使用构造函数RegExp(pattern, flags)的第二个参数flags传递修饰符。
11.3.9.html为正则表达式设置修饰符,如下所示。
动手写11.3.9

执行11.3.9.html,输出结果到网页,如下图所示。

图11.3.9 正则表达式修饰符

g模式捕获满足正则表达式的全部字符串。u模式中{61}是大写字母A的ASCII码97对应的十六进制
数值。ES5不支持4个字节的UTF-16编码,因此使用u模式匹配4字节的UTF字符。比如点号(.)匹配任
意小于等于0xFFFF的字符串。
11.3.10.html使用u修饰符匹配高阶Unicode字符串,如下所示。
动手写11.3.10
执行11.3.10.html,输出结果到网页,如下图所示。

图11.3.10 正则表达式u模式

原字符串在网页上只显示为一个字符串,但实际是长度为2的UTF-8字符串。

11.3.7 分组捕获

使用小括号()将满足匹配模式的子字符串从整体匹配中获取出来,每一个(pattern)即表示一个分组匹
配。
11.3.11.html使用分组存放正则表达式匹配的多个子字符串,如下所示。
动手写11.3.11

执行11.3.11.html,输出结果到网页,如下图所示。
图11.3.11 正则表达式分组捕获

分组捕获常用于查找匹配字符串中的子字符串,并使用$0、$1等表示第一个分组、第二个分组,
最多支持$9。

11.3.8 优先级

JavaScript的正则表达式遵循从左到右的计算顺序,与算术表达式类似。正则表达式的特殊字符同样
具有优先级,按照优先级对正则表达式字符排序,如下表所示。
表11.3.5 正则表达式优先级

11.3.12.html使用或操作符(|)匹配两个字符串选项,如下所示。
动手写11.3.12

执行11.3.12.html,输出结果到网页,如下图所示。
图11.3.12 正则表达式优先级

提示
转义字符(\)的优先级最高,其他情况下可以使用()提升优先级。对于一些复杂的正则表达式,使
用括号还可以让表达式更加清晰和易于理解。

11.3.9 注释

正则表达式使用“(?#comment)”表示注释内容,注释用于增强正则表达式的可读性;解释正则表达
式时可以忽略这部分内容。JavaScript并不支持在正则表达式中加入注释,也不建议使用注释,但可以直
接在代码上方增加注释。

11.4 正则表达式函数
正则表达式与字符串组合使用;JavaScript字符串有以下几个函数支持使用正则表达式进行操作。

11.4.1 regexp.exec()

使用regexp.exec(string)执行一次正则表达式匹配,字符串符合表达式时返回匹配的数组结果,没有
包含符合匹配模式的子字符串时返回null,语法如下:

参数说明:
◇ regexp:表示正则表达式对象、正则表达式变量。
◇ string:必选参数,用于搜索的原字符串。
11.4.1.html使用exec()方法执行正则表达式匹配,如下所示。
动手写11.4.1

执行11.4.1.html,输出结果到网页,如下图所示。
图11.4.1 使用regexp.exec获取匹配结果

11.4.2 regexp.test()

使用regexp.test(string)判断字符串是否包含正则表达式匹配的子字符串,包含时返回true,不包含时
返回false,语法如下:

参数说明:
◇ regexp:表示正则表达式对象、正则表达式变量。
◇ string:必选参数,用于搜索的原字符串,测试该字符串是否包含指定模式的子字符串。
11.4.2.html使用test()方法判断子字符串是否存在,如下所示。
动手写11.4.2

执行11.4.2.html,输出结果到网页,如下图所示。

图11.4.2 使用regexp.test测试匹配模式

使用test()只需要判断正则表达式是否包含指定模式的子字符串,不需要返回子字符串。

11.4.3 string.match()
字符串的match()方法,用于执行一次正则表达式匹配,返回匹配的子字符串数组,没有包含符合匹
配模式的子字符串时返回null,语法如下:

参数说明:
◇ string:表示字符串、字符串变量或字符串对象。
◇ regexp:必选参数,表示用于匹配的正则表达式。
11.4.3.html使用字符串的match()方法获取匹配的子字符串数组,如下所示。
动手写11.4.3

执行11.4.3.html,输出结果到网页,如下图所示。

图11.4.3 使用string.match获取匹配结果

提示
函数regexp.exec(string)和string.match(regexp)的返回结果很相似,但两者的区别是修饰符g对exec()函
数没有影响。

11.4.4 string.replace()
字符串函数string.replace(regexp, newSubstr)支持使用正则表达式查找字符串,并进行替换。如果原
字符串包含指定模式的子字符串,使用新字符串替换,则返回替换后的字符串;如果没有查询到满足匹
配模式的字符串,则返回结果与原字符串一样。语法如下:

参数说明:
◇ string:表示字符串、字符串变量或字符串对象。
◇ regexp:必选参数,表示用于搜索子字符串的正则表达式。
◇ newSubstr:必选参数,表示新的子字符串;如果regexp参数使用了分组捕获,则newSubstr可以
使用$0~$9表示对应匹配的分组字符串。
11.4.4.html使用正则表达式查找子字符串并进行替换,如下所示。
动手写11.4.4

执行11.4.4.html,输出结果到网页,如下图所示。

图11.4.4 使用string.replace替换字符串

string.replace(regexp, callback)支持使用匹配字符串调用callback函数并将函数返回值作为新字符串的
替代值。callback替换函数的第一个参数以表示匹配的字符串,从第二个参数开始表示分组捕获的子字
符串,因此callback接收的参数数量会根据分组捕获数量变化。
11.4.5.html使用分组将匹配的数字加1,如下所示。
动手写11.4.5

执行11.4.5.html,输出结果到网页,如下图所示。
图11.4.5 使用函数修改string.replace替换时匹配的子字符串

11.4.5 string.search()

使用string.search(regexp)搜索满足匹配模式的子字符串,如果匹配成功,返回首次匹配的子字符串
的索引,没有匹配的子字符串则返回-1,语法如下:

参数说明:
◇ string:表示字符串、字符串变量或字符串对象。
◇ regexp:必选参数,表示用于搜索子字符串的正则表达式。
11.4.6.html使用string.search()搜索指定模式的子字符串,如下所示。
动手写11.4.6

执行11.4.6.html,输出结果到网页,如下图所示。
图11.4.6 使用string.search搜索匹配模式

使用string.search()获取匹配字符串索引,如果返回值大于-1,则说明字符串包含指定模式的子字符
串。这个函数达到的效果与regexp.test()类似。

11.4.6 string.split()

使用string.split(regexp)根据正则表达式分割字符串,返回分割后的字符串列表,语法如下:

参数说明:
◇ string:表示字符串、字符串变量或字符串对象。
◇ regexp:必选参数,表示用于分割子字符串的正则表达式。
11.4.7.html根据正则表达式分割字符串,如下所示。
动手写11.4.7

执行11.4.7.html,输出结果到网页,如下图所示。

图11.4.7 使用string.split分割字符串

提示
当分割模式regexp包含分组时,string.split(regexp)的返回结果包含分组匹配的结果,比如使用/(\d+)/
分割字符串,返回的字符串数组会包含\d+匹配的数字。
11.5 小结
本章详细介绍了JavaScript正则表达式,包括匹配、分组、元字符、限定符、修饰符等,以及正则表
达式与字符串结合使用的场景。虽然使用正则表达式搜索的性能要低于使用字符串直接搜索,但是在应
用开发中,要善于使用正则表达式。

11.6 知识拓展
11.6.1 预查匹配

分组匹配配合限定符有四种模式,这四种模式组成了JavaScript的预查匹配,如下表所示。
表11.6.1 预查匹配说明

使用简单的ab字符串匹配,如下表所示。
表11.6.2 预查匹配示例

11.6.1.html使用预查匹配搜索字符串,如下所示。
动手写11.6.1
执行11.6.1.html,输出结果到网页,如下图所示。

图11.6.1 预查匹配

预查匹配用于判断目标字符串的前后字符串是否满足指定条件,这种匹配模式也称为断言。比如期
望只查找字符串abcd中的bc,其他字符串中的bc都不是目标字符串,这个字符串中的a、d就是断言的条
件。

11.6.2 贪婪匹配
在满足条件的情况下,尽可能匹配更多的字符串,即为贪婪匹配;反之则是非贪婪匹配。贪婪匹配
是字符串中满足指定模式的最长字符串,非贪婪匹配是满足指定模式的最短字符串。
在11.3.4 限定符一节中,量词 *、+、{n,} 都是尽可能多地匹配字符串,也就是贪婪匹配,在这些量
词后加上问号(? ),就变成非贪婪匹配。
11.6.2.html使用贪婪模式和非贪婪模式匹配字符串,如下所示。
动手写11.6.2

执行11.6.2.html,输出结果到网页,如下图所示。

图11.6.2 贪婪匹配

正则表达式/\d+/尽可能匹配更多的数字,/\d+?/最多匹配一个数字。/a*?/表示0个、1个或n个字符a都
可以,但是出现n个a时,只返回1个a。

11.6.3 常用正则表达式

在查找子字符串和判断字符串是否满足条件时,经常使用正则表达式。本节将列举网页开发中常用
的JavaScript正则表达式。
1.手机号正则表达式
越来越多的网站,不管是手机网页,还是电脑网页,都在使用手机号码注册账号。目前手机号除了
11、12号码段未开通外,其他号码段都已开通,比如新开的17、19号码段,正则表达式如下:

11.6.3.html判断字符串是否为手机号,如下所示。
动手写11.6.3

执行11.6.3.html,输出结果如下图所示。

图11.6.3 使用正则表达式判断手机号

下面的正则表达式就不解读每段字符的含义了,读者可以根据本节介绍的知识进行印证。
2.Email邮箱正则表达式

11.6.4.html判断字符串是否为邮箱,如下所示。
动手写11.6.4

执行11.6.4.html,输出结果如下图所示。
图11.6.4 使用正则表达式判断邮箱

3.URL网址正则表达式

11.6.5.html判断字符串是否为网址,如下所示。
动手写11.6.5

执行11.6.5.html,输出结果如下图所示。

图11.6.5 使用正则表达式判断网址

4.IP地址正则表达式

11.6.6.html判断字符串是否为IP地址,如下所示。
动手写11.6.6
执行11.6.6.html,输出结果如下图所示。

图11.6.6 使用正则表达式判断IP

5.座机传真正则表达式

座机号码类型较多,包括××××-×××××××、××××-××××××××、×××-×××××××、×××-××××××××、
×××××××和××××××××等,例如0511-1234567、021-12345678。
11.6.7.html判断字符串是否为座机,如下所示。
动手写11.6.7

执行11.6.7.html,输出结果如下图所示。

图11.6.7 使用正则表达式判断座机

6.身份证号码正则表达式
身份证号码最后一位会出现字母X,也可能是小写x,使用修饰符i表示忽略大小写。
11.6.8.html判断字符串是否为身份证号码,如下所示。
动手写11.6.8

执行11.6.8.html,输出结果如下图所示。

图11.6.8 使用正则表达式判断身份证号码

7.日期正则表达式

使用正则表达式对日期的格式做简单验证,检查年、月、日是否正常。
11.6.9.html判断字符串是否为日期,如下所示。
动手写11.6.9

执行11.6.9.html,输出结果如下图所示。
图11.6.9 使用正则表达式判断日期

8.用户名正则表达式

一些网站让用户设置用户名时,只能使用包含字母、数字、下划线且长度为5~16位的名称,这个
正则表达式用于检测用户名是否符合要求。
11.6.10.html判断字符串是否为用户名,如下所示。
动手写11.6.10

执行11.6.10.html,输出结果如下图所示。

图11.6.10 使用正则表达式判断用户名
第12章 原型链
计算机编程语言对现实生活中的物体进行归纳总结,将多个物体共有的属性和行为定义成一种数据
结构,这个数据结构就是类。类是物体在代码中的抽象定义,根据类可以创建同类的多个物体,比如使
用Car表示小汽车,每执行一次代码new Car()就可以创建一台小汽车,每台小汽车是类Car具体化之后生
成的对象。
在现实生活中有很多种类的物体,在面向对象的编程世界中也有很多类,而现实生活中物体的行为
则对应代码中类的函数。继承关系是类与类之间最常见的一种关系。如果类Child从类Parent继承属性和
函数,则将类Child称为子类,将类Parent称为父类。子类拥有父类的全部功能,并扩展一些特有的功
能。比如,使用Fruit表示水果,使用Apple表示苹果,因为苹果是水果中的一种,所以将Apple作为Fruit
的子类。
编程语言定义一个基类,所有其他类都直接或间接地从这个基类继承属性和方法。JavaScript是基于
原型的面向对象编程语言,不是基于类的面向对象编程语言,虽然概念不同,但是在行为上原型和类是
相似的。JavaScript定义了一个顶层原型,这个顶层原型类似于基类;在JavaScript中,全部有值的数据
(除了null、undefined)都从这个顶层原型继承属性和方法。原型与子原型之间通过链式结构(类似于
类的继承)连接在一起,形成了原型链。本章将详细介绍JavaScript原型链的相关知识,展开面向对象的
JavaScript编程世界。

12.1 原型链介绍
在JavaScript中,使用原型对现实生活中的物体进行抽象,原型使用Prototype表示;每个有值数据的
__proto__ 属性指向该数据结构的原型对象并从该原型对象继承属性和方法,比如字符串'a' 拥有它的原
型对象'a'.__proto__ 的所有属性和方法。
12.1.1.html查看对象的原型对象和原型的方法,如下所示。
动手写12.1.1

执行12.1.1.html,输出结果到网页,如下图所示。
图12.1.1 原型链介绍

原型链是一种链式结构,能够以'a'.__proto__.__proto__ 的形式不断访问原型的原型,直到对象的
__proto__属性为null,该对象就是最顶层原型。目前JavaScript设置的最顶层原型是Object.prototype。

12.2 原型属性
常量null表示空值,因此没有原型;变量的值为undefined时表示未定义的值,在赋值之前无法确定
它的数据类型,因此没有原型。除了这两种数据,其他数据都具有原型属性__proto__,该属性与对应的
构造函数的prototype属性是同一个。
对象的构造函数具有prototype属性,对象具有__proto__属性,它们指向同一个对象,都表示对象的
原型。

12.2.1 prototype属性

1.原型对象
所有类型的数据都通过对应的构造函数(constructor)创建。构造函数是数据的初始化方法,比如
数值的构造函数是Number、字符串的构造函数是String、数组的构造函数是Array。构造函数初始化对象
的值之后,将自身的prototype属性设置赋值给对象的__proto__属性,让对象能够通过__proto__访问对应
原型上的属性和方法。
提示
这里的对象包括全部类型的有值数据,比如布尔值、数字、字符串、数组、函数、对象,不仅仅是
使用typeof操作符得到返回值为object的数据,有时候也会以一切皆对象来代表这些数据。
12.2.1.html查看构造函数的prototype属性,如下所示。
动手写12.2.1
执行12.2.1.html,输出结果到网页,如下图所示。

图12.2.1 原型对象prototype

2.原型继承
在JavaScript中每声明一个函数,就会创建一个原型对象。修改构造函数的原型属性,就可以在两个
原型之间实现继承关系。
12.2.2.html修改构造函数的prototype属性实现原型继承,如下所示。
动手写12.2.2
执行12.2.2.html,输出结果到网页,如下图所示。

图12.2.2 使用原型继承属性和方法

因为使用Rect.prototype = new Shape()修改了Rect的原型对象,使Rect的实例具有Shape的属性和方


法,紧接着使用Rect.prototype.constructor = Rect是为了修正Rect实例的构造函数。虽然不修正也不会造
成明显的负面影响,但修正之后更加自然。
提示
面向对象编程语言Java使用关键字extends实现类继承,有明确的语法结构,形如class A extends B。
JavaScript在ECMAScript 6之前没有提供这种语法结构,而是以上述示例中的形式修改构造函数的
prototype属性来实现继承关系。

12.2.2 __proto__属性

对象的__proto__属性是对象的原型。访问对象的属性时,如果属性不存在,则会查找对象的
__proto__是否拥有对应的属性。如果原型链上所有的__proto__对象都没有该属性,则返回undefined;
如果某一个__proto__对象拥有该名称的属性,则返回该属性。
在JavaScript中,对象属性名称的第一个字符是下划线(_)或美元符号($)时,表示该属性具有
特殊含义,是私有属性或者是受保护的属性,一般不需要开发者访问该属性。
对象的__proto__属性和对象构造函数的prototype属性是同一个,都表示该对象的原型。
12.2.3.html查看对象的__proto__属性,如下所示。
动手写12.2.3

执行12.2.3.html,输出结果到网页,如下图所示。
图12.2.3 查看实例对象的私有属性__proto__

以双下划线(__)作为起始字符的属性名时,具有特殊含义,一般不需要开发者在代码中操作该属
性,上述示例仅是为了展示对象的原型。

12.3 原型扩展
对象从构造函数的prototype继承属性和方法,因此扩展prototype的功能,就可以扩展此类对象的功
能,比如给小汽车Car的prototype对象增加自动停车autoPark()函数,则所有小汽车对象都拥有自动停车
功能。

12.3.1 扩展原型

原型也是一个普通对象,只是它作为其他对象的原型,稍显特殊。JavaScript允许原型在创建之后继
续修改,因此为原型增加函数时,它的对象就会立刻拥有该函数。
12.3.1.html为小汽车Car的原型增加autoPark()函数以实现自动停车功能,如下所示。
动手写12.3.1

执行12.3.1.html,输出结果到网页,如下图所示。

图12.3.1 扩展原型,增加小汽车的自动停车功能
为Car的原型增加autoPark()函数之后,所有小汽车对象(包括以前创建的和新创建的对象)都立刻
拥有了该函数。
提示
新增原型函数就可以很容易地为原型扩展功能,由此可见,JavaScript的原型链非常灵活。为了避免
修改原型,也会使用Object.freeze(prototype) 冻结原型。

12.3.2 修改原型

修改原型包括两种操作:修改原型的部分属性或函数和替换整个原型。比如,将小汽车的最大挡位
从5改为3,或者将小汽车的原型从机动车改为非机动车;虽然与实际行为不符,但是在JavaScript中允许
替换构造函数的整个prototype属性。
1.修改原型属性
修改原型的属性和方法与修改普通对象一样,直接为原型的属性赋值即可。
12.3.2.html修改小汽车原型的最大速度,将所有小汽车对象的最大速度设置从200公里每小时改为
160公里每小时,如下所示。
动手写12.3.2

执行12.3.2.html,输出结果到网页,如下图所示。

图12.3.2 修改原型,调整小汽车最大速度

2.替换整个原型
替换整个原型有两种方式:
(1)为构造函数的prototype属性设置新的值;
(2)使用Object.setPrototypeOf(instance,prototype)函数修改对象的原型。
这两种方式的效果不同:替换构造函数的prototype属性,只能影响新创建的对象实例,修改之前所
创建的对象的原型仍然是旧的原型;Object.setPrototypeOf(instance,prototype)函数只能修改当前参数的原
型,不影响构造函数的prototype属性。
12.3.3.html为构造函数设置新的prototype,如下所示。
动手写12.3.3

执行12.3.3.html,输出结果到网页,如下图所示。

图12.3.3 替换整个prototype属性

设置新的prototype属性之后,使用该构造函数新创建的对象的原型全部改为新的原型,但是之前所
创建的对象的原型不会改变。
Object.setPrototypeOf(instance, prototype)主要用于修改当前对象的原型,不会修改对象构造函数的
prototype属性。
提示
直接修改对象的__proto__属性也可以达到与使用Object.setPrototypeOf()函数一样的效果,但是修改
这类具有特殊含义的__proto__属性不符合代码规范,建议使用更规范的Object.setPrototypeOf()函数。

12.4 原型继承
原型继承是JavaScript唯一实现的继承方式,使用该技能可以让对象对外共享属性和方法,提高代码
复用能力。本节将介绍如何使用原型链进行继承。

12.4.1 私有方法
在编程语言中,private和public表示属性和方法的访问范围控制。private表示对象的属性和方法只能
通过当前对象访问;public表示任何继承该原型的对象都可以访问原型的属性和方法。
JavaScript只有函数作用域和全局作用域,在函数内部声明的变量能在该函数内部任意位置访问,
JavaScript没有提供private、public作为关键字控制变量的访问范围,因此使用函数作用域模拟实现类似
private、public的访问控制。
12.4.1.html在函数内部声明局部变量模拟private访问,如下所示。
动手写12.4.1

执行12.4.1.html,输出结果到网页,如下图所示。

图12.4.1 私有属性和方法

12.4.2 公有方法
原型链上的方法全部是公有方法,任何继承该原型的直接对象和后代对象都能使用这些方法。使用
原型方法可以减少重复代码,提高代码复用能力。
12.4.2.html在原型链上定义方法实现public访问,如下所示。
动手写12.4.2

执行12.4.2.html,输出结果到网页,如下图所示。

图12.4.2 公有属性和方法

12.4.3 实例方法

原型对象被不同实例共享,因此会出现多个实例调用原型方法时操作同一个原型属性的情况。当对
象的方法需要使用局部变量时,可以将方法直接赋值给实例。
12.4.3.html在实例对象上定义方法,如下所示。
动手写12.4.3
执行12.4.3.html,输出结果到网页,如下图所示。

图12.4.3 实例属性和方法

12.5 小结
本章介绍了JavaScript面向对象编程的基石——原型和原型链,以及原型扩展、原型修改、原型判断
等操作,并使用原型模拟了类继承中的私有和公有访问控制。在上层应用开发中,可能较少使用到原
型。为了更深入地了解JavaScript编程思想,建议读者深入学习本章内容。

12.6 知识拓展
12.6.1 原型陷阱

扩展原型会影响已经创建和即将创建的实例,但如果直接对修改构造函数的prototype属性重新赋
值,会导致新创建的实例对象使用新的原型,而之前所创建的对象仍然使用旧的原型。
12.6.1.html修改构造函数的prototype属性,让新旧对象继承不同的原型对象,如下所示。
动手写12.6.1

执行12.6.1.html,输出结果到网页,如下图所示。

图12.6.1 原型陷阱

12.6.2 继承方式

JavaScript基于原型链继承,在实际使用中除了使用原型链继承外,还有构造继承、实例继承、拷贝
继承、组合继承、寄生继承等多种方式。
1.原型继承
12.6.2.html使用原型继承在不同对象之间传递属性和方法,如下所示。
动手写12.6.2
执行12.6.2.html,输出结果到网页,如下图所示。

图12.6.2 原型继承

采用原型继承的方式简单、容易理解,但有以下缺点:
◇ 所有子类共享的Child.prototype是一个父类的实例。
◇ 构造子类实例时,没有向父类构造函数传参,导致父类实例的部分属性不能传递给子类实例。
◇ 无法实现多继承。
2.构造继承
12.6.3.html在构造函数内部调用父类的构造函数,继承父类构造函数内部定义的属性和方法,如下
所示。
动手写12.6.3
执行12.6.3.html,输出结果到网页,如下图所示。

图12.6.3 构造继承

采用构造继承的方式避免了在多个子类实例共享父类实例,但也导致子类实例不能继承父类的原
型,因此child instanceof Supper等于false。
3.实例继承
12.6.4.html在子类构造函数内部调用父类构造函数,创建父类实例对象,并设置子类构造函数内部
定义的属性和方法,实现实例继承,如下所示。
动手写12.6.4
执行12.6.4.html,输出结果到网页,如下图所示。

图12.6.4 实例继承

相比于构造继承,实例继承虽然能够从父类获取原型属性和方法,但产生的问题更加严重,它修改
了实例与构造函数的对应关系,导致new Child()返回的是child对象而不是Child的实例。
4.拷贝继承
将“父类”原型上所有的属性和方法赋值(拷贝)给“子类”实例对象,实现继承。
12.6.5.html使用拷贝实现继承,如下所示。
动手写12.6.5
执行12.6.5.html,输出结果到网页,如下图所示。

图12.6.5 拷贝继承

拷贝继承解决了子类实例child访问Child原型属性和方法的问题,但它与实例继承存在同样严重的
问题,即修改了构造函数本应该返回的实例,导致child instanceof Child等于false,并且导致了更多的性
能开销。
5.组合继承
采用原型继承和构造继承组合的方式,确保子类实例拥有不同的父类实例属性。
12.6.6.html使用组合继承,如下所示。
动手写12.6.6
执行12.6.6.html,输出结果到网页,如下图所示。

图12.6.6 组合继承

组合继承仍然有一个缺点,就是调用两次父类构造函数,在子类实例和Child.prorotype都具有父类
的实例属性。访问子类实例属性时会屏蔽Child.prototype上的同名属性,目前这种继承方式较能被接
受。
6.寄生组合继承
寄生组合继承就是在子类和父类之间设置空对象,实现继承。
12.6.7.html使用寄生组合继承,如下所示。
动手写12.6.7

执行12.6.7.html,输出结果到网页,如下图所示。
图12.6.7 寄生组合继承

通过中间的空白函数去掉Child.prototype上Supper的实例属性。这种方式并没有消除多余的Supper实
例,反而让原型链多了一层。
7.proto继承
在单继承上,上述继承方式都没有完美解决冗余的Supper实例、instanceof不符合预期、构造函数返
回其他函数的实例等问题,虽然名称不同但实现方式类似。只有修改Child.prototype的原型,才能解决
上述问题。
12.6.8.html修改子类的__proto__属性实现继承,如下所示。
动手写12.6.8

执行12.6.8.html,输出结果到网页,如下图所示。
图12.6.8 proto继承

在Child构造函数中,调用Supper构造函数不是必须的,但需要具备这种能力。
第13章 BOM浏览器对象模型
解释执行JavaScript代码的环境称为宿主环境,浏览器是JavaScript最常见的宿主环境。浏览器提供
了一些对象和函数,让JavaScript能更好地完成人机交互,这些对象统一称为浏览器对象模型。本章将详
细介绍如何正确使用浏览器提供的对象和函数。

13.1 BOM介绍
在浏览器内,有一些能够直接通过JavaScript调用的对象和函数。浏览器对象模型,英文全称
Browser Object Model,简称BOM,是指能够直接使用的对象和函数,使用这些函数能够进行网页管
理,与用户完成各种交互。它们并不属于语言标准,而是由不同浏览器厂商实现的可用代码,比如文档
对象document、历史对象history、导航器对象navigator、窗口对象window等。
到目前为止,BOM仍然没有统一的标准定义,但是各个主流浏览器支持的浏览器对象模型都比较
接近并且应用广泛,已经成为事实标准。
万维网联盟为了让在浏览器中运行的JavaScript实现规范化,已经在HTML5中提出了部分规范标
准,比如通知对象notification。
提示
万维网(World Wide Web)联盟,简称W3C,致力于发展各项Web规范,解决Web应用在各类平台
上的兼容问题。著名的HTML5标准就是W3C推动发布的。
13.1.1.html使用部分浏览器对象,如下所示。
动手写13.1.1

执行13.1.1.html,输出结果到网页,如下图所示。

图13.1.1 BOM浏览器对象介绍

浏览器对象模型必须在浏览器中才能正常运行,JavaScript代码虽然能够在非浏览器环境执行,但上
述示例使用了浏览器特有的对象,因此只能在浏览器中运行。
13.2 document文档对象
document对象表示当前网页的文档对象。如果网页嵌套多个frame/iframe标签,则当前网页具有多个
document对象,但顶层document对象只有一个,即与当前网址关联的网页文档对象。
document又是一个特殊的DOM对象(DOM对象将在第14章DOM文档对象模型介绍),因此
document同时具有BOM和DOM的功能。
document对象可以快速访问网页中的节点及节点集合,比如表单、图像、链接、嵌入对象等。

13.2.1 对象集合

document将网页中具有相同特殊含义的标签映射到一个集合中,如下表所示。
表13.2.1 document对象集合

13.2.1.html使用document快速访问网页中的表单、图像、超链接、脚本等集合,如下所示。
动手写13.2.1
执行13.2.1.html,输出结果到网页,如下图所示。
图13.2.1 document对象集合

13.2.2 对象属性

document对象的属性包括网页节点、网页Cookie、文档标题、文档修改时间和文档关联信息等。
document对象属性如下表所示。
表13.2.2 document对象属性

(续上表)

13.2.2.html访问文档对象document的属性,如下所示。
动手写13.2.2

执行13.2.2.html,输出结果到网页,如下图所示。

图13.2.2 document对象属性

◇ document.body表示文档<body>节点,从上到下解析HTML文档直至解析到<body>标签之前,
document.body都为null。
◇ 因为<script>标签必然存在于<head>标签或<body>标签中,如果在这两个标签外侧,浏览器会进
行容错,排列一个最接近正确文档的结构,因此在执行JavaScript代码时,document.head必然已经存
在。
◇ 在解析执行JavaScript代码时,document.readyState处于loading状态,解析完成之后加载其他多媒
体资源,document.readyState处于interactive状态,即使不需要加载其他资源,interactive状态也会出现一
次,加载完成之后进入complete状态。
document作为文档节点,包含基本的children、childNodes、firstNode、lastNodes、nodeType等属
性,这部分属性将在第14章DOM文档对象模型介绍。

13.2.3 对象方法

document对象提供了常用的节点获取方法:getBy系列和querySelector系列。如下表所示。
表13.2.3 document对象方法
◇ document.getElementById(id),即使存在多个相同id,也返回第一个节点,从语义上不应该存在
多个相同id。
◇ document.getElements*()方法返回的是节点集合HTMLCollection实例。
◇ document.querySelector(selector)返回一个满足selector条件的节点,即使有多个节点满足条件也只
返回第一个,selector表示CSS元素选择器。
◇ document.querySelectorAll(selector)返回的是节点集合NodeList实例。
13.2.3.html使用getBy系列和querySelector系列获取元素,如下所示。
动手写13.2.3

执行13.2.3.html,输出结果到网页,如下图所示。
图13.2.3 document对象方法

HTMLCollection和NodeList并不是数组,只是和数组的数据结构类似,不具有数组相关方法,俗称
伪数组。函数内部的arguments对象也属于伪数组。
提示
HTMLCollection使用时一般将其实例转换为数组,比如使用boldCollection=
[].slice.call(boldCollection),转换成数组后,节点变化不再影响数组长度。

13.3 location导航对象
location对象包含当前网址的相关信息和多个网页访问方法,document和window对象都拥有一个
location属性,这三者都指向同一个对象。使用window.location、document.location和直接使用location是
等价的。

13.3.1 网页网址

location对象包含多个属性,各属性含义如下表所示。
表13.3.1 location属性列表

这些属性支持对location赋值,赋值导致location对象变化,会触发网址跳转或刷新。
13.3.1.html查看location对象属性列表,如下所示。
动手写13.3.1

执行13.3.1.html,输出结果到网页,如下图所示。

图13.3.1 location网址对象

使用file:协议,因此host和hostname为空字符串;origin属性的支持不够广泛,一般不建议使用;
pathname可能与文件路径一致,但目前绝大部分网站采用网址rewrite,因此不能通过pathname判断文件
所在目录。
location.hash属于纯浏览器端行为,在后端脚本中没有对应的方法来获取location.hash的值。

13.3.2 网页访问

对location属性赋值以改变当前网址,location提供三种方法:
1.location.assign(newUrl),加载新的网页到当前location对象,增加一条网址访问历史记录;
2.location.reload(),重新载入当前网址,不产生新的历史记录;
3.location.replace(newUrl),使用新网址替换当前网址,不产生新的历史记录。
location.href表示当前完整网址,因此对href进行赋值,同样能实现新的网页访问。
13.3.2.html使用location的方法进行网页跳转,如下所示。
动手写13.3.2
◇ 当网页信息与服务器有明显差异时,使用location.reload()重新载入网页。
◇ 使用location.replace()替换浏览器网址记录,可避免用户退回历史记录时进入敏感信息页面,比
如限制相同订单信息只能提交一次。
◇ 对location.href赋值,效果和location.assign()一致,对location直接赋值也可达到同样的效果。
在修改网址时需要注意是修改location的一部分还是全部,动手写13.3.2中的newUrl变量只是一个域
名,属于host,没有包含协议,不是一个完整的网址。使用newUrl对location.href赋值时,浏览器会使用
当前location对象的其他部分进行补齐,但在该示例中出现了意外结果。对网址赋值,需要注意指明协
议。

13.4 history历史对象
history对象包含浏览器当前标签页的访问历史记录和多个操作方法。

13.4.1 网页操作

history包含访问历史记录,history.length表示当前标签页的历史记录长度。history提供三种方法用于
访问历史记录跳转,如下表所示。
表13.4.1 history跳转方法

13.4.1.html查看历史记录数量,使用history方法控制网页前进后退,如下所示。
动手写13.4.1
执行13.4.1.html,输出结果到网页,如下图所示。

图13.4.1 history历史对象

13.4.2 网页刷新

不论是使用location跳转新的网址,还是使用history跳转,都会导致浏览器刷新网页,重新请求大部
分重复的CSS、JS等文件。使用location.hash修改网址的hash部分,不会导致网页刷新,但网址并不美
观,而且会影响SEO,让搜索引擎误判两个不同hash的网页为同一个网页。
history提供了pushState()函数。通过pushState()修改网址时,浏览器不会刷新网页,所有CSS、JS以
及图片资源都还存在,此时使用JavaScript控制页面切换,可以实现无感知刷新网页。该函数接收三个参
数,语法如下:

参数说明:
◇ history:表示历史对象。
◇ data:表示该状态对应的数据对象;state的含义是状态,pushState就像数组一样将这个state放到
数组列表中,当触发popstate事件时,浏览器将这个data对象从数组中取出,传递给popstate事件。
◇ title:表示网页标题,但是目前所有浏览器都会忽略该参数,不论设置什么值都没有效果,因此
在使用history.pushState()函数时要配合修改document.title属性才能修改网页标题,也可以通过修改<title>
标签来修改网页标题。
◇ newUrl:表示调用history.pushState()之后网址栏显示的新网址。
在执行过history.pushState()函数的页面,当用户点击后退或前进时,会触发window对象的onpopstate
事件。在网页中监听onpopstate事件,接收state变化通知,从而进行网址切换。这种切换不会导致网页刷
新,对用户更友好。
13.4.2.html使用pushState()方法修改网址,如下所示。
动手写13.4.2
执行13.4.2.html,注意网址变化时网页不会刷新,如下图所示。

图13.4.2 使用history.pushState()刷新网页

history.pushState()需要配合window.onpopstate()事件一起使用,才能完全实现浏览器网页的前进、后
退、刷新和跳转。
点击浏览器后退按钮,第一次触发popstate事件,因为之前没有对该网址设置stateData,所以此时
event.state为null;接下来点击浏览器前进按钮,第二次触发popstate事件,此时event.state即为调用函数
history.pushState()设置的stateData。
history.replaceState()与location.replace()类似,直接替换当前访问state,不会增加新的历史记录。
使用history.pushState()可以实现与location.assign()一样的效果,且完全不需要二次加载整个网页。

13.5 navigator浏览器对象
navigator导航对象包含浏览器代理标识、浏览器厂商、浏览器版本等信息。navigator属性作为只读
属性,一般用于判断浏览器环境,不需要对其赋值。
navigator不是JavaScript的语言标准,但所有浏览器都支持。navigator的常见属性如下表所示。
表13.5.1 navigator属性列表
navigator还包括了一些与设备相关的信息,比如navigator.cpuClass表示CPU等级,
navigator.deviceMemory表示设备内存,但并不是所有浏览器都支持它们。

13.5.1 浏览器信息

浏览器组成部分较多,根据navigator的appCodeName、appName、appVersion、product等属性无法
准确判断浏览器的名称和版本,一般根据与HTTP请求头HTTP_USER_AGENT对应的navigator.userAgent
判断浏览器信息。
13.5.1.html使用正则表达式判断浏览器名称和版本,如下所示。
动手写13.5.1
执行13.5.1.html,输出浏览器名称和版本,如下图所示。

图13.5.1 获取浏览器名称和版本

Chrome允许通过插件修改navigator.userAgent,从而导致判断失败。但是通过navigator.userAgent获
取浏览器信息和后端根据类似于HTTP_USER_AGENT的HTTP请求头获取浏览器信息,两者可以保持一
致,因此有很多代码实例都是通过userAgent获取浏览器信息。
提示
判断navigator.userAgent时需要注意,可能包含多个浏览器信息,比如“Chrome/68.0.3402.0
Safari/537.36”“Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134”。浏览器可以在navigator.userAgent属性
中加入任意标识,开发者应该根据使用场景选择合适的值作为浏览器名称。

13.5.2 多语言与本地化
由于没有规定navigator的语言属性,导致在不同浏览器出现了四个可以用于标识浏览器语言的属
性:language、browserLanguage、userLanguage、systemLanguage。
13.5.2.html查看浏览器的语言属性,如下所示。
动手写13.5.2
执行13.5.2.html,注意Chrome和IE10支持的语言属性不同,如下两图所示。

图13.5.2 获取浏览器语言(Chrome)

图13.5.3 获取浏览器语言(IE10)

这四个属性中,language来自MDN,browserLanguage、systemLanguage、userLanguage来自
MSDN。由于在新版本的IE中已经废弃systemLanguage和userLanguage,因此采用(navigator.language ||
navigator.browserLanguage).toLowerCase() 获取浏览器语言,然后加载不同语言文件。
13.5.3.html根据浏览器的显示语言选择加载对应的语言包,如下所示。
动手写13.5.3
13.5.3-zh-cn.js:

执行13.5.3.html,输出语言包里的文字,如下图所示。

图13.5.4 网页本地化

13.6 window窗口对象
window对象表示当前文档所在窗口,document.defaultView即指向window对象。
13.6.1 全局作用域

窗口对象window是特殊的浏览器对象,它的所有属性都拥有和全局变量一样的作用域。因此在浏
览器中,全局作用域就是window作用域。
13.6.1.html查看frame框架数量和window的全局属性,如下所示。
动手写13.6.1

执行13.6.1.html,输出结果到网页,如下图所示。

图13.6.1 window窗口对象全局作用域

设置window属性之后,其访问方式和全局变量一致,作为属性删除之后则不能再直接访问。

13.6.2 对话框
window对象提供三种对话框:alert、confirm和prompt。这三种对话框都会阻塞后续JavaScript代码
的执行,而且必须在用户点击按钮后才能取消,因此,若浏览器检测到网站不正常使用这类对话框,就
会提醒用户是否不再显示相应的对话框。
1.alert警告对话框
alert警告对话框,显示一段警告信息和一个确认按钮,一般用于提示缺少关键信息,防止遗漏信
息。
13.6.2.html在登录时检测用户名和密码是否输入,没有输入则弹出alert警告框,如下所示。
动手写13.6.2

执行13.6.2.html,输出结果到网页,如下图所示。

图13.6.2 alert警告对话框
在登录界面,如果未输入账号或密码,点击登录会弹出对话框提醒输入密码。
2.confirm确认对话框
confirm对话框会显示一段提醒信息和两个按钮:取消和确认。它一般用于重要操作的二次确认,比
如清空回收站、确认转账等不可撤销的操作。confirm(importantMessage)有两个返回值,点击确认返回
true,点击取消返回false,在点击按钮之前会阻止浏览器的其他行为。
13.6.3.html在执行清空缓存等重要操作或不可逆操作前弹出confirm确认框进行二次确认,如下所
示。
动手写13.6.3

执行13.6.3.html,输出结果到网页,如下图所示。

图13.6.3 confirm确认对话框

3.prompt信息对话框
prompt对话框在confirm对话框的基础上增加了一个输入框,除选择true和false之外,允许用户输入
其他信息。prompt(importantMessage, defaultText)显示对话框,里面有一段提示文字和一个输入框;
defaultText是输入框内的默认字符串,用户点击确定时返回输入的值,点击取消返回null,在点击按钮之
前会阻止浏览器的其他行为。
13.6.4.html使用prompt对话框接收输入的短信验证码,如下所示。
动手写13.6.4
执行13.6.4.html,输出结果到网页,如下图所示。

图13.6.4 prompt信息对话框

13.6.3 新建窗口

要在网页中展示一段额外信息,但是又不愿意占据当前网页的空间时,可以使用window.open()函数
打开一个具名窗口。用户在新窗口内阅读完成之后,可以再回到原窗口。比如,在账号注册页面,让用
户在新窗口内阅读《注册协议》等信息,当滚动条滑到底部,新窗口通知原来的网页用户已阅读完成。
open()函数也常用于打开一个下载页面。当浏览器检测到新窗口返回的数据是一个文件时,会将文件保
存到本地并自动关闭新窗口,十分方便。
open(url, name, options)函数参数有name和options可选。如果第二次调用open()传递同名name参数,
则聚焦到同名窗口而不是新建一个窗口。options是一个字符串,各个配置项使用逗号连接,比
如'height=230,width=360'。options详细配置如下表所示。
表13.6.1 open()函数新建窗口选项
目前window.open()存在滥用现象,尤其是一些广告网站。浏览器为了避免open()函数滥用,当给用
户新建无关的窗口时,会对该函数调用进行检测,如果不是用户的行为(按键、触摸、点击鼠标等)触
发的函数调用,纯粹代码调用新建窗口是不会成功的。
动手写13.6.5

执行13.6.5.html,输出结果到网页,如下图所示。

图13.6.5 新建窗口
在动手写13.6.5中点击按钮“点击新建窗口”,打开新窗口,在新窗口中显示13.6.1.html的内容。

13.6.4 关闭窗口
window.close()用于关闭当前窗口。浏览器为了避免恶意脚本关闭用户浏览器,只允许通过
JavaScript代码新建的窗口由JavaScript代码关闭。
动手写13.6.6

执行13.6.6.html,输出结果到网页,如下图所示。

图13.6.6 关闭窗口

新建的窗口“13.6.2.html”能够被正常关闭。关闭当前网页时,因为当前窗口不是通过window.open()
创建的,所以浏览器认为不符合用户本意,会在控制台显示一段警告“Scripts may close only the windows
that were opened by it”,表示脚本只能关闭通过脚本创建的窗口。
window.name表示当前窗口的名称,window.openner表示窗口的创建者,一般是函数window.open()
的调用者。

13.6.5 窗口位置

窗口具有多个与位置相关的属性,如下表所示。
表13.6.2 窗口位置属性列表

(续上表)

上述表格中的属性单位均为像素,且均为整数值。不同浏览器支持的属性不完全一致,比如Firefox
支持screenX、screenY,不支持screenLeft、screenTop,而且与其他浏览器处于相同位置时还可能出现负
数,使用时请注意浏览器兼容性。
动手写13.6.7
执行13.6.7.html,window窗口的位置属性与浏览器有关,如下图所示。

图13.6.7 窗口位置属性(Chrome)

图13.6.8 窗口位置属性(IE10)

13.6.6 缩放事件
window.onresize事件监听浏览器窗口大小变化,根据当前大小计算主要显示区域的高度,确保
<body>标签高度占据整个浏览器窗口,且<html>标签不出现滚动条。除了缩放浏览器窗口大小,还会缩
放网页,检测window.devicePixelRatio属性判断网页是否经过缩放。
window.devicePixelRatio表示当前网页缩放的百分比,等于1表示网页没有缩放,小于1表示网页缩
小,大于1表示网页放大。
动手写13.6.8

执行13.6.8.html,输出结果到网页,如下图所示。

图13.6.9 缩放事件

打开网页13.6.8.html,先将浏览器窗口调整到上图大小,调整过程中控制台会输出resize事件对象、
文档显示区域高宽、浏览器是否经过缩放;再按住Ctrl键,滑动鼠标滑轮,浏览器会将网页进行对应缩
放,此时在resize事件中检测devicePixelRatio,已经不是默认的1,而是与110%对应的1.1。

13.6.7 异步调用
异步调用是指代码不需要马上执行,可以等待外部环境满足条件之后再执行。这就像是我们要烧一
壶开水,水不会马上烧开,在烧水期间可以去洗菜、切肉,等到水烧开时再去关闭电源。执行网页代码
也会有这样的情况,比如在网页加载过程中,通常会存在1秒到几秒的白屏状态,此时可以在网页加载
完成之后再去执行指定函数。
JavaScript的运行场景与异步调用相似,比如等待用户点击“同意”按钮、等待用户选择文件、等待用
户提交表单,异步调用能够在有限空间内提高代码的执行效率。
JavaScript提供了三个函数,用于执行异步代码,分别是:setTimeout()、setInterval()、
requestAnimationFrame()。
这三个函数的使用方式类似,语法如下:

参数说明:
◇ callback:必选参数,表示需要异步执行函数。
◇ delay:可选参数,表示等待几毫秒之后调用callback函数,如果没有指定该参数,则会在本段代
码块执行完毕之后调用callback函数。
使用requestAnimationFrame()函数设置延时执行的异步代码时不需要为异步代码指定delay参数,在
浏览器的下一个周期会自动调用callback函数。
提示
setTimeout()在计时到达时调用一次callback函数;setInterval()在每次计时到达时都调用一次callback
函数,比如每隔100毫秒更新网页上展示的时间。
函数setTimeout()和setInterval()还会返回一个计时器ID,在计时到达之前,调用对应的
clearTimeout(timeoutId)、clearInterval(intervalId)可以清除计时器,阻止调用callback函数。使用
cancelAnimationFrame()可以清除requestAnimationFrame()函数设置的延时调用,这个方法不属于标准函
数,不过目前基本上只有最新的浏览器支持。
函数setTimeout()和setInterval()的参数callback也可以是一段JavaScript代码,但是一般不建议使用代
码作为这两个函数的参数。
动手写13.6.9
执行13.6.9.html,输出结果到网页,如下图所示。
图13.6.10 异步回调

setTimeout()和setInterval()的delay参数可以传递任意自然数,但是浏览器会限制最小时间。每个浏
览器的最小延时不同,感兴趣的读者可以使用类似这样的代码“console.log(Date.now());
setTimeout(function(){console.log(Date.now());},1);”进行测试。使用clearTimeout()和clearInterval()清除过
期或者不存在的计时器时不会产生副作用,可以放心使用。

13.7 Notification
HTML5中新增了Notification接口,这让浏览器像手机APP一样,可以在桌面显示通知。虽然
HTML5更多是一个描述HTML规范的版本,但也制定了与用户交互的细则,实现这些交互行为需要和
JavaScript一起配合。

13.7.1 请求权限

桌面通知与获取geolocation地理位置信息一样,需要先获得用户授权。只有用户点击“允许”按钮,
才能创建和显示通知。
动手写13.7.1

执行13.7.1.html,输出结果到网页,如下图所示。
图13.7.1 请求通知权限

如果点击“允许”,下一次访问网页可以立刻再次申请通知权限;如果点击了“禁止”,则需在一段较
长的时间之后才能再次申请权限。
若在HTTP而不是HTTPS安全协议下请求通知权限,可能会被浏览器直接拒绝,如下图所示。

图13.7.2 非安全域请求通知权限

13.7.2 显示通知

获得通知权限之后,使用构造函数Notification(title, options)创建通知并在合适的时间显示,语法如
下:

参数说明:
◇ title:必选参数,表示通知标题。
◇ options:可选参数,表示通知的配置属性。
options参数是一个对象,支持多项配置,如下表所示。
表13.7.1 通知选项
动手写13.7.2

执行13.7.2.html,输出结果到网页,如下图所示。

图13.7.3 显示通知
13.8 小结
本章介绍了JavaScript宿主环境中的浏览器对象模型(BOM),包括document、location、history、
navigator、window,以及HTML5新增的通知对象Notification。BOM功能丰富,可以完成大量与用户和
系统的交互,在实战中读者一定要掌握BOM对象的常见用法。

13.9 知识拓展
13.9.1 防止网页嵌套

使用frame/iframe嵌套网页异步加载网页插件,比如讨论插件。网页嵌套存在的一个明显的问题
是,用户会误以为当前访问的网页是当前浏览器网址栏显示的地址。如果钓鱼网页嵌套了一个非常著名
的网站,用户会误以为当前访问的网页就是这个著名的网站,但实际上这是第三方网站嵌套。此时出现
一条类似于“当前账号已登出”的信息,就可能引导用户在钓鱼网站输入用户名和密码。
非标准协议Frame-Options支持设置网页嵌套条件,在成为标准之前,需要使用X-Frame-Options替
代。
X-Frame-Options支持三种设置:
1.DENY,禁止当前网页在任何网页的frame/iframe中显示;
2.SAMEORIGIN,允许当前网页在同域网页的frame/iframe中显示,禁止在其他域显示;
3.ALLOW-FROM uri,允许当前网页在指定uri的网页中显示。
动手写13.9.1
执行13.9.1.html需要配合服务器端程序语言PHP一起使用,直接使用浏览器打开该网页不会达到防
嵌套效果。此处展示使用网址打开该页面的效果,如下图所示。

图13.9.1 X-Frame-Options防止网页嵌套
13.9.2 BOM跨域限制

使用<iframe>标签可以在网页中嵌套任意网址,但出于安全考虑,浏览器不允许不同域之间的BOM
对象互相访问。
网址的结构如下:

以网址https://www.01kuaixue.com:443/doc?course=JavaScript#13.9.2为例,“https:”表示协议,“www.
01kuaixue.com”表示主机,“443”表示端口,“/doc”表示路径,“?course=JavaScript”表示查询参
数,“#13.9.2”表示锚点(页面内部导航)。
同域是指两个网址的协议、主机、端口三要素都相同,若其中任何一个不相同,就不是同域。
13.9.2.html使用<iframe>引入其他页面,如下所示。
动手写13.9.2

执行13.9.2.html,输出结果到网页,在浏览器控制台能够看到错误提示,表示跨域访问被阻止,如
下图所示。

图13.9.2 BOM跨域安全限制

无论是在当前网址对应的页面,还是<iframe>标签引入的网页,两者之间的BOM对象都不能互相访
问。
第14章 DOM文档对象模型
使用JavaScript可以操作网页中的标签。网页中的文本标签在JavaScript中是以对象的形式存在的,
调用这些对象的属性和方法,就能实现丰富的交互行为,比如修改文字外观、展示动画、渲染图像、选
择日期、提交表单。在网页编程中,使用JavaScript操作文档中的标签对象是很常见的。本章将详细介绍
网页文档对象的相关知识和网页性能优化。

14.1 DOM介绍
网页中的标签,比如<div>在JavaScript中是一个HTMLDivElement对象,这种以对象形式定义的网
页标签,称为文档对象。每种标签都有定义对应的JavaScript对象,它们组成了文档对象模型,即
Document Object Model,简称DOM。
文档对象模型由W3C组织统一定义,具有明确标准,这一点与BOM不同。DOM虽然在浏览器中被
大量使用,但它是一种与语言、平台、浏览器无关的接口定义。任何实现DOM标准的容器,都可以解
析HTML、XML等标签形式的文档,操作标签数据、修改标签展现形式。
在使用BOM和DOM时,我们会看到两者有互相借鉴的一些方法和属性,比如window对象的
addEventListener()函数和标签的addEventListener()函数用法一致,document文档对象也是特殊的DOM对
象,包含了与DOM相同的方法和属性。
DOM采用树形结构表示文档中的各个元素。14.1.1.html在JavaScript中以树状形式表示一个文档页
面,如下所示。
动手写14.1.1

文档14.1.1.html的文档对象结构如下图所示。
图14.1.1 文档对象模型

DOM标准除了包含HTML文档规范,还包括DOM样式、事件等:
◇ DOM2 Core,包含DOM1 Core规范,定义了DOM文档树形结构模型,并增加了部分新特性。
◇ DOM2 HTML,继承DOM1 HTML规范,对HTML部分进行了扩展,DOM还包括XML等文档。
◇ DOM2 Events,规定了事件标准,比如事件传递机制、事件捕获、冒泡、取消等。
◇ DOM2 Style,规定了DOM与CSS交互标准。
◇ DOM2Traversal,规定了DOM迭代访问标准。
◇ DOM2 Range,规定了TextRange等指定范围的操作标准。
◇ DOM2 Views,规定了视图的访问和更新标准。
DOM2是在DOM1的基础(DOM1 Core和DOM1 HTML)上进行扩展。需要知道的是,浏览器并没
有完整实现整个标准,而且出于历史竞争原因,部分浏览器会兼容其他浏览器自行定义的标准和W3C标
准。

14.2 DOM节点
在html文档中,document对象包含文档类型声明(doctype)和<html>节点,如下图所示。

图14.2.1 document文档结构

14.2.1 节点分类

DOM文档树形结构由不同节点组成,图14.1.1文档对象模型展示的都是节点,各个节点具有特定的
分类:
◇ 根节点,最顶层的<html>标签称为根节点,是HTML文档的最顶层标签。
◇ 父节点,节点的上一层节点称为父节点,比如<html>是<head>和<body>的父节点,<head>是
<meta>和<title>的父节点。
◇ 子节点,节点的下一层称为子节点,比如<head>是<html>的子节点,<meta>是<head>的子节
点。
◇ 兄弟节点,具有相同父节点的节点称为兄弟节点,比如<head>和<body>互为兄弟节点,<meta>
和<title>互为兄弟节点。
◇ 后代节点,子节点的下一层节点及下下一层的节点称为后代节点,比如<title>和<div>是<html>
的后代节点。
◇ 叶子结点,DOM中文档树形结构最底层的节点称为叶子节点,比如文本“DOM介绍”“text-2”是叶
子节点。
每个节点都是node,只有node节点类型(nodeType)是1的节点才称为元素(Element),即标签
(Tag)。父节点和子节点的关系是相对的。
每个节点根据节点类型分为:
◇ 元素节点,HTML文档的标签,比如<html>、<head>、<body>、<h1>、<div>。
◇ 文本节点,元素节点中包含的文本内容,比如<title>标签中的文本“DOM介绍”、<div>标签包含
的“text-2”。
◇ 属性节点,元素节点的属性,比如<meta>标签的“charset”属性、<h1>标签的“id”属性。
节点类型nodeType还包括其他类型,比如注释节点。节点类型如下表所示。
表14.2.1 节点类型列表

提示
文档全部由节点组成,标签是节点的一种类型,通常情况下,也使用元素表示标签。节点是总体统
称,标签和元素是同一个意思。在代码中,合理的用法是使用node表示节点,tag表示标签,element表
示元素。
14.2.1.html查看标签、文本、注释的节点类型,如下所示。
动手写14.2.1
执行14.2.1.html,输出结果到网页,如下图所示。

图14.2.2 节点类型

所有节点都具有nodeName属性,用来表示节点名称,比如comment.nodeName是“#comment”,
document.nodeName是“#document”,text.nodeName是“#text”。其中,标签具有特殊的tagName,比如
h1.nodeName和h1.tagName都是大写的H1。

14.2.2 节点对象
节点有很多类型,本节介绍最常用的元素节点。
元素节点Element实现了Node接口和EventTarget接口,并且继承了ParentNode和ChildNode。该元素
节点的属性如下表所示。
表14.2.2 Element元素节点属性列表

14.2.2.html查看Element元素节点的各个属性,如下所示。
动手写14.2.2
执行14.2.2.html,输出结果到网页,如下图所示。

图14.2.3 Element元素节点属性列表

Element从Node继承的属性如下表所示。
表14.2.3 Element继承自Node的节点属性列表
(续上表)

动手写14.2.3
执行14.2.3.html,输出结果到网页,如下图所示。

图14.2.4 Node节点属性列表

提示
在网页中,连续的空白(空格、Tab、换行)会默认显示为一个空格;元素的innerText属性是渲染
的本文内容,textContent是完整的文本内容。因此innerText会剔除不需要显示的换行和空白,
textContent则会保留。

14.2.3 节点关系

节点之间的关系有三种:父子关系、兄弟关系和属性包含关系。
标签包含其他标签,形如“<ul><li></li><ul>”,其中<ul>和<li>标签属于父子关系。标签从上往下排
列在一起,形如“<a></a><b></b>”,其中<a>和<b>标签属于兄弟关系。拥有同一个父标签的子标签都属
于兄弟关系。标签包含属性时,形如“<meta charset="UTF-8">”,其中“<meta>”和charset属于属性包含关
系,charset是标签<meta>的属性。
动手写14.2.4

在14.2.4.html中,DOM的属性结构是DOCTYPE文档类型声明和顶层节点<html>标签。<html>标签
包含了所有网页元素,是整个网页DOM节点中唯一没有parentNode的节点。<html>标签与<head>、
<body>标签是父子节点关系,<head>标签和<body>标签是兄弟节点关系,<a>标签和属性href是属性包
含关系。
这种树形结构非常直观,方便开发者操作各个节点。

14.3 节点属性
节点属性是指元素节点具有的属性。属性也是一种节点类型,它的nodeType值为2,一个元素可以
具有多个属性。属性节点的名称可以是任意字符串,而它的值都会转化为字符串,结构如下:
元素<div>的id属性是字符串first-div,data-nth属性在原始文档中虽然是数字1,但转化成节点属性
时,会变成字符串1。
属性节点属于Node,继承了Node的属性和方法,但是因为属性没有父节点,所以属性不能作为元
素的子节点。

14.3.1 获取属性

使用tag.getAttribute(name)函数获取属性的值,使用tag.attributes获取全部属性列表,语法如下:

参数说明:
◇ tag:表示HTML元素(标签)。
◇ name:必选参数,表示节点属性名称,因为HTML不区分标签大小写,所以<div> 和 <DIV>都表
示同一种标签,tag.getAttribute(name)也受此影响,name参数不区分大小写;如果元素没有名为name的
属性,则返回null。
14.3.1.html使用tag.getAttribute()函数和tag.attributes获取节点属性,如下所示。
动手写14.3.1

执行14.3.1.html,输出结果到网页,如下图所示。
图14.3.1 获取节点属性

提示
若HTML代码中没有指定属性的值,则属性的值会被设置为空字符串。一旦拥有HTML的部分属
性,则它们只能有一个值,比如readonly、checked等属性,即使将它们设置为其他值,该设置也不会生
效。
14.3.2.html查看特殊属性readonly、checked等的值,如下所示。
动手写14.3.2

执行14.3.2.html,输出结果到网页,如下图所示。

图14.3.2 属性默认值

这类具有默认值的属性如readonly、checked等,一旦设置,不论其属性值是什么,都会生效。若要
让其失效,只能将属性删除。删除属性将在14.3.3小节中介绍。

14.3.2 设置属性

使用tag.setAttribute(name, value)设置元素的属性值,如果属性已经存在,则更新该值,不存在则使
用指定名称和属性值新建属性。语法如下:

参数说明:
◇ tag:表示HTML元素(标签)。
◇ name:必选参数,表示节点属性名称。
◇ value:必选参数,表示节点属性的值,如果value不是字符串,则会转换成字符串之后再设置成
属性的值。
调用tag.setAttribute(name, value)必须包含两个参数,否则会触发错误“Uncaught TypeError: Failed to
execute 'setAttribute' on 'Element': 2 arguments required, but only 1 present.”,这表示执行setAttribute()函数
失败,同时会停止程序执行。
动手写14.3.3

执行14.3.3.html,输出结果到网页,如下图所示。
图14.3.3 设置属性

提示
tag.setAttribute(name, value)除了可以设置标准属性,也可以设置其他任意名称的属性,常用于设置
一些自定义属性,在这些属性中存储特定数据。

14.3.3 删除属性

一般使用tag.removeAttribute()删除属性,无论属性是否存在。对于具有唯一值(默认值)的属性,
如readonly、checked等,不论如何设置属性的值,都不能让其失效,只能删除。
tag.removeAttribute()用法简单,只需要传递属性名即可,语法如下:

若属性名name不存在,该函数也可以正常执行,不会出现错误。
14.3.4.html按照顺序操作节点属性,先给<input>元素设置readonly属性,此时鼠标点击<input>元素
不会获得焦点;再删除<input>元素的readonly属性,此时鼠标点击<input>元素可以获得焦点,如下所
示。
动手写14.3.4

执行14.3.4.html,点击按钮删除输入框、单选框的属性,注意观察网页变化,如下图所示。
图14.3.4 删除属性

14.3.4 数据属性

HTML5标准允许给属性名增加“data-”前缀,然后将这类与网页渲染无关的属性统一归纳到数据属
性集合中。数据属性集合一个对象类型。使用数据属性既可以方便存放元素特有的数据,又可以避免使
用setAttribute()设置随意的属性名。比如在网页中展示大量图像时,为了避免网页一开始就下载全部图
像,会将<img src="图像地址">改为<img data-src="图像地址">,当网页滚动条滑动到某一个<img>标签
时,再将该标签的src属性设置为data-src的值,这样就可以减少下载不必要的图像,降低服务器带宽成
本。这个data-src数据属性可以暂存src属性的值。数据属性还可以存放元素不需要展示但需要参与操作
的属性值。
每个标签对应的DOM对象自动拥有dataset属性,dataset包含当前标签中全部使用“data-”作为前缀的
属性。dataset集合对象的键值没有“data-”前缀。
动手写14.3.5
执行14.3.5.html,依次点击两个书名,观察网页展示的提示文字,如下图所示。

图14.3.5 数据属性

提示
如果属性存在,使用tag.dataset.key和tag.getAttribute('data-key')这两种方式获取标签的属性值效果是
一样的。属性不存在时,前者等于undefined,后者返回null。

14.4 元素操作
DOM文档由节点组成,节点中最常见的是元素Element。所有系统内置的标签,比如a、b、div、
header、i、input、p、span、strong等,都是HTMLElement实例。HTMLElement继承自Element。HTML
文档的容错性很强,允许创建未知的标签。未知的标签是HTMLUnknownElement实例,虽然派生自
HTMLElement,但没有附加任何属性和方法。
HTML文档还包括其他继承自Element的元素,比如矢量图元素SVGElement。

14.4.1 访问元素
访问元素是指通过各种方式获取元素对象,比如通过函数document.getElementBy*系列函数、
document.querySelector*系列函数,以及父子、兄弟节点关系获取节点。
document.getElementBy*系列函数,全部通过document对象调用,比如document.getElement
ById("main"),如下所示:
表14.4.1 document.getElementBy*系列函数
document.querySelector*系列函数,也是全部通过document对象调用,比如document.query
Selector("#main"),如下所示:
表14.4.2 document.querySelector*系列函数

CSS选择器是指通过CSS样式规则匹配HTML元素的选择器,比如#id对应ID选择器、.main对应CSS
类选择器。如果读者对CSS选择器不是很熟悉,此处可暂不深入学习,本书22.2一节还会对CSS选择器
做详细介绍。
14.4.1.html使用get和query系列函数获取元素,如下所示。
动手写14.4.1
执行14.4.1.html,输出结果到网页,如下图所示。

图14.4.1 访问节点

14.4.2 遍历元素

当循环对元素的所有节点进行操作时,需要遍历元素。遍历元素可以从兄弟节点或后代节点中查询
满足条件的元素,此时需要使用children、firstChildElement、nextElementSibling等属性获取完整的节点
树。
动手写14.4.2
执行14.4.2.html,输出结果到网页,如下图所示。

图14.4.2 遍历元素

14.4.3 新建元素
使用document.createElement(tagName)创建元素,元素创建之后使用appendChild()方法将其添加到某
个标签,作为该标签的子元素,语法如下:
参数说明:
◇ document:表示DOM文档对象,一个网页对应一个document对象。
◇ tagName:必选参数,标签名称。
动手写14.4.3

执行14.4.3.html,输出结果到网页,如下图所示。

图14.4.3 使用document.createElement创建元素

DOM支持创建任意名称的标签,和普通标签一样可以正常使用。
动手写14.4.4

执行14.4.4.html,输出结果到网页,如下图所示。

图14.4.4 使用浏览器解析HTML字符串创建元素

使用浏览器解析HTML可以方便地创建DOM树形结构,实际应用中时常采用拼接HTML字符串来实
现局部修改。
动手写14.4.5
执行14.4.5.html,输出结果到网页,如下图所示。

图14.4.5 使用document.createElementNS创建矢量图元素

在HTML中可以引入其他XML格式的文档,也能够使用和DOM类似的方式操作节点,但必须有命
名空间(namespace),比如操作HTML中的SVG文档时,必须使用document.createElementNS()创建相关
节点。

14.4.4 插入元素

插入节点有两种方式:一是appendChild(),插入到子节点列表末尾;二是insertBefore(),插入到指
定节点之前。
1.appendChild()
使用node.appendChild(newChildNode)将新的子节点添加到子节点列表末尾,语法如下:

参数说明:
◇ parentNode:表示父节点。
◇ newChildNode:必选参数,表示新的子节点。新的子节点会被添加到parentNode的子节点列表末
尾。
动手写14.4.6
执行14.4.6.html,输出结果到网页,如下图所示。

图14.4.6 使用node.appendChild在末尾追加子节点

2.insertBefore()
使用node.insertBefore(newChildNode, referenceNode)将子节点插入到指定子节点之前,语法如下:

参数说明:
◇ parentNode:表示父节点。
◇ newChildNode:必选参数,表示新的子节点。
◇ referenceNode:可选参数,表示参照节点。参照节点必须是parentNode的直接子节点,如果提供
参照节点,则将newChildNode添加到该节点之前;如果没有参照节点,则将newChildNode添加到
parentNode的子节点列表末尾。
动手写14.4.7
执行14.4.7.html,输出结果到网页,如下图所示。

图14.4.7 使用node.insertBefore插入子节点

提示
使用appendChild()和insertBefore()方法添加子节点,如果newChildNode已经存在于DOM树形结构
中,则newChildNode会先从原始位置移除。
3.insertAfter()
DOM标准没有提供insertAfter()方法,但可以组合使用insertBefore()和nextSibling模拟实现。
动手写14.4.8
执行14.4.8.html,输出结果到网页,如下图所示。

图14.4.8 模拟实现insertAfter插入节点

如果refChild节点有下一个兄弟节点,则在该兄弟节点前插入新节点,否则在最后追加新节点。

14.4.5 查找元素
继承自Element的HTMLElement,拥有Element的query*系列方法。Element同时具有getElements-
ByClassName、getElementsByTagName方法,这些方法与14.4.1节介绍的document.getElementBy*、
document.querySelector*系列方法完全一样。
使用Element调用querySelector、querySelectorAll、getElementsByClassName、
getElementsByTagName,可以将查询范围限制在当前Element,方便将其他标签内具有的同类元素排
除。
动手写14.4.9

执行14.4.9.html,输出结果到网页,如下图所示。
图14.4.9 使用getElement和querySelector函数查找元素

document既是BOM对象,也是DOM对象,具有getElementById、getElementsByName、get
ElementsByTagName、getElementsByClassName、 querySelector和querySelectorAll 等六个方法;其他元
素具有getElementsByTagName、getElementsByClassName、querySelector和querySelectorAll 等四个方
法。

14.4.6 复制元素

使用node.insertBefore()添加子节点,如果子节点是其他元素的子节点,则会将其从原始位置移除;
为了保留原始节点,需要在对节点进行复制之后进行其他操作。
使用node.cloneNode()创建节点的副本,返回复制的节点,语法如下:

参数说明:
◇ node:表示要进行复制的节点。
◇ deepClone:可选参数。默认情况下,cloneNode()方法只复制节点本身,不复制它的后代节点,
但如果deepClone参数为true,则复制节点及其后代节点。
14.4.10.html使用cloneNode()连续复制多个节点,如下所示。
动手写14.4.10
执行14.4.10.html,输出结果到网页,如下图所示。

图14.4.10 使用node.cloneNode复制节点

使用node.cloneNode()浅度克隆节点li1获得空节点li2,深度克隆节点ul1获得节点ul2,ul2具有与ul1
同样的结构。

14.4.7 删除元素

使用node.removeChild(childNode)删除子节点,并返回删除的节点,语法如下:

参数说明:
◇ parentNode:表示父节点。
◇ childNode:必选参数,表示要删除的子节点,childNode必须是parentNode的直接子节点。
动手写14.4.11
执行14.4.11.html,输出结果到网页,如下图所示。

图14.4.11 使用node.removeChild删除节点

删除节点li1,li1从DOM树中移出,但并不会立刻消失,变量仍然引用这两个节点,将这两个节点
设置为null,删除的节点没有被引用之后才会消失。
提示
节点只能通过父节点删除,不能直接删除自身。

14.4.8 替换元素

使用node.replaceChild(newChildNode, oldChildNode)将旧的子节点oldChildNode替换为新的子节点
newChildNode,并返回被替换的子节点,语法如下:

参数说明:
◇ parentNode:表示父节点。
◇ newChildNode:必选参数,表示要加入的新节点。
◇ oldChildNode:必选参数,表示要被替换的子节点。oldChildNode必须是parentNode的直接子节
点。
14.4.12.html使用replaceChild将<b>标签替换为<i>标签,如下所示。
动手写14.4.12

执行14.4.12.html,输出结果到网页,如下图所示。

图14.4.12 使用node.replaceChild替换节点

提示
使用newChildNode替换oldChildNode时,如果newChildNode已经存在于DOM树结构中,则
newChildNode会先从原始位置移除。
被替换的子节点oldChildNode必须是节点的子节点,否则会触发错误“Uncaught DOMException:
Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node.”。

14.5 节点字符串
节点字符串主要有两种:innerHTML和innerText。innerHTML是浏览器解析HTML之后未转义的
HTML字符串。innerText是浏览器解析HTML之后转义并渲染在网页上的文字。
HTML文档通过文本编辑,字符串“<html>”中的大于号和小于号具有特殊含义,表示两个字符之间
是标签名。在网页上显示大于号和小于号需要使用对应的字符,比如 “&gt;”在网页中会显示为大于
号,“&lt;” 在网页中会显示为小于号。基本的转义字符如下表所示。
表14.5.1 HTML转义字符列表
(续上表)

14.5.1 文本节点

HTML中的文本也属于一种节点,即文本节点,nodeType为3。文本节点属于叶子节点,在文档树
最底层;文本节点不再有子节点。
使用document.createTextNode(text)创建文本节点,返回textNode,语法如下:

参数说明:
◇ document:表示DOM文档对象。
◇ text:必选参数,表示文本内容;如果text不是字符串,则将其转换为字符串。
文本节点是Node的一种类型, textNode的nodeValue属性就是文本内容。文本节点使用简单,一般
很少直接操作。
14.5.1.html创建文本节点,如下所示。
动手写14.5.1

执行14.5.1.html,输出结果到网页,如下图所示。

图14.5.1 使用document.createTextNode创建文本节点

14.5.2 插入文本节点
文本节点可以像标签一样被插入到指定位置。使用node.appendChild(textNode)、
node.insertBefore(textNode, oldNode)向文档插入文本节点。
动手写14.5.2

执行14.5.2.html,输出结果到网页,如下图所示。

图14.5.2 插入文本节点

文本节点作为独立节点,并不会与前后的文本节点自动合并。使用文本节点能够降低网页渲染消
耗。
元素子节点只有或只需要一个文本节点时,直接设置元素的innerText或textContent属性也能插入文
本节点。
动手写14.5.3
执行14.5.3.html,输出结果到网页,如下图所示。

图14.5.3 修改元素的innerText

提示
直接修改元素的innerText属性,即使只修改一个字符,也要替换元素的全部子节点。修改文本节点
的nodeValue同样可以实现文本内容修改。

14.5.3 节点HTML

元素的innerHTML表示元素所有的后代节点的HTML字符串。innerHTML属性最早由IE提出并成为
HTML规范的一部分。因为HTML不区分大小写,所以早期IE的innerHTML返回的innerHTML使用大写
表示标签名称。
动手写14.5.4

执行14.5.4.html,输出结果到网页,如下图所示。

图14.5.4 查看元素的innerHTML

innerHTML与innerText的区别是:
◇ innerText表示元素渲染之后的文本内容。
◇ innerHTML表示元素渲染之前以HTML语法表示的文本内容。
动手写14.5.5

执行14.5.5.html,输出结果到网页,如下图所示。

图14.5.5 innerHTML和innerText的区别

innerHTML会将独立的<、>、&等字符转换为对应编码&lt; 、&gt; 、&amp;。

14.5.4 插入HTML

修改元素的innerHTML属性和修改innerText一样,会替换元素的所有子节点。浏览器会重新根据
innerHTML属性解析并生成子节点和后代节点。
动手写14.5.6
执行14.5.6.html,输出结果到网页,如下图所示。

图14.5.6 插入innerHTML

修改innerHTML快捷地重新组织子节点内容时,代码比使用node的appendChild()、insertBefore()、
removeChild()等更清晰直观。
提示
innerHTML是Element定义的属性,而不是Node定义的属性,因此只有元素节点能修改
innerHTML。文本节点不能修改innerHTML,但可以修改textContent。

14.6 样式表
DOM2 Style规范(全称:文档对象样式规范)规定了样式访问和设置的方式。在网页编程中,经常
使用JavaScript控制元素的CSS样式属性。在第3版CSS(层叠样式表)规范发布之前,基本都使用
JavaScript实现CSS动画。

14.6.1 获取样式
元素的style属性是行内样式,浏览器解析该属性之后生成对应的style对象,访问该style对象可以获
得一部分样式。使用getComputedStyle(tag)获得元素完整的样式对象computedStyle,该对象包含了浏览
器解析并修正之后的值。
computedStyle在不同的浏览器中可能是不同对象的实例,但不影响使用。比如:在Chrome中,
computedStyle是CSSStyleDeclaration实例;在Firefox中,computedStyle是CSS2Properties实例。
使用getComputedStyle(tag)获取计算样式,语法如下:

参数说明:
◇ window:当前网页下的window对象,调用window对象的属性和方法时,可以省略window参数。
◇ tag:必选参数,表示目标元素。
函数getComputedStyle()的返回值computedStyle是一个对象结构的数据,包含了外部样式、内部样
式、行内样式、动画叠加生效之后的全部样式。
动手写14.6.1

执行14.6.1.html,输出结果到网页,如下图所示。
图14.6.1 获取tag.style

动手写14.6.2

执行14.6.2.html,输出结果到网页,如下图所示。

图14.6.2 获取computedStyle

计算样式会将样式值转换为合适的表示方式,比如统一单位、统一不识别的样式值。
使用CSS限制图像的最大高宽时,图像如果大于该限制,就会出现缩放,此时想要获得图像的真实
高宽需要先设置style,让最大高宽失效,再获取computedStyle,最后得到的才是图像的真实高宽。
动手写14.6.3
执行14.6.3.html,输出结果到网页,如下图所示。
图14.6.3 获取图像真实高宽

提示
HTML5定义了更为便捷的naturalHeight、naturalWidth属性,分别表示图像的真实高度和宽度,在图
像加载完成之后即可获得,不受图像样式影响。

14.6.2 设置样式

直接对tag.style的属性赋值,即可设置样式。style对象的key值是合法的标识符,在CSS中使用短横
线风格的样式名要转换成驼峰式标识符之后才能被识别。
动手写14.6.4

执行14.6.4.html,输出结果到网页,如下图所示。
图14.6.4 设置样式显示和隐藏元素

在卡片与轮播器之间切换时经常会修改元素可见性,或者将元素移到视窗之外。
样式值要求携带单位,比如像素使用的单位有pc、pt、px,或者百分比。若没有设置正确的单位,
则可能导致样式设置失败或得到意外的效果。
动手写14.6.5

图14.6.5 设置样式时指定单位

执行14.6.5.html,输出结果到网页,如下图所示。除了设置元素大小时需要指定单位,设置颜色时
也需要指定颜色格式,比如 rgb(x,x,x) 、rgb(x%, x%, x%)、#rrggbb 。
14.6.3 动画效果

异步调用方法如setTimeout()、setInterval()、requestAnimationFrame()与样式设置组合调用,可实现
网页动画效果。比如,在2秒钟的时间内,使用函数setTimeout以50毫秒的时间间隔连续更新标签的样
式,这就能形成一段2秒的动画。
动手写14.6.6
执行14.6.6.html,第二个div元素会逐步减小高度,直到消失在网页中,如下图所示。

图14.6.6 slideUp动画效果

每秒播放动画数量达到24帧及以上时,即动画更新时间在41毫秒以内,人眼不会有明显的滞涩感
(卡顿)。设置合适的动画频率,能在能耗和体验之间达到平衡。setTimeout()函数有最小响应时间,一
般情况下更新时间间隔设置为13毫秒比较合适。
提示
与setTimeout()相比,setInterval()函数设置的计时器会一直重复执行,如果没有及时调用
clearInterval()将计时器清除,会造成无效代码一直运行。因此建议更多地使用setTimeout()来设置计时
器,在本次计时器回调内判断是否需要再设置一次计时器,这样即使没有正确关闭计时器,最多也只重
复执行一次代码。
14.7 小结
本章详细介绍了DOM文档类型,节点的创建、更新、删除、替换、遍历等操作,以及相关的样式
设置和浏览器重绘等知识。网页编程涉及很多DOM操作,读者一定要熟练掌握DOM的相关知识。

14.8 知识拓展
浏览器渲染更新包括重绘和重排,重绘和重排的数量直接影响网页性能,比如同时进行大量动画必
然需要更高的性能开销,渲染更多的节点必然需要申请更多内存。CPU和内存占用超过限制时,可能会
让网页陷入假死。如果使用的是早期的浏览器且未对网页资源占用进行限制,还可能导致操作系统失去
响应。

14.8.1 浏览器重绘

浏览器重绘是指元素外观改变,导致浏览器重新计算元素位置、外观的几何结构,比如设置
visibility、width、height、background、color、border-radius等。这给人直观的感觉是,网页样式发生了
变化。
动手写14.8.1
执行14.8.1.html,输出结果到网页,如下图所示。

图14.8.1 浏览器重绘

第四个元素设置了样式visibility,且值为hidden,因此只有占位,但并不显示该元素。任何样式的
改变、文本更新都会触发浏览器重绘,浏览器重绘不一定改变文档树的布局。

14.8.2 浏览器重排

浏览器重排是指网页节点顺序进行了改变或者元素的布局样式发生变化,比如float、position进行了
变化,导致浏览器重新计算文档树,排列文档流。
动手写14.8.2
执行14.8.2.html,输出结果到网页,打开浏览器控制台,查看网页文档结构,如下图所示。

图14.8.2 浏览器重排

元素li1设置浮动之后,移出文档流,排到文档右侧;元素li3设置绝对定位之后,移出文档流,虽然
有显示但不占用后续文档空间,因此li4与li3在显示上出现重叠;元素li5被添加到ul元素子节点末尾。这
些操作都会触发重排,并且一定伴随重绘。
重绘和重排并没有说一方性能压力一定大于另一方,两者都具有高性能的消耗操作,减少操作才能
降低性能消耗。使用DocumentFragment组织节点,可以将多次重排操作合并成一次。使用GPU硬件加速
让浏览器的部分图像操作交由GPU处理,降低CPU压力。
第15章 事件处理
当用户使用鼠标点击提交表单时,执行表单验证程序,验证通过则提交数据到服务器,验证失败则
显示一段文字提示用户修改未通过的表单项。在这个过程中,鼠标点击是事件,验证程序是事件回调,
验证提示是事件反馈,控制这个流程完整运行就是JavaScript的事件处理,也称为事件驱动。JavaScript
以事件驱动的方式控制了图像化界面下的一切操作。本章将详细介绍JavaScript完整的事件处理机制。

15.1 事件介绍
事件指用户在操作网页过程中的行为,包括进入网页、点击鼠标、按下键盘、触摸屏幕、选择文
件、观看视频等一系列操作。发生事件之后,触发相应程序代码执行,完成一系列指令,称为事件驱动
(Event Driver);用户的行为,称为事件(Event);处理事件的函数,称为事件处理器(Event
Handler)。
除了用户行为之外,事件还包括网络状态更新等不受用户控制的行为。将用户的操作封装成一个事
件对象,再调用相应的函数处理,能使图形化编程更加容易。

15.1.1 什么是事件

事件是一个可以被响应的动作,比如用户点击鼠标是为了聚焦输入框,按下键盘是为了输入手机
号,触摸屏幕是为了打开新网页等。
JavaScript的常见事件包括页面事件、鼠标事件、键盘事件、表单事件、触摸事件,还有与CSS3相
关的动画事件。常见事件如下表所示。
表15.1.1 JavaScript常见事件
动手写15.1.1
执行15.1.1.html,输出结果到网页,如下图所示。

图15.1.1 鼠标点击事件

动手写15.1.2

执行15.1.2.html,输出结果到网页,如下图所示。
图15.1.2 键盘按下事件

15.1.2 事件冒泡

事件冒泡是事件的向上传递方式,指事件从事件发生的元素开始,按照文档树逐级向上传播到最外
层的节点,接着传递到document,最后传递到window。整个行为类似于气泡从水中冒出,因此称为事件
冒泡。
假设文档结构如下:

那么在<a>标签上点击时,事件冒泡就是点击事件依次经过<a>、<div>、<body>、<html>,接着传
递到document,最后传递到window。
想要在事件发生时执行事件回调,需要使用EventTarget.addEventListener()向目标元素添加事件监听
器,事件监听器也就是事件回调,语法如下:

参数说明:
◇ target:表示目标对象,比如鼠标点击同意按钮时勾选选项框,那么这个同意按钮就是目标对
象。目标对象可以是标签、文档对象(document)、window,以及其他支持添加事件监听器的对象。
◇ type:必选参数,表示事件类型,比如点击(click)、按键(keydown)。
◇ listener:必选参数,表示事件监听器。监听器是一个可执行函数,当在目标对象上发生指定类型
的事件时,执行这个listener函数。
◇ capture:可选参数,表示在事件捕获还是在冒泡阶段执行listener函数,默认为false,也就是在冒
泡阶段执行。
事件捕获也是事件传播机制,与事件冒泡传播方向完全相反,相关内容将在下一节介绍。
如果在程序执行一段时间后,不想继续在目标对象上监听该事件,就需要使用
EeventTarget.removeEventListener()移除事件监听器,语法如下:

使用removeEventListener()移除事件监听器时,必须与addEventListener()函数执行时的参数一致,不
能缺少任何一个。
提示
使用removeEventListener()移除没有添加过的事件监听器不会触发错误,因此不必担心。
removeEventListener()可以重复调用,确保事件监听器被移除。
15.1.3.html查看事件冒泡示例,如下所示。
动手写15.1.3
执行15.1.3.html,输出结果到网页,如下图所示。

图15.1.3 事件冒泡

依次对document、<body>、<div>、<button>添加click事件监听器,点击按钮之后依次触发
buttonListener、divListener、bodyListener、documentListener。尽管divListener先于buttonListener设置监
听器,但在冒泡阶段divListener会在buttonListener之后触发。

15.1.3 事件捕获

事件捕获也是一种传递方式,但与事件冒泡正好相反。事件捕获由window传递给document,再按照
文档树逐级向下传递到具体发生事件的元素。事件捕获的目的是在事件到达目标对象之前捕获它,捕获
之后可以选择停止传递,也可以让其正常传递。
动手写15.1.4
执行15.1.4.html,输出结果到网页,如下图所示。

图15.1.4 事件捕获

提示
事件捕获和事件冒泡一样,都不受到事件监听器添加顺序的影响。事件捕获按照DOM树依次向下
到达事件触发源。

15.2 事件属性
事件发生时浏览器会创建一个事件对象Event实例,在各个事件监听器之间传递。事件对象是引用
型数据,修改事件的属性会影响之后的事件监听器。在IE浏览器中,event是一个全局对象;在其他浏览
器中,event是临时变量。

15.2.1 基本属性
事件有很多属性,不同事件的属性差异较大,比如鼠标事件没有键盘相关信息、触摸事件与接触设
备有关、键盘事件支持多个按键。各种事件的基本属性如下表所示。
表15.2.1 事件的基本属性

(续上表)

动手写15.2.1

执行15.2.1.html,输出结果到网页,如下图所示。
图15.2.1 事件基本属性

15.2.2 鼠标事件属性
鼠标事件属性包括鼠标按键和鼠标位置,均与鼠标有关,如下表所示。
表15.2.2 鼠标事件属性

修改clientX、clientY、screenX、screenY等属性不会修改鼠标的真实位置。事件的坐标系均采用第
四象限,X轴以向右为正值,Y轴以向下为正值。
动手写15.2.2
执行15.2.2.html,输出结果到网页,如下两图所示。

图15.2.2 鼠标事件mouseenter属性
图15.2.3 鼠标事件click属性

鼠标事件在两个节点上产生交互是relatedTarget,它是除了target之外的另外一个元素。如果仅在一
个元素上产生交互,则relateTarget为null。

15.2.3 键盘事件属性

键盘事件属性包括键盘按键和特定按键信息,均与键盘有关,如下表所示。
表15.2.3 键盘事件属性

(续上表)

注意event.key可以区分大小写,event.code不能区分大小写。按下Shift+a时,若大写键盘锁定未开
启,则event.key是A,event.code仍然是keyA。目前在编写事件处理回调时仍然可以使用event.which,因
为这个属性在各个浏览器中代表的含义都一致。
提示
键盘事件属性可以和鼠标事件属性同时存在,并不冲突,比如按住Ctrl键时点击网页超链接,会在
新窗口打开网页。
动手写15.2.3

执行15.2.3.html,输出结果到网页,如图15.2.4、图15.2.5和图15.2.6所示。

图15.2.4 键盘事件属性①

图15.2.5 键盘事件属性②
图15.2.6 键盘事件属性③

虽然event.which不是标准属性,但由于鼠标事件对象没有key和keyCode属性,而which在键盘事件
和鼠标事件中都存在,因此目前仍有较多使用event.which的案例。

15.3 事件方法
事件一般作为只读属性在各个监听器之间传递。事件方法较少,主要有控制冒泡和组织默认行为,
还有一般不需要主动调用的事件初始化方法。

15.3.1 停止冒泡

事件冒泡是指事件由子元素向父元素传递,如果不希望事件被父元素处理,则可以使用
event.stopPropagation()阻止事件继续传递。该函数不接收参数,直接调用即可。
15.3.1.html分别为ul、li元素设置点击事件监听器,并在第二个li元素的click事件中停止冒泡,如下
所示。
动手写15.3.1
执行15.3.1.html,输出结果到网页,如下图所示。

图15.3.1 停止事件冒泡

在动手写15.3.1中点击元素li1,事件冒泡传递给ul;点击元素li2时,因为在li2的事件监听器中调用
event.stopPropagation()阻止冒泡,所以无论点击多少次li2,都不会将事件传递给ul。
提示
在事件捕获阶段调用event.stopPropagation()也可以阻止事件向事件源传播。一旦在该阶段阻止传
播,为事件源设置的监听器就不会执行。
动手写15.3.2
执行15.3.2.html,输出结果到网页,如下图所示。

图15.3.2 停止事件捕获

在ul的事件回调中检测到事件触发者是li2元素时,调用event.stopPropagation()停止事件捕获,阻止
事件进一步向下传递,因此点击ul并不会触发li2元素的click事件回调。

15.3.2 停止同级冒泡
向同一个元素添加多个事件监听器,事件发生时,会按照添加顺序依次调用多个监听器。使用
event.stopImmediatePropagation()阻止调用该元素后续的事件监听器,该函数不接收参数,直接调用即
可。
动手写15.3.3
执行15.3.3.html,输出结果到网页,如下图所示。

图15.3.3 停止事件同级冒泡

为元素li1和li2依次添加点击事件监听器,在clickListener1中检测到事件触发者是li2时,调用
event.stopImmediatePropagation()停止同级冒泡,在li2元素上添加的位于clickListener1之后的其他事件监
听器则不会执行。

15.3.3 阻止默认操作

使用event.preventDefault()阻止事件默认行为,比如阻止按键Ctrl+C复制网页文本、阻止打开新的标
签页。该函数不接收参数,直接调用即可。
动手写15.3.4
执行15.3.4.html,输出结果到网页,如下图所示。

图15.3.4 阻止默认操作

在剪贴板复制文字“event.defaultPrevented”,然后在输入框中选中文字并按下Ctrl+C组合键,再次到
控制台按下Ctrl+V,粘贴的字符串仍然是“event.defaultPrevented”,说明没有复制成功。
提示
event.preventDefault()方法有效的前提是该事件的默认行为能被阻止,一旦成功阻止,
event.defaultPrevented就会变成true。

15.4 页面事件
页面事件包括网页加载、浏览、缩放、离开等行为触发的事件。网页缩放事件在13.6.6一节做过介
绍,本节介绍与页面操作相关的其他事件。

15.4.1 网页加载

网页加载包括网页HTML文件、网页引用的图像、外部CSS、外部JavaScript等资源文件的加载。
window.onload事件在这些资源加载完成并执行之后触发。
动手写15.4.1
执行15.4.1.html,输出结果到网页,如下图所示。

图15.4.1 页面加载事件window.onload

代码15.4.1.js在控制台输出一条Hello信息,先于load事件之前执行。
HTML文档对象document和<body>标签同样具有load事件,body.onload事件和window.onload事件等
价,并且使用addEventListener()为document、body添加load事件监听器都不会生效。
动手写15.4.2
执行15.4.2.html,输出结果到网页,如下图所示。

图15.4.2 与window.onload等价的body.onload

大部分情况下,只需要等待浏览器将<html>标签解析完成并生成完整的DOM树就可以对网页元素
进行处理,不需要等待图像资源、框架资源等加载完成。在HTML文档生成完整的DOM树之后触发事件
DOMContentLoaded,之后再对DOM树进行修改不会重复触发DOMContentLoaded。
动手写15.4.3
执行15.4.3.html,输出结果到网页,如下图所示。

图15.4.3 页面加载事件DOMContentLoaded

文档对象document的DOMContentLoaded在所有JavaScript脚本加载完成和DOM树第一次生成完成时
触发,不需要等待网页中的其他多媒体资源加载完成,明显早于window.onload事件,因此一般使用
DOMContentLoaded替代load事件,加速代码执行。

15.4.2 资源加载

除了网页加载完成会触发事件load,其他资源加载完成也会触发load事件,比如图像、脚本、框
架。
动手写15.4.4

执行15.4.4.html,输出结果到网页,如下图所示。

图15.4.4 图像和脚本加载事件

代码15.4.4-2.js不存在,触发error事件;15.4.4-1.js和15.4.4.png都加载成功,触发load事件。

15.4.3 网页滑动
网页滑动事件是指使用鼠标或光标滑动网页。滑动事件并不是按照1像素滑动的,而是按照浏览器
设置的默认值滑动,比如一次滑动100像素。
动手写15.4.5
执行15.4.5.html,输出结果到网页,如下图所示。

图15.4.5 网页滑动事件

滑动一次鼠标会多次触发window的scroll事件。按照每滑动6个像素响应一次scroll事件,滑动一次鼠
标会触发17次scroll事件。在X轴滑动鼠标也会触发scroll事件。网页默认将滚动条显示在<html>标签上,
因此在一般情况下,window.scrollY与document.documentElement.scrollTop等价。
鼠标的滑动事件mousewheel除了用于网页滑动,也可以用于控制日期选择器、Slider幻灯片滑动。
mousewheel并不属于标准事件,常见浏览器仅有Chrome、Edge、IE支持,在Firefox下需要使用
DOMMouseScroll代替。
动手写15.4.6
执行15.4.6.html,输出结果到网页,如下两图所示。

图15.4.6 鼠标滑动事件(Chrome)

图15.4.7 鼠标滑动事件(Firefox)

目前浏览器没有统一鼠标滑动事件,需要对两个事件兼容时,一般直接添加两个事件。这两个事件
还有一个明显的区别:鼠标向下滑动时,mousewheel事件的wheelDelta属性为负值,DOMMouseScroll事
件的detail属性为正值。

15.4.4 网页卸载
网页卸载是指与load相反的事件,即beforeunload和unload。unload事件是指网页刷新、跳转、关闭
等行为触发浏览器将当前网页卸载。beforeunload事件发生在卸载之前。
动手写15.4.7
执行15.4.7.html,输出结果到网页,如下图所示。

图15.4.8 网页卸载事件

发生unload事件时,网页的所有资源会立刻释放,向服务器发送请求几乎都会失败,此时只能完成
一些可以立刻完成的操作,比如缓存资源、清理Cookie等。
在unload事件发生前,会触发beforeunload事件。如果在beforeunload事件中,event.returnValue不为
空,是非空字符串,那么浏览器会显示一段提醒文字,即event.returnValue的值,用户可以选择继续关闭
网页还是留在当前页面。beforeunload常用于提醒用户数据未完成保存,需要继续确认是否保存。
动手写15.4.8

执行15.4.8.html,输出结果到网页,如下图所示。

图15.4.9 beforeunload事件(Chrome)

设置了beforeunload事件的页面,浏览器会提醒数据可能不会被缓存。部分广告网站为了防止
用户关闭网页,故意展示一段夸大其词的文字,如下图所示。
图15.4.10 beforeunload事件(IE)

为了避免网站显示迷惑性的文字,吓唬用户,部分浏览器全部使用统一的文字描述,比如图15.4.9
提示“系统可能不会保存您所做的更改”。

15.4.5 标签事件

document对象具有hidden、visibilityState属性,标识当前文档的显示状态。
◇ 标签页隐藏时,document.hidden为true,document.visibilityState为hidden。
◇ 标签页显示时,document.hidden为false,document.visibilityState为visible。
标签页显示状态改变时触发事件visibilitychange,此时根据文档显示状态执行相应操作,比如暂停
视频播放。
动手写15.4.9

执行15.4.9.html,输出结果到网页,如下图所示。

图15.4.11 标签显示与隐藏事件
普通HTMLElement元素也具有hidden属性,设置hidden属性等价于设置style.display为hidden。
动手写15.4.10

执行15.4.10.html,输出结果到网页,如下图所示。

图15.4.12 元素hidden属性

设置元素hidden属性,实际上是应用浏览器的默认样式 [hidden]{display:none} 。对于希望在初始化


前隐藏、初始化之后显示的元素的非常使用,只需在初始化脚本执行之后将元素的hidden属性移除即
可。

15.5 键盘事件
键盘事件包括keydown、keypress、keyup,分别表示键盘按下、按住和弹起。按住事件是指长时间
按某个键时,连续触发keypress事件。监控键盘事件,一般用于设置自定义的快捷键。
动手写15.5.1

执行15.5.1.html,输出结果到网页,如下图所示。
图15.5.1 键盘keydown事件

动手写15.5.2

执行15.5.2.html,输出结果到网页,如下图所示。

图15.5.2 键盘keypress事件

动手写15.5.3
执行15.5.3.html,输出结果到网页,如下图所示。

图15.5.3 键盘keyup事件

键盘事件触发顺序:按下(keydown)→按住(keypress)→弹起(keyup)。键盘长按事件触发顺
序:按下(keydown)→按住(keypress)→按下(keydown)→按住(keypress)…→弹起(keyup)。
键盘长按直到键盘弹起。
动手写15.5.4
执行15.5.4.html,输出结果到网页,如下图所示。

图15.5.4 键盘长按事件

15.6 鼠标事件
鼠标事件和键盘事件都是网页开发中常见的事件,但鼠标事件的使用多于键盘事件。鼠标事件包括
鼠标按下、移动、弹起、单击、双击和滑动。双击事件dblclick表示双击鼠标主键(一般为左键),其
可访问性较差,在实际开发中应该尽量避免使用双击事件。鼠标滑动事件已在15.4.3 网页滑动一节做了
详细介绍。

15.6.1 鼠标点击
按下鼠标任意按钮,触发事件mousedown。
动手写15.6.1

执行15.6.1.html,输出结果到网页,如下图所示。
图15.6.1 鼠标mousedown事件

松开鼠标任意按钮,触发事件mouseup。DOM不支持mousepress事件。
动手写15.6.2

执行15.6.2.html,输出结果到网页,如下图所示。

图15.6.2 鼠标mouseup事件

点击鼠标(按下并弹起)主键触发事件click。click和dblclick事件都仅限于点击鼠标主键(一般为左
键),点击右键不会触发click事件。当元素获取焦点时,按下键盘上的回车键,同样会在这个元素上触
发click事件。
动手写15.6.3

执行15.6.3.html,输出结果到网页,如下图所示。

图15.6.3 鼠标click事件

双击事件dblclick对于手指操作不灵敏的用户不够友好,因为操作难度太大。对于普通用户来说,
由于需要猜测哪个按钮双击会有不同效果,因此不建议使用双击事件。
动手写15.6.4

执行15.6.4.html,输出结果到网页,如下图所示。

图15.6.4 鼠标dblclick事件

触发dblclick事件前会触发两次click事件,容易与普通操作产生冲突。

15.6.2 鼠标移动

鼠标移入元素时,触发事件mouseenter。
动手写15.6.5

执行15.6.5.html,输出结果到网页,如下图所示。
图15.6.5 鼠标mouseenter事件

鼠标从文档内部移入目标元素内部时,event.relatedTarget是鼠标移入元素前最后接触鼠标的元素。
从浏览器边界直接移入元素内部时,event.relatedTarget为null。
鼠标移出元素时,触发事件mouseleave。
动手写15.6.6

执行15.6.6.html,输出结果到网页,如下图所示。
图15.6.6 鼠标mouseleave事件

鼠标从目标元素内部移到文档其他元素时,event.relatedTarget是最新接触鼠标的元素。直接移到浏
览器边界时,event.relatedTarget为null。
鼠标在元素内进行移动时,触发事件mousemove。
动手写15.6.7

执行15.6.7.html,输出结果到网页,如下图所示。

图15.6.7 鼠标mousemove事件

15.6.3 拖拽元素

组合使用mousedown、mousemove、mouseup实现元素拖动。在鼠标按下时记录元素初始位置,在
鼠标移动期间根据移动相对位置修改元素定位位置,在鼠标弹起时放下元素。
动手写15.6.8
执行15.6.8.html,输出结果到网页,如下图所示。

图15.6.8 使用鼠标拖拽元素

鼠标按下时设置mousemove、mouseup事件监听器,鼠标弹起时移除这两个事件监听器。
15.7 触摸事件
触摸事件是指与触摸相关的事件,包括touchstart、touchmove、touchend、touchcancel。手机上的触
摸事件可以模拟鼠标事件。触摸事件语句具有与鼠标事件类似的属性;触摸事件特有的属性如下表所
示。
表15.7.1 触摸事件特有属性列表

(续上表)

Touch对象具有与鼠标事件对象类似的属性,触摸对象属性如下表所示。
表15.7.2 触摸对象属性列表

15.7.1 触摸开始
使用手指或手写笔触摸屏幕,触发事件touchstart。
动手写15.7.1
执行15.7.1.html,输出结果到网页,如下图所示。

图15.7.1 触摸touchstart事件

目前大部分手机和平板设备都支持多点触摸,在触摸过程中计算两个触摸点的距离可以实现图像放
大等操作。

15.7.2 触摸移动

触摸开始之后用手指按住,继续在屏幕上移动,触发事件touchmove。
动手写15.7.2
执行15.7.2.html,输出结果到网页,如下图所示。

图15.7.2 触摸touchmove事件

15.7.3 触摸结束

触摸点离开接触面时,触发touchend事件。
动手写15.7.3
执行15.7.3.html,输出结果到网页,如下图所示。

图15.7.3 触摸touchend事件

15.7.4 触摸取消

当触摸事件被外部(操作系统)强制取消时,触发touchcancel,比如来电响铃、闹钟响铃、触摸点
太多超出限制等。
动手写15.7.4
执行15.7.4.html,输出结果到网页,如下图所示。

图15.7.4 触摸touchcancel事件

使用touch事件移动元素,操作与使用鼠标拖拽元素类似,但需要在touchend和touchcancel事件触发
时取消touchmove事件监听器。

15.8 事件模拟
浏览器允许通过脚本创建并触发事件。

15.8.1 创建事件
使用document.createEvent(type)创建事件,新建的事件对象不能直接使用,必须先调用
event.initEvent()初始化。参数type表示事件模块名,是统称,而不是具体的事件类型。
使用event.initEvent(type, bubbles, cancelable)初始化事件。
◇ type表示具体的事件类型,比如click、keydown。
◇ bubbles表示事件是否冒泡。
◇ cancelable表示事件默认动作是否允许取消。
不同分类的事件,需要调用不同的初始化方法。
动手写15.8.1

执行15.8.1.html,输出结果到网页,如下图所示。

图15.8.1 创建事件

推荐使用对应事件的构造函数创建事件,比如使用键盘事件构造函数KeyboardEvent、使用鼠标事
件构造函数MouseEvent、使用UI事件构造函数UIEvent。

15.8.2 触发事件

使用eventTarget.dispatchEvent(event)在指定元素eventTarget触发事件event。通过JavaScript脚本创建
并触发的事件不受浏览器信任,其操作会受到一定限制。
动手写15.8.2

执行15.8.2.html,输出结果到网页,如下图所示。

图15.8.2 触发事件

通过JavaScript模拟触发的事件不受浏览器的信任,因此event.isTrusted值为false;用户操作原生触发
的事件,event.isTrusted为true。

15.9 小结
本章详细介绍了DOMEvent事件模型、事件传播机制、事件属性和方法,以及常见的鼠标、键盘、
页面、触摸事件,最后介绍了使用JavaScript模拟事件。事件是网页编程中与用户进行交互的核心,读者
一定要达到熟练掌握甚至是精通的程度。

15.10 知识拓展
15.10.1 事件委托
事件委托是事件处理元素的转移,简单地说就是通过父元素监听子元素的事件,子元素不需要设置
事件监听器,在子元素上发生的事件会通过事件冒泡向上传递给父元素。
动手写15.10.1
执行15.10.1.html,输出结果到网页,如下图所示。

图15.10.1 事件传递给父元素

在父元素接收到事件时,根据event.target判断事件是由哪个子元素触发的。在动手写15.10.1的
itemClick事件回调中,通过event.target判断事件触发的子元素,而不需要为两个标题元素都添加事件监
听器。
事件委托能够大幅减少重复的事件监听器。比如,列表<ul>有10000个<li>标签,如果为每个<li>标
签设置监听器,则需要设置10000次,但是只向父元素<ul>添加事件监听器则只需要1次。
动手写15.10.2
执行15.10.2.html,输出结果到网页,如下图所示。

图15.10.2 在父元素监听子元素事件

在动手写15.10.2中,给<ul>标签添加一个click事件监听器,即可监听全部子元素click事件。
事件委托能够监听未来的子元素。向父元素设置事件监听器之后,在以后不确定的时间新增的子元
素触发事件也会传播到父元素,同样只需要根据event.target就能判断来自哪个子元素。
动手写15.10.3
执行15.10.3.html,输出结果到网页,如下图所示。

图15.10.3 监听未来元素的事件

点击按钮新增一个元素<li>,<li>元素没有设置任何事件监听器,但是通过事件委托,<li>的父元素
<ul>可以监听任何时间点发生在<li>元素内的点击事件。

15.10.2 自定义事件

自定义事件是指事件名称不符合标准,由程序控制触发的事件。使用模拟实现鼠标长按事件,说明
如何执行自定义事件。
动手写15.10.4
执行15.10.4.html,输出结果到网页,如下图所示。

图15.10.4 鼠标长按事件

提示
浏览器不能触发自定义事件,因此需要依赖脚本在运行中触发自定义事件。初始化鼠标事件时,只
有将eventProxy.initMouseEvent('mousepress', true, true)的第二个参数canBubble设置为true,才能冒泡传递
到document。
第16章 表单对象
表单对象是接收用户向网页发送数据的载体。使用表单对象可以让用户输入文本、选择文件、进行
语音录制,最后将这些数据传输给服务器。本章将详细介绍表单对象和表元素的使用方法。

16.1 表单元素
表单<form>标签经常用于组织各种表单控件,包括输入框、单选框、复选框、文本框等表单标签,
接收用户输入数据,并提交至服务器。

16.1.1 表单对象

每个<form>标签就是一个表单对象。document.forms是网页中实时存在的表单<form>标签的集合。
<form>标签的name属性直接作为document的一个属性,通过document.formname可以直接访问该表单。
16.1.1.html通过document.forms属性访问表单对象,如下所示。
动手写16.1.1

执行16.1.1.html,输出结果到网页,如下图所示。

图16.1.1 访问表单对象

提示
<form>标签的name属性与document的属性同名时,会覆盖document原有的属性,因此设置表单名称
时务必要含义清晰且避开document原有的属性。
表单对象HTMLFormElement继承自HTMLElement,其特有的属性如下表所示。
表16.1.1 表单属性列表

动手写16.1.2

执行16.1.2.html,输出结果到网页,如下图所示。
图16.1.2 表单属性

表单对象具有两个方法,这两个方法不接收参数,可直接调用:
◇ form.reset(),重置所有表单控件为初始值或默认值。
◇ form.submit(),提交表单对象。
动手写16.1.3

执行16.1.3.html,输出结果到网页,如图16.1.3、图16.1.4和图16.1.5所示。

图16.1.3 修改表单值

图16.1.4 重置表单

图16.1.5 提交表单

图16.1.3显示鼠标焦点位于多行文本输入框,正在修改表单控件的值。图16.1.4显示鼠标焦点位于重
置表单按钮,点击重置按钮后,表单控件所有值被重置为初始值。图16.1.5显示提交表单到当前网址。
表单对象具有特定的提交事件submit,发生在表单提交之前。如果在submit事件中阻止事件默认行
为,则表单不会提交。
动手写16.1.4

执行16.1.4.html,输出结果到网页,如下图所示。

图16.1.6 阻止表单提交

提示
如果表单的submit事件回调返回false,那么点击表单的提交按钮就无法提交表单。

16.1.2 控件列表

表单有很多控件,包括表单按钮、文本输入框、分组、标签、下拉框等;各式各样的表单元素可以
在不同场景下接收用户传递的数据。控件列表如下所示。
表16.1.2 表单控件列表
动手写16.1.5

执行16.1.5.html,输出结果到网页,如下图所示。
图16.1.7 表单控件

通过form.elements可以直接访问表单的控件,也可以通过具名控件的名称进行访问。在动手写
16.1.5中,通过form.elements.input直接访问表单的<input>元素。
提示
表单控件可以单独使用,不必存在于<form>标签中,比如将其用作视频的播放/暂停按钮,此时不
需要将表单内容提交到服务器。
表单控件<input>是特殊的表单控件,可以支持多种输入类型;设置<input>标签的type属性,可以渲
染不同的输入框。<input>标签的type属性如下表所示。
表16.1.3 <input>标签的type属性列表

(续上表)
动手写16.1.6

执行16.1.6.html,输出结果到网页,如下图所示。

图16.1.8 时间、范围、日期控件

<input>标签支持很多类型,比如color、number、password类型的输入框。读者可以编写程序,使用
浏览器查看实际效果。

16.1.3 控件属性

表单控件多种多样,开发者可以根据控件类型将控件的属性分为基本属性和特有属性,通过修改这
些属性控制表单行为,实现更好的交互行为。
1.基本属性
基本属性是指表单控件都具有的属性,如下表所示。
表16.1.4 表单控件的基本属性

2.按钮控件属性
按钮控件<button>属性如下表所示。
表16.1.5 按钮控件属性

3.下拉控件属性
下拉控件<select>属性如下表所示。
表16.1.6 下拉控件属性

(续上表)

4.下拉选项属性
下拉选项<option>属性如下表所示。
表16.1.7 下拉列表选项属性
5.下拉分组属性
下拉分组<optgroup>仅对选项分组,不支持选中,属性如下表所示。
表16.1.8 下拉列表分组属性

6.<input>属性
输入框<input>和<button>都拥有formaction、formenctype、formmethod等属性。<input>会根据type设
置为不同的输入控件,不同控件的属性含义一致,但不一定会完全使用到。<input>的其他属性如下表所
示。
表16.1.9 输入控件属性

(续上表)
7.<textarea>属性
文本控件<textarea>和输入控件<input>都拥有maxlength、minlength、placeholder、readonly、
required、spellcheck,其他属性如下表所示。
表16.1.10 文本控件的其他属性

16.2 表单事件
表单和控件特有的事件包括获取焦点、失去焦点、内容修改、表单提交。

16.2.1 获取焦点

表单控件有两种方式获得焦点:鼠标点击和使用Tab键依次聚焦到下一个输入控件。控件获得焦点
会触发两个事件:focus和focusin。两个事件的区别是:focus事件只在获得焦点的元素时触发,而且不
会将事件冒泡到父元素,而focusin会像普通事件传播一样,冒泡到父元素。
动手写16.2.1
执行16.2.1.html,输出结果到网页,如下图所示。

图16.2.1 获取焦点事件

输入框<input>获得焦点时,先触发focus事件,focus事件不会冒泡;再触发focusin事件,focusin事
件冒泡到body。如果以事件捕获形式给body添加focus事件监听器,则可以在事件捕获阶段
(Event.CAPTURING_PHASE)获取focus事件。
设置element.onfocus属性为其增加focus事件监听器,调用element.focus()方法让元素主动获得焦点。
动手写16.2.2
执行16.2.2.html,输出结果到网页,如下图所示。

图16.2.2 聚焦输入框

提示
使用input.focus()获取焦点之后,依次触发focus、focusin事件,focus事件不会向<input>标签的父元
素冒泡,focusin事件会以冒泡形式传播给<input>标签的父元素、祖先元素。

16.2.2 失去焦点

控件失去焦点会触发两个事件:blur和focusout。blur事件不支持冒泡,而focusout事件支持冒泡。
动手写16.2.3
执行16.2.3.html,输出结果到网页,如下图所示。

图16.2.3 失去焦点事件

网页初始加载完成时,<input>元素自动获得焦点;鼠标点击其他位置时,依次触发blur、focusout
事件,blur事件不进行冒泡传递,focusout事件冒泡传递到<body>。
开发手机端网页时,调用input元素的focus()方法让控件获得焦点,此时手机的虚拟键盘会自动打
开;调用blur()方法让控件失去焦点,手机的虚拟键盘会自动收起。

16.2.3 内容修改

如果控件失去焦点之后的值与获得焦点前的值不一致,则在控件失去焦点时触发该控件的change事
件。
动手写16.2.4
执行16.2.4.html,输出结果到网页,如下图所示。

图16.2.4 内容修改事件

<input>元素失去焦点前会检查获取焦点时输入框的值与当前值是否一致,若不一致则触发change事
件,然后触发blur事件,最后触发focusout事件。

16.2.4 提交事件

点击表单提交按钮(<button type="submit">、<input type="submit">),触发表单submit事件。


动手写16.2.5
执行16.2.5.html,输出结果到网页,如下图所示。

图16.2.5 表单提交事件

在动手写16.2.5中,点击按钮1和按钮2都能触发表单提交事件,在onsubmit回调中返回false,可以阻
止表单提交,在addEventListener()添加的事件回调中需要使用event.preventDefault()阻止表单提交;点击
按钮3,调用form.submit(),直接提交表单,不触发submit事件,这点与focus和blur事件不同。

16.3 表单提交
表单提交会自动将表单内的可用元素(非disabled)序列化成参数字符串,并根据method属性选择
对应的HTTP请求方式将数据提交到服务器。

16.3.1 使用GET提交
设置<form method="get">将以GET方式提交表单数据,method的值不区分大小写。
动手写16.3.1
执行16.3.1.html,输出结果到网页,如下图所示。

图16.3.1 使用GET方式提交表单

16.3.2 使用POST提交

设置<form method="POST">将以POST方式提交表单数据。
动手写16.3.2

执行16.3.2.html,输出结果到网页,如下图所示。

图16.3.2 使用POST方式提交表单

16.3.3 文件上传

如果表单包含文件选择框,则需要明确设置enctype属性为multipart/form-data,表明提交到服务器的
数据中包含文件内容,服务器根据boundary识别出普通字符串和文件内容。
动手写16.3.3
执行16.3.3.html,输出结果到网页,如下图所示。

图16.3.3 使用表单上传文件

提示
Content-Type是指数据的内容类型,服务器和浏览器分别根据该值对数据进行解析和展示,比如
JavaScript源代码文件的Content-Type是text/javascript,图像的Content-Type是image/jpeg、image/png等。
向服务器提交数据时,如果没有正确设置Content-Type,极有可能导致服务器不能正确解析数据。

16.4 小结
本章详细介绍了网页表单和表单控件,以及表单事件、表单提交、表单文件上传等。表单控件在网
页编程中比较常见,除了按钮外,大部分数据交互需要依赖表单控件与用户交互完成,希望读者能熟练
掌握表单对象的相关操作。

16.5 知识拓展
16.5.1 表单序列化

表单序列化是指将表单中的具名有效控件(非disabled)按照key=value形式组织成URL查询参数。
表单中具有文件选择框<input type="file">时,序列化需要读取文件内容并设置boundary。它的格式较为
复杂,本节将展示表单字符串序列化和数组序列化过程。
16.5.1.html对表单进行序列化,如下所示。
动手写16.5.1
序列化部分代码,如下所示。
执行16.5.1.html,输出结果到网页,如下图所示。
图16.5.1 表单序列化

服务器端自动将label参数解析为数组,其他参数解析为字符串。

16.5.2 无刷新提交

浏览器加载网页完成时会触发load事件,表示网页加载完成;相应地,在当前窗口刷新网页、加载
新网页、关闭网页,都会触发与load对应的unload事件。网页发生unload事件时,直到新网页载入之前,
用户都不能操作网页元素。
在当前网页进行表单提交也会触发unload事件。为了让用户在提交表单时也能继续操作网页,可将
表单提交到隐藏的iframe窗口。
动手写16.5.2

16.5.2.json:
执行16.5.2.html,输出结果到网页,如下图所示。

图16.5.2 无刷新提交表单
第17章 AJAX异步通信
使用<form>标签提交数据时,网页会被重新加载,在此期间不能响应用户的任何操作。为了实现在
不刷新网页的情况下能与服务器进行通信,减少无用等待时间,异步通信应运而生。本章将详细介绍
JavaScript的异步通信机制。

17.1 AJAX介绍
AJAX全称Asynchronous JavaScript and XML,即异步的JavaScript和XML,是另一种表单提交方
式。在第16章表单对象中介绍过表单的提交方式,接收表单提交的框架(主框架或子框架)时需要进行
刷新才能将数据提交到服务器。AJAX可以在用户无感知的状态下将表单提交到服务器。

17.1.1 异步提交

使用AJAX接口XMLHttpRequest,默认创建的异步的HTTP请求,监听XMLHttpRequest实例的
readystatechange事件,判断请求是否处理完毕,再接收到服务器返回结果。整个过程没有资源的加载标
识。
提示
在发送异步请求的过程中将网页关闭,如果服务器已接收到浏览器发送的完整数据,那么请求就会
发送成功,但绝大多数情况下,请求都会发送失败。

17.1.2 XML语言

可扩展标记语言XML能方便地组织任意类型的数据,它的设计初衷是用于传输和存储数据。XML
文档与HTML文档相似,都是以文本形式存储使用标签包含的数据。XML文档的第一行会以<?xml
version="1.0" encoding="UTF-8"?>的形式表示该文档是一个XML文档,其中version表示XML文档的版
本,encoding表示当前文档的字符编码。
17.1.1.xml使用XML文档描述本书第1章,如下所示。
动手写17.1.1

执行17.1.1.xml,如下图所示。
图17.1.1 XML文档示例

浏览器提示没有为该XML文档设置关联样式,也就是说,XML可以像HTML一样由样式控制展
示。实际上HTML是XML的一种,HTML更倾向于数据展示,XML则倾向于数据定义和传递。
提示
XML的主要目标是数据的存储,HTML的主要目标则是数据的展示。XML没有预定义标签,可以
自行定义任何标签,因此使用起来比较灵活自由。

17.1.3 XMLHttpRequest对象

XMLHttpRequest对象用于发送HTTP请求,设计初期是为了处理XML语言(和HTML数据格式非常
相似)标记的数据。异步通信技术发展到现在,XMLHttpRequest对象可以接收任意类型的数据,尤其
是JSON数据,也可以接收文本数据和二进制数据,已不局限于只接收XML文档。JSON也是一种以文本
存储的数据格式,在稍后小节将会进行介绍。
XMLHttpRequest实例包含了与本次表单提交请求相关的属性,如下表所示。
表17.1.1 XMLHttpRequest属性列表
请求未返回前,response*属性都是null,不能读取。
动手写17.1.2

执行17.1.2.html,输出结果到网页,如下图所示。

图17.1.2 XMLHttpRequest实例属性

17.1.4 NGINX服务器配置
使用浏览器直接打开电脑上的HTML文件时,浏览器网址会以file:协议开始,形
如“file:///F:/code/17/02/17.2.2.html”。浏览器打开这种file:协议的网页时,出于安全考虑,不允许使用
XMLHttpRequest对象发送GET/POST请求,如果使用XMLHttpRequest会触发错误,在控制台显示“Cross
origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.”。
在形如“http://www.01kuaixue.com”这样的网页下,可以使用XMLHttpRequest发送GET/POST请求,
为了让本书的代码在使用域名的网址下访问,需要借助NGINX服务器在本机电脑配置简单的Web网站服
务器。
搭建NGXIN服务器参考步骤如下:
1.在nginx下载页面 http://nginx.org/en/download.html找到windows版本链接;
2.将nginx压缩包nginx.zip解压到任意目录,点击运行 nginx.exe;
3.将本章代码拷贝到nginx的html目录;
4.在浏览器访问 http://localhost/17/01/17.1.2.html,如下图所示。

图17.1.3 使用localhost访问网页

本章提供的示例都会以“http://localhost/17/01/17.1.2.html”这种网址演示,读者可将本章代码拷贝到
与ngxin.exe同级的html目录,这样就能用这种网址进行访问。

17.2 XML数据
XML规定了数据的组织结构,与HTML使用标签标示元素一样,XML也是使用标签表示节点,节
点之间的关系和HTML一样,HTML就是一种特殊形式的XML文档。XML的主要目的是数据存储,并不
是为了替代HTML。

17.2.1 XML文档

XML以文本形式组织各个节点,任何文本编辑器都可以处理XML文档。XML文档严格要求标签必
须闭合和正常嵌套。XML对标签大小写敏感,这点与HTML不同。XML要求元素节点的属性的值加上
引号,而HTML自动加引号有时会导致自闭合标签的斜线识别错误。
提示
XML也允许标签自闭合,但必须包含结束标签的斜线,比如HTML的
换行标签,在XML文档中必须加上斜线,
是正确的换行,
则是错误的用法。
动手写17.2.1
执行17.2.1.html,输出结果到网页,如下图所示。

图17.2.1 XML文档区分大小写

17.2.2 XML解析

使用DOMParser解析XML或HTML文档,XML解析失败时不会报错,而是返回一段HTML标签,用
于查看文档的错误信息。
动手写17.2.2

执行17.2.2.html,输出结果到网页,如下图所示。
图17.2.2 使用DOMParser解析XML文档

任何满足XML格式的文档都可以使用DOMParser解析。

17.3 JSON数据
JSON和XML类似,是一种偏向于数据传输和存储的数据格式,全称JavaScript Object Notation,使
用纯文本存储。从名称上看,JSON与JavaScript关联紧密,其实其他语言也经常使用JSON传输数据。

17.3.1 JSON文档

JSON文档以“key:value”形式保存数据,是一种更轻量的文档。JSON文档的阅读、解析、访问都比
较简单。
JSON文档的key必须使用双引号包围起来,使用{}表示数据集合,使用[]表示数组,这种结构是借
鉴JavaScript的,书写方式如下:

在键值对“key:value”中,冒号前面的是key,必须使用双引号包围起来,当value是字符串时,也只
能使用双引号包围起来,不能使用单引号。
17.3.1.json使用JSON文档描述本书第1章,如下所示。
动手写17.3.1
动手写17.3.1展示的JSON数据与JavaScript的对象非常接近,解析JSON之后获取的对象就是
JavaScript的普通对象。因此JavaScript更适合直接操作JSON,而HTML更适合结合XML使用。

17.3.2 JSON解析

使用JSON.parse()解析JSON字符串,返回解析后的JavaScript对象,语法如下:

参数说明:
◇ JSON:表示浏览器提供的JSON转换对象。
◇ jsonText:必选参数,表示JSON字符串,如果该参数不符合JSON语法规则,JSON.parse()函数会
抛出异常错误,在浏览器控制台显示“Uncaught SyntaxError: Unexpected token u in JSON at position
100”,表示这段字符串中某个字符不符合JSON语法。
◇ reviver:可选参数,表示“key:value”键值对中value的改造函数,该参数接收两个参数,形如
reviver(key,value),可以在解析函数返回对象前修改value的值。
解析JSON字符串时,基本都希望获得原始真实的value数据,很少使用reviver参数。
17.3.2.html使用JSON.parse()解析一段字符串,如下所示。
动手写17.3.2

执行17.3.2.html,输出结果到网页,如下图所示。
图17.3.1 使用JSON.parse()解析JSON字符串

提示
为了避免使用JSON.parse()解析不合法的字符串导致程序异常退出,可以使用try…catch语句将解析
代码包围起来,捕获异常错误。
在GET/POST请求中,只能传输字符串,不能传输JavaScript对象。若要传输对象、数组等结构化的
数据,需要先将它们转换为JSON字符串,传输给服务器之后,服务器再将JSON字符串解析成结构化数
据。
使用JSON.stringify()可以将JavaScript对象转换成JSON字符串,这个转换的过程称为序列化。与之
对应地,将字符串解析成结构化数据称为反序列化。
JSON.stringify()最多支持三个参数,语法如下:

参数说明:
◇ JSON:表示浏览器提供的JSON转换对象。
◇ value:必选参数,表示需要序列化的对象、数组等,其他简单类型的数据也可以序列化。
◇ replacer:可选参数,表示属性值的转换函数,与JSON.parse(jsonText, reviver)函数的reviver参数
相似;接收两个参数,形如replacer(key, value),可以在序列化前修改每一对“key:value”中的value;不传
递该参数或者使用null,都表示不需要转换。
◇ space:可选参数,表示JSON文档使用的缩进空白,主要用于美化输出的文档;不使用该参数
时,序列化后的JSON字符串全部输出到一行。
17.3.3.html使用JSON.stringify()序列化JavaScript对象,如下所示。
动手写17.3.3

执行17.3.3.html,输出结果到网页,如下图所示。
图17.3.2 使用JSON.stringify()序列化对象

任何JavaScript变量都可以使用JSON.stringify()序列化成JSON字符串。如果对象存在递归或交叉引
用,则序列化失败。
动手写17.3.4

执行17.3.4.html,输出结果到网页,如下图所示。

图17.3.3 交叉引用对象序列化失败

17.3.3 JSON与XML对比
与XML相比,JSON更轻量。这两种文档都具有闭合限制,因此正常的解析顺序都要求两个文档全
部加载完成才能解析。
相同文档使用JSON表示比使用XML表示更加节省字节,占用的存储空间和网络带宽更少。JSON支
持使用键值直接访问数据节点,解析和访问更加快速。
17.4 AJAX应用
17.4.1 XMLHttpRequest初始化

使用XMLHttpRequest构造函数创建请求实例xhr,xhr是XMLHttpRequest每个单词的首字母组成的缩
写。调用xhr.open()方法开启请求,最多支持五个参数,语法如下:

参数说明:
◇ xhr:表示通过new XMLHttpRequest()创建的请求实例xhr。
◇ method:必选参数,表示要使用的HTTP方法,常见的是GET、POST,也可以是其他HTTP方
法,比如DELETE、HEAD、PUT。
◇ url:必选参数,表示请求的网址。
◇ async:可选参数,表示是否使用异步方式发送HTTP请求,默认是true,异步发送请求在请求发
送过程中仍然可以自由操作网页,请求处理完成之后,xhr调用函数通知请求发送完毕;如果设置为
false,也就是以同步方式发送请求,那么在请求发送过程中网页不响应点击、按键等操作。
◇ user:可选参数,如果请求的网址需要验证用户名,需要提供该参数。
◇ password:可选参数,如果请求的网址需要验证用户名和密码,需要提供该参数。
17.4.1.html创建XMLHttpRequest实例并调用open()方法开启HTTP请求,如下所示。
动手写17.4.1

执行17.4.1.html,输出结果到网页,如下图所示。

图17.4.1 XMLHttpRequest初始化
17.4.2 设置readystatechange

在使用XMLHttpRequest发送HTTP请求的过程中,请求会经过多状态,xhr.readyState属性表示请求
当前所处的状态,状态列表如下所示:
表17.4.1 XMLHttpRequest状态列表

每当请求的状态改变时,会触发xhr的readystatechange事件,有两种方式为xhr设置状态改变事件回
调,一是调用xhr.addEventListener()添加事件监听器,二是设置xhr.onreadystatechange赋值一个事件回
调。
提示
JavaScript中的常量基本都与当前对象有关,上述的状态常量使用XMLHttpRequest即可访问,比如
XMLHttpRequest.DONE的值为4。
17.4.2.html监听请求状态改变事件,如下所示。
动手写17.4.2

XMLHttpRequest继承了XMLHttpRequestEventTarget和EventTarget的属性和方法,因此可以使用
addEventListener和removeEventListener等方法以及onload、onerror等事件。
在readyState等于4时触发readystatechange事件,然后触发load事件。error事件并不可靠,即使请求
的网址出现404错误,仍然触发load事件,而不是error事件,这一点跟<iframe>框架加载网页无论结果如
何都触发load事件类似。
动手写17.4.3
执行17.4.3.html,输出结果到网页,如下图所示。

图17.4.2 设置XMLHttpRequest事件监听器

17.4.3 设置HTTP请求头信息
HTTP请求分为请求头、请求正文两部分,这两部分都可以设置信息,发送给服务器。服务器设置
的请求头的最大字节长度会比请求正文的长度小很多,比如NGINX默认情况下,设置的请求头长度限
制为4KB,因此多数情况下使用请求正文传输信息。
使用xhr.setRequestHeader()设置请求的头部信息,有严格的调用顺序,需要在xhr.open()函数调用之
后以及xhr.send()函数之前,语法如下:

参数说明:
◇ xhr:表示XMLHttpRequest请求实例。
◇ name:必选参数,表示头信息名称,头信息和网址中的参数相似,具有名称和值两个属性。
◇ value:必选参数,表示头信息的值。
17.4.4.html设置请求头信息,如下所示。
动手写17.4.4

执行17.4.4.html,输出结果到网页,如下图所示。

图17.4.3 设置HTTP请求头信息

提示
为了让服务器识别请求是以AJAX形式(使用XMLHttpRequest对象)发送,需要设置头信息参数X-
Requested-With为XMLHttpRequest。

17.4.4 发送请求
使用xhr.send()发送请求,send()方法支持接收请求数据作为请求体(body)发送到服务器。如果请
求方式为异步,则send()方法直接返回;如果请求方式为同步,则等到请求完成(接收到服务器响应内
容)后再返回。
使用xhr.send()发送请求数据,语法如下:

参数说明:
◇ xhr:表示XMLHttpRequest请求实例。
◇ data:可选参数,表示发送的请求数据。
参数data支持多种类型的数据,如下表所示。
表17.4.2 send()方法data参数支持的类型列表

调用xhr.send()发送data时,一般会使用字符串,因为JavaScript不能直接使用字符串表示二进制的文
件内容,比如上传图像到服务器时,需要借助ArrayBuffer、ArrayBufferView、Blob等类型的变量存放二
进制内容,再传递给send()函数。
使用GET、HEAD、OPTIONS等请求方法时,不需要设置请求正文,此时调用send()方法不应该传
递数据。

17.4.5 获取HTTP响应头信息

使用xhr.getAllResponseHeaders()获取所有响应头信息,不需要传递任何参数。使用
xhr.getResponseHeader(key)获取指定响应头信息,key忽略大小写。若响应头不存在则返回null,语法如
下:

参数说明:
◇ xhr:表示XMLHttpRequest请求实例。
◇ name:必选参数,表示服务器返回头信息的名称。
17.4.5.html使用getResponseHeader()获取返回头信息,如下所示。
动手写17.4.5
执行17.4.5.html,输出结果到网页,如下图所示。

图17.4.4 获取HTTP响应头信息

提示
使用getAllResponseHeaders()和getResponseHeader()方法,只有在请求状态达到
HEADERS_RECEIVED之后才有效,即xhr.readyState大于等于2,该值表示浏览器接收到服务器返回的
头信息。

17.4.6 获取响应文本
使用xhr.responseText获取响应正文的文本内容,如果响应正文是二进制数据,则该属性不能获得正
常的值。
17.4.6.html使用XMLHttpRequest获取文本文件的内容,并展示到网页,如下所示。
动手写17.4.6
执行17.4.6.html,输出结果到网页,如下图所示。

图17.4.5 获取响应文本

17.4.7 获取JSON数据

XMLHttpRequest最早不是为JSON设计的,因此xhr没有与JSON对应的属性,使用JSON.parse()将响
应文本解析成JSON字符串。
17.4.7.html使用JSON.parse()尝试解析响应的文本内容,并将其转换为JavaScript对象,如下所示。
动手写17.4.7

执行17.4.7.html,输出结果到网页,如下图所示。
图17.4.6 获取JSON数据

使用Object.defineProperty()为xhr定义responseJSON属性。
动手写17.4.8

执行17.4.8.html,输出结果到网页,如下图所示。

图17.4.7 定义responseJSON属性

17.4.8 获取XML数据
使用xhr.responseXML获取xml格式的响应正文,并且解析成XML对象。如果响应正文不是HTML或
XML,或者无法正常解析,则为null。
动手写17.4.9

执行17.4.9.html,输出结果到网页,如下图所示。

图17.4.8 获取XML数据

目前使用JSON格式传输数据的应用较多,已较少使用xhr.responseXML属性。

17.4.9 获取二进制数据

使用xhr.response可以获取请求返回的原始对象,xhr.response属性的数据格式受到xhr.response Type
属性的影响,在调用send()方法前,设置xhr.responseType的值,xhr自动根据该值将返回数据解析为相应
的对象,两者的对应关系如下表所示。
表17.4.3 response与responseType关系

当服务器返回的数据是二进制,使用xhr.response可以获取正确的blob对象,比如使用XML
HttpRequest获取图像文件、Excel文件、PDF文件等。
17.4.10.html使用XMLHttpRequest获取图像文件并展示,如下所示。
动手写17.4.10
执行17.4.10.html,输出结果到网页,如下图所示。

图17.4.9 获取二进制数据

设置xhr的responseType属性为blob,浏览器将返回数据解析为Blob实例,再通过FileReader即可读取
图像二进制数据。若不设置responseType为blob,则获取的xhr.response为乱码。使用FileReader读取文件
将在第20章多媒体的内容中介绍。

17.5 小结
本章详细介绍了新的表单提交方式AJAX,以及XML和JSON数据的获取和解析。AJAX技术在网络
编程中经常使用,XMLHttpRequest实例方法丰富,涉及较多HTTP网络编程知识,可在实战中熟练掌
握。

17.6 知识拓展
17.6.1 请求进度条
使用xhr.onreadystatechange可以监听状态改变事件,但不能详细知晓请求的发送进度。
XMLHttpRequest的upload属性是一个XMLHttpRequestUpload对象,用于触发上传进度改变事件。
XMLHttpRequestUpload具有abort、error、load、loadstart、loadend、progress、timeout等事件。在进
行上传文件等比较耗时的请求时,一般只需要监听progress事件,该事件的对象包含三个特有属性,这
三个属性都是只读属性,如下表所示。
表17.6.1 上传进度事件对象的特有属性

17.6.1.html监听请求进度事件,如下所示。
动手写17.6.1
执行17.6.1.html,输出结果到网页,如下图所示。

图17.6.1 请求进度条

因为默认配置的静态文件不支持发送POST请求,所以在动手写17.6.1中,NGINX最后返回状态码
405提示服务器不支持或返回413提示请求报文太大,显示不支持POST请求。通过进度事件,定时计算
已上传字节数可以获得上传百分比和上传速度。
提示
虽然目前大部分浏览器都支持xhr.upload属性,但是IE9浏览器以及早期的浏览器并不支持该属性,
因此可以通过if(xhr.upload)判断当前浏览器是否支持。
17.6.2 跨域请求

在服务器端的程序中,后端代码能向任意网址发送HTTP请求,在浏览器端JavaScript也可以通过
XMLHTTPRequest向跨域网址发送请求。前面章节已经介绍了网址组成部分,当两个网址的协议、主机
(域名/IP)、端口三要素有一个不同时,就是不同域。使用XMLHTTPRequest发送的请求,如果请求网
址与当前网页的网址不同域,就是跨域。
出于安全考虑,浏览器会检测跨域网址返回的信息是否满足跨域请求限制,该限制也称为HTTP访
问控制,英文全称Cross-Origin Resource Sharing,简称CORS,也就是跨域资源共享(访问)。在发送跨
域请求前,浏览器先发送一条OPTIONS请求,获取对方服务器与当前网页约定的Access-Control限制,
一般只需要设置三条限制选项,如下表所示。
表17.6.2 Access-Control限制选项

浏览器获取OPTIONS请求响应的头信息,读取上述三个header,与即将发送的请求网址进行比较,
只有请求网址满足限制条件时才发送真正的HTTP请求。浏览器缓存验证成功结果,避免每次都进行验
证。如果验证失败,在控制台输出错误“No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost' is therefore not allowed access”。验证失败结果不会缓存,直到下一次发送
跨域请求时,仍然会验证。
服务器接收到跨域请求时,输出相应的header信息,PHP参考代码如下:

其他语言可以参照这三条请求头信息进行设置。
第18章 CSS网页特效
展示网页时需要使用CSS让网页看起来更加美观、更易于操作。CSS的全称是层叠样式表,是
Cascading Style Sheets的缩写,主要用于控制HTML文档的展现形式,包括字体、颜色、边距、透视、动
画等。W3C制定了JavaScript操作CSS的标准方法,本章将详细介绍如何使用脚本修改网页样式,实现网
页特效。

18.1 CSS操作
网页由很多个元素组成,比如表单、按钮、输入框、文字等,这些元素的样式都由CSS控制。CSS
样式分为三种,分别是外联样式、内部样式和内联样式,如下所示:

元素的样式由上述三种样式叠加而成,获取样式需要使用getComputedStyle(tag)方法,设置样式的
直接修改元素的style属性即可。

18.1.1 样式对象

标签元素的style属性是浏览器解析内联样式生成的,即使当前标签没有内联样式,也会设置一个默
认的样式对象。
18.1.1.html查看标签的style属性字符串和标签对应DOM对象的style属性对象,如下所示。
动手写18.1.1
执行18.1.1.html,输出结果到网页,如下图所示。

图18.1.1 样式对象

提示
标签DOM对象的style属性是一个CSSStyleDeclaration实例,浏览器为了区分系统对象的类型,会给
这些对象设置一个名称,用法跟使用花括号({})声明的普通对象是一样的。若内部样式没有设置对应
的值,则style的属性值是空字符串。

18.1.2 计算样式

元素的style对象只包含了内联样式,并不能表示当前生效的全部样式,使用getComputedStyle()可以
获得计算后元素的所有CSS属性的值的集合,这个集合也称为计算样式。
18.1.2.html获得标题的计算样式,如下所示。
动手写18.1.2
执行18.1.2.html,输出结果到网页,如下图所示。

图18.1.2 计算样式

提示
如果元素正在执行动画,使用getComputedStyle()可以获得当前时刻真实的样式,通过浏览器的
Elements控制台,还可以查看元素正在变化的style属性字符串。

18.1.3 设置样式
设置元素样式,直接修改DOM对象的style属性即可,用法如下:

字体颜色和大小在CSS文件中用color、font-size表示,而在JavaScript中需要转换成camelCase驼峰式
写法,正好对应color、fontSize。
18.1.3.html修改style对象让元素的字体变大,如下所示。
动手写18.1.3

执行18.1.3.html,输出结果到网页,如下图所示。
图18.1.3 设置样式

提示
style的属性值为空字符串时表示该样式没有设置,同样地,将style的属性值设置为空字符,就可以
将其从标签的内联样式中删除。

18.2 CSS大小
18.2.1 CSS高宽

计算样式会将样式的值统一,假设行内样式是width:auto,那么计算样式返回的是元素的真实宽
度。
动手写18.2.1

执行18.2.1.html,输出结果到网页,如下图所示。

图18.2.1 CSS高宽

元素<h1>的宽度根据父元素<body>的内容宽度进行变化,因此<body>的padding增大,<h1>的宽度
就会减小。

18.2.2 元素高宽
元素的offsetHeight、offsetWidth属性表示元素的高宽,不支持小数,但元素的height、width样式支
持小数。当元素的高宽样式为小数时,offsetHeight、offsetWidth等于样式值对应的数字四舍五入后取
整。
18.2.2.html查看元素的样式高宽和属性高宽,如下所示。
动手写18.2.2

执行18.2.2.html,输出结果到网页,如下图所示。

图18.2.2 元素高宽

18.2.3 视窗大小

浏览器设备用于展示网页的区域称为视窗,比如手机屏幕展示网页,那么在该手机上的视窗就是整
个屏幕;电脑以显示器展示网页,显示器就是当前视窗。
展示网页时,CSS使用的像素单位是相对单位,与设备的像素单位并不相等。假设某一台手机的屏
幕分辨率是2160×1080,按照1080像素展示网页时,因为手机屏幕小,字体会显得非常小。为了解决网
页字体显示过小的问题,在手机上支持使用2个设备像素表示网页的1个显示像素。默认情况下,这种显
示方式没有开启,要让浏览器按照这种形式显示网页,需要在网页中使用<meta name="viewport"
content="width=device-width">标签指明。
在手机上完整适配的网页,还可以给viewport增加minimum-scale=1.0和maximum-scale=1.0,表示禁
止在手机缩放网页,避免双指误操作缩放。
1.电脑网页在手机屏幕上默认展示形式
动手写18.2.3

执行18.2.3.html,输出结果到网页,如右图所示。
动手写18.2.3没有适配在手机浏览器上的显示效果,按照屏幕分辨率显示网页,文字显得很小,只
有将网页放大才能看清其中的文字。
2.使用viewport让网页适配手机屏幕

图18.2.3 视窗大小,缩小显示

18.2.4.html在<head>标签中增加viewport设置,如下所示。
动手写18.2.4

执行18.2.4.html,输出结果到网页,如下图所示。
图18.2.4 视窗大小,正常显示

18.3 CSS动画
网页动画是指一段连续变化的样式,在CSS中有animation和transition这两种动画类型。animation着
重于动画流程的控制,对元素多个属性的变化进行控制,支持设置多个关键帧,浏览器自动根据关键帧
设置元素的样式。transition偏向于属性的过渡,设置多个样式属性的起始值和结束值,浏览器自动产生
过渡效果。

18.3.1 淡入淡出

淡入效果是指元素透明度从0渐变到1。
动手写18.3.1
执行18.3.1.html,输出结果到网页,如下图所示。
图18.3.1 CSS淡入

淡出效果是指元素透明度从当前透明度渐变到0,最后设置display样式为none。
动手写18.3.2

执行18.3.2.html,输出结果到网页,如下图所示。
图18.3.2 CSS淡出

在动画结束时将元素的display样式设置为none,元素不再显示,避免占据浏览器的显示空间。

18.3.2 滑入滑出

向下滑入是指元素高度从0像素渐变展开到正常高度。
动手写18.3.3
执行18.3.3.html,输出结果到网页,如下图所示。

图18.3.3 CSS滑入

向上滑出是指元素高度从当前高度渐变收起到0像素。
动手写18.3.4
执行18.3.4.html,输出结果到网页,如下图所示。
图18.3.4 CSS滑出

18.4 小结
本章介绍了JavaScript与CSS的交互方式、移动端网页高宽与设备真实高宽的关系,并以淡入淡出、
滑入滑出两种动画演示了CSS动画流程。使用JavaScript实现CSS动画的底层代码较多,可以采用一些成
熟的JavaScript动画库。

18.5 知识拓展
默认样式
在网页中插入一个空标签,再通过元素的计算样式,获取标签的默认样式和单位。
动手写18.5.1
执行18.5.1.html,输出结果到网页,如下图所示。

图18.5.1 默认样式

<span>标签的display样式默认值是inline,在指定网页中获取标签的默认样式会受到当前网页基准样
式的影响,假设在基准样式中将<span>元素的display设置为inline-block,那么getDefaultDisplay('span')返
回'inline-block'。
第19章 数据存储
在网站开发中,经常需要进行数据处理来实现页面交互效果。实现数据存储有几种常见方式,包括
Cookie存储、Session存储,以及Local Storage存储等。这些存储方式适用于不同的业务场景,本章将详
细讲述这些存储方式的原理和区别。

19.1 Cookie
Cookie,有时也使用Cookies表示,是网站为了辨识用户,在浏览器端设置的字符串。因为HTTP协
议是无状态的,所以浏览器每次与服务器进行交互时,需要将本地存储的Cookie添加到请求头信息中,
服务器读取Cookie并识别来自同一个用户的请求。
Cookie具有多个属性,如下表所示。
表19.1.1 Cookie属性列表

Cookie的有效范围受到域名限制,以零壹快学的域名为例,01kuaixue.com是顶级域名,
www.01kuaixue.com、video.01kuaixue.com等都是01kuaixue.com的子域名(二级域名),
javascript.book.01kuaixue.com、javascript.video.01kuaixue.com等也是子域名(三级域名)。
提示
开发者可以根据01kuaixue.com前面的小数点数量区分是二级域名还是三级域名。为了让属于
01kuaixue.com的所有网站共享Cookie,可以将Cookie的域名属性设置为.01kuaixue.com,注意最前面有
一个小数点。
Cookie的domain属性让子域名能获取顶级域名的cookie,但不同子域名不能获取对方专用的
cookie。Cookie的path属性也遵从类似domain的限制。
一般情况下,为了让所有子域名和路径都能获取cookie,会将cookie的domain属性设置为“小数点
+顶级域名”(类似于“.01kuaixue.com”),将path属性设置为“/”。
浏览器不支持在document.origin为'null'的网页设置cookie,也就是不支持在“file:///F:/code/17/
02/17.2.2.html”这样的网页下设置Cookie,需要先将本章代码拷贝到第17章配置的NGINX服务器的html
目录,再通过localhost网址访问。

19.1.1 Cookie获取
使用document.cookie获取Cookie的键值列表。
动手写19.1.1

执行19.1.1.html,输出结果到网页,如下图所示。

图19.1.1 Cookie获取

document.cookie只返回当前网页能读取到的有效cookie,仅包括cookie名称和cookie的值。JavaScript
没有提供原生的cookie解析方法,需要将document.cookie按照分号、等号分割之后再获取对应名称的
Cookie值。
动手写19.1.2

执行19.1.2.html,输出结果到网页,如下图所示。

图19.1.2 Cookie解析
cookie的值经过encodeURIComponent转义,取得原始字符串之后再使用decodeURIComponent对其反
转义。

19.1.2 Cookie设置

设置Cookie只需要对document.cookie赋值即可,Cookie的各个属性和属性值用等号连接,最后再用
分号将每对属性连接,紧跟在value之后,语法如下:

每次设置Cookie时,如果Cookie已存在,则根据expires判断是否有效,有效则设置Cookie的值,无
效则删除。设置Cookie的值不影响其他Cookie。
动手写19.1.3

执行19.1.3.html,输出结果到网页,如下图所示。

图19.1.3 Cookie设置

提示
浏览器对Cookie的总存储空间和条目数量有限制,因此不能无限设置Cookie。目前各个浏览器的大
小限制接近4KB,但是数量限制并不统一。因为使用较多Cookie会增加HTTP报文大小,所以不建议在
Cookie中存放太多信息。

19.1.3 Cookie删除

删除Cookie只要将Cookie的有效期设置为过去的某一时间,此时浏览器就会忽略Cookie的值,并将
其删除。如果只修改Cookie的值,而不是设置有效期,则不能将Cookie删除。这是浏览器与服务器的约
定,所以要注意明确告知已失效的Cookie的名称。
动手写19.1.4
执行19.1.4.html,输出结果到网页,如下图所示。

图19.1.4 Cookie删除

提示
删除Cookie时,必须与现有Cookie的domain、path属性完全对应,否则会导致删除失败。

19.1.4 Cookie安全

Cookie数据在浏览器端对用户完全可见,因此有可能被修改。HTTP报文使用明文传输,若Cookie
中包含重要信息(比如用户认证信息),可能被第三方网关获取,存在泄露风险。
Cookie保存了用户标识,网站使用Cookie时,需要对用户负责,可以参见以下几种方式提高Cookie
的安全性:
1.为网站配置HTTPS证书,仅支持使用HTTPS协议访问;
2.对Cookie中的敏感信息进行加解密,避免在浏览器端直接查看原文;
3.在服务器端设置Cookie的httponly属性,浏览器将禁止脚本修改该Cookie;
4.设置Cookie的security属性,让Cookie仅在HTTPS下传输,避免明文被网关获取;
5.自定义网页背景颜色等只用于展示的非敏感信息可以不用处理。
19.2 Session
Session是另一种数据存储方式,与Cookie相比,只需要在浏览器端存放一个session id,用户的全部
信息就都存放在服务器,浏览器每次发送HTTP请求时携带该session id,服务器会根据session id,获取
对应的用户信息。
网站配置Session之后,用户首次访问网页时,服务器会生成一段较长的随机字符串作为session id,
分配给该用户,并在服务器上生成一个小文件,用户在注册、登录过程中使用的信息全部存放在这个
session小文件中。
提示
使用Session可以减少HTTP报文大小,因为只需要传输session id,其他信息已全部存放在服务器
中,不需要在浏览器和服务器之间进行传输。
1.Session获取
session id作为Cookie的一部分,浏览器并不知道哪个cookie表示session id。session id一般是比较随
机、无规律的字符串,开发者可以人工分析网站的cookie键值,猜测哪个cookie表示session id。
2.Session设置
浏览器无法获取除了session id之外的其他session信息,因此不能设置Session。
3.Session安全
Session相对于Cookie,比较难以构造,因为不同网站生成session id的算法不一样,甚至可能是开发
者自行配置的随机字符串。浏览器不能获取服务器Session中的其他信息。
session id作为Cookie信息的一部分跟随HTTP报文一起发送给服务器,仍可以被第三方网关获取。
因此不管是使用Cookie,还是使用Session存放用户认证信息,都建议为网站增加HTTPS证书。

19.3 WebStorage
因为浏览器对Cookie的总存储大小和数量做了限制,大部分服务器对HTTP报文的Cookie部分也有
限制,所以不能在Cookie中存放大量数据。
目前支持较为广泛的本地大容量存储方案是WebStorage,其使用相比于其他本地存储方案如
IndexedDB、WebSQL更为简单。
WebStorage让开发者在浏览器中存储更多的数据,这部分数据并不需要跟随HTTP报文发送到服务
器。大多数浏览器提供的WebStorage容量限制是5M,个别浏览器更是支持20M,而Cookie的容量一般是
4KB,因此WebStorage的容量至少是Cookie的1200倍。
WebStorage在浏览器端有两种对应的实现机制:localStorage和sessionStorage。localStorage存储的数
据在浏览器进程关闭之后重新打开仍然有效;sessionStorage存储的数据在浏览器进程退出之后销毁,即
只在浏览器打开期间有效。

19.3.1 localStorage对象
Storage是WebStorage标准的实现方式,浏览器提供的localStorage是一个Storage实例。localStorage
以“key:value”的形式存放数据,同时也提供了三个方法,分别用于获取数据、设置数据和删除数据。
1.获取数据
使用localStorage.getItem(name)获取数据,返回本地存储的字符串,语法如下:
参数说明:
◇ localStorage:表示本地存储对象。
◇ name:必选参数,表示存储数据的名称,name对应的值不存在时返回null。
19.3.1.html获取localStorage中存储的字符串,如下所示。
动手写19.3.1

执行19.3.1.html,输出结果到网页,如下图所示。

图19.3.1 使用localStorage.getItem获取数据

提示
数据的key值与localStorage的原有属性冲突时,必须使用getItem()方法获取数据。建议不要使用
localStorage的原有属性(length、getItem等)作为数据的名称,避免造成误解和未知问题。
2.设置数据
使用localStorage.setItem(name, value)设置数据,语法如下:

参数说明:
◇ localStorage:表示本地存储对象。
◇ name:必选参数,表示存储数据的名称。
◇ value:必选参数,表示存储数据的值,任意数据类型,但最终都会被转换成字符串。
19.3.2.html在localStorage中存储数据,如下所示。
动手写19.3.2
执行19.3.2.html,输出结果到网页,如下图所示。

图19.3.2 使用localStorage.setItem设置数据

3.删除数据
使用localStorage.removeItem(name)删除数据,语法如下:

参数说明:
◇ localStorage:表示本地存储对象。
◇ name:必选参数,表示数据的名称。
调用removeItem()删除不存在的数据项时不会触发错误。
19.3.3.html删除本地存储的数据,如下所示。
动手写19.3.3
执行19.3.3.html,输出结果到网页,如下图所示。

图19.3.3 使用localStorage.removeItem删除数据

localStorage存储的数据可以直接通过localStorage[name]操作,也可以使用delete操作符删除。
动手写19.3.4

执行19.3.4.html,输出结果到网页,如下图所示。

图19.3.4 使用delete删除数据

4.清除数据
使用localStorage.clear()清除该域名对应的本地数据。该函数不接收任何参数,直接调用即可。
动手写19.3.5
执行19.3.5.html,输出结果到网页,如下图所示。

图19.3.5 使用localStorage.clear清除数据

localStorage只根据域名区分不同网站的数据,不支持其他选项,因此只要域名相同,任意路径都可
以获得其他页面存储的数据。
5.length属性
localStorage.length属性表示当前存储的数据数量。

19.3.2 sessionStorage对象

sessionStorage和localStorage一样是Storage实例,使用方式一致。sessionStorage在同域网页之间共享
数据,并且随着浏览器进程关闭之后,sessionStorage保存的数据会全部销毁,而localStorage存储的数据
在未调用删除或清除方法前会一直存在。
动手写19.3.6
执行19.3.6.html,输出结果到网页,如下图所示。

图19.3.6 sessionStorage操作

提示
sessionStorage对象除了数据存放的生命周期与浏览器进程一致,其他方面与localStorage没有区别,
两者的属性和方法都是一样的,都继承自Storage。

19.3.3 Cookie与Storage对比
Cookie和localStorage的区别有:
1.Cookie信息跟随HTTP报文发送到服务器,localStorage不发送;
2.Cookie包含失效时间,localStorage永久有效,浏览器卸载之后也可能存在;
3.Cookie原则上应该由服务器创建,浏览器再进行读写,localStorage由浏览器创建;
4.Cookie容量限制大约为4KB,localStorage容量限制大约为5M;
5.Cookie数量限制大约为每个域名50个(现代浏览器),localStorage在容量限制内没有数量限制;
6.Cookie可能出现重复(顶级域名和二级域名允许设置同名cookie、根目录和子目录允许设置同名
cookie),localStorage使用name作为数据唯一标识,不会重复;
7.Cookie和localStorage数据都可以被修改,不能作为可靠数据来源。
19.4 小结
本章介绍了Cookie、Storage在浏览器中存储用户信息,以及Session在服务器端存储用户信息;并介
绍了Cookie、Storage的操作方式和Cookie、Session、Storage的安全性。使用何种方式存储用户信息并不
重要,重点在于要确保用户数据安全和网站数据安全。用户信息一定要谨慎对待。
第20章 多媒体
网页可以将文字、图像、音视频等内容组织在一起,W3C为此制定了JavaScript读取文件、图像和
控制音视频的标准。本章将介绍多媒体文件的操作方法。

20.1 文件
HTML5增加了JavaScript读取文件的相关接口标准,使用FileReader接口读取本地文件和网络文件
(服务器输出结果)。出于安全考虑,FileReader不能读取任意本地磁盘的文件,只能读取用户行为携
带的文件对象,比如用户点击文件选择按钮、拖拽文件到浏览器窗口并放下。另外,FileReader不支持
在浏览器将文件写入本地磁盘。
使用FileReader读取本地图片文件并显示在网页上,可以方便用户修改头像和预览要上传的图像文
件,结合最新的网页画板还能直接在网页上实现图像裁切。

20.1.1 Blob对象

JavaScript采用UTF-8对字符串进行编码,因此不能使用字符串表示二进制文件的内容,需要借助对
象Blob来表示文件的原始数据对象。使用Blob可以按照字节对文件进行操作,利用这个特性将大型文件
切割成多个小片段,再调用XMLHttpRequest并行上传多个片段,也可以实现文件断点续传。
Blob除了操作二进制文件,也可以存储普通文本文件,方法如下:

参数说明:
◇ blob:表示构造函数Blob()创建的实例。
◇ array:可选参数,表示放入blob对象的数据列表,这个列表可以是字符串、二进制缓存数组
(ArrayBuffer)或另一个blob对象。
◇ options:可选参数,表示blob对象的属性,有两个属性,形如{type: 'image/png', endings:
'transparent'}。其中属性type表示blob的文档类型,默认为空字符串;endings表示文档的结束符,默认为
transparent,表示使用当前操作系统的换行符。
构造函数Blob()创建的实例blob包含两个属性:
◇ size表示数据对象的字节大小。
◇ type表示数据对应的MIME类型;如果是未知类型的数据,该值为空字符串。
20.1.1.html将数组转换为Blob对象,如下所示。
动手写20.1.1
执行20.1.1.html,输出结果到网页,如下图所示。

图20.1.1 Blob对象

一般较少直接使用Blob对象,更多的是使用继承自Blob的文件接口File。

20.1.2 File对象

文件接口File继承自Blob,表示本地磁盘上对应的一个文件对象。任何支持Blob类型的函数都支持
使用File调用,比如使用XMLHttpRequest的send(file)方法将文件发送到服务器。
File对象具有多个属性,如下表所示。
表20.1.1 File对象属性列表

20.1.2.html选取本地文件,并在网页上展示该文件的信息,如下所示。
动手写20.1.2
执行20.1.2.html,输出结果到网页,如下图所示。

图20.1.2 File对象

File对象是文件在内存中映射的对象,不直接表示文件内容,获取文件内容需要使用对应的read方
法。

20.1.3 读取文件

使用FileReader接口可以读取Blob对象表示的数据内容,常用于读取file实例的文件内容。
创建reader实例不需要任何参数,使用newFileReader()即可获得一个Reader对象,该对象具有三个属
性,如下表所示。
表20.1.2 Reader对象属性列表

reader实例提供了三种读取Blob对象的方法,如下表所示。
表20.1.3 三种读取Blob对象的方法
这三种读取方法的调用形式都一样,只是返回数据reader.result的数据格式会不同,可以在不同场景
中使用。较常用的方法是readAsDataURL(),语法如下:

参数说明:
◇ reader:表示FileReader实例。
◇ blob:必选参数,表示blob、file等数据对象。
FileReader读取数据都采取异步回调形式,通知程序文件已读取完成;通过这种形式读取文件不会
阻塞浏览器操作。在读取文件过程中,有多个相关的事件,如下表所示。
表20.1.4 FileReader事件列表

(续上表)

除了onload事件,其他事件在不同浏览器中实现还没有统一,判断文件读取完成只需要监听onload
事件即可,使用方式如下所示:

动手写20.1.3
执行20.1.3.html,输出结果到网页,如下图所示。

图20.1.3 读取图像文件

<img>元素的src属性支持data:协议的base64字符串,将FileReader返回的base64字符串直接赋值给src
属性,实现在网页上展示电脑本机的图像。
FileList表示文件对象列表,具有一个length属性表示文件对象数量,它是一个伪数组对象。
FileReader具有多个read函数,用于获取不同的文件内容,比如读取文本文件、二进制文件:
◇ reader.readAsArrayBuffer(blob),读取blob实例,读取完成之后result以ArrayBuffer形式表示文件数
据。
◇ reader.readAsBinaryString(blob),非标准方法,已从W3C草案中废除,result表示数据的原始二进
制形式。
◇ reader.readAsDataURL(blob),result以base64编码形式表示文件内容,result会包含data:数据协议
头和MIME信息,常用于读取二进制文件。
◇ reader.readAsText(blob),result表示文件的可读文本内容,常用于读取文本文件。
动手写20.1.4

执行20.1.4.html,输出结果到网页,如下图所示。

图20.1.4 读取文本文件

base64编码是一种使用64个可输出字符表示数据的方式,因为二进制内容不能直接用于显示和不能
直接在JSON中使用等原因,常常先将二进制数据进行base64编码,再用于数据传递。base64编码也可以
用于编码普通文本数据。
浏览器的WindowBase64.btoa()用于对数据进行base64编码,WindowBase64.atob()用于对编码数据解
码。
动手写20.1.5

执行20.1.5.html,输出结果到网页,如下图所示。

图20.1.5 WindowBase64编解码

btoa只支持对ASCII表示的数据编码,不支持中、日、韩文等Unicode字符编码,对超出边界的字符
串编码会触发错误“Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded
contains characters outside of the Latin1 range.”。
使用encodeURIComponent将字符串先转义成URL编码,再进行base64编码,可以解决越界问题。
动手写20.1.6

执行20.1.6.html,输出结果到网页,如下图所示。
图20.1.6 Unicode base64编解码

JavaScript处理图像数据时,一般将图像二进制转换为base64编码,而不是直接操作二进制字符串。
使用JSON作为数据传输时,不能直接使用二进制数据,要将二进制转换为base64编码才能存放在JSON
文件中。

20.2 画板
浏览器支持通过接口在画板上绘制任意图像,并且可以导出图像数据,而且也可用于裁切图片。比
如让用户设置头像时,能够先在网页上使用FileReader()读取并显示图像,再让用户选择合适区域,裁切
之后再上传到服务器。也就是说,画板让用户在浏览器上即可完成图片裁切,不再需要传递给服务器坐
标区域让服务器来裁切。

20.2.1 canvas标签

画板<canvas>可以用于图像显示、图形绘制,还可以用于制作动画、截取视频帧,实现视频播放。
使用<canvas>加载图像需要先获取画板的上下文对象(画图对象),语法如下:

参数说明:
◇ canvas:表示<canvas>标签对应的DOM对象。
◇ contextType:必选参数,上下文对象的类型,一般选用"2d",表示二维的绘图对象。
绘图对象ctx提供了drawImage()函数,能够直接将一张图像绘制到画板上。该函数有三种调用形
式,接收的参数较多,需分开展示,语法如下:

参数说明:
◇ ctx:表示二维(2D)绘图对象。
◇ img:必选参数,表示<img>标签对应的DOM对象。
◇ dx:绘制在画布上X轴的起始位置,d是单词destination的首字母。
◇ dy:绘制在画布上Y轴的起始位置。
◇ dWidth:绘制在画布上的宽度,绘制宽度大于图像宽度时,实现图像放大;绘制宽度小于图像
宽度时,实现图像缩小。
◇ dHeight:绘制在画布上的高度。
◇ sx:从图像X轴的起始位置开始绘制,起始位置大于0时,从sx开始裁切图像,s是单词source的
首字母。
◇ sy:从图像Y轴的起始位置开始绘制。
◇ sWidth:在图像上选取矩形区域,该值为矩形区域的宽度。
◇ sHeight:在图像上选取矩形区域,该值为矩形区域的高度。
ctx.drawImage()每种形式使用的参数必须齐全。
动手写20.2.1

执行20.2.1.html,输出结果到网页,如下图所示。

图20.2.1 使用canvas显示图像
20.2.2 canvas绘图

canvas元素的getContent()方法返回CanvasRenderingContext2D实例,能实现2D平面图形绘制。
CanvasRenderingContext2D提供了大量图像绘制函数,本节将介绍图形绘制、样式设置、文字绘制等内
容。
绘制图形,包括直线、矩形、圆形、多边形、曲线等,绘图对象ctx提供的绘图方法非常多,此处
仅作简单介绍,绘图流程如下:

动手写20.2.2

执行20.2.2.html,输出结果到网页,如下图所示。
图20.2.2 使用canvas绘制图形

样式设置,包括线条样式、填充样式等。
动手写20.2.3

执行20.2.3.html,输出结果到网页,如下图所示。

图20.2.3 canvas样式设置

文字绘制,包括字体内容、字体样式等。
动手写20.2.4
执行20.2.4.html,输出结果到网页,如下图所示。

图20.2.4 canvas文字绘制

文字按照像素绘制在画布,因此在<canvas>上绘制文字时,需要从左上角计算文字的起始位置。

20.3 音频
早期的网页播放视频需要借助插件,比如使用Flash插件。直到目前为止,仍然有很多网站使用
Flash实现音频播放,比如QQ空间和一些在线音乐播放网站。
在最新的HTML5标准中,增加了浏览器处理音频文件的标准,使用JavaScript即可控制音频的加
载、播放等行为。

20.3.1 audio标签

<audio>标签表示网页的音频内容。音频格式复杂,不同浏览器支持的音频格式也不统一,目前支
持较多的音频文件包括mp3、aac和webm;视频文件mp4包含音频内容,所以浏览器也支持播放部分视
频文件中的音频内容。
动手写20.3.1

执行20.3.1.html,输出结果到网页,如下图所示。
图20.3.1 播放音频

<audio>标签支持包含多个<source>标签,浏览器可以选择最恰当的文件进行播放。
动手写20.3.2

20.3.2 audio属性

<audio>元素属性如下表所示。
表20.3.1 audio属性列表

(续上表)

<audio>元素默认提供播放器控制按钮,同时支持接口控制播放行为,如下表所示。
表20.3.2 audio常用方法列表
使用audio函数可以定制网页播放器,不需要通过插件实现。
动手写20.3.3

执行20.3.3.html,输出结果到网页,如下图所示。

图20.3.2 自定义音频播放器

提示
浏览器获取音频文件与CSS、JS等不同,它只需要获取一部分音频数据就可以进行播放。浏览器网
络控制台显示音频文件的HTTP状态码是206,而不是常见的200。

20.4 视频
HTML5也增加了浏览器处理音频文件的标准,可以直接在网页中播放视频,而不需要依赖其他插
件,使用JavaScript即可控制视频的加载、播放等行为。

20.4.1 video标签

<audio>标签表示网页的视频内容。目前浏览器支持较多的视频文件基本上只有mp4,以后可能会支
持图像压缩比更高的视频格式。
动手写20.4.1

执行20.4.1.html,输出结果到网页,如右图所示。

20.4.2 video属性

<video>视频元素包含<audio>元素内容,除了具有与<audio>相同的属性,还具有一些特定属性,如
下表所示。

图20.4.1 播放视频

表20.4.1 video属性列表

动手写20.4.2
执行20.4.2.html,输出结果到网页,如下图所示。

图20.4.2 播放事件

视频播放完成之后触发pause事件,20.4.2.html在控制台输出video pause。
<video>与<audio>使用方法类似,同样具有canPlayType()、load()、play()、pause()等方法。

20.5 小结
本章介绍了浏览器支持的多媒体接口,包括文件、画板、音视频等。文件操作是数据交互的常见方
式,读者应在实际应用中熟练掌握;绘图画板canvas实际应用复杂度较高,可以进行更加深入的学习。
第21章 多线程
多线程是指在操作系统上同时运行多个线程,当线程之间依赖的资源不冲突时,可以最大程度提升
程序运行速度;当多个线程需要访问同一个资源时,也可以互相等待,与单线程相比并不会降低运行速
度。本章将介绍JavaScript的多线程使用方式,在进行耗时运算时,使用多线程有助于提高代码网页响应
速度。

21.1 浏览器线程
JavaScript从一开始就被设计为依附在浏览器上运行的脚本语言,只需要实现辅助功能,并不需要进
行大数据计算。JavaScript基于事件机制实现异步回调,让程序运行的时候感觉像是异步运行。
JavaScript本质上仍然是单线程,现在的浏览器运行速度都很快,普通的代码运行时间基本在毫秒
级,因此用户感知不到执行JavaScript代码时浏览器处于阻塞状态。假设执行一段耗时5秒钟的JavaScript
代码,那么在这5秒钟之内,浏览器不能响应用户的任何操作,点击、按键都是无效的。
21.1.1.html使用for循环执行10万次元素创建并添加到网页,如下所示。
动手写21.1.1

执行21.1.1.html时,会明显感觉到网页卡顿,等待数秒之后,才能滑动鼠标查看网页内容,如下图
所示。

图21.1.1 浏览器卡顿
在脚本执行期间点击输入框,无法获得焦点,浏览器进入假死状态。JavaScript持续占用浏览器,导
致UI渲染被阻塞。除了DOM操作,还有绘制动画、大数据计算等需要运行时间较长的代码都会阻塞UI
线程,为了解决这些问题,JavaScript提供了WebWorker多线程解决方案。

21.2 WebWorker
HTML5提出的多线程标准WebWorker用于分离多个线程,将主要线程(网页渲染、响应用户操作
等)与需要费时或独立计算的线程分离开,这样可以保证主线程连续运行而不被阻塞(进入假死状
态)。
浏览器要求WebWork不能在file:协议下进行访问,因此将本章代码拷贝到第17章配置的NGINX服务
器的html目录,通过localhost访问。
尽管提供了WebWorker,但是仍然没有为JavaScript提供类似Thread直接操作线程的接口。

21.2.1 创建线程

使用Worker(url)构造函数创建一个后台任务,url参数表示JavaScript文件的网址。worker表示工作
者,在JavaScript中,使用worker表示一个线程。
动手写21.2.1

21.2.1.js:

执行21.2.1.html,输出结果到网页,如下图所示。

图21.2.1 使用Worker创建后台任务

提示
JavaScript允许除了在网页主线程中创建worker,也可以在worker中继续创建其他worker。

21.2.2 线程通信
Worker内部和外部(全局Window)是完全独立的环境,两者的变量和方法不能直接调用,互相之
间通过消息机制实现通信。
在外部使用work.postMessage(message)向worker内部发送消息,work接收消息并处理完成后,在内
部使用postMessage(message)向外部(全局Window)发送消息,语法如下:

参数说明:
◇ worker:表示通过newWorker(url)创建的进程实例。
◇ message:必选参数,表示发送的消息内容,数据格式可以是字符串、数字、数组、对象等;在
worker内部onmessage(event)事件接收到消息时,使用event.data可以获得这条消息内容。
在外部使用worker.onmessage事件回调接收work内部发送的消息,语法如下:

参数说明:
◇ worker:表示Worker进程实例。
◇ callback:必选参数,表示worker内部使用postMessage(message)发送消息时的回调函数,
event.data表示接收到的消息内容。
动手写21.2.2

21.2.2.js:
执行21.2.2.html,输出结果到网页,如下图所示。

图21.2.2 线程通信

使用worker.onerror接收worker内部发生的错误,error事件的event对象具有三个属性,表示与错误相
关的信息,语法如下:

参数说明:
◇ worker:表示Worker进程实例。
◇ callback:必选参数,表示worker内部代码运行发生错误时的回调函数。event参数具有三个属
性:message,表示可读的错误信息;filename,表示发生错误的文件名;lineno,表示发生错误所在的
行号。
21.2.3.html监听worker内部发生的错误,如下所示。
动手写21.2.3

21.2.3.js:

执行21.2.3.html,输出结果到网页,如下图所示。
图21.2.3 线程发生错误

21.2.3 结束线程

使用worker.terminate可以立即结束worker进程,不会等待worker完成剩余操作,该函数不接收任何
参数,直接调用即可。
动手写21.2.4

21.2.4.js:

执行21.2.4.html,输出结果到网页,如下图所示。
图21.2.4 结束线程

提示
调用work.terminate()结束worker之后,继续向worker发送消息,不会触发失败提示,但是worker无
法做出回应。

21.3 线程安全
出于安全考虑,new Worker(url)要求文件地址所在域和当前网址所在域属于同一个域,否则会触发
类似于“Uncaught DOMException: Failed to construct 'Worker': Script at 'jquery.js' cannot be accessed from
origin 'null'.”的错误。
动手写21.3.1

执行21.3.1.html,输出结果到网页,如下图所示。

图21.3.1 Worker同域限制

21.4 小结
本章介绍了使用WebWorker将复杂、耗时的计算代码封装到独立的Worker后台线程中。WebWorker
多线程运行方式提供了一种非算法形式的JavaScript性能提升,掌握WebWorker的使用方式,可以解决很
多复杂的性能问题。

21.5 知识拓展
斐波那契数列
斐波那契数列,也称为黄金分割数列,是指形如“1、1、2、3、5、8、13、21、34……”这样一个无
限延伸的数列。数列第一个数字是1,第二个数字是1,从第三个数字开始,每个数字是前两个数字的和
值。
动手写21.5.1

执行21.5.1.html,输出结果到网页,如下图所示。

图21.5.1 使用递归实现斐波那契数列

计算40个以上数字的斐波那契数列耗时明显增加,在计算数列期间,点击网页任何地方浏览器均不
响应,进入假死状态。未经优化的递归函数虽然可以获取数列的值,但运行时间较长,此时浏览器不会
响应鼠标和键盘操作,但使用Worker可以将递归函数封装到后台线程中,使网页能够正常操作。
动手写21.5.2

21.5.2.js:

执行21.5.2.html,输出结果到网页,如下图所示。
图21.5.2 使用Worker获取斐波那契数列

在动手写21.5.2中,虽然CPU使用消耗很高,正常情况下网页已经假死,但使用Worker封装高频计
算之后,网页仍然正常响应鼠标和键盘操作。
将递归实现改为循环实现之后,效率得到明显提升。
动手写21.5.3

执行21.5.3.html,输出结果到网页,如下图所示。

图21.5.3 使用循环实现斐波那契数列

改进数列生成方式之后,消耗时间都在1毫秒以下。合适的算法和数据结构能够显著提升程序性
能。除了学习语言的基本用法,建议读者也了解一些算法和数据结构相关的知识。
第22章 jQuery
函数库可以将简洁实用的函数归纳整理到一起,共享给他人使用。随着网页编程的发展,越来越多
优秀的函数库能够开源免费使用,使用这些函数库可以显著提高项目开发的效率。本章将详细介绍在网
页编程中经常使用的JavaScript函数库jQuery。

22.1 jQuery介绍
jQuery是一个JavaScript函数库,它提供了大量简洁易用的方法,功能强大,跨浏览器支持,代码精
简,使用最小化压缩文件压缩并经过gzip之后,整个代码只有30KB左右。
jQuery插件机制完善,对于jQuery不支持的功能可以通过第三方插件实现。
先在jQuery官方网站 http://jquery.com/download/下载最新版本的源代码。其中,jquery.min.js是压缩
版本(compressed, production),适合网站的线上版本使用;jquery.js是开发版本(uncompressed,
development),适合用于阅读源代码,深入学习jQuery的设计理念。
动手写22.1.1
执行22.1.1.html,输出结果到网页,如下图所示。

图22.1.1 首次使用jQuery

在JavaScript中,$是一个合法的标识符,jQuery默认使用$作为函数jQuery的简写。$(callback);
在文档初始化即DOMContentLoaded事件触发时,执行callback回调;$(selector) 以CSS选择器的方式选
中DOM元素,并且扩展了多个使用的选择器。jQuery还提供了很多简洁易用的函数,并且兼容不同浏览
器的差异,是一款跨浏览器平台的JavaScript函数库。
为方便统一使用,本章所有示例默认已经通过<script>引入jQuery源代码。
XMLHttpRequest对象使用的代码比较冗长,jQuery提供了简洁的使用接口,本章第9节中AJAX相关
的示例可拷贝到NGINX的html目录,通过localhost访问。

22.1.1 元素集合

jQuery对象支持元素集合,因此每个jQuery对象具有length属性,jQuery对象是伪数组。调用jQuery
提供的设置方法时,会影响集合里的每个元素。调用获取方法时,返回值一般情况下是集合里第一个元
素对应的值或属性;如果集合为空,返回值一般情况下是undefined。
动手写22.1.2

执行22.1.2.html,输出结果到网页,如下图所示。
图22.1.2 jQuery对象集合

调用jQuery构造函数返回一个jQuery实例,jQuery内部封装了new操作,不需要显示使用new调用构
造函数。

22.1.2 链式调用

jQuery的设置方法都支持链式调用。链式调用是指对象的方法返回结果为对象自身。
动手写22.1.3

执行22.1.3.html,输出结果到网页,如下图所示。
图22.1.3 jQuery链式调用

在动手写22.1.3中,jQuery对象li包含两个<li>元素,jQuery将单个元素与多个元素封装在元素集合
这一对象,让多个元素与单个元素的操作方式一致。li.attr('id', 'title') 返回对象li,继续调用 li.css('color',
'red') 返回对象li,继续调用li.prop('hidden', false) 。
元素集合和链式调用是jQuery的两大主要特色,很大程度上简化了DOM编程。

22.2 选择器
jQuery提供的元素选择器非常实用,包含与CSS选择器一样的基本选择器,并扩展了元素筛选、可
见性筛选、表单筛选等方便实用的选择器。

22.2.1 CSS选择器

CSS选择器是指与CSS一样的选择器,如下表所示。
表22.2.1 CSS选择器

jQuery支持多个CSS选择器组合使用,比如input[data-prev-value="1"]。
动手写22.2.1
执行22.2.1.html,输出结果到网页,如下图所示。

图22.2.1 jQuery支持多个CSS选择器

jQuery最早出现在2006年,由约翰·瑞森(John Resig)创建并发布。在document.query Selector*()方


法出现之前,jQuery就几乎支持全部CSS选择器。

22.2.2 表单选择器
jQuery针对表单元素扩展了表单选择器,如下表所示。
表22.2.2 表单选择器

(续上表)

动手写22.2.2
执行22.2.2.html,输出结果到网页,如下图所示。

图22.2.2 表单选择器聚焦到“账户”输入框

表单选择器选中的元素只要符合相应含义,与是否在<form>元素内无关。

22.2.3 元素筛选
jQuery提供多个元素筛选的选择器,如下表所示。
表22.2.3 元素筛选选择器

动手写22.2.3

执行22.2.3.html,输出结果到网页,如下图所示。
图22.2.3 元素筛选选择器

22.2.4 子元素筛选

jQuery的子元素筛选与CSS子元素选择器有一定的重合,如下表所示。
表22.2.4 子元素筛选选择器

动手写22.2.4
执行22.2.4.html,输出结果到网页,如下图所示。
图22.2.4 子元素筛选选择器

CSS子元素选择器从1开始计数,不包含-type的选择器,与CSS计数一致;包含-type的选择器,与
jQuery对象元素的索引一致。CSS索引(从1开始)与JavaScript索引(从0开始)不一致,子元素筛选选
择器除了first-child、nth-child、last-child、only-child等指向性特别明确的选择器,建议其余选择器使用
更加明确的id、class选择器代替,减少上层应用的阅读理解难度。

22.2.5 内容筛选

jQuery提供了两个内容筛选选择器,如下表所示。
表22.2.5 内容筛选选择器

动手写22.2.5
执行22.2.5.html,输出结果到网页,如下图所示。

图22.2.5 内容筛选

22.2.6 可见性筛选
jQuery提供了两个可见性选择器,如下表所示。
表22.2.6 可见性筛选选择器

当满足以下任意一种情况时,jQuery认为元素是不可见的:
◇ 元素的display样式为none。
◇ 元素是type=hidden的表单控件。
◇ 元素的高度和宽度都为0。
◇ 元素的祖先元素是隐藏的,该元素也当作隐藏元素。
动手写22.2.6

执行22.2.6.html,输出结果到网页,如下图所示。

图22.2.6 可见性筛选

22.3 DOM
jQuery基本封装了DOM操作的全部方法,从节点访问、创建、修改、移动、删除等方法,到节点属
性操作、样式、事件等,并且增加了节点数据存储。

22.3.1 新建节点
使用本章第2节中提供的选择器可以轻松选择元素,jQuery支持使用闭合标签字符串快速创建元
素。
动手写22.3.1

执行22.3.1.html,输出结果到网页,如下图所示。

图22.3.1 jQuery 新建节点

闭合标签中有嵌套标签也会一并创建,与JavaScriptDOM函数相比,jQuery提供的DOM新建方式更
加简洁,并且结构清晰。

22.3.2 拷贝节点

jQuery提供了三种节点拷贝方法,如下表所示。
表22.3.1 拷贝节点的方法

动手写22.3.2
执行22.3.2.html,输出结果到网页,如下图所示。
图22.3.2 拷贝节点

拷贝节点时若包含数据和事件,jQuery内部会对新节点自动设置对应的数据和事件监听器。设置
withDataAndEvents为true时,deepWithDataAndEvents也默认为true,若不希望拷贝子节点的数据和事件
监听器,则需要显式设置为false,即clone(true, false)。

22.3.3 插入节点

jQuery提供了多种节点插入方式,包括内部插入、外部插入、包裹元素等。
1.插入子节点
在元素内部插入节点如下表所示。
表22.3.2 插入节点

动手写22.3.3
执行22.3.3.html,输出结果到网页,如下图所示。

图22.3.3 在目标节点内部插入节点

2.逆向插入子节点
jQuery提供了append和prepend对应的逆向调用函数appendTo、prependTo,如下表所示。
表22.3.3 逆向插入节点

动手写22.3.4

执行22.3.4.html,输出结果到网页,如下图所示。
图22.3.4 将节点插入其他节点内部

3.插入兄弟节点
在兄弟节点前后插入节点如下表所示。
表22.3.4 插入兄弟节点

动手写22.3.5

执行22.3.5.html,输出结果到网页,如下图所示。
图22.3.5 在目标节点前后插入兄弟节点

在动手写22.3.5中,第一次调用after()方法时,$('li') 只获取一个<li>元素,第二次调用$('li') 时,
获取两个<li>元素,加上初始的一个<li>元素,因此最终共有四个<li>元素。
4.包裹节点
jQuery提供了以下包裹元素的方法,如下表所示。
表22.3.5 包裹节点

(续上表)

动手写22.3.6

执行22.3.6.html,输出结果到网页,如下图所示。
图22.3.6 包裹元素

5.修改元素内容
修改元素的innerHTML、textContent等属性可以实现变相插入子节点,如下表所示。
表22.3.6 修改元素内容

(续上表)

动手写22.3.7
执行22.3.7.html,输出结果到网页,如下图所示。

图22.3.7 获取和设置HTML、文本

22.3.4 移除节点

jQuery对象是元素集合,允许移除全部元素或根据条件移除元素,如下表所示。
表22.3.7 移除节点

1..remove()
动手写22.3.8

执行22.3.8.html,输出结果到网页,如下图所示。
图22.3.8 移除节点

.remove()方法会移除元素和子节点的数据和事件监听器,若在元素移除一段时间之后又需要将元素
重新插入DOM树,则需要使用.detach()方法,.detach()会保留元素的数据和事件监听器。
2..detach()
动手写22.3.9

执行22.3.9.html,输出结果到网页,如下图所示。
图22.3.9 分离节点

在动手写22.3.9中,节点<li id="li2">在分离之后重新添加到网页上,点击事件回调仍然能够正常触
发。
3..empty()
.empty()指清空元素所有子节点。
动手写22.3.10

执行22.3.10.html,输出结果到网页,如下图所示。

图22.3.10 清空节点

4..unwrap()
.unwrap()是指移除元素的父元素,将当前元素和它们的兄弟元素按照原先的顺序插入到父元素的父
元素。
5..unwrap(selector)
.unwrap(selector)是指移除元素匹配selector的父元素,是wrap()的逆向调用函数。
动手写22.3.11
执行22.3.11.html,输出结果到网页,如下图所示。

图22.3.11 移除包裹

22.3.5 替换节点

jQuery提供两种替换节点的方式:替换目标节点和使用新节点替换自身,如下表所示。
表22.3.8 替换节点

替换节点时如果当前jQuery对象集合元素与目标节点数量不匹配,jQuery会拷贝集合中的第一个元
素,用来替换目标节点。
1..replaceAll()
动手写22.3.12
执行22.3.12.html,输出结果到网页,如下图所示。

图22.3.12 替换目标节点

2..replaceWith()
动手写22.3.13

执行22.3.13.html,输出结果到网页,如下图所示。
图22.3.13 替换当前节点

22.3.6 遍历节点

jQuery提供完整的节点遍历方法和过滤方法,包括集合过滤、父元素遍历、子元素遍历、兄弟节点
遍历,如下表所示。
表22.3.9 jQuery遍历节点

(续上表)
22.4 属性操作
jQuery提供HTML属性、DOM属性,以及表单控件的值的操作函数。

22.4.1 获取属性
每个标签对应HTML元素和DOM对象,jQuery提供了两种方法分别获取HTML属性和DOM属性,还
增加了一个快捷方法,用于获取表单控件的值,如下表所示。
表22.4.1 获取属性

动手写22.4.1
执行22.4.1.html,输出结果到网页,如下图所示。

图22.4.1 获取属性

HTML属性是指元素标签在HTML网页中的属性,DOM属性是指元素对应的DOM对象的属性。
jQuery的attr()和prop()方法除了分别操作HTML属性和DOM属性,还将属性名和属性值修正为标准形
式:
◇ HTML标签中出现readonly属性,不论属性为何值,attr()方法均返回readonly,但getAttribute()返
回属性原始值。
◇ DOM对象没有readonly属性,正确名称是readOnly,prop()方法获取readonly属性时,自动转换为
readOnly。

22.4.2 设置属性
jQuery提供了设置属性的方法,如下表所示。
表22.4.2 设置属性
动手写22.4.2

执行22.4.2.html,输出结果到网页,如下图所示。

图22.4.2 设置属性

22.4.3 移除属性
jQuery提供了两种方法移除元素的属性,如下表所示。
表22.4.3 移除属性

动手写22.4.3

执行22.4.3.html,输出结果到网页,如下图所示。

图22.4.3 移除属性

22.5 样式表
22.5.1 获取样式
jQuery支持一次性获取多个样式,如下表所示。
表22.5.1 获取样式

动手写22.5.1
执行22.5.1.html,输出结果到网页,如下图所示。

图22.5.1 获取样式

若样式名称不存在,则返回undefined。

22.5.2 设置样式

设置元素样式的方法如下表所示。
表22.5.2 设置样式的方法

css()方法自动将样式名称由短横线模式转换为驼峰式,两种书写方式都支持。
动手写22.5.2
执行22.5.2.html,输出结果到网页,如下图所示。

图22.5.2 设置样式

jQuery提供了元素的class操作方法,如下表所示。
表22.5.3 class的操作方法

1..addClass()
动手写22.5.3
执行22.5.3.html,输出结果到网页,如下图所示。

图22.5.3 添加样式名

提示
addClass()会自动排除重复的样式名称,不必担心重复添加。
2..removeClass()
动手写22.5.4
执行22.5.4.html,输出结果到网页,如下图所示。

图22.5.4 移除样式名

3..toggleClass()
动手写22.5.5

执行22.5.5.html,输出结果到网页,如下图所示。
图22.5.5 切换样式名

4..hasClass()
动手写22.5.6

执行22.5.6.html,输出结果到网页,如下图所示。

图22.5.6 检测包含样式名

jQuery的hasClass()方法使用字符串包含检测是否具有对应样式名,如果要检测多个样式名称,应该
使用hasClass(classNameA)&&hasClass(classNameB)方式。

22.5.3 单位
jQuery样式可以不用指定样式值的单位,所以jQuery可以尝试为样式的值添加默认单位,比
如.css(fontSize, 16),这样jQuery会自动将16修改为16px后再设置元素的样式。
动手写22.5.7
执行22.5.7.html,输出结果到网页,如下图所示。

图22.5.7 自动补齐单位

CSS样式line-height不指定单位时,应用em。设置此类具有默认单位的样式时,注意使用正确的单
位。

22.5.4 尺寸
jQuery提供与CSS盒模型对应的高宽设置和获取方法,如下表所示。
表22.5.4 jQuery获取元素尺寸的方法
动手写22.5.8

执行22.5.8.html,输出结果到网页,如下图所示。
图22.5.8 CSS盒子模型

元素高宽对应的设置方法,如下表所示。
表22.5.5 设置高宽的方法

height(value)和width(value)系列方法支持接收参数value表示高度或宽度的值,即可设置每个元素对
应CSS盒子模型的值;最终实际是通过修改元素的height、width的样式值实现,并不会修改padding、
border。
动手写22.5.9
执行22.5.9.html,输出结果到网页,如下图所示。

图22.5.9 设置高宽
22.5.5 位置

jQuery提供了两个与元素定位(position)的函数,如下表所示。
表22.5.6 获取位置

动手写22.5.10
执行22.5.10.html,输出结果到网页,如下图所示。

图22.5.10 获取位置

offset()支持设置元素的位置,如下表所示。
表22.5.7 设置位置

动手写22.5.11
执行22.5.11.html,输出结果到网页,如下图所示。

图22.5.11 设置位置

jQuery提供了用于操作滚动条的方法,如下表所示。
表22.5.8 设置滚动条位置

动手写22.5.12

执行22.5.12.html,输出结果到网页,如下图所示。

图22.5.12 滚动条位置

直接在<script>标签内置滚动条位置会失败,此时文档还未加载完整,要使用$(callback)在
DOMContentLoaded事件触发之后获取和修改滚动条位置。
使用.offsetParent()获取元素的第一个定位祖先元素,即祖先元素的position样式为absolute、fixed、
relative的其中一项,该方法不接收参数,直接调用即可。
动手写22.5.13
执行22.5.13.html,输出结果到网页,如下图所示。

图22.5.13 获取定位祖先元素

22.6 事件
jQuery提供了事件绑定、事件解绑、事件模拟等方法。
22.6.1 绑定与解绑

jQuery的事件管理涵盖了普通事件、事件委托、自销毁监听器,如下表所示。
表22.6.1 jQuery的事件管理

动手写22.6.1
执行22.6.1.html,输出结果到网页,如下图所示。

图22.6.1 事件绑定与解绑

◇one事件点击之后自动解绑,控制台显示发生一次点击事件之后,one()绑定的事件不再触发。
◇ <li>元素点击事件计数counter达到2之后,解绑该事件,在控制台看到只输出了<ul>元素捕获到
的点击事件。
◇ 在所有事件绑定之后新增的<li class="li-2">元素列表2</li>触发点击事件,被<ul>元素捕获到。
jQuery管理了全部的事件监听器,因此支持移除全部监听器,而不像element.removeEventListener
(type, callback)必须指定监听器。
22.6.2 事件模拟

jQuery提供了两种事件模拟的方法,如下表所示。
表22.6.2 事件模拟的方法

动手写22.6.2

执行22.6.2.html,输出结果到网页,如下图所示。

图22.6.2 事件模拟
控制台显示trigger()方法触发的事件传递给了<ul>元素,triggerHandler()方法触发的事件没有传递给
<ul>元素。

22.6.3 事件对象

JavaScript原生的Event事件对象有部分属性是只读,不支持修改,jQuery对其进行了封装,使用
jQuery.Event对象。使用event.originalEvent要访问原生的事件对象。
动手写22.6.3

执行22.6.3.html,输出结果到网页,如下图所示。

图22.6.3 事件对象

对于大部分原生事件的属性和方法,jQuery.Event都进行了拷贝,但部分类型事件特有的属性,
jQuery没有拷贝,比如触摸事件的touches、changedTouches等属性。最新版本的jQuery通过
Object.defineProperty()的方式拷贝了几乎全部原生Event的属性和方法。

22.6.4 文档初始化
jQuery的$(callback) 默认将callback作为DOMContentLoaded事件回调,确保DOM树加载完成之后立
刻执行程序。jQuery内部记录DOMContentLoaded事件是否已触发,在触发之后再调用$(callback) 即可
执行callback。
动手写22.6.4
执行22.6.4.html,输出结果到网页,如下图所示。
图22.6.4 文档初始化

22.7 数据
jQuery支持为元素设置数据记录,而不需要修改元素的HTML属性。jQuery内部设置了专用的数据
存储器,提供的数据操作方法如下表所示。
表22.7.1 数据操作方法

22.7.1 获取数据

动手写22.7.1
执行22.7.1.html,输出结果到网页,如下图所示。

图22.7.1 获取数据

在HTML5 data-属性发布之后,jQuery自动将HTML元素的data-*属性存放的数据引用到jQuery内部
的存储器中。
动手写22.7.2
执行22.7.2.html,如下图所示。

图22.7.2 获取data属性数据

22.7.2 设置数据

动手写22.7.3

执行22.7.3.html,输出结果到网页,如下图所示。
图22.7.3 设置数据

22.7.3 移除数据

动手写22.7.4

执行22.7.4.html,输出结果到网页,如下图所示。
图22.7.4 移除数据

22.8 动画
jQuery提供了完整的动画管理队列和自定义动画函数,并实现了基本的显示隐藏、淡入淡出、滑入
滑出动画。jQuery为了方便扩展动画,为动画队列设置了默认名称fx。
jQuery在提供的方法里又增加toggle,表示在两种状态之间切换。

22.8.1 显示隐藏

jQuery提供了三个简洁的显示隐藏动画函数,这些函数都接收两个参数,若两个参数都没有则直接
显示和隐藏元素,无动画过程,如下表所示。
表22.8.1 显示隐藏动画的方法

动手写22.8.1
执行22.8.1.html,输出结果到网页,如下图所示。

图22.8.1 显示隐藏动画

jQuery执行hide动画之后,自动将元素的display设置为none;执行动画hide、show动画之前,自动恢
复元素的display样式,并将overflow设置为hidden,避免动画执行期间出现子元素溢出;执行hide、show
动画时,自动计算width、height、padding、margin、opacity等样式,实现渐变。

22.8.2 滑动动画
滑动动画的方法如下表所示。
表22.8.2 滑入滑出动画的方法

动手写22.8.2

执行22.8.2.html,输出结果到网页,如下图所示。

图22.8.2 滑动动画
22.8.3 淡入淡出

渐变动画分为淡入和淡出,方法如下表所示。
表22.8.3 淡入淡出动画的方法

动手写22.8.3
执行22.8.3.html,输出结果到网页,如下图所示。

图22.8.3 淡入淡出动画

22.8.4 动画队列
动画队列是指多个动画按照顺序执行。比如淡入→淡出→淡入→滑出,动画必须按照顺序串行执
行;如果并行执行,动画就会陷入混乱。jQuery的动画队列函数如下表所示。
表22.8.4 设置动画队列的方法

(续上表)
jQuery自行管理动画队列,一般不需要显示执行dequeue函数,但如果使用queue()添加了新的动画函
数,则需要结合dequeue()在合适的时间执行下一个动画函数。
动手写22.8.4
执行22.8.4.html,输出结果到网页,如下图所示。

图22.8.4 动画队列

在动手写22.8.4中,依次为<h1>添加hide、show、fadeOut、fadeIn、slideUp、slideDown等动画,并
在显示阶段穿插自定义的queue函数。设置自定义queue函数之后,需要调用dequeue触发后续queue(动
画或其他自定义queue函数),否则动画会一直处于inprogress状态。

22.8.5 清除动画

正在执行动画时,想要清除动画有多种方式,如下表所示。
表22.8.5 清除动画的方法
动手写22.8.5
执行22.8.5.html,输出结果到网页,如下图所示。

图22.8.5 清除动画

一般情况下,使用finish()方法停止执行当前动画并进入结束状态。

22.8.6 自定义动画

jQuery的animate()方法用于执行自定义动画,语法如下:

参数说明:
◇ properties:必选参数,表示动画执行完成后元素的CSS样式,jQuery也支持部分可计算的DOM属
性用于展示动画,比如scrollTop、scrollLeft。
◇ duration:可选参数,表示动画的执行时间。
◇ easing:可选参数,表示动画缓动函数,jQuery提供linear和swing两种,animate方法默认使用
swing。
◇ callback:可选参数,表示动画执行完毕的回调函数。
缓动函数用于计算每个时间点对应的CSS样式值,比如匀速函数、加速函数、匀加速函数、先加速
再减速函数等,使用合适的缓动函数让动画更真实。
动手写22.8.6

执行22.8.6.html,输出结果到网页,如下图所示。
图22.8.6 自定义动画

animate除了执行CSS动画,还可以执行部分修改属性导致外观改变的动画,比如scrollTop、
scrollLeft等。
jQuery提供的动画都具有默认动画时间,默认是jQuery.fx.speeds._default,时间为200毫秒,同时提
供了jQuery.fx.speeds.fast和jQuery.fx.speeds.slow两个选项。

22.9 AJAX
使用JavaScript原生的XMLHTTPRequest对象执行AJAX请求,代码较冗长。将本节示例代码拷贝到
NGINX的html目录,通过localhost访问。

22.9.1 发送请求

jQuery提供了简洁的调用方法ajax()函数,并在ajax()的基础上增加get、post、getJSON、getScript、
load等快捷方法, 如下表所示。
表22.9.1 jQuery的ajax快捷方法

动手写22.9.1
执行22.9.1.html,输出结果到网页,如下图所示。
图22.9.1 AJAX快捷方法

$.getJSON('22.9.1.json')获取JSON文件之后,将JSON字符串转换为JavaScript对象,传递给回调函
数。
$.getScript('22.9.1.js')为了避免浏览器缓存脚本,会自动在网址后增加一段查询参数,形
如“_=123456789”,获取脚本并执行。
$.get('22.9.1.txt')获取文件的文本字符串。
jQuery提供了底层的jQuery.ajax(options)方法,用于控制完整的AJAX请求流程,如下表所示。
表22.9.2 jQuery的ajax参数

(续上表)
(续上表)
动手写22.9.2

执行22.9.2.html,输出结果到网页,如下图所示。
图22.9.2 AJAX底层方法

先触发success或error回调函数,再触发statusCode配置的回调函数,最后触发complete回调函数。
jQuery的ajax快捷方法也都支持接收一个options参数,使用方式与jQuery.ajax()一致,同时使用快捷
方法的默认值覆盖ajax()函数的默认值。

22.9.2 全局事件

jQuery为DOM元素增加了全局的AJAX事件,每次AJAX请求发生时,都会在元素上触发对应的事
件,如下表所示。
表22.9.3 AJAX事件列表

动手写22.9.3
执行22.9.3.html,输出结果到网页,如下图所示。

图22.9.3 AJAX事件

使用AJAX事件可以在指定DOM元素上控制全部的AJAX请求过程,根据请求的生命周期展示网络
请求提示。

22.10 小结
本章详细介绍了jQuery这一款JavaScript库函数,jQuery对象封装了大部分原生JavaScript的CSS、
DOM、Event、Ajax方法,并且比原生方法更简单易用。

22.11 知识拓展
22.11.1 框架兼容
$作为jQuery的别名,如果网页中存在其他框架也使用$作为函数名时,只需要执行
jQuery.noConflict()函数,即可释放对$的占据,$会恢复到原来的值。
22.11.1.html使用noConflict()释放jQuery占据的$符号,如下所示。
动手写22.11.1

执行22.11.1.html,输出结果到网页,如下图所示。

图22.11.1 框架兼容

调用jQuery.noConflict()之后,jQuery释放对标识符$的控制权。释放$的控制权之后,在
jQuery(callback)的回调函数内仍然可以使用$作为局部变量。
如果网页中只有一个版本的jQuery,调用jQuery.noConflict(true)将释放jQuery标识符的控制权。如果
网页中包含两个版本的jQuery,在第二个版本中调用jQuery.noConflict(true)将返回全局的jQuery给第一个
版本。
22.11.2.html释放jQuery占据的$符号,但是在$(callback)的回调函数内部继续使用$符号表示
jQuery,如下所示。
动手写22.11.2
执行22.11.2.html,输出结果到网页,如下图所示。

图22.11.2 局部jQuery标识符

在动手写22.11.2中,只有一个版本的jQuery,jQuery.noConflict(true)返回唯一的jQuery,标识符
jQuery被重置为undefined。

22.11.2 插件开发
jQuery插件开发非常简单,目前有大量jQuery插件,包括开源的前端框架Bootstrap也基于jQuery开
发了对应的JavaScript框架。
22.11.3.html为jQuery增加disable插件,如下所示。
动手写22.11.3
执行22.11.3.html,输出结果到网页,如下图所示。

图22.11.3 jQuery插件开发

使用形如$.fn.pluginMethod =function(){}的方式,即可为jQuery增加插件功能。
动手写22.11.4
执行22.11.4.html,输出结果到网页,如下图所示。

图22.11.4 鼠标长按插件

长按<button>按钮,<body>元素会接收到两次长按事件:一次是从<button>元素冒泡传递的事件,
另一次是<body>元素监听到的长按事件。
第23章 项目实战
学以致用、用以促学,在掌握了JavaScript基本原理之后,本章以一款工资个税计算器实例展示项目
实战的基本流程。

23.1 项目分析
项目分析是项目生命周期的重要环节,决定了项目是否做以及怎样做。落实到软件工程师,则需要
考虑可行性、时间成本、人力成本、可维护性等方面。项目分析要形成文档,为项目指明目标,这个目
标是能够实现或者能够分阶段实现的。如果一个项目比较复杂,可以拆分成多个子项目,子项目实现各
自的小目标,最终一起实现项目整体目标。
项目分析得出的结论,要具有充足的理由。切勿在没有充足理由的情况下,得出可以做或不可以做
的结论。

23.2 技术选型
前端开发包罗万象,不仅仅是网页千差万别,前端框架也是百花齐放。前端技术选型涉及库与框架
的选择、构建流程选择、模块化开发与简单使用。将目前流行的三款MVVM框架进行对比,如下表所
示。
表23.2.1 前端MVVM框架对比

三者之间并不是互相替代的,甚至可以组合使用,但选择合适的工具可以解决团队开发中的大部分
问题。着重强调维护性的工具,要么使用引导比较欠缺,要么本身问题不少。
选择框架要更加注重兼容性、维护性(支持力度),选择工具要更加注重易用性。选择工具是为了
解决已有问题,而不是为了产生新问题。另外,选择框架要很慎重,除了框架本身,还需要考虑团队成
员对这个框架的掌握程度和学习成本。考虑兼容性是希望能够更加简单地升级框架。

23.3 个税计算器
1.计算器项目分析
个人所得税,简称个税,是国家依据个人所得税法对本国公民、居住在本国境内的个人的所得和境
外个人来源于本国的所得征收的一种所得税。对个人而言,直观的感受是每月按照月工资和纳税比例缴
纳个税。
个人纳税税额,是月工资扣除五险一金、个税免征额之后,按照税率表计算所得需上缴税款的数
额。这个计算过程中只有五险一金是按照地方标准实施,以北京和上海为例,个人缴纳养老保险和医疗
保险略有不同。地方政府设定公积金参考范围,因此也会出现同一城市不同企业的员工缴纳公积金的比
例不同。部分企业为了提高员工的购房能力,还会缴纳补充公积金。
本次实战设计的个税计算器支持设置五险一金比例、补充公积金比例,相同税前工资根据不同纳税
比例计算得出的税后工资会有所不同。
2.计算机框架选型
本章使用一款新的MVVM框架Vue实现个税计算器。MVVM利用ViewModel将Model的数据绑定到
View,Model是JavaScript中的对象,View是网页中的HTML代码,ViewModel是框架设计的连接对象。
在网页中,修改JavaScript对象的值,MVVM框架会自动修改对应的HTML元素,不需要再进行烦
琐的DOM创建、修改、删除等操作。利用ViewModel,可以将Model映射到多个View上,对应关系极其
清晰明了。
个税计算器使用CSS样式库bootstrap.css美化页面。Bootstrap主要是一款CSS框架,同时基于jQuery
实现了部分JavaScript组件,本节只使用Bootstrap的CSS框架。

23.3.1 网页结构

实际项目往往会很复杂,一个网页通过<script>标签引入几十甚至上百个JavaScript文件,选择合适
的代码结构对项目管理非常有帮助。本示例采用的代码结构如下:

浏览器执行JavaScript会阻塞网页渲染,因此JavaScript框架代码和计算器代码放置在网页</body>结
束标签前面,如下所示。
23.3.2 税率表

个人纳税依据高收入者多纳税的原则,将纳税比例分成七个等级。示例采用从2018年10月1日开始
施行的税率表,如下所示。

速算扣除数和税率表,如下图所示。
图23.3.1 税率表(5000)

23.3.3 五险一金

每个城市的五险一金缴纳标准不完全一致,五险一金缴费系数不能超过上年平均工资的300%。根
据各地五险一金比例,设置计算比例,如下图所示。

图23.3.2 五险一金缴费系数

提示
各地上一年的平均工资不一样,五险一金的计算与当地收入水平有关,本节以15000元作为五险一
金的缴纳上限。

23.3.4 纳税计算
计算器的HTML代码按照工资、五险一金、税率表划分为三个部分,如下所示。
1.工资
工资部分按照现行5000元免征额缴税,支持设置本省上年度社平工资。
2.五险一金
五险一金和补充公积金,两个项目都支持配置缴费比例,每个城市规定缴费比例略有不同,注意区
分。
3.税率表
税率表按照7级展示应纳税所得额、税率、速算扣除数。
4.脚本代码
使用Vue定义ViewModel,将个税计算器使用的数据项和上述HTML文件结合起来,如下所示。
执行上述代码,按照应纳税所得额和应纳个税计算公式,计算税后工资,如下图所示。

图23.3.3 计算税后工资

输入税前工资,自动计算税后工资。计算过程和结果使用的数字,使用预先定义的函数,
ViewModel根据对应关系,自动计算并更新HTML元素的内容。

23.4 小结
本章使用个税计算器展示了在项目开发中JavaScript和HTML、CSS的结合使用,希望各位读者通过
本章的实战项目进一步掌握JavaScript。最后,欢迎你加入JavaScript的开发之旅!

You might also like