第1章 什么是 JavaScript

JavaScript 是一门用来与网页交互的脚本语言,包含以下三个组成部分。 ECMAScript:由 ECMA-262 定义并提供核心功能。 文档对象模型(DOM):提供与网页内容交互的方法和接口。 浏览器对象模型(BOM):提供与浏览器交互的方法和接口。 JavaScript 的这三个部分得到了五大 Web 浏览器(IE、Firefox、Chrome、Safari 和 Opera)不同程度 的支持。所有浏览器基本上对 ES5(ECMAScript 5)提供了完善的支持,而对 ES6(ECMAScript 6)和 ES7(ECMAScript 7)的支持度也在不断提升。这些浏览器对 DOM 的支持各不相同,但对 Level 3 的支 持日益趋于规范。HTML5 中收录的 BOM 会因浏览器而异,不过开发者仍然可以假定存在很大一部分 公共特性

第2章 HTML 中的 JavaScript

JavaScript 是通过script元素插入到 HTML 页面中的。这个元素可用于把 JavaScript 代码嵌入到
HTML 页面中,跟其他标记混合在一起,也可用于引入保存在外部文件中的 JavaScript。本章的重点可
以总结如下。
要包含外部 JavaScript 文件,必须将 src 属性设置为要包含文件的 URL。文件可以跟网页在同
一台服务器上,也可以位于完全不同的域。
所<script元素会依照它们在网页中出现的次序被解释。在不使用 defer 和 async 属性的
情况下,包含在script元素中的代码必须严格按次序解释。
对不推迟执行的脚本,浏览器必须解释完位于script元素中的代码,然后才能继续渲染页面
的剩余部分。为此,通常应该把script元素放到页面末尾,介于主内容之后及/body标签
之前。
可以使用 defer 属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出
的次序执行。
可以使用 async 属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异
步脚本不能保证按照它们在页面中出现的次序执行。
通过使用noscript元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启
用脚本,则noscript元素中的任何内容都不会被渲染

第3章 语言基础

JavaScript 的核心语言特性在 ECMA-262 中以伪语言 ECMAScript 的形式来定义。ECMAScript 包含
所有基本语法、操作符、数据类型和对象,能完成基本的计算任务,但没有提供获得输入和产生输出的
机制。理解 ECMAScript 及其复杂的细节是完全理解浏览器中 JavaScript 的关键。下面总结一下
ECMAScript 中的基本元素。
ECMAScript 中的基本数据类型包括 Undefined、Null、Boolean、Number、String 和 Symbol。
与其他语言不同,ECMAScript 不区分整数和浮点值,只有 Number 一种数值数据类型。
Object 是一种复杂数据类型,它是这门语言中所有对象的基类。
严格模式为这门语言中某些容易出错的部分施加了限制。
ECMAScript 提供了 C 语言和类 C 语言中常见的很多基本操作符,包括数学操作符、布尔操作符、
关系操作符、相等操作符和赋值操作符等。
这门语言中的流控制语句大多是从其他语言中借鉴而来的,比如 if 语句、for 语句和 switch
语句等。
ECMAScript 中的函数与其他语言中的函数不一样。
不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
不指定返回值的函数实际上会返回特殊值 undefined。

第 4 章 变量、作用域与内存

JavaScript 变量可以保存两种类型的值:原始值和引用值。原始值可能是以下 6 种原始数据类型之
一:Undefined、Null、Boolean、Number、String 和 Symbol。原始值和引用值有以下特点。
原始值大小固定,因此保存在栈内存上。
从一个变量到另一个变量复制原始值会创建该值的第二个副本。
引用值是对象,存储在堆内存上。
包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。
typeof 操作符可以确定值的原始类型,而 instanceof 操作符用于确保值的引用类型。
任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个
上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。执行上下文可以总结
如下。
执行上下文分全局上下文、函数上下文和块级上下文。
代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃
至全局上下文中的变量。
全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
变量的执行上下文用于确定什么时候释放内存。
JavaScript 是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。JavaScript 的垃圾回收
程序可以总结如下。
离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript 引擎不再使用这种算
法,但某些旧版本的 IE 仍然会受这种算法的影响,原因是 JavaScript 会访问非原生 JavaScript 对
象(如 DOM 元素)。
引用计数在代码中存在循环引用时会出现问题。
解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对
象、全局对象的属性和循环引用都应该在不需要时解除引用。

