原书
JavaScript高级程序设计(第4版).pdf

笔记各个标记示意:

  • ❌ 表示错误的示意内容
  • ✅ 表示正确的示意内容
  • ⚠️ 表示警告
  • **⭐️ **表示重要内容
  • 红色字体表示需要重点注意容易犯错的地方
  • 蓝色字体表示当前较为重要的知识点
  • 橙色字体表示当前内容有一些规范性限制或者警告
  • 加粗字体表示强调内容
  • 斜体字体表示解释内容

JavaScript是什么?

JavaScript 是一门用来与网页交互的脚本语言,包含以下三个组成部分。
 ECMAScript:由 ECMA-262 定义并提供核心功能。
 文档对象模型(DOM):提供与网页内容交互的方法和接口。
 浏览器对象模型(BOM):提供与浏览器交互的方法和接口

变量

JavaScript 变量可以保存两种类型的值:原始值和引用值。
原始值可能是以下 6 种原始数据类型之一:Undefined、Null、Boolean、Number、String 和 Symbol。原始值和引用值有以下特点。

  • 原始值大小固定,因此保存在栈内存上。
  •  从一个变量到另一个变量复制原始值会创建该值的第二个副本。
  •  引用值是对象,存储在堆内存上。
  •  包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
  •  从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。
  •  typeof 操作符可以确定值的原始类型,而 instanceof 操作符用于确保值的引用类型。
1
2
3
4
5
console.log(typeof 'abc' == 'string')

class A { constructor(_name){this.name=_name}}
var a = new A('a')
console.log((a instanceof A))

作用域

任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。执行上下文可以总结如下。

  •  执行上下文分全局上下文、函数上下文和块级上下文。
  •  代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
  •  函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
  •  全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
  •  变量的执行上下文用于确定什么时候释放内存。

垃圾回收

JavaScript 是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。JavaScript 的垃圾回收程序可以总结如下。

  •  离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
  •  主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
  • 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript 引擎不再使用这种算法,但某些旧版本的 IE 仍然会受这种算法的影响,原因是 JavaScript 会访问非原生 JavaScript 对 象(如 DOM 元素)。
  •  引用计数在代码中存在循环引用时会出现问题。
  •  解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。

基本对象

JavaScript 中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。

  •  引用值与传统面向对象编程语言中的类相似,但实现不同。
  •  Date 类型提供关于日期和时间的信息,包括当前日期、时间及相关计算。
  •  RegExp 类型是 ECMAScript 支持正则表达式的接口,提供了大多数基础的和部分高级的正则表达式功能。
  • Object 类型是一个基础类型,所有引用类型都从它继承了基本的行为。 
    
  •  Array 类型表示一组有序的值,并提供了操作和转换值的能力。
  •  定型数组(typed array)包含一套不同的引用类型,用于管理数值在内存中的类型。
    • ArrayBuffer 是所有定型数组及视图引用的基本单位。

JavaScript 比较独特的一点是,函数实际上是 Function 类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。

1
Function instanceof Object  // true

由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有 3 种原始值包装类型:Boolean、Number 和 String。它们都具备如下特点。

  •  每种包装类型都映射到同名的原始类型。
  •  以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相应的数据。
  •  涉及原始值的语句执行完毕后,包装对象就会被销毁。

当代码开始执行时,全局上下文中会存在两个内置对象:**Global **和 Math
Global 对象在 大多数 ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函 数都是 Global 对象的属性。
Math 对象包含辅助完成复杂计算的属性和方法。

ECMAScript 6 新增了一批引用类型:Map、WeakMap、Set 和 WeakSet。这些类型为组织应用程序 数据和简化内存管理提供了新能力。

继承

JavaScript 的继承主要通过原型链来实现。原型链涉及把构造函数的原型赋值为另一个类型的实例。
这样一来,子类就可以访问父类的所有属性和方法,就像基于类的继承那样。原型链的问题是所有继承的属性和方法都会在对象实例间共享,无法做到实例私有。盗用构造函数模式通过在子类构造函数中调用父类构造函数,可以避免这个问题。这样可以让每个实例继承的属性都是私有的,但要求类型只能通过构造函数模式来定义(因为子类不能访问父类原型上的方法)。目前最流行的继承模式是组合继承,即通过原型链继承共享的属性和方法,通过盗用构造函数继承实例属性。
除上述模式之外,还有以下几种继承模式。

  •  原型式继承可以无须明确定义构造函数而实现继承,本质上是对给定对象执行浅复制。这种操 作的结果之后还可以再进一步增强。
  •  与原型式继承紧密相关的是寄生式继承,即先基于一个对象创建一个新对象,然后再增强这个 新对象,最后返回新对象。这个模式也被用在组合继承中,用于避免重复调用父类构造函数导 致的浪费。
  •  寄生组合继承被认为是实现基于类型继承的最有效方式。

ECMAScript 6 新增的类
ECMAScript 6 新增的类很大程度上是基于既有原型机制的语法糖。类的语法让开发者可以优雅地定义向后兼容的类,既可以继承内置类型,也可以继承自定义类型。类有效地跨越了对象实例、对象原型和对象类之间的鸿沟。
使用Class继承时需要注意:

  1. super 只能在派生类构造函数和静态方法中使用。
  2. 不能单独引用 super 关键字,要么用它调用构造函数,要么用它引用静态方法。
  3. 调用 super()会调用父类构造函数,并将返回的实例赋值给 this。
  4. super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
  5. 如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的参数
  6. 在类构造函数中,不能在调用 super()之前引用 this。
  7. 如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象
