20.1 Atomics 与 SharedArrayBuffer
跨文档消息传递
跨文档消息,有时候也简称为 XDM(cross-document messaging),是一种在不同执行上下文(如不
同工作线程或不同源的页面)间传递信息的能力。例如,www.wrox.com 上的页面想要与包含在内嵌窗
格中的 p2p.wrox.com 上面的页面通信。在 XDM 之前,要以安全方式实现这种通信需要很多工作。XDM
以安全易用的方式规范化了这个功能。
注意 跨上下文消息用于窗口之间通信或工作线程之间通信。本节主要介绍使用
postMessage()与其他窗口通信 。关于工作线程之间通信、MessageChannel 和
BroadcastChannel,可以参考第 27 章。
XDM 的核心是 postMessage()方法。除了 XDM,这个方法名还在 HTML5 中很多地方用到过,
但目的都一样,都是把数据传送到另一个位置。
postMessage()方法接收 3 个参数:消息、表示目标接收源的字符串和可选的可传输对象的数组(只
与工作线程相关)。第二个参数对于安全非常重要,其可以限制浏览器交付数据的目标。下面来看一个
例子:
let iframeWindow = document.getElementById(“myframe”).contentWindow;
iframeWindow.postMessage(“A secret”, “http://www.wrox.com”);
最后一行代码尝试向内嵌窗格中发送一条消息,而且指定了源必须是"http://www.wrox.com"。
如果源匹配,那么消息将会交付到内嵌窗格;否则,postMessage()什么也不做。这个限制可以保护
信息不会因地址改变而泄露。如果不想限制接收目标,则可以给 postMessage()的第二个参数传"*",
但不推荐这么做。
接收到 XDM 消息后,window 对象上会触发 message 事件。这个事件是异步触发的,因此从消息
发出到接收到消息(接收窗口触发 message 事件)可能有延迟。传给 onmessage 事件处理程序的 event
对象包含以下 3 方面重要信息。
data:作为第一个参数传递给 postMessage()的字符串数据。
origin:发送消息的文档源,例如"http://www.wrox.com"。 source:发送消息的文档中 window 对象的代理。这个代理对象主要用于在发送上一条消息的
窗口中执行 postMessage()方法。如果发送窗口有相同的源,那么这个对象应该就是 window
对象。
接收消息之后验证发送窗口的源是非常重要的。与 postMessage()的第二个参数可以保证数据不
会意外传给未知页面一样,在 onmessage 事件处理程序中检查发送窗口的源可以保证数据来自正确的
地方。基本的使用方式如下所示:
window.addEventListener(“message”, (event) => {
// 确保来自预期发送者
if (event.origin == “http://www.wrox.com”) {
// 对数据进行一些处理
processMessage(event.data);
// 可选:向来源窗口发送一条消息
event.source.postMessage(“Received!”, “http://p2p.wrox.com”);
}
});
大多数情况下,event.source 是某个 window 对象的代理,而非实际的 window 对象。因此不能
通过它访问所有窗口下的信息。最好只使用 postMessage(),这个方法永远存在而且可以调用。
XDM 有一些怪异之处。首先,postMessage()的第一个参数的最初实现始终是一个字符串。后来,
第一个参数改为允许任何结构的数据传入,不过并非所有浏览器都实现了这个改变。为此,最好就是只
通 过 postMessage() 发送字符串。如果需要传递结构化数据,那么最好先对该数据调用
JSON.stringify(),通过 postMessage()传过去之后,再在 onmessage 事件处理程序中调用
JSON.parse()。
在通过内嵌窗格加载不同域时,使用 XDM 是非常方便的。这种方法在混搭(mashup)和社交应用
中非常常用。通过使用 XDM 与内嵌窗格中的网页通信,可以保证包含页面的安全。XDM 也可以用于
同源页面之间通信。
20.3 Encoding API
Encoding API 主要用于实现字符串与定型数组之间的转换。规范新增了 4 个用于执行转换的全局类:
TextEncoder、TextEncoderStream、TextDecoder 和 TextDecoderStream。
注意 相比于批量(bulk)的编解码,对流(stream)编解码的支持很有限。
20.3.1 文本编码
Encoding API 提供了两种将字符串转换为定型数组二进制格式的方法:批量编码和流编码。把字符
串转换为定型数组时,编码器始终使用 UTF-8。
20.4 File API 与 Blob API
Web 应用程序的一个主要的痛点是无法操作用户计算机上的文件。2000 年之前,处理文件的唯一方
式是把放到一个表单里,仅此而已。File API 与 Blob API 是为了让 Web 开发者
能以安全的方式访问客户端机器上的文件,从而更好地与这些文件交互而设计的。
20.4.1 File 类型
File API 仍然以表单中的文件输入字段为基础,但是增加了直接访问文件信息的能力。HTML5 在
DOM 上为文件输入元素添加了 files 集合。当用户在文件字段中选择一个或多个文件时,这个 files
集合中会包含一组 File 对象,表示被选中的文件。每个 File 对象都有一些只读属性。
name:本地系统中的文件名。
size:以字节计的文件大小。
type:包含文件 MIME 类型的字符串。
lastModifiedDate:表示文件最后修改时间的字符串。这个属性只有 Chome 实现了。
例如,通过监听 change 事件然后遍历 files 集合可以取得每个选中文件的信息:
let filesList = document.getElementById(“files-list”);
filesList.addEventListener(“change”, (event) => {
let files = event.target.files,
i = 0,
len = files.length;
while (i < len) {
const f = files[i];
console.log(${f.name} (${f.type}, ${f.size} bytes)
);
i++;
}
});
原生拖放
拖放事件
媒体元素
属性
属性 | 数据类型 | 说明 |
---|---|---|
autoplay | B | 取得或设置autoplay |
controls | B | 取得或设置controls属性,用于显示或隐藏浏览器内置的控件 |
currentLoop | Int | 媒体文件已循环的次数 |
currentSrc | S | 当前播放媒体文件的URL |
currentTime | F | 已经播放的秒数 |
defaultPlaybackRate | F | 取得或设置默认默认播放速度 |
duration | F | 媒体总播放时间 |
ended | B | 是否播放完成 |
loop | B | 取得或设置媒体文件播放完成后是否从头开始播放 |
muted | B | 取得或设置是否静音 |
20.7 Notifications API
使用HTML5 Notification实现桌面通知
Notifications API 用于向用户显示通知。无论从哪个角度看,这里的通知都很类似 alert()对话框:
都使用 JavaScript API 触发页面外部的浏览器行为,而且都允许页面处理用户与对话框或通知弹层的交
互。不过,通知提供更灵活的自定义能力。
Notifications API 在 Service Worker 中非常有用。渐进 Web 应用(PWA,Progressive Web Application)
通过触发通知可以在页面不活跃时向用户显示消息,看起来就像原生应用。
20.7.1 通知权限
Notifications API 有被滥用的可能,因此默认会开启两项安全措施:
通知只能在运行在安全上下文的代码中被触发;
通知必须按照每个源的原则明确得到用户允许。
用户授权显示通知是通过浏览器内部的一个对话框完成的。除非用户没有明确给出允许或拒绝的答
复,否则这个权限请求对每个域只会出现一次。浏览器会记住用户的选择,如果被拒绝则无法重来。
页面可以使用全局对象 Notification 向用户请求通知权限。这个对象有一个 requestPemission()
方法,该方法返回一个期约,用户在授权对话框上执行操作后这个期约会解决。
Notification.requestPermission()
.then((permission) => {
console.log(‘User responded to permission request:’, permission);
});
“granted"值意味着用户明确授权了显示通知的权限。除此之外的其他值意味着显示通知会静默失
败。如果用户拒绝授权,这个值就是"denied”。一旦拒绝,就无法通过编程方式挽回,因为不可能再
触发授权提示。
20.7.2 显示和隐藏通知
Notification 构造函数用于创建和显示通知。最简单的通知形式是只显示一个标题,这个标题内
容可以作为第一个参数传给 Notification 构造函数。以下面这种方式调用 Notification,应该会
立即显示通知:
new Notification(‘Title text!’);
可以通过 options 参数对通知进行自定义,包括设置通知的主体、图片和振动等:
new Notification(‘Title text!’, {
body: ‘Body text!’,
image: ‘path/to/image.png’,
vibrate: true
});
20.8 Page Visibility API
Web 开发中一个常见的问题是开发者不知道用户什么时候真正在使用页面。如果页面被最小化或隐
藏在其他标签页后面,那么轮询服务器或更新动画等功能可能就没有必要了。Page Visibility API 旨在为
开发者提供页面对用户是否可见的信息。
这个 API 本身非常简单,由 3 部分构成。
document.visibilityState 值,表示下面 4 种状态之一。
页面在后台标签页或浏览器中最小化了。
页面在前台标签页中。
实际页面隐藏了,但对页面的预览是可见的(例如在 Windows 7 上,用户鼠标移到任务栏图标
上会显示网页预览)。
页面在屏外预渲染。
visibilitychange 事件,该事件会在文档从隐藏变可见(或反之)时触发。
document.hidden 布尔值,表示页面是否隐藏。这可能意味着页面在后台标签页或浏览器中被最小
化了。这个值是为了向后兼容才继续被浏览器支持的,应该优先使用 document.visibilityState
检测页面可见性。
要想在页面从可见变为隐藏或从隐藏变为可见时得到通知,需要监听 visibilitychange 事件。
document.visibilityState 的值是以下三个字符串之一:
“hidden”
“visible”
“prerender”
20.9 Streams API
Streams API 是为了解决一个简单但又基础的问题而生的:Web 应用如何消费有序的小信
息块而不是大块信息?这种能力主要有两种应用场景。
大块数据可能不会一次性都可用。网络请求的响应就是一个典型的例子。网络负载是以连续信
息包形式交付的,而流式处理可以让应用在数据一到达就能使用,而不必等到所有数据都加载
完毕。
大块数据可能需要分小部分处理。视频处理、数据压缩、图像编码和 JSON 解析都是可以分成小
部分进行处理,而不必等到所有数据都在内存中时再处理的例子。
第 24 章在讨论网络请求和远程资源时会介绍 Streams API 在 fetch()中的应用,不过 Streams API
本身是通用的。实现 Observable 接口的 JavaScript 库共享了很多流的基础概念。
注意 虽然 Fetch API已经得到所有主流浏览器支持,但 Streams API则没有那么快得到支持。
20.9.1 理解流
提到流,可以把数据想像成某种通过管道输送的液体。JavaScript 中的流借用了管道相关的概念,
因为原理是相通的。根据规范,“这些 API 实际是为映射低级 I/O 原语而设计,包括适当时候对字节流
的规范化”。Stream API 直接解决的问题是处理网络请求和读写磁盘。
Stream API 定义了三种流。
可读流:可以通过某个公共接口读取数据块的流。数据在内部从底层源进入流,然后由消费者
(consumer)进行处理。
可写流:可以通过某个公共接口写入数据块的流。生产者(producer)将数据写入流,数据在内
部传入底层数据槽(sink)。
转换流:由两种流组成,可写流用于接收数据(可写端),可读流用于输出数据(可读端)。这
两个流之间是转换程序(transformer),可以根据需要检查和修改流内容。
块、内部队列和反压
流的基本单位是块(chunk)。块可是任意数据类型,但通常是定型数组。每个块都是离散的流片段,
可以作为一个整体来处理。更重要的是,块不是固定大小的,也不一定按固定间隔到达。在理想的流当
中,块的大小通常近似相同,到达间隔也近似相等。不过好的流实现需要考虑边界情况。
前面提到的各种类型的流都有入口和出口的概念。有时候,由于数据进出速率不同,可能会出现不
匹配的情况。为此流平衡可能出现如下三种情形。
流出口处理数据的速度比入口提供数据的速度快。流出口经常空闲(可能意味着流入口效率较
低),但只会浪费一点内存或计算资源,因此这种流的不平衡是可以接受的。
流入和流出均衡。这是理想状态。
流入口提供数据的速度比出口处理数据的速度快。这种流不平衡是固有的问题。此时一定会在
某个地方出现数据积压,流必须相应做出处理。
流不平衡是常见问题,但流也提供了解决这个问题的工具。所有流都会为已进入流但尚未离开流的
块提供一个内部队列。对于均衡流,这个内部队列中会有零个或少量排队的块,因为流出口块出列的速
20.10 计时 API
页面性能始终是 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
20.12 Web Cryptography API
Web Cryptography API 描述了一套密码学工具,规范了 JavaScript 如何以安全和符合惯例的方式实现
加密。这些工具包括生成、使用和应用加密密钥对,加密和解密消息,以及可靠地生成随机数。
注意 加密接口的组织方式有点奇怪,其外部是一个Crypto对象,内部是一个SubtleCrypto
对象。在 Web Cryptography API 标准化之前,window.crypto 属性在不同浏览器中的实
现差异非常大。为实现跨浏览器兼容,标准 API 都暴露在 SubtleCrypto 对象上。
历史状态管理
haschange事件
知道url参数什么时候发生变化
history.pushState()
状态管理API,在不加载页面的情况下改变浏览器的url,
参数:状态对象,新状态的标题,相对url
第二个参数可传入一个空字符串
会创建新的历史状态,可“后退”,会触发popstate事件。
popstate事件
有一个state属性,包含当初第一个参数传给pushState()的状态对象
得到状态对象后,需自己把页面重置为状态对象中数据表示的状态。
replaceState()
跟新当前状态,不会在历史状态栈中创建新状态,只会重写当前状态。
history.replaceState({name:"cc"},"")