第 5 章 基本引用类型

JavaScript 中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。
引用值与传统面向对象编程语言中的类相似,但实现不同。
Date 类型提供关于日期和时间的信息,包括当前日期、时间及相关计算。
RegExp 类型是 ECMAScript 支持正则表达式的接口,提供了大多数基础的和部分高级的正则表
达式功能。
JavaScript 比较独特的一点是,函数实际上是 Function 类型的实例,也就是说函数也是对象。因
为函数也是对象,所以函数也有方法,可以用于增强其能力。
由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有 3 种原始值包装类
型:Boolean、Number 和 String。它们都具备如下特点。
每种包装类型都映射到同名的原始类型。
以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相
应的数据。
涉及原始值的语句执行完毕后,包装对象就会被销毁。
当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在
大多数 ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函
数都是 Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法。

第 6 章 集合引用类型

JavaScript 中的对象是引用值,可以通过几种内置引用类型创建特定类型的对象。
引用类型与传统面向对象编程语言中的类相似,但实现不同。
Object 类型是一个基础类型,所有引用类型都从它继承了基本的行为。
Array 类型表示一组有序的值,并提供了操作和转换值的能力。
定型数组包含一套不同的引用类型,用于管理数值在内存中的类型。
Date 类型提供了关于日期和时间的信息,包括当前日期和时间以及计算。
RegExp 类型是 ECMAScript 支持的正则表达式的接口,提供了大多数基本正则表达式以及一些
高级正则表达式的能力。
JavaScript 比较独特的一点是,函数其实是 Function 类型的实例,这意味着函数也是对象。由于
函数是对象,因此也就具有能够增强自身行为的方法。
因为原始值包装类型的存在,所以 JavaScript 中的原始值可以拥有类似对象的行为。有 3 种原始值
包装类型:Boolean、Number 和 String。它们都具有如下特点。
每种包装类型都映射到同名的原始类型。
在以读模式访问原始值时,后台会实例化一个原始值包装对象,通过这个对象可以操作数据。
涉及原始值的语句只要一执行完毕,包装对象就会立即销毁。
JavaScript 还有两个在一开始执行代码时就存在的内置对象:Global 和 Math。其中,Global 对
象在大多数 ECMAScript 实现中无法直接访问。不过浏览器将 Global 实现为 window 对象。所有全局
变量和函数都是 Global 对象的属性。Math 对象包含辅助完成复杂数学计算的属性和方法。
ECMAScript 6 新增了一批引用类型:Map、WeakMap、Set 和 WeakSet。这些类型为组织应用程序
数据和简化内存管理提供了新能力。

第 7 章 迭代器与生成器

迭代是一种所有编程语言中都可以看到的模式。ECMAScript 6 正式支持迭代模式并引入了两个新的
语言特性:迭代器和生成器。
迭代器是一个可以由任意对象实现的接口,支持连续获取对象产出的每一个值。任何实现 Iterable
接口的对象都有一个 Symbol.iterator 属性,这个属性引用默认迭代器。默认迭代器就像一个迭代器
工厂,也就是一个函数,调用之后会产生一个实现 Iterator 接口的对象。
迭代器必须通过连续调用 next()方法才能连续取得值,这个方法返回一个 IteratorObject。这
个对象包含一个 done 属性和一个 value 属性。前者是一个布尔值,表示是否还有更多值可以访问;后
者包含迭代器返回的当前值。这个接口可以通过手动反复调用 next()方法来消费,也可以通过原生消
费者,比如 for-of 循环来自动消费。
生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了 Iterable 接口,
因此可用在任何消费可迭代对象的地方。生成器的独特之处在于支持 yield 关键字,这个关键字能够
暂停执行生成器函数。使用 yield 关键字还可以通过 next()方法接收输入和产生输出。在加上星号之
后,yield 关键字可以将跟在它后面的可迭代对象序列化为一连串值。

第 8 章 对象、类与面向对象编程