1
2
3
4
5
6
7
8
Object.create = function (o) {
// 创建一个新的空构造函数
var F = function () {};
// 将构造函数的原型设置为参数o
F.prototype = o;
// 返回新创建的F实例对象
return new F();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ES6之前的继承
// 原型式继承,使用Object.create来实现继承,将父对象,作为原型链
let baseObj={
name:'张三',
sex:'男'
}
let childObj1= Object.create(baseObj)
childObj1.name='李四'
let childObj2=Object.create(baseObj,{name:{value:'王五'}})
console.log(childObj1)
console.log(childObj2)

// 组合式继承
function ParentClass (_name){
this.name = _name;
this.sayName=function(){console.log('my name is '+this.name)}
}

function ChildClass(_sex,_name){
ParentClass.call(this,_name) //# 此处完成继承
this.sex = _sex
this.saySex = function(){console.log('my sex is '+ sex);}
}

var cc= new ChildClass('女','张三')

// ------------------
// Es6之后使用Class继承

class ParentClass {
constructor(_name){
this.name = _name
}
sayName(){
console.log('my name is :'+this.name)
}
}
class ChildClass extends ParentClass {
constructor(_sex,_name){
super(_name)
this.sex= _sex
}
saySex(){
console.log('my sex is :'+this.sex)
}
}

var cc = new ChildClass('女','李四')

如果想声明一个抽象基类,可以使用 new.target 判断,排除基类被实例化
如果实例化派生类时,在基类中的this,可以访问到派生类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class abstractBaseClass{
constructor(){
// 使用new.target判断,禁止该基类被实例化
if(new.target == abstractBaseClass){
throw new Error('抽象基类不能被实例化')
}
// 使用 this.访问派生类的属性
if(!this.childMethod){
throw new Error('派生类需要实现childMethod方法')
}
}
}

class childClass1 extends abstractBaseClass {
childMethod(){

}
}
class childClass2 extends abstractBaseClass {

}


new childClass1() // 不报错
new childClass2() // 报错 派生类需要实现childMethod方法

new abstractBaseClass() // 报错 抽象基类不能被实例化

代理

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

1
2
3
4
const target = { id :'target'}
const targetProxy =new Proxy(target,{})


迭代 (待完善…)

函数

函数是ECMAScript中最有意思的部分之一,这主要是因为函数实际上是对象。每个函数都是Function类型的实例,而 Function 也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是 指向函数对象的指针,而且不一定与函数本身紧密绑定。

  • 函数是 JavaScript 编程中最有用也最通用的工具。ECMAScript 6 新增了更加强大的语法特性,从而让开发者可以更有效地使用函数。 
    
  •  函数表达式与函数声明是不一样的。函数声明要求写出函数名称,而函数表达式并不需要。没有名称的函数表达式也被称为匿名函数。
1
2
3
4
5
6
7
// 匿名函数
// 箭头函数
()= >{

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

异步

Promise 和 async/await 没啥好说的

BOM

虽然 ECMAScript 把浏览器对象模型(BOM,Browser Object Model)描述为 JavaScript 的核心,但实际上 BOM 是使用 JavaScript 开发 Web 应用程序的核心。BOM 提供了与网页无关的浏览器功能对象。
多年来,BOM 是在缺乏规范的背景下发展起来的,因此既充满乐趣又问题多多。毕竟,浏览器开发商都按照自己的意愿来为它添砖加瓦。最终,浏览器实现之间共通的部分成为了事实标准,为 Web 开发提供了浏览器间互操作的基础。HTML5 规范中有一部分涵盖了 BOM 的主要内容,因为 W3C 希望将JavaScript 在浏览器中最基础的部分标准化。
浏览器对象模型(BOM,Browser Object Model)是以 window 对象为基础的,这个对象代表了浏览器窗口和页面可见的区域。window 对象也被复用为 ECMAScript 的 Global 对象,因此所有全局变量和函数都是它的属性,而且所有原生类型的构造函数和普通函数也都从一开始就存在于这个对象之上。

  • 常用的对象有,window,location,navigator,screen,history 
    
  •  要引用其他 window 对象,可以使用几个不同的窗口指针。
  •  通过 location 对象可以以编程方式操纵浏览器的导航系统。通过设置这个对象上的属性,可以改变浏览器 URL 中的某一部分或全部。
  •  使用 replace()方法可以替换浏览器历史记录中当前显示的页面,并导航到新 URL。
  •  navigator 对象提供关于浏览器的信息。提供的信息类型取决于浏览器,不过有些属性如userAgent 是所有浏览器都支持的。

BOM 中的另外两个对象也提供了一些功能。screen 对象中保存着客户端显示器的信息。这些信息通常用于评估浏览网站的设备信息。history 对象提供了操纵浏览器历史记录的能力,开发者可以确定历史记录中包含多少个条目,并以编程方式实现在历史记录中导航,而且也可以修改历史记录。

客户端检测

浏览器能力检测

虽然浏览器厂商齐心协力想要实现一致的接口,但事实上仍然是每家浏览器都有自己的长处与不 足。跨平台的浏览器尽管版本相同,但总会存在不同的问题。这些差异迫使 Web 开发者要么面向最大 公约数而设计,要么(更常见地)使用各种方法来检测客户端,以克服或避免这些缺陷。
客户端检测一直是 Web 开发中饱受争议的话题,这些话题普遍围绕所有浏览器应支持一系列公共特性,理想情况下是这样的。而现实当中,浏览器之间的差异和莫名其妙的行为,让客户端检测变成一 种补救措施,而且也成为了开发策略的重要一环。如今,浏览器之间的差异相对 IE 大溃败以前已经好 很多了,但浏览器间的不一致性依旧是 Web 开发中的常见主题。
要检测当前的浏览器有很多方法,每一种都有各自的长处和不足。问题的关键在于知道客户端检测应该是解决问题的最后一个举措。任何时候,只要有更普适的方案可选,都应该毫不犹豫地选择。首先要设计最常用的方案,然后再考虑为特定的浏览器进行补救。
客户端检测是 JavaScript 中争议最多的话题之一。因为不同浏览器之间存在差异,所以经常需要根 据浏览器的能力来编写不同的代码。客户端检测有不少方式,但下面两种用得最多。

  •  能力检测,在使用之前先测试浏览器的特定能力。例如,脚本可以在调用某个函数之前先检查 它是否存在。这种客户端检测方式可以让开发者不必考虑特定的浏览器或版本,而只需关注某 些能力是否存在。能力检测不能精确地反映特定的浏览器或版本。
  •  用户代理检测,通过用户代理字符串确定浏览器。用户代理字符串包含关于浏览器的很多信息, 通常包括浏览器、平台、操作系统和浏览器版本。用户代理字符串有一个相当长的发展史,很 多浏览器都试图欺骗网站相信自己是别的浏览器。用户代理检测也比较麻烦,特别是涉及 Opera 会在代理字符串中隐藏自己信息的时候。即使如此,用户代理字符串也可以用来确定浏览器使 用的渲染引擎以及平台,包括移动设备和游戏机。

在选择客户端检测方法时,首选是使用能力检测。特殊能力检测要放在次要位置,作为决定代码逻 辑的参考。用户代理检测是最后一个选择,因为它过于依赖用户代理字符串。
浏览器也提供了一些软件和硬件相关的信息。这些信息通过 screen 和 navigator 对象暴露出来。 利用这些 API,可以获取关于操作系统、浏览器、硬件、设备位置、电池状态等方面的准确信息。

DOM

文档对象模型(DOM,Document Object Model)是 HTML 和 XML 文档的编程接口。DOM 表示由多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。脱胎于网景和微软早期的动态 HTML(DHTML,Dynamic HTML),DOM 现在是真正跨平台、语言无关的表示和操作网页的方式。

  • Node 是基准节点类型,是文档一个部分的抽象表示,所有其他类型都继承 Node。
  • Document 类型表示整个文档,对应树形结构的根节点。在 JavaScript 中,document 对象是Document 的实例,拥有查询和获取节点的很多方法。
  • Element 节点表示文档中所有 HTML 或 XML 元素,可以用来操作它们的内容和属性。
  • 其他节点类型分别表示文本内容、注释、文档类型、CDATA 区块和文档片段。

DOM 编程在多数情况下没什么问题,在涉及 script 和style元素时会有一点兼容性问题。因 为这些元素分别包含脚本和样式信息,所以浏览器会将它们与其他元素区别对待。
要理解 DOM,最关键的一点是知道影响其性能的问题所在。DOM 操作在 JavaScript 代码中是代价 比较高的,NodeList 对象尤其需要注意。NodeList 对象是“实时更新”的,这意味着每次访问它都 会执行一次新的查询。考虑到这些问题,实践中要尽量减少 DOM 操作的数量。
MutationObserver 是为代替性能不好的 MutationEvent 而问世的。使用它可以有效精准地监控 DOM 变化,而且 API 也相对简单。

DOM 的扩展

尽管 DOM API 已经相当不错,但仍然不断有标准或专有的扩展出现,以支持更多功能。2008 年以 前,大部分浏览器对 DOM 的扩展是专有的。此后,W3C 开始着手将这些已成为事实标准的专有扩展编 制成正式规范。
基于以上背景,诞生了描述 DOM 扩展的两个标准:Selectors API 与 HTML5。这两个标准体现了社 区需求和标准化某些手段及 API 的愿景。另外还有较小的 Element Traversal 规范,增加了一些 DOM 属性。 专有扩展虽然还有,但这两个规范(特别是 HTML5)已经涵盖其中大部分。

  • Selectors API 为基于 CSS 选择符获取 DOM 元素定义了几个方法:querySelector()、 querySelectorAll()和 matches()。
  • Element Traversal 在 DOM 元素上定义了额外的属性,以方便对 DOM 元素进行遍历。这个需求 是因浏览器处理元素间空格的差异而产生的。
  • HTML5 为标准 DOM 提供了大量扩展。其中包括对 innerHTML 属性等事实标准进行了标准化, 还有焦点管理、字符集、滚动等特性。

DOM 扩展的数量总体还不大,但随着 Web 技术的发展一定会越来越多。浏览器仍然没有停止对专有扩展的探索,如果出现成功的扩展,那么就可能成为事实标准,或者最终被整合到未来的标准中。

DOM2 & DOM3

DOM1(DOM Level 1)主要定义了 HTML 和 XML 文档的底层结构。DOM2(DOM Level 2)和DOM3(DOM Level 3)在这些结构之上加入更多交互能力,提供了更高级的 XML 特性。实际上,DOM2和 DOM3 是按照模块化的思路来制定标准的,每个模块之间有一定关联,但分别针对某个 DOM 子集。 这些模式如下所示。

  •  DOM Core:在 DOM1 核心部分的基础上,为节点增加方法和属性。
  •  DOM Views:定义基于样式信息的不同视图。
  •  DOM Events:定义通过事件实现 DOM 文档交互。
  •  DOM Style:定义以编程方式访问和修改 CSS 样式的接口。
  •  DOM Traversal and Range:新增遍历 DOM 文档及选择文档内容的接口。
  •  DOM HTML:在 DOM1 HTML 部分的基础上,增加属性、方法和新接口。
  •  DOM Mutation Observers:定义基于 DOM 变化触发回调的接口。这个模块是 DOM4 级模块, 用于取代 Mutation Events。

DOM2 规范定义了一些模块,用来丰富 DOM1 的功能。DOM2 Core 在一些类型上增加了与 XML命名空间有关的新方法。这些变化只有在使用 XML 或 XHTML 文档时才会用到,在 HTML文档中则没 有用处。DOM2 增加的与 XML命名空间无关的方法涉及以编程方式创建 Document 和 DocumentType 类型的新实例。
DOM2 Style 模块定义了如何操作元素的样式信息。

  •  每个元素都有一个关联的 style 对象,可用于确定和修改元素特定的样式。
  •  要确定元素的计算样式,包括应用到元素身上的所有 CSS规则,可以使用getComputedStyle() 方法。
1
getComputedStyle(document.getElementsByClassName('active___20_Uq')[0])
  •  通过 document.styleSheets 集合可以访问文档上所有的样式表。 DOM2 Traversal and Range 模块定义了与 DOM 结构交互的不同方式。
  •  NodeIterator 和 TreeWalker 可以对 DOM 树执行深度优先的遍历。
  •  NodeIterator 接口很简单,每次只能向前和向后移动一步。TreeWalker 除了支持同样的行 为,还支持在 DOM 结构的所有方向移动,包括父节点、同胞节点和子节点。
  •  范围是选择 DOM 结构中特定部分并进行操作的一种方式。
  •  通过范围的选区可以在保持文档结构完好的同时从文档中移除内容,也可复制文档中相应的部分。

事件

JavaScript 与 HTML 的交互是通过事件实现的,事件代表文档或浏览器窗口中某个有意义的时刻。可以使用仅在事件发生时执行的监听器(也叫处理程序)订阅事件。在传统软件工程领域,这个模型叫“观察者模式”,其能够做到页面行为(在 JavaScript 中定义)与页面展示(在 HTML 和 CSS 中定义)的分离。

事件是 JavaScript 与网页结合的主要方式。最常见的事件是在 DOM3 Events 规范或 HTML5 中定义的。虽然基本的事件都有规范定义,但很多浏览器在规范之外实现了自己专有的事件,以方便开发者更好地满足用户交互需求,其中一些专有事件直接与特殊的设备相关。
围绕着使用事件,需要考虑内存与性能问题。例如:

  •  最好限制一个页面中事件处理程序的数量,因为它们会占用过多内存,导致页面响应缓慢;
  •  利用事件冒泡,事件委托可以解决限制事件处理程序数量的问题;
  •  最好在页面卸载之前删除所有事件处理程序。

使用 JavaScript 也可以在浏览器中模拟事件。DOM2 Events 和 DOM3 Events 规范提供了模拟方法,可以模拟所有原生 DOM 事件。键盘事件一定程度上也是可以模拟的,有时候需要组合其他技术。IE8及更早版本也支持事件模拟,只是接口与 DOM 方式不同。 事件是 JavaScript 中最重要的主题之一,理解事件的原理及其对性能的影响非常重要

DOM 事件对象

DOM 合规的浏览器中,event 对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0或 DOM2)指定事件处理程序,都会传入这个 event 对象。
在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际目标。如果事件处理程序直接添加在了意图的目标,则 this、currentTarget 和 target 的值是一样的。

事件类型

DOM3 Events 定义了如下事件类型。

  • 用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件。
    • DOMActivate:元素被用户通过鼠标或键盘操作激活时触发(比 click 或 keydown 更通用)。这个事件在 DOM3 Events 中已经废弃。因为浏览器实现之间存在差异,所以不要使用它
    • load:在 window 上当页面加载完成后触发,在窗套(< frameset >)上当所有窗格(< frame >)都加载完成后触发,在< img >元素上当图片加载完成后触发,在< object >元素上当相应对象加载完成后触发。
    • unload:在 window 上当页面完全卸载后触发,在窗套上当所有窗格都卸载完成后触发,在< object >元素上当相应对象卸载完成后触发。
    • abort:在< object >元素上当相应对象加载完成前被用户提前终止下载时触发。
    • error:在 window 上当 JavaScript 报错时触发,在< img >元素上当无法加载指定图片时触发,在< object >元素上当无法加载相应对象时触发,在窗套上当一个或多个窗格无法完成加载时触发。
    • select:在文本框(< input >或 textarea)上当用户选择了一个或多个字符时触发。
    • resize:在 window 或窗格上当窗口或窗格被缩放时触发。
    • scroll:当用户滚动包含滚动条的元素时在元素上触发。< body >元素包含已加载页面的滚动条。
  • 焦点事件(FocusEvent):在元素获得和失去焦点时触发。焦点事件在页面元素获得或失去焦点时触发。这些事件可以与 document.hasFocus()和document.activeElement 一起为开发者提供用户在页面中导航的信息。焦点事件有以下 6 种。
    • blur:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。
    • DOMFocusIn:当元素获得焦点时触发。这个事件是 focus 的冒泡版。Opera 是唯一支持这个事件的主流浏览器。DOM3 Events 废弃了 DOMFocusIn,推荐 focusin。  DOMFocusOut:当元素失去焦点时触发。这个事件是 blur 的通用版。Opera 是唯一支持这个事件的主流浏览器。DOM3 Events 废弃了 DOMFocusOut,推荐 focusout
    • focus:当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持。
    • focusin:当元素获得焦点时触发。这个事件是 focus 的冒泡版。
    • focusout:当元素失去焦点时触发。这个事件是 blur 的通用版。
  • 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。**页面中的所有元素都支持鼠标事件。**除了 mouseenter 和 mouseleave,所有鼠标事件都会冒泡,都可以被取消,而这会影响浏览器的默认行为。

DOM3 Events定义了 9 种鼠标事件。

  • click:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。这主要是基于无障碍的考虑,让键盘和鼠标都可以触发 onclick 事件处理程序。
  • dblclick:在用户双击鼠标主键(通常是左键)时触发。这个事件不是在 DOM2 Events 中定义的,但得到了很好的支持,DOM3 Events 将其进行了标准化。
  • mousedown:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发。
  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseenter 事件不是在 DOM2 Events 中定义的,而是 DOM3 Events中新增的事件。
  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseleave 事件不是在 DOM2 Events 中定义的,而是 DOM3 Events中新增的事件。
  • mousemove:在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。
  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。这个事件不能通过键盘触发。
  • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。
  • mouseup:在用户释放鼠标键时触发。这个事件不能通过键盘触发。
  • 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
  • 输入事件(InputEvent):向文档中输入文本时触发。
  • 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
    • keydown,用户按下键盘上某个键时触发,而且持续按住会重复触发。
    • keypress,用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc 键也会触发这个事件。DOM3 Events 废弃了 keypress 事件,而推荐 textInput 事件。
    • keyup,用户释放键盘上某个键时触发。
  • 合成事件(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入字符时触发。

除了这些事件类型之外,HTML5 还定义了另一组事件,而浏览器通常在 DOM 和 BOM 上实现专有事件。这些专有事件基本上都是根据开发者需求而不是按照规范增加的,因此不同浏览器的实现可能不同

  • HTML5事件
    • oncontextmenu: 右键菜单事件
    • beforeunload :这个事件会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。这个事件不能取消,否则就意味着可以把用户永久阻拦在一个页面上。相反,这个事件会向用户显示一个确认框,其中的消息表明浏览器即将卸载页面,并请用户确认是希望关闭页面,还是继续留在页面上。
    • DOMContentLoaded :事件会在 DOM 树构建完成后立即触发,而不用等待图片、JavaScript文件、CSS 文件或其他资源加载完成。相对于 load 事件,DOMContentLoaded 可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能够更快地与页面交互。
    • readystatechange 事件
    • pageshow:其会在页面显示时触发,无论是否来自往返缓存。在新加载的页面上,pageshow 会在 load 事件之后触发;在来自往返缓存的页面上,pageshow 会在页面状态完全恢复后触发。注意,虽然这个事件的目标是 document,但事件处理程序必须添加到 window 上。
    • pagehide,这个事件会在页面从浏览器中卸载后,在 unload 事件之前触发。与 pageshow 事件一样,pagehide 事件同样是在 document 上触发,但事件处理程序必须被添加到 window。
    • hashchange:HTML5 增加了 hashchange 事件,用于在 URL 散列值(URL 最后#后面的部分)发生变化时通知开发者。这是因为开发者经常在 Ajax 应用程序中使用 URL 散列值存储状态信息或路由导航信息。onhashchange 事件处理程序必须添加给 window,每次 URL 散列值发生变化时会调用它
  • 设备事件
    • orientationchange 事件:每当用户旋转设备改变了模式,就会触发 orientationchange 事件。移动 Safari 在 window 上暴露了 window.orientation 属性,它有以下 3 种值之一:0 表示垂直模式,90 表示左转水平模式(主屏幕键在右侧),–90 表示右转水平模式(主屏幕键在左)。所有 iOS 设备都支持 orientationchange 事件和 window.orientation 属性。
    • deviceorientation 事件: DeviceOrientationEvent规范定义的事件。如果可以获取设备的加速计信息,而且数据发生了变化,这个事件就会在 window 上触发。要注意的是,deviceorientation 事件只反映设备在空间中的朝向,而不涉及移动相关的信息。
    • devicemotion 事件:DeviceOrientationEvent 规范也定义了 devicemotion 事件。这个事件用于提示设备实际上在移动,而不仅仅是改变了朝向。例如,devicemotion 事件可以用来确定设备正在掉落或者正拿在一个行走的人手里。
  • 触摸事件
    • touchstart:手指放到屏幕上时触发(即使有一个手指已经放在了屏幕上)。
    • touchmove:手指在屏幕上滑动时连续触发。在这个事件中调用 preventDefault()可以阻止滚动。
    • touchend:手指从屏幕上移开时触发。
    • touchcancel:系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。
  • 手势事件
    • gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上时触发。
    • gesturechange:任何一个手指在屏幕上的位置发生变化时触发。
    • gestureend:其中一个手指离开屏幕时触发。

属性&方法

所有事件对象都会包含下表列出的这些公共属性和方法。

  • bubbles 布尔值 只读 表示事件是否冒泡
  • cancelable 布尔值 只读 表示是否可以取消事件的默认行为
  • currentTarget 元素 只读 当前事件处理程序所在的元素
  • defaultPrevented 布尔值 只读 true 表示已经调用 preventDefault()方法(DOM3 Events 中新增)
  • detail 整数 只读 事件相关的其他信息
  • eventPhase 整数 只读 表示调用事件处理程序的阶段:1 代表捕获阶段,2 代表到达目标,3 代表冒泡阶段
  • preventDefault() 函数 只读 用于取消事件的默认行为。只有 cancelable 为 true 才可以调用这个方法
  • stopImmediatePropagation() 函数 只读 用于取消所有后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3 Events 中新增)
  • stopPropagation() 函数 只读 用于取消所有后续事件捕获或事件冒泡。只有 bubbles为 true 才可以调用这个方法
  • target 元素 只读 事件目标
  • trusted 布尔值 只读 true 表示事件是由浏览器生成的。false 表示事件是开发者通过 JavaScript 创建的(DOM3 Events 中新增)
  • type 字符串 只读 被触发的事件类型
  • View AbstractView 只读 与事件相关的抽象视图。等于事件所发生的 window 对象

动画与 Canvas 图形

图形和动画已经日益成为浏览器中现代 Web 应用程序的必备功能,但实现起来仍然比较困难。视觉上复杂的功能要求性能调优和硬件加速,不能拖慢浏览器。目前已经有一套日趋完善的 API 和工具可以用来开发此类功能。
毋庸置疑,< canvas >是 HTML5 最受欢迎的新特性。这个元素会占据一块页面区域,让 JavaScript可以动态在上面绘制图片。< canvas >最早是苹果公司提出并准备用在控制面板中的,随着其他浏览器 的迅速跟进,HTML5 将其纳入标准。目前所有主流浏览器都在某种程度上支持< canvas >元素。
与浏览器环境中的其他部分一样,< canvas >自身提供了一些 API,但并非所有浏览器都支持这些 API,其中包括支持基础绘图能力的 2D 上下文和被称为 WebGL 的 3D 上下文。支持的浏览器的最新版 本现在都支持 2D 上下文和 WebGL。

requestAnimationFrame 是简单但实用的工具,可以让 JavaScript 跟进浏览器渲染周期,从而更 加有效地实现网页视觉动效。
HTML5 的< canvas >元素为 JavaScript 提供了动态创建图形的 API。这些图形需要使用特定上下文 绘制,主要有两种。
第一种是支持基本绘图操作的 2D 上下文:

  •  填充和描绘颜色及图案
  •  绘制矩形
  •  绘制路径
  •  绘制文本
  •  创建渐变和图案

第二种是 3D 上下文,也就是 WebGL。WebGL 是浏览器对 OpenGL ES 2.0 的实现。OpenGL ES 2.0 是游戏图形开发常用的一个标准。WebGL 支持比 2D 上下文更强大的绘图能力,包括:

  •  用 OpenGL 着色器语言(GLSL)编写顶点和片段着色器;
  •  支持定型数组,限定数组中包含数值的类型;
  •  创建和操作纹理。

目前所有主流浏览器的较新版本都已经支持< canvas >标签。

表单脚本

尽管 HTML 和 Web 应用自诞生以来已经发生了天翻地覆的变化,但 Web 表单几乎从来没有变过。

表单基础

Web 表单在 HTML 中以< form >元素表示,在 JavaScript 中则以 HTMLFormElement 类型表示。
获取表单方法有:

1
2
3
4
5
6
// 通过选择器
let form = document.getElementById("form1");
// 取得页面中的第一个表单
let firstForm = document.forms[0];
// 取得名字为"form2"的表单
let myForm = document.forms["form2"];

提交表单的方法有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< !-- 通用提交按钮 -- > 
< input type="submit" value="Submit Form" >
< !-- 自定义提交按钮 -- >
< button type="submit" >Submit Form< /button >
< !-- 图片按钮 -- >
< input type="image" src="graphic.gif" >

// 也可以用js使用submit方法来提交
let form = document.getElementById("myForm");
// 提交表单
form.submit()

// 监听表单提交
form.addEventListener("submit", (event) = > {
// 阻止表单提交
event.preventDefault();
});

属性&方法

  • acceptCharset:服务器可以接收的字符集,等价于 HTML 的 accept-charset 属性。
  • action:请求的 URL,等价于 HTML 的 action 属性。
  • elements:表单中所有控件的 HTMLCollection。  enctype:请求的编码类型,等价于 HTML 的 enctype 属性。
  • length:表单中控件的数量。
  • method:HTTP 请求的方法类型,通常是"get"或"post",等价于 HTML 的 method 属性。
  • name:表单的名字,等价于 HTML 的 name 属性。
  • reset():把表单字段重置为各自的默认值。
  • submit():提交表单。
  • target:用于发送请求和接收响应的窗口的名字,等价于 HTML 的 target 属性

JavaScript 可以增加现有的表单字段以提供新功能或增强易用性。为此,表单字段也暴露了属性、方法和事件供 JavaScript 使用。

  •  可以使用标准或非标准的方法全部或部分选择文本框中的文本。
  •  所有浏览器都采用了 Firefox 操作文本选区的方式,使其成为真正的标准。
  •  可以通过监听键盘事件并检测要插入的字符来控制文本框接受或不接受某些字符。

所有浏览器都支持剪贴板相关的事件,包括 copy、cut 和 paste。剪贴板事件在不同浏览器中的实现有很大差异。
在文本框只限某些字符时,可以利用剪贴板事件屏幕粘贴事件。
选择框也是经常使用 JavaScript 来控制的一种表单控件。借助 DOM,操作选择框比以前方便了很多。使用标准的 DOM 技术,可以为选择框添加或移除选项,也可以将选项从一个选择框移动到另一个选择框,或者重排选项。
富文本编辑通常以使用包含空白 HTML 文档的内嵌窗格来处理。通过将文档的 designMode 属性设置为"on",可以让整个页面变成编辑区,就像文字处理软件一样。另外,给元素添加 contenteditable属性也可以将元素转换为可编辑区。默认情况下,可以切换文本的粗体、斜体样式,也可以使用剪贴板功能。JavaScript 通过 execCommand()方法可以执行一些富文本编辑功能,通过queryCommandEnabled()、queryCommandState()和 queryCommandValue()方法则可以获取有关文本选区的信息。由于富文本编 辑区不涉及表单字段,因此要将富文本内容提交到服务器,必须把 HTML 从 iframe 或contenteditable 元素中复制到一个表单字段。

JavaScript API

随着 Web 浏览器能力的增加,其复杂性也在迅速增加。从很多方面看,现代 Web 浏览器已经成为构建于诸多规范之上、集不同 API 于一身的“瑞士军刀”。浏览器规范的生态在某种程度上是混乱而无序的。一些规范如 HTML5,定义了一批增强已有标准的 API 和浏览器特性。而另一些规范如 WebCryptography API 和 Notifications API,只为一个特性定义了一个 API。不同浏览器实现这些新 API 的情况也不同,有的会实现其中一部分,有的则干脆尚未实现。
最终,是否使用这些比较新的 API 还要看项目是支持更多浏览器,还是要采用更多现代特性。有些 API 可以通过腻子脚本来模拟,但腻子脚本通常会带来性能问题,此外也会增加网站 JavaScript 代码的体积。

Atomics & ShareArrayBuffer

SharedArrayBuffer 对象
用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。
Atomics对象
提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作(多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。)。

Atomics方法

  • Atomics.add()

将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。

  • Atomics.and()

将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值。

  • Atomics.compareExchange()

如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。

  • Atomics.exchange()

将数组中指定的元素更新为给定的值,并返回该元素更新前的值。

  • Atomics.isLockFree(size)

可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回 true;否则就意味着对于该数组,Atomics 对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。

  • Atomics.load()

返回数组中指定元素的值。

  • Atomics.notify()

唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。

  • Atomics.or()

将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。

  • Atomics.store()

将数组中指定的元素设置为给定的值,并返回该值。

  • Atomics.sub()

将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。

  • Atomics.wait()

检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 “ok”、“not-equal” 或 “time-out”。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用 wait())。

  • Atomics.xor()

将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

ta[0];
// 0

ta[0] = 5;
// 5

Atomics.add(ta, 0, 12);
// 5
Atomics.load(ta, 0);
// 17 ✅
// 12 ❌

Atomics.and(ta, 0, 1);
// 17
Atomics.load(ta, 0);
// 1

Atomics.compareExchange(ta, 0, 5, 12);
Atomics.load(ta, 0); // 12

Atomics.exchange(ta, 0, 12);
Atomics.load(ta, 0); // 12

Atomics.isLockFree(1); // true
Atomics.isLockFree(2); // true
Atomics.isLockFree(3); // false
Atomics.isLockFree(4); // true

Atomics.or(ta, 0, 1);
Atomics.load(ta, 0); // 5

Atomics.store(ta, 0, 12); // 12

Atomics.sub(ta, 0, 2);
Atomics.load(ta, 0); // 3

Atomics.xor(ta, 0, 1);
Atomics.load(ta, 0); // 4

XDM - PostMessage

XDM 的核心是 postMessage()方法。除了 XDM,这个方法名还在 HTML5 中很多地方用到过,但目的都一样,都是把数据传送到另一个位置。
postMessage()方法接收 3 个参数:消息、表示目标接收源的字符串和可选的可传输对象的数组(只与工作线程相关)。第二个参数对于安全非常重要,其可以限制浏览器交付数据的目标。

语法

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • message

将要发送到其他 window 的数据。它将会被结构化克隆算法(en-US)序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。[1]

  • targetOrigin

通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用 postMessage 传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的 origin 属性完全一致,来防止密码被恶意的第三方截获。*如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的 targetOrigin,而不是 。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

  • transfer 可选

是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

event 对象

  • data

从其他 window 中传递过来的对象。

  • origin

调用 postMessage 时消息发送方窗口的 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成。例如“https://example.org (隐含端口 443)”、“http://example.net (隐含端口 80)”、“http://example.com:8080”。请注意,这个 origin 不能保证是该窗口的当前或未来 origin,因为 postMessage 被调用后可能被导航到不同的位置。

  • source

对发送消息的窗口对象的引用; 您可以使用此来在具有不同 origin 的两个窗口之间建立双向通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ----- 来源窗口 ----- 
let iframeWindow = document.getElementById("myframe").contentWindow;
iframeWindow.postMessage("A secret", "http://www.wrox.com");

// ----- 目标窗口 -----
window.addEventListener("message", (event) = > {
// 确保来自预期发送者
if (event.origin == "http://www.wrox.com") {
// 对数据进行一些处理
// processMessage(event.data);
// 可选:向来源窗口发送一条消息
event.source.postMessage("Received!", "http://p2p.wrox.com");
}
});

Encoding

Encoding API 提供了一种机制来处理各种字符编码文本,包括传统的非 UTF-8 编码。
API 提供了四个接口:TextDecoderTextEncoderTextDecoderStreamTextEncoderStream

TextDecoder

语法

new TextDecoder([utfLabel, options])

  • utfLabel 可选; 一个字符串,默认是 “utf-8”。可以是任意有效的编码
  • options 可选;一个具有属性的对象:
    • fatal: 一个布尔值,表示在解码无效数据时,TextDecoder.decode() 方法是否必须抛出 TypeError。默认是 false,这意味着解码器将用替换字符替换错误的数据。

方法

decode(buffer, options)

  • buffer 可选;一个 ArrayBuffer、TypedArray 或包含要解码的编码文本的 DataView 对象。
  • options 可选;具有以下属性的对象:
    • stream:一个布尔标志,表示在后续调用 decode() 将跟随附加数据。如果以分块的形式处理数据,则设置为 true;如果是最终的分块或者数据没有分块,则设置为 false。默认是 false。
1
2
3
4
5
let utf8decoder = new TextDecoder(); // default 'utf-8' or 'utf8'
let u8arr = new Uint8Array([240, 160, 174, 183]);
console.log(utf8decoder.decode(u8arr));


TextEncoder

语法

new TextEncoder();

方法

  • encode() 方法接受一个字符串作为输入,返回一个对参数中给定的文本的编码后的 Uint8Array,编码的方法通过 TextEncoder 对象指定。
  • encodeInto(string, uint8Array), 将编码后的内容写入到unit8Array中

File API & Blob API

File API 仍然以表单中的文件输入字段为基础,但是增加了直接访问文件信息的能力。HTML5 在DOM 上为文件输入元素添加了 files 集合。当用户在文件字段中选择一个或多个文件时,这个 files集合中会包含一组 File 对象,表示被选中的文件。每个 File 对象都有一些只读属性。

  • name:本地系统中的文件名。
  • size:以字节计的文件大小。
  • type:包含文件 MIME 类型的字符串。
  • lastModifiedDate:表示文件最后修改时间的字符串。这个属性只有 Chome 实现了。

FileReader

FileReader类型表示一种异步文件读取机制。可以把FileReader 想象成类似于XMLHttpRequest,只不过是用于从文件系统读取文件,而不是从服务器读取数据。FileReader 类型提供了几个读取文件数据的方法。

  • readAsText(file, encoding):从文件中读取纯文本内容并保存在 result 属性中。第二个参数表示编码,是可选的。
  • readAsDataURL(file):读取文件并将内容的数据 URI 保存在 result 属性中。
  • readAsBinaryString(file):读取文件并将每个字符的二进制数据保存在 result 属性中。
  • readAsArrayBuffer(file):读取文件并将文件内容以 ArrayBuffer 形式保存在 result 属性。

这些读取数据的方法为处理文件数据提供了极大的灵活性。例如,为了向用户显示图片,可以将图片读取为数据 URI,而为了解析文件内容,可以将文件读取为文本。因为这些读取方法是异步的,所以每个 FileReader 会发布几个事件,其中 3 个最有用的事件是:

  • progress 事件每 50 毫秒就会触发一次,其与 XHR 的 progress 事件具有相同的信息:lengthComputable、loaded 和 total。此外,在 progress 事件中可以读取 FileReader 的 result属性,即使其中尚未包含全部数据。
  • error 事件会在由于某种原因无法读取文件时触发。触发 error 事件时,FileReader 的 error属性会包含错误信息。这个属性是一个对象,只包含一个属性:code。这个错误码的值可能是 1(未找到文件)、2(安全错误)、3(读取被中断)、4(文件不可读)或 5(编码错误)。
  • load 事件会在文件成功加载后触发。如果 error 事件被触发,则不会再触发 load 事件。

FileReaderSync

FileReaderSync 类型就是 FileReader 的同步版本。这个类型拥有与 FileReader相同的方法,只有在整个文件都加载到内存之后才会继续执行。FileReaderSync 只在工作线程中可用,因为如果读取整个文件耗时太长则会影响全局。

Blob

blob 表示二进制大对象(binary larget object),是 JavaScript 对不可修改二进制数据的封装类型。包含字符串的数组、ArrayBuffers、ArrayBufferViews,甚至其他 Blob 都可以用来创建 blob。Blob构造函数可以接收一个 options 参数,并在其中指定 MIME 类型。
对象 URL 有时候也称作 Blob URL,是指引用存储在 File 或 Blob 中数据的 URL。对象 URL 的优点是不用把文件内容读取到 JavaScript 也可以使用文件。只要在适当位置提供对象 URL 即可。要创建对象 URL,可以使用 **window.URL.createObjectURL()**方法并传入 File 或 Blob 对象。这个函数返回的值是一个指向内存中地址的字符串。因为这个字符串是 URL,所以可以在 DOM 中直接使用。
使用完数据之后,最好能释放与之关联的内存。只要对象 URL 在使用中,就不能释放内存。如果想表明不再使用某个对象 URL,则可以把它传给** window.URL.revokeObjectURL()**。页面卸载时,所有对象 URL 占用的内存都会被释放。不过,最好在不使用时就立即释放内存,以便尽可能保持页面占用最少资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let filesList = document.getElementById("files-list"); 
filesList.addEventListener("change", (event) = > {
let info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = event.target.files,
reader = new FileReader(),
// 这里读取对象URL
url = window.URL.createObjectURL(files[0]);
if (url) {
if (/image/.test(files[0].type)) {
output.innerHTML = `< img src="${url}" >`;
} else {
output.innerHTML = "Not an image.";
}
} else {
output.innerHTML = "Your browser doesn't support object URLs.";
}
});

媒体元素 video & audio

HTML5 新增了两个与媒体相关的元素,即< audio >和< video >,从而为浏览器提供了嵌入音频和视频的统一解决方案。这两个元素既支持 Web 开发者在页面中嵌入媒体文件,也支持 JavaScript 实现对媒体的自定义控制。

1
2
3
4
5
< !-- 嵌入视频 -- > 
< video src="conference.mpg" id="myVideo" >Video player not available.< /video >
< !-- 嵌入音频 -- >
< audio src="song.mp3" id="myAudio" >Audio player not available.< /audio >

< audio >元素还有一个名为 Audio 的原生 JavaScript 构造函数,支持在任何时候播放音频。Audio类型与 Image 类似,都是 DOM 元素的对等体,只是不需插入文档即可工作。要通过 Audio 播放音频,只需创建一个新实例并传入音频源文件:

1
2
3
4
let audio = new Audio("sound.mp3"); 
EventUtil.addHandler(audio, "canplaythrough", function(event) {
audio.play();
});
  • 创建 Audio 的新实例就会开始下载指定的文件。下载完毕后,可以调用 play()来播放音频。
  • 在 iOS 中调用 play()方法会弹出一个对话框,请求用户授权播放声音。为了连续播放,必须在onfinish 事件处理程序中立即调用 play()。

属性

  • autoplay Boolean 取得或设置 autoplay 标签
  • buffered TimeRanges 对象,表示已下载缓冲的时间范围
  • bufferedBytes ByteRanges 对象,表示已下载缓冲的字节范围
  • bufferingRate Integer 平均每秒下载的位数
  • bufferingThrottled Boolean 表示缓冲是否被浏览器截流
  • controls Boolean 取得或设置 controls 属性,用于显示或隐藏浏览器内置控件
  • currentLoop Integer 媒体已经播放的循环次数
  • currentSrc String 当前播放媒体的 URL
  • currentTime Float 已经播放的秒数
  • defaultPlaybackRate Float 取得或设置默认回放速率。默认为 1.0 秒
  • duration Float 媒体的总秒数
  • ended Boolean 表示媒体是否播放完成
  • loop Boolean 取得或设置媒体是否应该在播放完再循环开始
  • muted Boolean 取得或设置媒体是否静音
  • networkState Integer 表示媒体当前网络连接状态。0 表示空,1 表示加载中,2 表示加载元数据,3 表示加载了第一帧,4 表示加载完成
  • paused Boolean 表示播放器是否暂停
  • playbackRate Float 取得或设置当前播放速率。用户可能会让媒体播放快一些或慢一些。与
  • defaultPlaybackRate 不同,该属性会保持不变,除非开发者修改
  • played TimeRanges 到目前为止已经播放的时间范围
  • readyState Integer 表示媒体是否已经准备就绪。0 表示媒体不可用,1 表示可以显示当前帧,2 表示媒体可以开始播放,3 表示媒体可以从头播到尾
  • seekable TimeRanges 可以跳转的时间范围
  • seeking Boolean 表示播放器是否正移动到媒体文件的新位置
  • src String 媒体文件源。可以在任何时候重写
  • start Float 取得或设置媒体文件中的位置,以秒为单位,从该处开始播放
  • totalBytes Integer 资源需要的字节总数(如果知道的话)
  • videoHeight Integer 返回视频(不一定是元素)的高度。只适用于< video >
  • videoWidth Integer 返回视频(不一定是元素)的宽度。只适用于< video >
  • volume Float 取得或设置当前音量,值为 0.0 到 1.0

事件

  • abort 下载被中断
  • canplay 回放可以开始,readyState 为 2
  • canplaythrough 回放可以继续,不应该中断,readState 为 3
  • canshowcurrentframe 已经下载当前帧,readyState 为 1
  • dataunavailable 不能回放,因为没有数据,readyState 为 0
  • durationchange duration 属性的值发生变化
  • emptied 网络连接关闭了
  • empty 发生了错误,阻止媒体下载
  • ended 媒体已经播放完一遍,且停止了
  • error 下载期间发生了网络错误
  • load 所有媒体已经下载完毕。这个事件已被废弃,使用 canplaythrough 代替
  • loadeddata 媒体的第一帧已经下载
  • loadedmetadata 媒体的元数据已经下载
  • loadstart 下载已经开始
  • pause 回放已经暂停
  • play 媒体已经收到开始播放的请求
  • playing 媒体已经实际开始播放了
  • progress 下载中
  • ratechange 媒体播放速率发生变化
  • seeked 跳转已结束
  • seeking 回放已移动到新位置
  • stalled 浏览器尝试下载,但尚未收到数据
  • timeupdate currentTime 被非常规或意外地更改了
  • volumechange volume 或 muted 属性值发生了变化
  • waiting 回放暂停,以下载更多数据

能力检测

也有 JavaScript API 可以用来检测浏览器是否支持给定格式和编解码器。这两个媒体元素都有一个名为 canPlayType()的方法,该方法接收一个格式/编解码器字符串,返回一个字符串值:“probably”、“maybe"或”"(空字符串),其中空字符串就是假值,意味着可以在 if 语句中像这样使用 canPlayType()

1
2
3
4
5
6
7
8
9
let audio = document.getElementById("audio-player"); 
// 很可能是"maybe"
if (audio.canPlayType("audio/mpeg")) {
// 执行某些操作
}
// 可能是"probably"
if (audio.canPlayType("audio/ogg; codecs=\"vorbis\"")) {
// 执行某些操作
}

Notifications API

Notifications API 用于向用户显示通知。无论从哪个角度看,这里的通知都很类似 alert()对话框:都使用 JavaScript API 触发页面外部的浏览器行为,而且都允许页面处理用户与对话框或通知弹层的交互。不过,通知提供更灵活的自定义能力。
Notifications API 有被滥用的可能,因此默认会开启两项安全措施:

  • 通知只能在运行在安全上下文的代码中被触发(HTTPS);
  • 通知必须按照每个源的原则明确得到用户允许(用户手动授权);

授权

“granted"值意味着用户明确授权了显示通知的权限。除此之外的其他值意味着显示通知会静默失败。如果用户拒绝授权,这个值就是"denied”。一旦拒绝,就无法通过编程方式挽回,因为不可能再触发授权提示。

1
2
3
4
Notification.requestPermission() 
.then((permission) = > {
console.log('User responded to permission request:', permission);
});

使用

生命周期方法:

  •  onshow 在通知显示时触发;
  •  onclick 在通知被点击时触发;
  •  onclose 在通知消失或通过 close()关闭时触发;
  •  onerror 在发生错误阻止通知显示时触发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var notify = new Notification('Title text!', {
body: 'Body text!',
image: 'path/to/image.png',
vibrate: true
});

// 事件
notify.onshow = () = > console.log('Notification was shown!');
notify.onclick = () = > console.log('Notification was clicked!');
notify.onclose = () = > console.log('Notification was closed!');
notify.onerror = () = > console.log('Notification experienced an error!');

// 调用close 关闭通知窗体
setTimeout(()= >{notify.close()},1000)

Page Visibility API

Page Visibility API 旨在为开发者提供页面对用户是否可见的信息。Web 开发中一个常见的问题是开发者不知道用户什么时候真正在使用页面。如果页面被最小化或隐藏在其他标签页后面,那么轮询服务器或更新动画等功能可能就没有必要了。

  • document.visibilityState 值(“hidden”,“visible”,“prerender”),表示下面 4 种状态之一。
    • 页面在后台标签页或浏览器中最小化了。
    • 页面在前台标签页中。
    • 实际页面隐藏了,但对页面的预览是可见的(例如在 Windows 7 上,用户鼠标移到任务栏图标上会显示网页预览)。
    • 页面在屏外预渲染。
  • visibilitychange 事件,该事件会在文档从隐藏变可见(或反之)时触发。
  • document.hidden 布尔值,表示页面是否隐藏。这可能意味着页面在后台标签页或浏览器中被最小化了。这个值是为了向后兼容才继续被浏览器支持的,应该优先使用 document.visibilityState检测页面可见性。

Streams API

Stream API 直接解决的问题是处理网络请求和读写磁盘。

计时 API (performance)

页面性能始终是 Web 开发者关心的话题。Performance 接口通过 JavaScript API 暴露了浏览器内部的度量指标,允许开发者直接访问这些信息并基于这些信息实现自己想要的功能。这个接口暴露在window.performance 对象上。所有与页面相关的指标,包括已经定义和将来会定义的,都会存在于这个对象上。
Performance 接口由多个 API 构成:

  • High Resolution Time API
  • Performance Timeline API
  • Navigation Timing API
  • User Timing API
  • Resource Timing API
  • Paint Timing API

Web Cryptography API (加密)

Web Cryptography API 描述了一套密码学工具,规范了 JavaScript 如何以安全和符合惯例的方式实现加密。这些工具包括生成、使用和应用加密密钥对,加密和解密消息,以及可靠地生成随机数。

生成随机数

crypto.getRandomValues()在全局 Crypto 对象上访问。与 Math.random()返回一个介于 0和 1之间的浮点数不同,getRandomValues()会把随机值写入作为参数传给它的定型数组。定型数组的类不重要,因为底层缓冲区会被随机的二进制位填充。

1
2
3
4
5
6
7
8
9
const array = new Uint8Array(1); 
for (let i=0; i< 5; ++i) {
console.log(crypto.getRandomValues(array));
}
// Uint8Array [41]
// Uint8Array [250]
// Uint8Array [51]
// Uint8Array [129]
// Uint8Array [35]

SubtleCrypto 对象

可以通过 window.crypto. subtle 访问,这个对象包含一组方法,用于执行常见的密码学功能,如加密、散列、签名和生成密钥。因为所有密码学操作都在原始二进制数据上执行,所以 SubtleCrypto 的每个方法都要用到 ArrayBuffer 和ArrayBufferView 类型。由于字符串是密码学操作的重要应用场景,因此 TextEncoder 和TextDecoder 是经常与 SubtleCrypto 一起使用的类,用于实现二进制数据与字符串之间的相互转换。
⚠️ 注意 SubtleCrypto 对象只能在安全上下文(https)中使用。在不安全的上下文中,subtle 属性是 undefined。


错误处理与调试

对于今天复杂的 Web 应用程序而言,JavaScript 中的错误处理十分重要。未能预测什么时候会发生错误以及如何从错误中恢复,会导致糟糕的用户体验,甚至造成用户流失。大多数浏览器默认不向用户报告 JavaScript 错误,因此在开发和调试时需要自己实现错误报告。不过在生产环境中,不应该以这种方式报告错误。
下列方法可用于阻止浏览器对 JavaScript 错误作出反应。

  •  使用 try/catch 语句,可以通过更合适的方式对错误做出处理,避免浏览器处理。
  •  定义 window.onerror 事件处理程序,所有没有通过 try/catch 处理的错误都会被该事件处理程序接收到(仅限 IE、Firefox 和 Chrome)。

开发 Web 应用程序时,应该认真考虑可能发生的错误,以及如何处理这些错误。

  •  首先,应该分清哪些算重大错误,哪些不算重大错误。
  •  然后,要通过分析代码预测很可能发生哪些错误。由于以下因素,JavaScript 中经常出现错误:
    •  类型转换;
    •  数据类型检测不足;
    •  向服务器发送错误数据或从服务器接收到错误数据。

IE、Firefox、Chrome、Opera 和 Safari 都有 JavaScript 调试器,有的内置在浏览器中,有的是作为扩 展,需另行下载。所有调试器都能够设置断点、控制代码执行和在运行时检查变量值。

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 处理转换。

JSON

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

方法

parse()

解析 JSON 字符串并返回对应的值,可以额外传入一个转换函数,用来将生成的值和其属性,在返回之前进行某些修改。
语法: JSON.parse(text[, reviver])

1
2
3
4
JSON.parse('{"p": 5}', function (k, v) {
if(k === '') return v; // 如果到了最顶层,则直接返回属性值,
return v * 2; // 否则将属性值变为原来的 2 倍。
});

stringify()

语法:JSON.stringify(value[, replacer [, space]])
返回与指定值对应的 JSON 字符串,可以通过额外的参数,控制仅包含某些属性,或者以自定义方法来替换某些 key 对应的属性值。

replacer参数

replacer 参数可以是一个函数或者一个数组。作为函数,它有两个参数,键(key)和值(value),它们都会被序列化。
在开始时,replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。随后每个对象或数组上的属性会被依次传入。
函数应当返回 JSON 字符串中的 value, 如下所示:

  • 如果返回一个 Number, 转换成相应的字符串作为属性值被添加入 JSON 字符串。
  • 如果返回一个 String, 该字符串作为属性值被添加入 JSON 字符串。
  • 如果返回一个 Boolean, “true” 或者 “false” 作为属性值被添加入 JSON 字符串。
  • 如果返回任何其他对象,该对象递归地序列化成 JSON 字符串,对每个属性调用 replacer 方法。除非该对象是一个函数,这种情况将不会被序列化成 JSON 字符串。
  • 如果返回 undefined,该属性值不会在 JSON 字符串中输出。
1
2
3
4
5
6
7
8
9
10
11
12
// 过滤掉所有的string类型的属性

function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}