对象在代码执行过程中的任何时候都可以被创建和增强,具有极大的动态性,并不是严格定义的实
体。下面的模式适用于创建对象。
工厂模式就是一个简单的函数,这个函数可以创建对象,为它添加属性和方法,然后返回这个
对象。这个模式在构造函数模式出现后就很少用了。
使用构造函数模式可以自定义引用类型,可以使用 new 关键字像创建内置类型实例一样创建自
定义类型的实例。不过,构造函数模式也有不足,主要是其成员无法重用,包括函数。考虑到
函数本身是松散的、弱类型的,没有理由让函数不能在多个对象实例间共享。
原型模式解决了成员共享的问题,只要是添加到构造函数 prototype 上的属性和方法就可以共
享。而组合构造函数和原型模式通过构造函数定义实例属性,通过原型定义共享的属性和方法。
JavaScript 的继承主要通过原型链来实现。原型链涉及把构造函数的原型赋值为另一个类型的实例。
这样一来,子类就可以访问父类的所有属性和方法,就像基于类的继承那样。原型链的问题是所有继承
的属性和方法都会在对象实例间共享,无法做到实例私有。盗用构造函数模式通过在子类构造函数中调
用父类构造函数,可以避免这个问题。这样可以让每个实例继承的属性都是私有的,但要求类型只能通
过构造函数模式来定义(因为子类不能访问父类原型上的方法)。目前最流行的继承模式是组合继承,
即通过原型链继承共享的属性和方法,通过盗用构造函数继承实例属性。
除上述模式之外,还有以下几种继承模式。
原型式继承可以无须明确定义构造函数而实现继承,本质上是对给定对象执行浅复制。这种操
作的结果之后还可以再进一步增强。
与原型式继承紧密相关的是寄生式继承,即先基于一个对象创建一个新对象,然后再增强这个
新对象,最后返回新对象。这个模式也被用在组合继承中,用于避免重复调用父类构造函数导
致的浪费。
寄生组合继承被认为是实现基于类型继承的最有效方式。
ECMAScript 6 新增的类很大程度上是基于既有原型机制的语法糖。类的语法让开发者可以优雅地定
义向后兼容的类,既可以继承内置类型,也可以继承自定义类型。类有效地跨越了对象实例、对象原型
和对象类之间的鸿沟。

第 9 章 代理与反射

代理是 ECMAScript 6 新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟出了
一片前所未有的 JavaScript 元编程及抽象的新天地。
从宏观上看,代理是真实 JavaScript 对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,
而这些捕获器可以拦截绝大部分 JavaScript 的基本操作和方法。在这个捕获器处理程序中,可以修改任
何基本操作的行为,当然前提是遵从捕获器不变式。
与代理如影随形的反射 API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射 API
看作一套基本操作,这些操作是绝大部分 JavaScript 对象 API 的基础。
代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟
踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可
观察对象。

第 10 章 函 数

函数是 JavaScript 编程中最有用也最通用的工具。ECMAScript 6 新增了更加强大的语法特性,从而
让开发者可以更有效地使用函数。
函数表达式与函数声明是不一样的。函数声明要求写出函数名称,而函数表达式并不需要。没
有名称的函数表达式也被称为匿名函数。
ES6 新增了类似于函数表达式的箭头函数语法,但两者也有一些重要区别。
JavaScript 中函数定义与调用时的参数极其灵活。arguments 对象,以及 ES6 新增的扩展操作符,
可以实现函数定义和调用的完全动态化。
函数内部也暴露了很多对象和引用,涵盖了函数被谁调用、使用什么调用,以及调用时传入了
什么参数等信息。
JavaScript 引擎可以优化符合尾调用条件的函数,以节省栈空间。
闭包的作用域链中包含自己的一个变量对象,然后是包含函数的变量对象,直到全局上下文的
变量对象。
通常,函数作用域及其中的所有变量在函数执行完毕后都会被销毁。
闭包在被函数返回之后,其作用域会一直保存在内存中,直到闭包被销毁。
函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。
立即调用的函数表达式如果不在包含作用域中将返回值赋给一个变量,则其包含的所有变量都
会被销毁。
虽然 JavaScript 没有私有对象属性的概念,但可以使用闭包实现公共方法,访问位于包含作用域
中定义的变量。
可以访问私有变量的公共方法叫作特权方法。
特权方法可以使用构造函数或原型模式通过自定义类型中实现,也可以使用模块模式或模块增
强模式在单例对象上实现。

第 11 章 期约与异步函数

长期以来,掌握单线程 JavaScript 运行时的异步行为一直都是个艰巨的任务。随着 ES6 新增了期约
和 ES8 新增了异步函数,ECMAScript 的异步编程特性有了长足的进步。通过期约和 async/await,不仅
可以实现之前难以实现或不可能实现的任务,而且也能写出更清晰、简洁,并且容易理解、调试的代码。
期约的主要功能是为异步代码提供了清晰的抽象。可以用期约表示异步执行的代码块,也可以用期
约表示异步计算的值。在需要串行异步代码时,期约的价值最为突出。作为可塑性极强的一种结构,期
约可以被序列化、连锁使用、复合、扩展和重组。
异步函数是将期约应用于 JavaScript 函数的结果。异步函数可以暂停执行,而不阻塞主线程。无论
是编写基于期约的代码,还是组织串行或平行执行的异步代码,使用异步函数都非常得心应手。异步函
数可以说是现代 JavaScript 工具箱中最重要的工具之一。

第 12 章 BOM

浏览器对象模型(BOM,Browser Object Model)是以 window 对象为基础的,这个对象代表了浏
览器窗口和页面可见的区域。window 对象也被复用为 ECMAScript 的 Global 对象,因此所有全局变
量和函数都是它的属性,而且所有原生类型的构造函数和普通函数也都从一开始就存在于这个对象之
上。本章讨论了 BOM 的以下内容。
要引用其他 window 对象,可以使用几个不同的窗口指针。
通过 location 对象可以以编程方式操纵浏览器的导航系统。通过设置这个对象上的属性,可
以改变浏览器 URL 中的某一部分或全部。
使用 replace()方法可以替换浏览器历史记录中当前显示的页面,并导航到新 URL。
navigator 对象提供关于浏览器的信息。提供的信息类型取决于浏览器,不过有些属性如
userAgent 是所有浏览器都支持的。
BOM 中的另外两个对象也提供了一些功能。screen 对象中保存着客户端显示器的信息。这些信息
通常用于评估浏览网站的设备信息。history 对象提供了操纵浏览器历史记录的能力,开发者可以确
定历史记录中包含多少个条目,并以编程方式实现在历史记录中导航,而且也可以修改历史记录。

第 13 章 客户端检测

客户端检测是 JavaScript 中争议最多的话题之一。因为不同浏览器之间存在差异,所以经常需要根
据浏览器的能力来编写不同的代码。客户端检测有不少方式,但下面两种用得最多。
能力检测,在使用之前先测试浏览器的特定能力。例如,脚本可以在调用某个函数之前先检查
它是否存在。这种客户端检测方式可以让开发者不必考虑特定的浏览器或版本,而只需关注某
些能力是否存在。能力检测不能精确地反映特定的浏览器或版本。
用户代理检测,通过用户代理字符串确定浏览器。用户代理字符串包含关于浏览器的很多信息,
通常包括浏览器、平台、操作系统和浏览器版本。用户代理字符串有一个相当长的发展史,很
多浏览器都试图欺骗网站相信自己是别的浏览器。用户代理检测也比较麻烦,特别是涉及 Opera
会在代理字符串中隐藏自己信息的时候。即使如此,用户代理字符串也可以用来确定浏览器使
用的渲染引擎以及平台,包括移动设备和游戏机。
在选择客户端检测方法时,首选是使用能力检测。特殊能力检测要放在次要位置,作为决定代码逻
辑的参考。用户代理检测是最后一个选择,因为它过于依赖用户代理字符串。
浏览器也提供了一些软件和硬件相关的信息。这些信息通过 screen 和 navigator 对象暴露出来。
利用这些 API,可以获取关于操作系统、浏览器、硬件、设备位置、电池状态等方面的准确信息。

第 14 章 DOM