var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
// 使用replacer 作为过滤器,使用两个空格 来替代原始的空格符
var jsonString = JSON.stringify(foo, replacer," ");

网络请求与远程资源

Ajax 是无须刷新当前页面即可从服务器获取数据的一个方法,具有如下特点。

  • 让 Ajax 迅速流行的中心对象是 XMLHttpRequest(XHR)。
  • 这个对象最早由微软发明,并在 IE5 中作为通过 JavaScript 从服务器获取 XML 数据的一种手段。
  • 之后,Firefox、Safari、Chrome 和 Opera 都复刻了相同的实现。W3C 随后将 XHR 行为写入 Web标准。

XMLHttpRequest

XHR 的一个主要限制是同源策略,即通信只能在相同域名、相同端口和相同协议的前提下完成。访问超出这些限制之外的资源会导致安全错误,除非使用了正式的跨域方案。这个方案叫作跨源资源共享(CORS,Cross-Origin Resource Sharing),XHR 对象原生支持 CORS。图片探测和 JSONP 是另外两种跨域通信技术,但没有 CORS 可靠。
所有现代浏览器都通过 XMLHttpRequest 构造函数原生支持 XHR 对象。

属性

  • onreadystatechange: 当 readyState 属性发生变化时,调用的事件处理器。
  • readyState :只读,返回 一个无符号短整型(unsigned short)数字,代表请求的状态码。
  • response :只读,返回一个 ArrayBuffer、Blob、Document,或 DOMString,具体是哪种类型取决于 XMLHttpRequest.responseType 的值。其中包含整个响应实体(response entity body)。
  • responseText :只读。返回一个 DOMString,该 DOMString 包含对请求的响应,如果请求未成功或尚未发送,则返回 null。
  • responseType:一个用于定义响应类型的枚举值(enumerated value)。
  • responseURL :只读,返回经过序列化(serialized)的响应 URL,如果该 URL 为空,则返回空字符串。
  • responseXML: 只读,返回一个 Document,其中包含该请求的响应,如果请求未成功、尚未发送或是不能被解析为 XML 或 HTML,则返回 null。
  • status: 只读,返回一个无符号短整型(unsigned short)数字,代表请求的响应状态。
  • statusText :只读,返回一个 DOMString,其中包含 HTTP 服务器返回的响应状态。与 XMLHTTPRequest.status 不同的是,它包含完整的响应状态文本(例如,“200 OK”)。
  • timeout:一个无符号长整型(unsigned long)数字,表示该请求的最大请求时间(毫秒),若超出该时间,请求会自动终止。
  • upload: 只读,返回一个 XMLHttpRequestUpload对象,用来表示上传的进度
  • withCredentials:一个布尔值(en-US),用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。