文档对象模型(DOM,Document Object Model)是语言中立的 HTML 和 XML 文档的 API。DOM
Level 1 将 HTML 和 XML 文档定义为一个节点的多层级结构,并暴露出 JavaScript 接口以操作文档的底
层结构和外观。
DOM 由一系列节点类型构成,主要包括以下几种。
Node 是基准节点类型,是文档一个部分的抽象表示,所有其他类型都继承 Node。
Document 类型表示整个文档,对应树形结构的根节点。在 JavaScript 中,document 对象是
Document 的实例,拥有查询和获取节点的很多方法。
Element 节点表示文档中所有 HTML 或 XML 元素,可以用来操作它们的内容和属性。
其他节点类型分别表示文本内容、注释、文档类型、CDATA 区块和文档片段。
DOM 编程在多数情况下没什么问题,在涉及script和style元素时会有一点兼容性问题。因
为这些元素分别包含脚本和样式信息,所以浏览器会将它们与其他元素区别对待。
要理解 DOM,最关键的一点是知道影响其性能的问题所在。DOM 操作在 JavaScript 代码中是代价
比较高的,NodeList 对象尤其需要注意。NodeList 对象是“实时更新”的,这意味着每次访问它都
会执行一次新的查询。考虑到这些问题,实践中要尽量减少 DOM 操作的数量。
MutationObserver 是为代替性能不好的 MutationEvent 而问世的。使用它可以有效精准地监控
DOM 变化,而且 API 也相对简单。

第 15 章 DOM 扩展

虽然 DOM 规定了与 XML 和 HTML 文档交互的核心 API,但其他几个规范也定义了对 DOM 的扩
展。很多扩展都基于之前的已成为事实标准的专有特性标准化而来。本章主要介绍了以下 3 个规范。
Selectors API 为基于 CSS 选择符获取 DOM 元素定义了几个方法:querySelector()、
querySelectorAll()和 matches()。
Element Traversal 在 DOM 元素上定义了额外的属性,以方便对 DOM 元素进行遍历。这个需求
是因浏览器处理元素间空格的差异而产生的。
HTML5 为标准 DOM 提供了大量扩展。其中包括对 innerHTML 属性等事实标准进行了标准化,
还有焦点管理、字符集、滚动等特性。
DOM 扩展的数量总体还不大,但随着 Web 技术的发展一定会越来越多。浏览器仍然没有停止对专
有扩展的探索,如果出现成功的扩展,那么就可能成为事实标准,或者最终被整合到未来的标准中。

第 16 章 DOM2 和 DOM3

DOM2 规范定义了一些模块,用来丰富 DOM1 的功能。DOM2 Core 在一些类型上增加了与 XML
命名空间有关的新方法。这些变化只有在使用 XML 或 XHTML 文档时才会用到,在 HTML 文档中则没
有用处。DOM2 增加的与 XML 命名空间无关的方法涉及以编程方式创建 Document 和 DocumentType
类型的新实例。
DOM2 Style 模块定义了如何操作元素的样式信息。
每个元素都有一个关联的 style 对象,可用于确定和修改元素特定的样式。
要确定元素的计算样式,包括应用到元素身上的所有 CSS规则,可以使用getComputedStyle()
方法。
通过 document.styleSheets 集合可以访问文档上所有的样式表。
DOM2 Traversal and Range 模块定义了与 DOM 结构交互的不同方式。
NodeIterator 和 TreeWalker 可以对 DOM 树执行深度优先的遍历。
NodeIterator 接口很简单,每次只能向前和向后移动一步。TreeWalker 除了支持同样的行
为,还支持在 DOM 结构的所有方向移动,包括父节点、同胞节点和子节点。
范围是选择 DOM 结构中特定部分并进行操作的一种方式。
通过范围的选区可以在保持文档结构完好的同时从文档中移除内容,也可复制文档中相应的部分。

第 17 章 事 件

事件是 JavaScript 与网页结合的主要方式。最常见的事件是在 DOM3 Events 规范或 HTML5 中定义
的。虽然基本的事件都有规范定义,但很多浏览器在规范之外实现了自己专有的事件,以方便开发者更
好地满足用户交互需求,其中一些专有事件直接与特殊的设备相关。
围绕着使用事件,需要考虑内存与性能问题。例如:
最好限制一个页面中事件处理程序的数量,因为它们会占用过多内存,导致页面响应缓慢;
利用事件冒泡,事件委托可以解决限制事件处理程序数量的问题;
最好在页面卸载之前删除所有事件处理程序。
使用 JavaScript 也可以在浏览器中模拟事件。DOM2 Events 和 DOM3 Events 规范提供了模拟方法,
可以模拟所有原生 DOM 事件。键盘事件一定程度上也是可以模拟的,有时候需要组合其他技术。IE8
及更早版本也支持事件模拟,只是接口与 DOM 方式不同。
事件是 JavaScript 中最重要的主题之一,理解事件的原理及其对性能的影响非常重要。

第 18 章 动画与 Canvas 图形

requestAnimationFrame 是简单但实用的工具,可以让 JavaScript 跟进浏览器渲染周期,从而更
加有效地实现网页视觉动效。
HTML5 的canvas元素为 JavaScript 提供了动态创建图形的 API。这些图形需要使用特定上下文
绘制,主要有两种。第一种是支持基本绘图操作的 2D 上下文:
填充和描绘颜色及图案
绘制矩形
绘制路径
绘制文本
创建渐变和图案
第二种是 3D 上下文,也就是 WebGL。WebGL 是浏览器对 OpenGL ES 2.0 的实现。OpenGL ES 2.0
是游戏图形开发常用的一个标准。WebGL 支持比 2D 上下文更强大的绘图能力,包括:
用 OpenGL 着色器语言(GLSL)编写顶点和片段着色器;
支持定型数组,限定数组中包含数值的类型;
创建和操作纹理。
目前所有主流浏览器的较新版本都已经支持canvas标签。

第 19 章 表单脚本

尽管 HTML 和 Web 应用自诞生以来已经发生了天翻地覆的变化,但 Web 表单几乎从来没有变过。
JavaScript 可以增加现有的表单字段以提供新功能或增强易用性。为此,表单字段也暴露了属性、方法
和事件供 JavaScript 使用。以下是本章介绍的一些概念。
可以使用标准或非标准的方法全部或部分选择文本框中的文本。
所有浏览器都采用了 Firefox 操作文本选区的方式,使其成为真正的标准。
可以通过监听键盘事件并检测要插入的字符来控制文本框接受或不接受某些字符。
所有浏览器都支持剪贴板相关的事件,包括 copy、cut 和 paste。剪贴板事件在不同浏览器中的
实现有很大差异。
在文本框只限某些字符时,可以利用剪贴板事件屏幕粘贴事件。
选择框也是经常使用 JavaScript 来控制的一种表单控件。借助 DOM,操作选择框比以前方便了很多。
使用标准的 DOM 技术,可以为选择框添加或移除选项,也可以将选项从一个选择框移动到另一个选择
框,或者重排选项。
富文本编辑通常以使用包含空白 HTML 文档的内嵌窗格来处理。通过将文档的 designMode 属性设
置为”on”,可以让整个页面变成编辑区,就像文字处理软件一样。另外,给元素添加 contenteditable
属性也可以将元素转换为可编辑区。默认情况下,可以切换文本的粗体、斜体样式,也可以使用剪贴板功
能。JavaScript 通过 execCommand()方法可以执行一些富文本编辑功能,通过 queryCommandEnabled()、
queryCommandState()和 queryCommandValue()方法则可以获取有关文本选区的信息。由于富文本编
辑区不涉及表单字段,因此要将富文本内容提交到服务器,必须把 HTML 从 iframe 或 contenteditable
元素中复制到一个表单字段。

第 20 章 JavaScript API

除了定义新标签,HTML5 还定义了一些 JavaScript API。这些 API 可以为开发者提供更便捷的 Web
接口,暴露堪比桌面应用的能力。本章主要介绍了以下 API。
Atomics API 用于保护代码在多线程内存访问模式下不发生资源争用。
postMessage() API 支持从不同源跨文档发送消息,同时保证安全和遵循同源策略。
Encoding API 用于实现字符串与缓冲区之间的无缝转换(越来越常见的操作)。
File API 提供了发送、接收和读取大型二进制对象的可靠工具。
媒体元素audio和video拥有自己的 API,用于操作音频和视频。并不是每个浏览器都会支
持所有媒体格式,使用 canPlayType()方法可以检测浏览器支持情况。
拖放 API 支持方便地将元素标识为可拖动,并在操作系统完成放置时给出回应。可以利用它创
建自定义可拖动元素和放置目标。
Notifications API 提供了一种浏览器中立的方式,以此向用户展示消通知弹层。
Streams API 支持以全新的方式读取、写入和处理数据。
Timing API 提供了一组度量数据进出浏览器时间的可靠工具。
Web Components API 为元素重用和封装技术向前迈进提供了有力支撑。
Web Cryptography API 让生成随机数、加密和签名消息成为一类特性。

第 21 章 错误处理与调试