方法

  • abort():如果请求已被发出,则立刻中止请求。
  • getAllResponseHeaders():以字符串的形式返回所有用 CRLF 分隔的响应头,如果没有收到响应,则返回 null。
  • getResponseHeader():返回包含指定响应头的字符串,如果响应尚未收到或响应中不存在该报头,则返回 null。
  • ⭐️ open():初始化一个请求。该方法只能在 JavaScript 代码中使用,若要在 native code 中初始化请求,请使用 openRequest()。

语法:**open(method, url, async, user, password);**

  • overrideMimeType():覆写由服务器返回的 MIME 类型。
  • ⭐️ send():发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回。
  • setRequestHeader():设置 HTTP 请求头的值。必须在 open() 之后、send() 之前调用 setRequestHeader() 方法。

事件

  • onreadystatechange : 当readyState 值改变的时候会触发该事件
  • abort:当 request 被停止时触发,例如当程序调用 XMLHttpRequest.abort() 时。 也可以使用 onabort 属性。
  • error:当 request 遭遇错误时触发。 也可以使用 onerror 属性
  • load:XMLHttpRequest请求成功完成时触发。 也可以使用 onload 属性。
  • loadend:当请求结束时触发,无论请求成功 ( load) 还是失败 (abort 或 error)。 也可以使用 onloadend 属性。
  • loadstart:接收到响应数据时触发。 也可以使用 onloadstart 属性。
  • progress:当请求接收到更多数据时,周期性地触发。 也可以使用 onprogress 属性。
  • timeout:在预设时间内没有接收到响应时触发。 也可以使用 ontimeout 属性。