对于今天复杂的 Web 应用程序而言,JavaScript 中的错误处理十分重要。未能预测什么时候会发生
错误以及如何从错误中恢复,会导致糟糕的用户体验,甚至造成用户流失。大多数浏览器默认不向用户
报告 JavaScript 错误,因此在开发和调试时需要自己实现错误报告。不过在生产环境中,不应该以这种
方式报告错误。
下列方法可用于阻止浏览器对 JavaScript 错误作出反应。
使用 try/catch 语句,可以通过更合适的方式对错误做出处理,避免浏览器处理。
定义 window.onerror 事件处理程序,所有没有通过 try/catch 处理的错误都会被该事件处理
程序接收到(仅限 IE、Firefox 和 Chrome)。
开发 Web 应用程序时,应该认真考虑可能发生的错误,以及如何处理这些错误。
首先,应该分清哪些算重大错误,哪些不算重大错误。
然后,要通过分析代码预测很可能发生哪些错误。由于以下因素,JavaScript 中经常出现错误:
 类型转换;
 数据类型检测不足;
 向服务器发送错误数据或从服务器接收到错误数据。
IE、Firefox、Chrome、Opera 和 Safari 都有 JavaScript 调试器,有的内置在浏览器中,有的是作为扩
展,需另行下载。所有调试器都能够设置断点、控制代码执行和在运行时检查变量值。

第 22 章 处理 XML

浏览器对使用 JavaScript 处理 XML 实现及相关技术相当支持。然而,由于早期缺少规范,常用的
功能出现了不同实现。DOM Level 2 提供了创建空 XML 文档的 API,但不能解析和序列化。浏览器为
解析和序列化 XML 实现了两个新类型。
DOMParser 类型是简单的对象,可以将 XML 字符串解析为 DOM 文档。
XMLSerializer 类型执行相反操作,将 DOM 文档序列化为 XML 字符串。
基于所有主流浏览器的实现,DOM Level 3 新增了针对 XPath API 的规范。该 API 可以让 JavaScript
针对 DOM 文档执行任何 XPath 查询并得到不同数据类型的结果。
最后一个与 XML相关的技术是 XSLT,目前并没有规范定义其 API。Firefox最早增加了 XSLTProcessor
类型用于通过 JavaScript 处理转换。

第 23 章 JSON

JSON 是一种轻量级数据格式,可以方便地表示复杂数据结构。这个格式使用 JavaScript 语法的一个
子集表示对象、数组、字符串、数值、布尔值和 null。虽然 XML 也能胜任同样的角色,但 JSON 更简
洁,JavaScript 支持也更好。更重要的是,所有浏览器都已经原生支持全局 JSON 对象。
ECMAScript 5 定义了原生 JSON 对象,用于将 JavaScript 对象序列化为 JSON 字符串,以及将 JSON
数组解析为 JavaScript 对象。JSON.stringify()和 JSON.parse()方法分别用于实现这两种操作。这
两个方法都有一些选项可以用来改变默认的行为,以实现过滤或修改流程。

第 24 章 网络请求与远程资源

Ajax 是无须刷新当前页面即可从服务器获取数据的一个方法,具有如下特点。
让 Ajax 迅速流行的中心对象是 XMLHttpRequest(XHR)。
这个对象最早由微软发明,并在 IE5 中作为通过 JavaScript 从服务器获取 XML 数据的一种手段。
之后,Firefox、Safari、Chrome 和 Opera 都复刻了相同的实现。W3C 随后将 XHR 行为写入 Web
标准。
虽然不同浏览器的实现有些差异,但 XHR 对象的基本使用在所有浏览器中相对是规范的,因此
可以放心地在 Web 应用程序中使用。
XHR 的一个主要限制是同源策略,即通信只能在相同域名、相同端口和相同协议的前提下完成。
访问超出这些限制之外的资源会导致安全错误,除非使用了正式的跨域方案。这个方案叫作跨源资源共
享(CORS,Cross-Origin Resource Sharing),XHR 对象原生支持 CORS。图片探测和 JSONP 是另外两种
跨域通信技术,但没有 CORS 可靠。
Fetch API 是作为对 XHR 对象的一种端到端的替代方案而提出的。这个 API 提供了优秀的基于期约
的结构、更直观的接口,以及对 Stream API 的最好支持。
Web Socket 是与服务器的全双工、双向通信渠道。与其他方案不同,Web Socket 不使用 HTTP,而
使用了自定义协议,目的是更快地发送小数据块。这需要专用的服务器,但速度优势明显。