收到响应后,XHR对象的以下属性会被填充上数据。

  • responseText:作为响应体返回的文本。
  • responseXML:如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应数据的 XML DOM 文档。
  • status:响应的 HTTP 状态。
  • statusText:响应的 HTTP 状态描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let xhr = new XMLHttpRequest();

// 每次 readyState 从一个值变成另一个值,都会触发 readystatechange 事件。

/* readyState 的值
0:未初始化(Uninitialized)。尚未调用 open()方法。
1:已打开(Open)。已调用 open()方法,尚未调用 send()方法。
2:已发送(Sent)。已调用 send()方法,尚未收到响应。
3:接收中(Receiving)。已经收到部分响应。
4:完成(Complete)。已经收到所有响应,可以使用了。
*/
xhr.onreadystatechange = function() {
// readyState = 4时表示请求完成,数据就绪
if (xhr.readyState == 4) {
// 收到请求后需要检查请求是否成功返回。如2xx和304命中缓存的表示成功返回
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};

xhr.open("get", "example.txt", false);

//send()方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传 null,
//因为这个参数在某些浏览器中是必需的。调用 send()之后,请求就会发送到服务器。
xhr.send(null);

JSONP

跨域解决方案,利用< script > js标签允许跨域,该方法仅支持Get方法,需要后端配合

1
2
3
4
5
6
7
 console.log(` 
You're at IP address ${response.ip}, which is in
${response.city}, ${response.region_name}`);
}
let script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

Fetch API

Fetch API 是作为对 XHR 对象的一种端到端的替代方案而提出的。这个 API 提供了优秀的基于期约的结构、更直观的接口,以及对 Stream API 的最好支持。
fetch()方法是暴露在全局作用域中的,包括主页面执行线程、模块和工作线程。调用这个方法,浏览器就会向给定 URL 发送请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let payload = JSON.stringify({ 
foo: 'bar'
});
let jsonHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
body: payload,
headers: jsonHeaders
}).then((response) = > {
console.log(response.status); // 404
console.log(response.ok); // false
});

Beacon API

W3C 引入了补充性的 Beacon API。这个 API 给 navigator 对象增加了一个sendBeacon()方法。这个简单的方法接收一个 URL 和一个数据有效载荷参数,并会发送一个 POST请求。可选的数据有效载荷参数有 ArrayBufferView、Blob、DOMString、FormData 实例。如果请求成功进入了最终要发送的任务队列,则这个方法返回 true,否则返回 false。
这个方法虽然看起来只不过是 POST 请求的一个语法糖,但它有几个重要的特性。

  • ** sendBeacon()并不是只能在页面生命周期末尾使用,而是任何时候都可以使用。**
  •  调用 sendBeacon()后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队列中的请求。
  • 浏览器保证在原始页面已经关闭的情况下也会发送请求。
  •  状态码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理。
  •  信标(beacon)请求会携带调用 sendBeacon()时所有相关的

Web Socket

Web Socket 是与服务器的全双工、双向通信渠道。与其他方案不同,Web Socket 不使用 HTTP,而使用了自定义协议,目的是更快地发送小数据块。这需要专用的服务器,但速度优势明显。

事件

  •  open:在连接成功建立时触发。
  •  error:在发生错误时触发。连接无法存续。
  •  close:在连接关闭时触发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let socket = new WebSocket("ws://www.example.com/server.php"); 
let stringData = "Hello world!";
let arrayBufferData = Uint8Array.from(['f', 'o', 'o']);
let blobData = new Blob(['f', 'o', 'o']);
socket.send(stringData);
socket.send(arrayBufferData.buffer);
socket.send(blobData);


socket.onopen = function() {
alert("Connection established.");
};
socket.onerror = function() {
alert("Connection error.");
};
socket.onclose = function() {
alert("Connection closed.");
};

安全

关于安全防护 Ajax 相关 URL 的一般理论认为,需要验证请求发送者拥有对资源的访问权限。可以通过如下方式实现。

  •  要求通过 SSL 访问能够被 Ajax 访问的资源。
  •  要求每个请求都发送一个按约定算法计算好的令牌(token)。

注意,以下手段对防护 CSRF 攻击是无效的。

  •  要求 POST 而非 GET 请求(很容易修改请求方法)。
  •  使用来源 URL 验证来源(来源 URL 很容易伪造)。
  •  基于 cookie 验证(同样很容易伪造)。

客户端存储

⚠️ 不要在 cookie 中存储重要或敏感的信息。cookie 数据不是保存在安全的环境中,因此任何人都可能获得。应该避免把信用卡号或个人地址等信息保存在 cookie 中。