第 25 章 客户端存储

Web Storage 定义了两个对象用于存储数据:sessionStorage 和 localStorage。前者用于严格
保存浏览器一次会话期间的数据,因为数据会在浏览器关闭时被删除。后者用于会话之外持久保存数据。
IndexedDB 是类似于 SQL 数据库的结构化数据存储机制。不同的是,IndexedDB 存储的是对象,而
不是数据表。对象存储是通过定义键然后添加数据来创建的。游标用于查询对象存储中的特定数据,而
索引可以针对特定属性实现更快的查询。
有了这些存储手段,就可以在客户端通过使用 JavaScript 存储可观的数据。因为这些数据没有加密,
所以要注意不能使用它们存储敏感信息。

第 26 章 模 块

模块模式是管理复杂性的永恒工具。开发者可以通过它创建逻辑彼此独立的代码段,在这些代码段
之间声明依赖,并将它们连接在一起。此外,这种模式也是经证明能够优雅扩展到任意复杂度且跨平台
的方案。
多年以来,CommonJS 和 AMD 这两个分别针对服务器端环境和受延迟限制的客户端环境的模块系
统长期分裂。两个系统都获得了爆炸性增强,但为它们编写的代码则在很多方面不一致,经常也会带有
冗余的样板代码。而且,这两个系统都没有在浏览器中实现。缺乏兼容导致出现了相关工具,从而让在
浏览器中实现模块模式成为可能。
ECMAScript 6 规范重新定义了浏览器模块,集之前两个系统之长于一身,并通过更简单的声明性语
法暴露出来。浏览器对原生模块的支持越来越好,但也提供了稳健的工具以实现从不支持到支持 ES6
模块的过渡。

第 27 章 工作者线程

工作者线程可以运行异步 JavaScript 而不阻塞用户界面。这非常适合复杂计算和数据处理,特别是
需要花较长时间因而会影响用户使用网页的处理任务。工作者线程有自己独立的环境,只能通过异步消
息与外界通信。
工作者线程可以是专用线程、共享线程。专用线程只能由一个页面使用,而共享线程则可以由同源
的任意页面共享。
服务工作者线程用于让网页模拟原生应用程序。服务工作者线程也是一种工作者线程,但它们更像
是网络代理,而非独立的浏览器线程。可以把它们看成是高度定制化的网络缓存,它们也可以在 PWA
中支持推送通知。

第 28 章 最佳实践

随着 JavaScript 开发日益成熟,最佳实践不断涌现。曾经的业余爱好如今也成为了正式的职业。因
此,前端开发也需要像其他编程语言一样,注重可维护性、性能优化和部署。
为保证 JavaScript 代码的可维护性,可以参考如下编码惯例。
其他语言的编码惯例可以作为添加注释和确定缩进的参考,但 JavaScript 作为一门适合松散类型
的语言也有自己的一些特殊要求。
由于 JavaScript 必须与 HTML 和 CSS 共存,因此各司其职尤为重要:JavaScript 负责定义行为,
HTML 负责定义内容,而 CSS 负责定义外观。
如果三者职责混淆,则可能导致难以调试的错误和可维护性问题。
随着 Web 应用程序中 JavaScript 代码量的激增,性能也越来越重要。因此应该牢记如下这些事项。
执行 JavaScript 所需的时间直接影响网页性能,其重要性不容忽视。
很多适合 C 语言的性能优化策略同样也适合 JavaScript,包括循环展开和使用 switch 语句而不
是 if 语句。
另一个需要重视的方面是 DOM 交互很费时间,因此应该尽可能限制 DOM 操作的数量。
开发 Web 应用程序的最后一步是上线部署。以下是本章讨论的相关要点。
为辅助部署,应该建立构建流程,将 JavaScript 文件合并为较少的(最好是只有一个)文件。
构建流程可以实现很多源代码处理任务的自动化。例如,可以运行 JavaScript 验证程序,确保没
有语法错误和潜在的问题。
压缩可以让文件在部署之前变得尽量小。
启用 HTTP 压缩可以让网络传输的 JavaScript 文件尽可能小,从而提升页面的整体性能。