限制

  • 不超过 300 个 cookie;
  • 每个 cookie 不超过 4096 字节;
  • 每个域不超过 20 个 cookie;
  • 每个域不超过 81 920 字节
  • 如果 cookie 总数超过了单个域的上限,浏览器就会删除之前设置的 cookie。

构成

  • **名称:**唯一标识 cookie 的名称。cookie 名不区分大小写,cookie 名必须经过 URL 编码。
  • **值:**存储在 cookie 里的字符串值。这个值必须经过 URL 编码。
  • **域(domain):**cookie 有效的域。发送到这个域的所有请求都会包含对应的 cookie。这个值可能包含子域(如www.wrox.com),也可以不包含(如.wrox.com 表示对 wrox.com 的所有子域都有效)。如果不明确设置,则默认为设置 cookie 的域。
  • **路径(path):**请求 URL 中包含这个路径才会把 cookie 发送到服务器。例如,可以指定 cookie 只能由http://www.wrox.com/books/访问,因此访问 http://www.wrox.com/下的页面就不会发送 cookie,即使请求的是同一个域。
  • **过期时间(expires):**表示何时删除 cookie 的时间戳(即什么时间之后就不发送到服务器了)。默认情况下,浏览器会话结束后会删除所有 cookie。不过,也可以设置删除 cookie 的时间。这个值是 GMT 格式(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定删除 cookie 的具体时间。这样即使关闭浏览器 cookie 也会保留在用户机器上。把过期时间设置为过去的时间会立即删除 cookie。
  • **安全标志(secure):**设置之后,只在使用 SSL 安全连接的情况下才会把 cookie 发送到服务器。例如,请求 https://www.wrox.com 会发送 cookie,而请求 http://www.wrox.com 则不会。
  • HTTPOnly: 设置之后,禁止JS访问该Cookie。但是服务器端可以访问到

使用

在 JavaScript 中处理 cookie 比较麻烦,因为接口过于简单,只有 BOM 的 document.cookie 属性。
根据用法不同,该属性的表现迥异。要使用该属性获取值时,document.cookie 返回包含页面中所有有效 cookie 的字符串(根据域、路径、过期时间和安全设置),以分号分隔,如下面的例子所示:
name1=value1;name2=value2;name3=value3
所有名和值都是 URL 编码的,因此必须使用 decodeURIComponent()解码。
在设置值时,可以通过 document.cookie 属性设置新的 cookie 字符串。这个字符串在被解析后会添加到原有 cookie 中。设置 document.cookie 不会覆盖之前存在的任何 cookie,除非设置了已有的cookie。设置 cookie 的格式如下,与 Set-Cookie 头部的格式一样:
name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure
在所有这些参数中,只有 cookie 的名称和值是必需的。下面是个简单的例子:
document.cookie = "name=Nicholas";

Web Storage

Web Storage 的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用 cookie 的问题。
Web Storage 规范最新的版本是第 2 版,这一版规范主要有两个目标:

  • 提供在 cookie 之外的存储会话数据的途径;
  • 提供跨会话持久化存储大量数据的机制。

Web Storage 的第 2 版定义了两个对象:localStorage 和 sessionStorage。
localStorage 是永久存储机制,sessionStorage 是跨会话的存储机制。这两种浏览器存储 API 提供了在浏览器中不受页面刷新影响而存储数据的两种方式。

Storage 类型

Storage 类型用于保存名/值对数据,直至存储空间上限(由浏览器决定)。Storage 的实例与其他对象一样,但增加了以下方法。

  • clear():删除所有值;不在 Firefox 中实现。
  • getItem(name):取得给定 _name _的值。
  • key(index):取得给定数值位置的名称。
  • removeItem(name):删除给定 _name _的名/值对
  • setItem(name, value):设置给定 _name _的值

getItem()、removeItem()和 setItem()方法可以直接或间接通过 Storage 对象调用。

限制

具体的限制取决于特定的浏览器。一般来说,客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数据存储空间。分析存储数据的页面的源可以加强这一限制

sessionStorage

sessionStorage 对象是 Storage 的实例
**sessionStorage 对象只存储会话数据,这意味着数据只会存储到浏览器关闭。**这跟浏览器关闭时会消失的会话 cookie 类似。存储在 sessionStorage 中的数据不受页面刷新影响,可以在浏览器崩溃并重启后恢复。
存储在sessionStorage 对象中的数据只能由最初存储数据的页面使用,在多页应用程序中的用处有限

localStorage

localStorage 对象是 Storage 的实例
**存储在 localStorage 中的数据会保留到通过 JavaScript 删除或者用户清除浏览器缓存。**localStorage 数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失

存储事件

每当 Storage 对象发生变化时,都会在文档上触发 storage 事件。使用属性或 setItem()设置值、使用 delete 或 removeItem()删除值,以及每次调用 clear()时都会触发这个事件。这个事件的事件对象有如下 4 个属性。

  • domain:存储变化对应的域。
  • key:被设置或删除的键。
  • newValue:键被设置的新值,若键被删除则为 null。
  • oldValue:键变化之前的值。

可以使用如下代码监听 storage 事件:
window.addEventListener("storage", (event) = > alert('Storage changed for ${event.domain}'));
对于 sessionStorage 和 localStorage 上的任何更改都会触发 storage 事件,但 storage 事件不会区分这两者。

IndexDB

Indexed Database API 简称 IndexedDB,是浏览器中存储结构化数据的一个方案。IndexedDB 用于代替目前已废弃的 Web SQL Database API。**IndexedDB 背后的思想是创造一套 API,方便 JavaScript 对象的存储和获取,同时也支持查询和搜索。 **
IndexedDB 的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确定输出。

使用

使用太过复杂,建议在具体使用的时候再去查阅相关文档。

限制

IndexedDB 数据库是与页面源(协议、域和端口)绑定的,因此信息不能跨域共享。这意味着 www.wrox.comp2p.wrox.com 会对应不同的数据存储。
其次,每个源都有可以存储的空间限制。当前 Firefox 的限制是每个源 50MB,而 Chrome 是 5MB。移动版 Firefox 有 5MB 限制,如果用度超出配额则会请求用户许可。
Firefox 还有一个限制——本地文本不能访问 IndexedDB 数据库。Chrome 没有这个限制。因此在本地运行本书示例时,要使用 Chrome。


模块

CommonJS

**CommonJS 规范概述了同步声明依赖的模块定义。**这个规范主要用于在服务器端实现模块化代码组织,但也可用于定义在浏览器中使用的模块依赖。CommonJS 模块语法不能在浏览器中直接运行。
一般认为,Node.js的模块系统使用了CommonJS规范,实际上并不完全正确。Node.js使用了轻微修改版本的 CommonJS,因为 Node.js 主要在服务器环境下使用,所以不需要考虑网络延迟问题。
无论一个模块在 require()中被引用多少次,模块永远是单例。
模块第一次加载后会被缓存,后续加载会取得缓存的模块。模块加载顺序由依赖图决定。
CommonJS 依赖几个全局属性如 require 和 module.exports。如果想在浏览器中使用 CommonJS模块,就需要与其非原生的模块语法之间构筑“桥梁”。模块级代码与浏览器运行时之间也需要某种“屏障”,因为没有封装的 CommonJS 代码在浏览器中执行会创建全局变量。这显然与模块模式的初衷相悖。
常见的解决方案是提前把模块文件打包好,把全局属性转换为原生 JavaScript 结构,将模块代码封装在函数闭包中,最终只提供一个文件。为了以正确的顺序打包模块,需要事先生成全面的依赖图。

AMD

异步模块定义(Asynchronous Module Definition)
CommonJS 以服务器端为目标环境,能够一次性把所有模块都加载到内存,而AMD的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的问题。AMD 的一般策略是让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖加载完成后立即执行依赖它们的模块。
**AMD 模块实现的核心是用函数包装模块定义。这样可以防止声明全局变量,并允许加载器库控制何时加载模块。**包装函数也便于模块代码的移植,因为包装函数内部的所有模块代码使用的都是原生JavaScript 结构。包装模块的函数是全局 define 的参数,它是由 AMD 加载器库的实现定义的。
AMD 模块可以使用字符串标识符指定自己的依赖,而 AMD 加载器会在所有依赖模块加载完毕后立即调用模块工厂函数。与 CommonJS 不同,AMD 支持可选地为模块指定字符串标识符。

1
2
3
4
5
6
7
// ID 为'moduleA'的模块定义。moduleA 依赖 moduleB,
// moduleB 会异步加载
define('moduleA', ['moduleB'], function(moduleB) {
return {
stuff: moduleB.doStuff();
};
});

UMD

通用模块定义(Universal Module Definition)
为了统一 CommonJS 和 AMD 生态系统,通用模块定义(UMD,Universal Module Definition)规范应运而生。UMD 可用于创建这两个系统都可以使用的模块代码。本质上,UMD 定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即调用的函数表达式(IIFE)中。虽然这种组合并不完美,但在很多场景下足以实现两个生态的共存。

ES6模块

ECMAScript 6 模块是作为一整块 JavaScript 代码而存在的。带有** type=“module”**属性的< script >标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中,也可以作为外部文件引入。

1
2
3
4
< script type="module" > 
// 模块代码
< / >
< script type="module" src="path/to/myModule.js" >< /script >

ECMAScript 6 模块的独特之处在于,既可以通过浏览器原生加载,也可以与第三方加载器和构建工具一起加载。有些浏览器还没有原生支持 ES6 模块,因此可能还需要第三方工具。事实上,很多时候使用第三方工具可能会更方便。
完全支持 ECMAScript 6 模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成的。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析完依赖图,应用程序就可以正式加载模块了。

特性

     - 模块代码只在加载后执行。 
     - 模块只能加载一次。 
     - 模块是单例。 
     - 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互。 
     - 模块可以请求加载其他模块。 
     - 支持循环依赖。 
     - ES6 模块默认在严格模式下执行。 
     - ES6 模块不共享全局命名空间。 
     - 模块顶级 this 的值是 undefined(常规脚本中是 window)。 
     - 模块中的 var 声明不会添加到 window 对象。 
     - ES6 模块是异步加载和执行的。

导出

导出语句必须在模块顶级,不能嵌套在某个块中。命名导出和默认导出不会冲突,所以 ES6 支持在一个模块中同时定义这两种导出。
命名导出(named export)就好像模块是被导出值的容器。

1
2
3
4
5
// 1
export const bar = 'bar';
// 2
const baz = 'baz';
export { baz };

默认导出(default export)就好像模块与被导出的值是一回事。
默认导出使用 default 关键字将一个值声明为默认导出,每个模块只能有一个默认导出。重复的默认导出会导致 SyntaxError。

1
2
3
4
5
// 1
const foo = 'foo';
export default foo;
// 2
export { foo as default };

错误的导出

1
2
3
4
5
6
// 行内默认导出中不能出现变量声明
export default const foo = 'bar';
// 只有标识符可以出现在 export 子句中
export { 123 as foo }
// 别名只能在 export 子句中出现
export const foo = 'foo' as myFoo;

导入

模块可以通过使用 import 关键字使用其他模块导出的值。与 export 类似,import 必须出现在模块的顶级。
模块标识符可以是相对于当前模块的相对路径,也可以是指向模块文件的绝对路径。**它必须是纯字符串,不能是动态计算的结果。**例如,不能是拼接的字符串
如果在浏览器中通过标识符原生加载模块,则文件必须带有.js 扩展名,不然可能无法正确解析。不过,如果是通过构建工具或第三方模块加载器打包或解析的 ES6 模块,则可能不需要包含文件扩展名。
**导入对模块而言是只读的,实际上相当于 const 声明的变量。**在使用执行批量导入时,赋值给别名的命名导出就好像使用 Object.freeze()冻结过一样。直接修改导出的值是不可能的,但可以修改导出对象的属性。同样,也不能给导出的集合添加或删除导出的属性。要修改导出的值,必须使用有内部变量和属性访问权限的导出方法。
命名导出可以使用
批量获取并赋值给保存导出集合的别名,而无须列出每个标识符

1
2
3
4
5
6
7
8
9
// ------ 导出文件 foo.js ------
export const foo = 'foo';
export default foo;

// ------ 使用文件 -----
import foo, * as Foo from './foo.js';
foo = 'foo'; // 错误
Foo.foo = 'foo'; // 错误
foo.bar = 'bar'; // 允许

导出转移

在B中将A导出的模块再次通过B导出

1
2
3
4
5
6
7
8
9
foo.js
export const baz = 'origin:foo';
bar.js
export * from './foo.js';
// 这里覆盖了baz
export const baz = 'origin:bar';
main.js
import { baz } from './bar.js';
console.log(baz); // origin:bar

兼容处理

对于想要尽可能在浏览器中原生使用 ECMAScript 6 模块的用户,可以提供两个版本的代码:基于模块的版本与基于脚本的版本。如果嫌麻烦,可以使用第三方模块系统(如 SystemJS)或在构建时将 ES6 模块进行转译,这都是不错的方案。

1
2
3
4
5
6
// 支持模块的浏览器会执行这段脚本 
// 不支持模块的浏览器不会执行这段脚本
< script type="module" src="module.js" >< /script >
// 支持模块的浏览器不会执行这段脚本
// 不支持模块的浏览器会执行这段脚本
< script nomodule src="script.js" >< /script >

工作者线程

**JavaScript 环境实际上是运行在托管操作系统中的虚拟环境。在浏览器中每打开一个页面,就会分配一个它自己的环境。这样,每个页面都有自己的内存、事件循环、DOM,等等。每个页面就相当于一个沙盒,不会干扰其他页面。对于浏览器来说,同时管理多个环境是非常简单的,因为所有这些环境都是并行执行的。 **
使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境。这个子环境不能与依赖单线程交互的 API(如 DOM)互操作,但可以与父环境并行执行代码

  • **工作者线程是以实际线程实现的。**例如,Blink 浏览器引擎实现工作者线程的 WorkerThread 就对应着底层的线程。
  • **工作者线程并行执行。**虽然页面和工作者线程都是单线程 JavaScript 环境,每个环境中的指令则可以并行执行。
  • **工作者线程可以共享某些内存。**工作者线程能够使用 SharedArrayBuffer 在多个环境间共享内容。虽然线程会使用锁实现并发控制,但 JavaScript 使用 Atomics 接口实现并发控制。工作者线程与线程有很多类似之处,但也有重要的区别。
  • **工作者线程不共享全部内存。**在传统线程模型中,多线程有能力读写共享内存空间。除了 SharedArrayBuffer 外,从工作者线程进出的数据需要复制或转移。
  • **工作者线程不一定在同一个进程里。**通常,一个进程可以在内部产生多个线程。根据浏览器引擎的实现,工作者线程可能与页面属于同一进程,也可能不属于。例如,Chrome 的 Blink 引擎对共享工作者线程和服务工作者线程使用独立的进程。
  • **创建工作者线程的开销更大。**工作者线程有自己独立的事件循环、全局对象、事件处理程序和其他 JavaScript 环境必需的特性。创建这些结构的代价不容忽视

无论形式还是功能,工作者线程都不是用于替代线程的。HTML Web 工作者线程规范是这样说的: _工作者线程相对比较重,不建议大量使用。例如,对一张 400 万像素的图片,为每个像素都启动一个工作者线程是不合适的。通常,工作者线程应该是长期运行的,启动成本比较高,每个实例占用的内存也比较大。 _

属性&方法

在工作者线程内部,没有 window的概念。这里的全局对象是 WorkerGlobalScope 的实例,通过 self 关键字暴露出来,**WorkerGlobalScope **属性和方法

  - self 上可用的属性是 window 对象上属性的严格子集。其中有些属性会返回特定于工作者线程的版本。 
  - navigator:返回与工作者线程关联的 WorkerNavigator。 
  - self:返回 WorkerGlobalScope 对象。 
  - location:返回与工作者线程关联的 WorkerLocation。 
  - performance:返回(只包含特定属性和方法的)Performance 对象。 
  - console:返回与工作者线程关联的 Console 对象;对 API 没有限制。 
  - caches:返回与工作者线程关联的 CacheStorage 对象;对 API 没有限制。 
  - indexedDB:返回 IDBFactory 对象。 
  - isSecureContext:返回布尔值,表示工作者线程上下文是否安全。 
  - origin:返回 WorkerGlobalScope 的源。 

类似地,self 对象上暴露的一些方法也是 window 上方法的子集。这些 self 上的方法也与 window上对应的方法操作一样。

  - atob() 
  - btoa() 
  - clearInterval() 
  - clearTimeout() 
  - createImageBitmap() 
  - fetch() 
  - setInterval() 
  - setTimeout() 

**WorkerGlobalScope **的子类
实际上并不是所有地方都实现了 WorkerGlobalScope。每种类型的工作者线程都使用了自己特的全局对象,这继承自 WorkerGlobalScope。

  - 专用工作者线程使用 DedicatedWorkerGlobalScope。 
  - 共享工作者线程使用 SharedWorkerGlobalScope。 
  - 服务工作者线程使用 ServiceWorkerGlobalScope。

事件

Worker 对象支持下列事件处理程序属性。

  • onerror:在工作者线程中发生 ErrorEvent 类型的错误事件时会调用指定给该属性的处理程序。
    • 该事件会在工作者线程中抛出错误时发生。
    • 该事件也可以通过 worker.addEventListener(‘error’, handler)的形式处理。
  • onmessage:在工作者线程中发生 MessageEvent 类型的消息事件时会调用指定给该属性的处理程序。
    • 该事件会在工作者线程向父上下文发送消息时发生。
    • 该事件也可以通过使用 worker.addEventListener(‘message’, handler)处理。
  • onmessageerror:在工作者线程中发生 MessageEvent 类型的错误事件时会调用指定给该属性的处理程序。
    • 该事件会在工作者线程收到无法反序列化的消息时发生。
    • 该事件也可以通过使用 worker.addEventListener(‘messageerror’, handler)处理。

Worker 对象还支持下列方法。

  • postMessage():用于通过异步消息事件向工作者线程发送信息。
  • terminate():用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止。

类型

专用工作者线程

专用工作者线程,通常简称为工作者线程、Web Worker 或 Worker,是一种实用的工¯¯¯¯¯具,可以让本单独创建一个 JavaScript 线程,以执行委托的任务。专用工作者线程,顾名思义,只能被创建它的页面使用。
**因为启用工作者线程代价很大,所以某些情况下可以考虑始终保持固定数量的线程活动,需要时就把任务分派给它们。**工作者线程在执行计算时,会被标记为忙碌状态。直到它通知线程池自己空闲了,才准备好接收新任务。这些活动线程就称为“线程池”或“工作者线程池”。

属性&方法

DedicatedWorkerGlobalScope 在 WorkerGlobalScope 基础上增加了以下属性和方法。

  - name:可以提供给 Worker 构造函数的一个可选的字符串标识符。 
  - postMessage():与 worker.postMessage()对应的方法,用于从工作者线程内部向父上下 、文发送消息。 
  - close():与 worker.terminate()对应的方法,用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止。 
  - importScripts():用于向工作者线程中导入任意数量的脚本。

在整个生命周期中,一个专用工作者线程只会关联一个网页(Web 工作者线程规范称其为一个文档)。除非明确终止,否则只要关联文档存在,专用工作者线程就会存在。如果浏览器离开网页(通过导航或关闭标签页或关闭窗口),它会将与其关联的工作者线程标记为终止,它们的执行也会立即停止。
**工作者线程的脚本文件只能从与父页面相同的源加载。**从其他源加载工作者线程的脚本文件会导致错误。但是在工作者线程内部,可以使用 importScripts()可以加载其他源的脚本。

创建

1
2
3
4
5
6
7
8
9
// -------emptyWorker.js------
// 空的 JS 工作者线程文件


// -------main.js------
// 尝试基于 https://example.com/worker.js 创建工作者线程
const sameOriginWorker = new Worker('./worker.js');
// 尝试基于 https://untrusted.com/worker.js 创建工作者线程
const remoteOriginWorker = new Worker('https://untrusted.com/worker.js'); // ❌ 这里会报错

错误处理

如果工作者线程脚本抛出了错误,该工作者线程沙盒可以阻止它打断父线程的执行。如下例所示, 其中的 try/catch 块不会捕获到错误:

1
2
3
4
5
6
7
8
9
10
// ------ main.js  ------ 
try {
const worker = new Worker('./worker.js');
console.log('no error');
} catch(e) {
console.log('caught error');
}
// no error
// ------ worker.js ------
throw Error('foo');

不过,相应的错误事件仍然会冒泡到工作者线程的全局上下文,因此可以通过在 Worker 对象上设置错误事件侦听器访问到。下面看这个例子:

1
2
3
4
5
6
// ------ main.js ------ 
const worker = new Worker('./worker.js');
worker.onerror = console.log;
// ErrorEvent {message: "Uncaught Error: foo"}
// ------ worker.js ------
throw Error('foo');

通信

  - **1. 使用工作者的postMessage()**
1
2
3
4
5
6
7
8
9
10
11
12
// factorialWorker.js
self.onmessage = ({data}) = > {
// 通过worker的postMessage传递信息main.js
self.postMessage(`factorialWorker - onmessage : ${(data)}`);
};

// main.js
const factorialWorker = new Worker('./factorialWorker.js');
// 此处监听worker的onmessage事件
factorialWorker.onmessage = ({data}) = > console.log(data);
// 传值
factorialWorker.postMessage(5);
  - **2. 使用MessageChannel的PostMessage()**

整体逻辑就是先用worker的postMessage去初始化MessageChannel的消息通道,后续的消息传递都使用MessageChannel的消息通道。worker的postMessage信道只第一次初始化的时候使用后续不再使用,所以执行完之后可以用null赋值。
MessageChannel 暴露出两个端口,port1 和 port2。 port1去发送消息。port2去接受监听消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// worker.js
// 在监听器中存储全局 messagePort
let messagePort = null;

// 在全局对象上添加消息处理程序
self.onmessage = ({ports}) = > {
// 只设置一次端口
if (!messagePort) {
// 初始化消息发送端口,
// 给变量赋值并重置监听器
messagePort = ports[0];
self.onmessage = null;
// 在全局对象上设置消息处理程序
messagePort.onmessage = ({data}) = > {
// 收到消息后发送数据
messagePort.postMessage(`MessageChannel-postMessage: ${data}`);
};
}
};

// --------------------------------------

// main.js
const channel = new MessageChannel();
const factorialWorker = new Worker('./worker.js');
// 把`MessagePort`对象发送到工作者线程
// 工作者线程负责处理初始化信道
factorialWorker.postMessage(null, [channel.port1]);
// 通过信道实际发送数据
channel.port2.onmessage = ({data}) = > console.log(data);
// 工作者线程通过信道响应
channel.port2.postMessage(5);
  - **3. 使用BroadcastChannel的PostMessage()**

BroadcastChannel是一个广播的存在,worker 和 main都对同一个信道进行使用,一个postMessage()发送消息,一个 onmessage来接收信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.js
const channel = new BroadcastChannel('worker_channel');
const worker = new Worker('./worker.js');
channel.onmessage = ({data}) = > {
console.log(`heard ${data} on page`);
}
setTimeout(() = > channel.postMessage('foo'), 1000);
// heard foo in worker
// heard bar on page

// -------------------------------------------
// worker.js
const channel = new BroadcastChannel('worker_channel');
channel.onmessage = ({data}) = > {
console.log(`heard ${data} in worker`);
channel.postMessage('bar');
}

数据传输

工作者线程是独立的上下文,因此在上下文之间传输数据就会产生消耗。在支持传统多线程模型的语言中,可以使用锁、互斥量,以及volatile 变量。在 JavaScript 中,有三种在上下文间转移信息的方式:结构化克隆算法(structured clone algorithm)、可转移对象(transferable objects)和共享数组缓冲区(shared array buffers)。

  1. 结构化克隆算法

**结构化克隆算法可用于在两个独立上下文间共享数据。**该算法由浏览器在后台实现,不能直接调用。在通过 postMessage()传递对象时,浏览器会遍历该对象,并在目标上下文中生成它的一个副本。
支持传递的对象

  -     Boolean 对象 
  -  String 对象 
  -  BDate 
  -  RegExp 
  -  Blob 
  -  File 
  -  FileList 
  -  ArrayBuffer 
  -  ArrayBufferView 
  -  ImageData 
  -  Array 
  -  Object 
  -  Map 
  -  Set

需要注意:

  -  复制之后,源上下文中对该对象的修改,不会传播到目标上下文中的对象。 
  -  结构化克隆算法可以识别对象中包含的循环引用,不会无穷遍历对象。 
  -  克隆 Error 对象、Function 对象或 DOM 节点会抛出错误。 
  -  结构化克隆算法并不总是创建完全一致的副本。 
  -  对象属性描述符、获取方法和设置方法不会克隆,必要时会使用默认值。 
  -  原型链不会克隆。 
  -  RegExp.prototype.lastIndex 属性不会克隆。
  1. 可转移对象

**可转移对象(transferable objects)可以把所有权从一个上下文转移到另一个上下文。**postMessage()方法的第二个可选参数是数组,它指定应该将哪些对象转移到目标上下文。在遍历消息负载对象时,浏览器根据转移对象数组检查对象引用,并对转移对象进行转移而不复制它们。
在其他类型的对象中嵌套可转移对象也完全没有问题。包装对象会被复制,而嵌套的对象会被转移
可转移对象仅支持以下类型

  - ArrayBuffer 
  - MessagePort 
  - ImageBitmap 
  - OffscreenCanvas

结构化克隆和可转移对象对比
他们两个的区别就是在postMessage()方法中传入了第二个参数,用来传递可转移对象的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 这个例子使用ArrayBuffer作为传递对象(因为结构化克隆和可转移对象都支持该类型)

// -------- main.js --------
const worker = new Worker('./worker.js');
// 创建 32 位缓冲区
const arrayBuffer = new ArrayBuffer(32);
console.log(`page's buffer size: ${arrayBuffer.byteLength}`); // 32

// !!!!!!!!!!! 区别在此:!!!!!!!!!!!

// #### 1. 结构化克隆
worker.postMessage({foo: {bar: arrayBuffer}});

// #### 2. 可转移对象 arrayBuffer。 第二个参数传入数组,装着arrayBuffer。
// #### arrayBuffer会被转移,而传递的对象 {foo:{bar:arrayBuffer}} 会被复制
worker.postMessage({foo: {bar: arrayBuffer}}, [arrayBuffer]);
console.log(`page's buffer size: ${arrayBuffer.byteLength}`); // 0

// -------- worker.js --------
self.onmessage = ({data}) = > {
console.log(`worker's buffer size: ${data.foo.bar.byteLength}`); // 32
};
  1. 共享数组缓冲

共享数组缓存和结构化克隆的区别就是postMessage()的第一个参数使用ShareArrayBuffer。利用ShareArrayBuffer的引用来共同引用同一块内存区域,这样上下文可以对同一块内存区域进行操作,达到数据传输的目的。
但是在不同上下文对同一块ShareArrayBuffer进行操作的时候由于没有锁的问题导致资源竞用,共享内存数据会被多次重复篡改或者某个上下文在修改前取了旧内存数据,导致期望结果和实际运行不同,此时可以使用Atomics API进行加锁,防止上述情况发生。
注意:由于 Spectre 和 Meltdown 的漏洞,所有主流浏览器在 2018 年 1 月就禁用了SharedArrayBuffer。从 2019 年开始,有些浏览器开始逐步重新启用这一特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// ---------- main.js  ---------- 
// 创建包含 4 个线程的线程池
const workers = [];
for (let i = 0; i < 4; ++i) {
workers.push(new Worker('./worker.js'));
}
// 在最后一个工作者线程完成后打印最终值
let responseCount = 0;
for (const worker of workers) {
worker.onmessage = () = > {
if (++responseCount == workers.length) {
console.log(`Final buffer value: ${view[0]}`);
}
};
}
// 初始化 SharedArrayBuffer
const sharedArrayBuffer = new SharedArrayBuffer(4);
const view = new Uint32Array(sharedArrayBuffer);
view[0] = 1;
// 把 SharedArrayBuffer 发给每个线程
for (const worker of workers) {

// !!!!!!1. 此处postMessage传递的是 shareArrayBuffer 对象
worker.postMessage(sharedArrayBuffer);
}
//(期待结果为 4000001)
// Final buffer value: 4000001

// ---------- worker.js ----------
self.onmessage = ({data}) = > {
const view = new Uint32Array(data);
// 执行 100 万次加操作
for (let i = 0; i < 1E6; ++i) {

// !!!!!!2.防止多个上下文(此处是多个worker线程)对shareArrayBuffer竞用,此处使用Atomics API 加锁
Atomics.add(view, 0, 1);
}
self.postMessage(null);
};

共享工作者线程

共享工作者线程与专用工作者线程非常相似。主要区别是共享工作者线程可以被多个不同的上下文使用,包括不同的页面。任何与创建共享工作者线程的脚本同源的脚本,都可以向共享工作者线程发送消息或从中接收消息。
共享工作者线程只要还有一个上下文连接就会持续存在。SharedWorker 对象上没有terminate()方法。在共享线程端口(稍后讨论)上调用 close()时,只要还有一个端口连接到该线程就不会真的终止线程。SharedWorker 的“连接”与关联 MessagePort 或 MessageChannel 的状态无关。只要建立了连接,浏览器会负责管理该连接。建立的连接会在页面的生命周期内持续存在,只有当页面销毁且没有连接时,浏览器才会终止共享线程。

创建

SharedWorker()只会在相同的标识不存在的情况下才创建新实例。如果的确存在与标识匹配的共享工作者线程,则只会与已有共享者线程建立新的连接。共享工作者线程标识源自解析后的脚本 URL、工作者线程名称和文档源
每次调用 SharedWorker()构造函数,无论是否创建了工作者线程,都会在共享线程内部触connect 事件。关键在于,共享线程与父上下文的启动和关闭不是对称的。每个新 SharedWorker 连接都会触发一个事件,但没有事件对应断开 SharedWorker 实例的连接(如页面关闭)。
随着与相同共享线程连接和断开连接的页面越来越多,connectedPorts 集合中会受到死端口的污染,没有办法识别它们。一个解决方案是在 beforeunload 事件即将销毁页面时,明确发送卸载消息,让共享线程有机会清除死端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var locationUrl = location.href; // https://www.example.com/

new SharedWorker('./shareWorker.js') // 以此为示例
new SharedWorker('./shareWorker.js') // 相同,解析后的脚本URL相同
new SharedWorker('shareWorker.js') // 相同,解析后的脚本URL相同
new SharedWorker(locationUrl+'./shareWorker.js') // 相同,解析后的脚本URL相同

new SharedWorker('./shareWorker.js?') // 不同,解析后的脚本URL不同,带了?


// 给name识别
new SharedWorker('./shareWorker.js',{name:'a'}) // 以此为示例
new SharedWorker('./shareWorker.js',{name:'a'}) // 相同

new SharedWorker('./shareWorker.js',{name:'b'}) // 不同,name不同

属性&方法&事件

SharedWorker 对象支持以下属性。

  • onerror:在共享线程中发生 ErrorEvent 类型的错误事件时会调用指定给该属性的处理程序。
    • 此事件会在共享线程抛出错误时发生。
    • 此事件也可以通过使用 sharedWorker.addEventListener(‘error’, handler)处理。
  • port:专门用来跟共享线程通信的 MessagePort。

**SharedWorkerGlobalScope **通过以下属性和方法扩展了 WorkerGlobalScope。

  • name:可选的字符串标识符,可以传给 SharedWorker 构造函数。
  • importScripts():用于向工作者线程中导入任意数量的脚本。
  • close():与 worker.terminate()对应,用于立即终止工作者线程。没有给工作者线程提供终止前清理的机会;脚本会突然停止。
  • onconnect:与共享线程建立新连接时,应将其设置为处理程序。connect 事件包括MessagePort 实例的 ports 数组,可用于把消息发送回父上下文。
    • 在通过 worker.port.onmessage 或 worker.port.start()与共享线程建立连接时都会触发 connect 事件。
    • connect 事件也可以通过使用 sharedWorker.addEventListener(‘connect’, handler)处理。

服务工作者线程

这个内容太复杂太多了。建议专门用的时候再看。先简单了解概念即可。
服务工作者线程与专用工作者线程和共享工作者线程截然不同。它的主要用途是拦截、重定向和修改页面发出的请求,充当网络请求的仲裁者的角色。
服务工作者线程是一种类似浏览器中代理服务器的线程。这可以让网页在没有网络连接的情况下正常使用,因为部分或全部页面可以从服务工作者线程缓存中提供服务。服务工作者线程也可以使用 Notifications API、Push API、Background Sync API 和Channel Messaging API。
与共享工作者线程类似,来自一个域的多个页面共享一个服务工作者线程。不过,为了使用 Push API等特性,服务工作者线程也可以在相关的标签页或浏览器关闭后继续等待到来的推送事件。在这个意义上,服务工作者线程就是用于把网页变成像原生应用程序一样的工具。
在调试服务工作者线程时,要谨慎使用浏览器的强制刷新功能(Ctrl+Shift+R)。强制刷新会强制浏览器忽略所有网络缓存,而服务工作者线程对大多数主流浏览器而言就是网络缓存。
由于服务工作者线程几乎可以任意修改和重定向网络请求,以及加载静态资源**,服务工作者线程API 只能在安全上下文(HTTPS)下使用**。在非安全上下文(HTTP)中,navigator.serviceWorker是 undefined。为方便开发,浏览器豁免了通过 localhost 或 127.0.0.1 在本地加载的页面的安全上下文规则

创建

**服务工作者线程是通过 ServiceWorkerContainer 来管理的,它的实例保存在 navigator.serviceWorker 属性中。该对象是个顶级接口,通过它可以让浏览器创建、更新、销毁或者与服务工作者线程交互。**ServiceWorkerContainer 没有通过全局构造函数创建,而是暴露了 register()方法,该方法以与 Worker()或 SharedWorker()构造函数相同的方式传递脚本 URL。register()方法返回一个期约,该期约解决为 ServiceWorkerRegistration 对象,或在注册失败时拒绝。
调用 navigator.serviceWorker.register()之后返回的期约会将注册成功的 ServiceWorkerRegistration 对象(注册对象)发送给处理函数。在同一页面使用同一 URL 多次调用该方法会返回相同的注册对象

1
2
3
4
5
6
7
8
// -------- emptyServiceWorker.js -------- 
// 空服务脚本

// -------- main.js --------
navigator.serviceWorker.register('./emptyServiceWorker.js').then((registration) = > {
// 注册成功后Promise返回的是 ServiceWorkerRegistration对象
console.log(registration);
});

ServiceWorkerContainer

ServiceWorkerContainer 接口是浏览器对服务工作者线程生态的顶部封装。它为管理服务工作者线程状态和生命周期提供了便利。

事件
  - oncontrollerchange:在 ServiceWorkerContainer 触发 controllerchange 事件时会调用指定的事件处理程序。
     - 此事件在获得新激活的 ServiceWorkerRegistration 时触发。
     - 此事件也可以使用 navigator.serviceWorker.addEventListener('controllerchange', handler)处理。
  - onerror:在关联的服务工作者线程触发 ErrorEvent 错误事件时会调用指定的事件处理程序。
     - 此事件在关联的服务工作者线程内部抛出错误时触发。
     - 此事件也可以使用 navigator.serviceWorker.addEventListener('error', handler)处理。
  - onmessage:在服务工作者线程触发 MessageEvent 事件时会调用指定的事件处理程序。
     - 此事件在服务脚本向父上下文发送消息时触发。
     - 此事件也可以使用 navigator.serviceWorker.addEventListener('message', handler)处理。
属性
  - ready:返回期约,解决为激活的 ServiceWorkerRegistration 对象。该期约不会拒绝。
  - controller:返回与当前页面关联的激活的 ServiceWorker 对象,如果没有激活的服务工作者线程则返回 null。
方法
  - register():使用接收的 url 和 options 对象创建或更新 ServiceWorkerRegistration。  getRegistration():返回期约,解决为与提供的作用域匹配的 ServiceWorkerRegistration对象;如果没有匹配的服务工作者线程则返回 undefined。
  - getRegistrations():返回期约,解决为与 ServiceWorkerContainer 关联的 ServiceWorkerRegistration 对象的数组;如果没有关联的服务工作者线程则返回空数组。
  - startMessage():开始传送通过 Client.postMessage()派发的消息。

ServiceWorkerRegistration

事件
  - onupdatefound:在服务工作者线程触发 updatefound 事件时会调用指定的事件处理程序。
     - 此事件会在服务工作者线程开始安装新版本时触发,表现为 ServiceWorkerRegistration.installing 收到一个新的服务工作者线程。
     - 此事件也可以使用 serv serviceWorkerRegistration.addEventListener('updatefound', handler)处理。
属性
  - scope:返回服务工作者线程作用域的完整 URL 路径。该值源自接收服务脚本的路径和在register()中提供的作用域。
  - navigationPreload:返回与注册对象关联的 NavigationPreloadManager 实例。
  - pushManager:返回与注册对象关联的 pushManager 实例。
  - installing:如果有则返回状态为 installing(安装)的服务工作者线程,否则为 null。  waiting:如果有则返回状态为 waiting(等待)的服务工作者线程,否则为 null。
  - active:如果有则返回状态 activating 或 active(活动)的服务工作者线程,否则为 null。

注意,这些属性都是服务工作者线程状态的一次性快照。这在大多数情况下是没有问题的,因为活动状态的服务工作者线程在页面的生命周期内不会改变状态,除非强制这样做(比如调用 ServiceWorkerGlobalScope.skipWaiting())。

方法
  - getNotifications():返回期约,解决为 Notification 对象的数组。
  - showNotifications():显示通知,可以配置 title 和 options 参数。
  - update():直接从服务器重新请求服务脚本,如果新脚本不同,则重新初始化。
  - unregister():取消服务工作者线程的注册。该方法会在服务工作者线程执行完再取消注册。

ServiceWorker

ServiceWorker 对象可以通过两种方式获得:通过 ServiceWorkerContainer 对象的 controller属性和通过 ServiceWorkerRegistration 的 active 属性。该对象继承 Worker 原型,因此包括其所有属性和方法,但没有 terminate()方法。

事件
  • onstatechange:ServiceWorker 发生 statechange 事件时会调用指定的事件处理程序。

此事件会在 ServiceWorker.state 变化时发生。
此事件也可以使用 serviceWorker.addEventListener(‘statechange’, handler)处理。

属性
  • scriptURL:解析后注册服务工作者线程的 URL。例如,如果服务工作者线程是通过相对路径’./serviceWorker.js’创建的,且注册在 https://www.example.com 上,则 scriptURL 属性将返回"https://www.example.com/serviceWorker.js"
  • state:表示服务工作者线程状态的字符串,可能的值如下。
  • installing
  • installed
  • activating
  • activated
  • redundant