Javascript 红宝书(下)¶
Javascript API¶
Atomics 与 SharedArrayBuffer¶
-
SharedArrayBuffer
- 与 ArrayBuffer 区别在于,ArrayBuffer 必须在不同执行上下文间切换;SharedArrayBuffer 可以被任意多个执行上下文同时使用。
- Subtopic 2
-
原子操作
-
概念
- Atomics API 用户保护代码在多线程内存访问模式下不发生资源争用。
-
算术及位操作方法
- Atomics.add()/sub()
- Atomics.or()/and()/xor()
-
原子读和写
- Atomics.load()
- Atomics.store()
-
原子交换
- Atomics.exchange()
- Atomics.compareExchange()
-
原子 Futex 操作与加锁
- Atomics API 模仿 Linux Futex (快速用户空间置换,fast user-space mutex) 的方法。
- Atomics.wait()
- Atomics.notify()
-
跨上下文消息¶
- 也称 XDM (cross-document messaging),一种在不同执行上文(或工作线程或不同源页面)间传递信息的能力。
Encoding API¶
-
概念
- 用于实现字符串与定型数组之间的转换。
-
文本编码
-
TextEncoder: 批量编码
- Javascript 会同步编码整个字符串
-
TextEncoderStream: 流编码
- TransformStream 形式的 TextEncoder。将解码后的文本流通过管道输入流编码器。
-
-
文本解码
- TextDecoder
- TextDecoderStream
File API 与 Blob API¶
- File 类型
- FileReader 类型
-
FileReaderSync 类型
- 只在工作线程中可用
-
Blob 与部分读取
- Blob 实际上是 File 的超类。
- 二进制大对象,是 Javascript 对不可修改的二进制数据类型的封装。
-
对象 URL 与 Blob
- URL.createObjectURL(): 返回一个指向内存中地址的字符串。
- 只要对象 URL 在使用中,就不会释放内存。可用 URL.revokeObjectURL()
-
读取拖放文件
- event.dataTransfer.files
媒体元素¶
原生拖放¶
Notification API¶
Page Visibility API¶
- document.visibilityState 值
- visibilitychange 事件
Stream API¶
-
理解流
- 可读流:数据在内部从底层源 (source) 进入流,由消费者处理。
- 可写流:生成者将数据写入流,传入底层数据槽 (sink)。
- 转换流:由两种流组成,之间是转换程序 (transformer)。
-
块 (chunk)、内部队列和反压
- 基本单位:块(chunk)
- 块入列速度快于出列,反压 (backpressure) 通知流入口停止发送数据,直到低于阈值。
- 阈值即高水位线 (high water mark),由排列策略决定。
-
可读流
- ReadableStreamDefaultController: 控制流的传入及关闭。
- ReadableStreamDefaultReader: 获得流得锁,保证只有这个 reader 可以读取。
- 示例
async function* ints() { for(let i = 0; i < 5; i++) { yield await new Promise(r => setTimeout(r, 1000, i)); } } const readableStream = new ReadableStream({ async start(controller) { console.log(controller); // ReadableStreamDefaultController 实例 for await (let chunk of ints()) { controller.enqueue(chunk); } controller.close(); } }); const defaultReader = readableStream.getReader(); (async function() { while(true) { const { done, value } = await defaultReader.read(); if (done) { break; } else { console.log(value); } } })();
-
可写流
- 创建 WriteableStream
- WriteableStreamDefaultWriter
- 示例
async function* ints() { for(let i = 0; i < 5; i++) { yield await new Promise(r => setTimeout(r, 1000, i)); } } const writeableStream = new WritableStream({ write(value) { console.log('writing', value); } }); const defaultWriter = writeableStream.getWriter(); (async function() { for await (let chunk of ints()) { await defaultWriter.ready; defaultWriter.write(chunk); } defaultWriter.close(); })();
-
转换流
- 转换流用于组合可读流与可写流。
- 示例
async function* ints() { for(let i = 0; i < 5; i++) { yield await new Promise(r => setTimeout(r, 1000, i)); } } const { readable, writable } = new TransformStream({ transform(chunk, controller) { console.log('transforming', chunk, controller); controller.enqueue(chunk * 2); } }); const defaultReader = readable.getReader(); const defaultWriter = writable.getWriter(); // 消费者 (async function() { while(true) { const { done, value } = await defaultReader.read(); if (done) { break; } else { console.log(value); } } })(); // 生产者 (async function() { for await (let chunk of ints()) { await defaultWriter.ready; defaultWriter.write(chunk); } defaultWriter.close(); })();
-
通过管道连接流
- pipeThrough(): 把 ReadableStream 接入 TransformStream。
- pipeTo(): 把 ReadableStream 接入 WriteableStream。
- 示例
async function* ints() { for(let i = 0; i < 5; i++) { yield await new Promise(r => setTimeout(r, 1000, i)); } } const integerStream = new ReadableStream({ async start(controller) { for await (let chunk of ints()) { controller.enqueue(chunk); } controller.close(); } }); const doublingStream = new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk * 2); } }); // 通过管道连接流 const pipedStream = integerStream.pipeThrough(doublingStream); const pipedReader = pipedStream.getReader(); (async function() { while(true) { const { done, value } = await pipedReader.read(); if (done) { break; } else { console.log(value); } } })();
Performance API¶
-
High Resolution Time API
- performance.now(): 从创建上下文开始计时。
-
Performance Timeline API
- 度量客户端延迟
- performance.getEntries(): 浏览器性能时间线的集合。
-
User Timing API
- performance.mark()
- performance.measure()
-
Navigation Timing API
- PerformanceNavigationTiming: 记录页面何时以及如何加载。
-
Resource Timing API
- PerformanceResourceTiming: 描述资源加载的速度。
Web Component¶
-
HTML 模版
-
DocumentFragment
- DocumentFragment 子元素不会导致布局重排。
-
<template>
- appendChild(): 转移DocumentFragment子节点
- importNode(): 克隆 DocumentFragment
-
模版脚本
- 脚本执行可以推迟到将 DocumentFragment 的内容添加到 DOM 树。
-
-
影子 DOM
-
理解 Shadow DOM
- 与 HTML 模版的区别在于,影子 DOM 内容会实际渲染到页面上。
-
创建 Shadow DOM
- 容纳影子DOM 的元素称为影子宿主 (shadow host)。影子 DOM 的根节点称为 shadow root。
- foo.attachShadow({ mode: 'open' }); // #shadow-root(open)
-
使用 Shadow DOM
-
合成与 Shadow DOM slot
<slot></slot>
内为 外部 DOM 的投射 (projection)。- 示例
document.body.innerHTML = ` <div id="foo"> <p>Foo</p> </div> ` document.querySelector('#foo') .attachShadow({ mode: 'open' }) .innerHTML = ` <div id="bar"> <slot></slot> </div> `
-
事件重定向
- 事件逃出 影子 DOM 重定向 (event retarget) 到宿主节点。
-
-
自定义元素
-
创建自定义元素
class FooElement extends HTMLElement {}
customElements.define('x-foo', FooElement);
-
添加 Web 组件内容
- 可以在自定义元素构造函数中,添加 Shadow DOM。
-
自定义元素生命周期
- constructor(): 实例化时调用
- connectedCallback():添加到 DOM 时
- disconnectedCallback():从 DOM 中移除时
- attributeChangedCallback(): 可观察属性 值变化时
- adoptedCallback():document.adoptNode() 时
-
反射自定义元素属性
- get 函数/set 函数
- 使用 observedAtributes()get 函数,让属性具备 可观察性。
-
升级自定义元素
- customElements.get()
- customElements.whenDefined()
- customElements.upgrade()
-
Web Cryptography API¶
-
生成随机数
- Math.random(): 只生成伪随机数
- PRNG,PseudoRadom Number Generator: 伪随机数生成器,算法固定,不安全。
- CSPRNG,Cryptographically PseudoRadom Number Generator: 密码学伪随机数生成器,安全。
crypto.getRandomValues(new Uint8Array(1))
-
使用 SubtleCrypto 对象
- 生成密码学摘要
- CryptoKey 与算法
- 生成 CryptoKey
- 导出和导入密钥
- 主密钥与派生密钥
- 使用非对称密钥签名和验证消息
- 使用对称密钥加密和解密
- 包装和解包密钥
错误处理与调试¶
错误处理¶
-
try/catch
- 只要代码包含 finally 子句,try 块或 catch 块中的 return 语句就会被忽略。
-
抛出错误
- Error 构造函数参数:错误消息。
-
error 事件
- 任何未被 try/catch 处理的错误都会在 window 对象上触发 error 事件。
调试技术¶
处理 XML¶
浏览器对 XML DOM 的支持¶
浏览器对 XPath 的支持¶
浏览器对 XSLT 的支持¶
JSON¶
语法¶
- 简单值/对象/数组
解析与序列化¶
-
序列化选项
- 过滤结果:JSON.stringify() 的第二个参数(数组/函数)。
- 字符串缩进
- toJSON() 方法:在要序列化的对象中添加。
-
解析选项
- JSON.parse():第二个参数 replacer。
网络请求与远程资源¶
XMLHttpRequest 对象¶
-
使用 XHR
-
status
- 200/304 等
-
readystate
- 0: 未初始化 (Unitialized)
- 1: 已打开 (Open)
- 2: 已发送 (Sent)
- 3: 接收中 (Receiving)
- 4: 完成 (Complete)
-
-
HTTP 头部
- GET/POST
-
XMLHttpRequest Level 2
- FormData 类型
- 超时
- overrideMimeType() 方法
进度事件¶
- load 事件
- progress 事件
跨资源共享¶
-
简单请求
- 服务器允许:
Access-Control-Allow-Origin:相同源,或 *
。 - GET 或 POST,没有自定义头部。
- 服务器允许:
-
预检请求 (preflighted request)
- 前提:允许自定义头部、除 GET 和 POST 外的方法。(复杂请求)
- 先向服务器发送 OPTION 方法的的预检请求。
-
凭据请求
- 带凭据请求:
withCredentials: true
- 服务器允许带凭据请求。
Access-Control-Allow-Credentials: true
- 带凭据请求:
替代性跨源技术¶
- 图谱探测 (image pings)
-
JSONP
- 服务端:
const {cb} = ctx.query; ctx.body =
${cb}('hello');
- 客户端:
function func(str) { console.log(str); } script.src = 'https://localhost:8000/api?cb=func';
- 服务端:
Fetch API¶
- 基本用法
- 常见 Fetch 请求模式
- Headers 对象
-
Request 对象
- 创建 request 对象
- 克隆 request 对象
-
在 fetch 中使用 request 对象
- request 对象只能被消费一下;多次调用需用 request.clone()
-
Response 对象
-
创建 respons 对象
- 静态方法:Response.redirect()/Response.err()
-
克隆 respons 对象
- 属性:bodyUsed; 响应体已被消费。
-
-
Request、Response 及 Body 混入
- Body.text(); // 返回期约,字符串
- Body.json(); // 返回期约,JSON
- Body.formData(); // 返回期约,FormData 实例
- Body.arrayBuffer(); // 返回期约,ArrayBuffer 实例
- Body.blob(); // 返回期约,Blob() 实例
- 一次性流:主体流构建于 ReadableStream 之上
- 使用 ReadableStream 主体
Beacon API¶
- navigate.sendBeacon(); // POST 请求的语法糖
- sendBeacon() 调用后,浏览器会把请求添加到一个内部的请求队列。
- 浏览器保证在原始页面已关时,也会发送请求。
- 信标(beacon) 请求会携带所有相关 cookie。
Web Socket¶
- 与服务器全双工、双向通信
- 创建时,首先发送 HTTP 建立连接
- 响应后,连接使用 HTTP 的 Upgrade 头部,从 HTTP 切换到 Web Socket 协议。
安全¶
客户端缓存¶
cookie¶
- 限制
-
cookie 的构成
- 名称:name
- 值:value
- 域:domain
- 路径
- 过期时间:expires
- 安全标志
-
Javascript 中的 Cookie
- 子 Cookie
Web Storage¶
- sessionStorage 对象
- localStorage 对象
- 存储事件:‘storage’
IndexedDB¶
-
数据库
- indexedDB.open(); // upsert 一个数据库,返回异步请求。
-
对象存储
onupgradeneeded
: 指定数据库的升级版本号,触发该事件;唯一可以修改数据库结构的地方。- db.createObjectStore('users', {keyPath: 'name'});
-
事物
- 任何时候,想要读取或修改数据,都要通过事物把所有修改操作组织起来。
- let transaction = db.transaction('users', 'readwrite')
- let store = transaction.objectStore('users')
-
插入对象
- store.add()
-
通过游标查询
- store.openCursor();// 返回 IDBCursor实例
-
属性
- direction
- key
- value
- primaryKey
-
方法
- update()
- delete()
- continue(key)
- advance(count)
-
键范围
- IDBKeyRange 实例:管理游标的范围
-
设置游标方向
-
索引
- 可以通过记录的键或使用一个index获取一个object store中记录。
- store.createIndex(); // 返回 IDBIndex 的实例
- 索引 vs 对象存储: 在索引上使用 openCursor(),result.key 属性中保存的是索引键,而非主键。
-
并发问题
- 不同标签页同时打开同一个网页
- 版本变化仅在只有一个标签页使用数据时,才能完成。
-
限制
- 同源限制
- 空格键限制
模块¶
理解模块模式¶
- 管理复杂性的永恒工具。
-
模块标志符
- 模块系统本质上是键/值实体。
- 一般说来,标识符在模拟模块的系统中是字符串;在原生实现的模块系统中是模块文件的实际路径。
-
模块依赖
- 模块系统的核心是管理依赖。
-
模块加载
- 依赖契约的派生概念。
- 递归评估并加载所有依赖,知道所有依赖模块都加载完成;只有整个依赖图都加载完成,才可以执行入口模块。
-
入口
- javascript 是顺序的,单线程的,entry point 是 代码执行的起点。
- 可以通过有向图来表示应用中各个模块的依赖关系。
-
异步依赖
- 利用
<script>
标签按需请求模块文件,而不会产生必需的依赖列表。 - 提升资源加载性能。
- 利用
-
动态依赖
if(condition) require('moduleA')
- 运行时决定是否加载 moduleA;
- 支持更复杂的依赖关系,但增加静态分析的难度。
-
静态分析
- 浏览器分析工具会检查代码结构,并在代码执行前推断其行为。
- 减小模块打包文件的体积。
-
循环依赖
- 执行深度优先的模块加载;
- 引用加载过的模块,是读取其缓存。
凑合的模块系统¶
- IIFE 封装模块定义
- 泄漏模块模式
ES6 之前的模块加载器¶
-
CommonJS
- CommonJS 规范概述来同步声明依赖的模块定义。
- 主要在服务端实现模块化代码组织。
if(condition) require('moduleA')
也是同步加载的。
-
AMD
- 模块声明自己的依赖,浏览器中的模块系统会按需获取依赖。
- AMD 模块实现的核心是用函数包装模块定义
- define('模块定义', ['模块依赖'], 模块工厂函数)
-
UMD
- 通用模块定义规范
ES6 模块¶
-
模块标签及定义
type="module"
- 解析到该标签时立即加载,文档解析完成后执行。
-
模块加载
- 模块行为
-
模块导出
- 命名导出 (named export)
- 默认导出 (default export)
-
模块导入
- 导入对模块而言只读,相当于const
- 使用 * 执行批量导入,相当于使用 Object.freeze(); * 对应 named export。
-
模块转移导出
- 工作者模块
- 向后兼容
工作者线程¶
简介¶
- WorkerThead 的价值:允许把主线程的工作转嫁给独立的实体,而不改变现有单线程模型。
-
工作者线程与线程
- 工作者线程是以实际线程实现的。
- 工作者线程并行执行。
-
工作者线程可以共享某些内存,但不共享全部内存。
- SharedArrayBuffer 提供共享,Atomics 进行并发控制。
-
工作者线程不一定在同一个进程里。
- 创建工作者线程的开销更大。
-
工作者线程的类型
- 专用工作者线程
-
共享工作者线程
- 可以被不同上下文或页面使用;要求同源脚本。
-
服务工作者线程
- 网络请求的仲裁者:拦截,重定向和修改。
-
WorkerGlobalScope
- 脚本中全局 self 是WorkerGlobalScope 的实例。
- 属性和方法
-
子类
- DedicatedWorkerGlobalScope
- SharedWorkerGlobalScope
- ServiceWorkerGlobalScope
专用工作者线程¶
- worker 实例与 DedicatedWorkerGlobalScope
- 专用工作者线程与隐式的 MessagePorts
-
专用工作者线程的生命周期
- 初始化 (initializing)
- 活动 (active)
-
终止 (terminated)
- self.close(); // 通知线程取消事件循环中的所有任务,组织新任务添加
- worker.terminate(); // 立即清理线程的消息队列并锁住。
-
配置Worker 选项:构造函数第二个参数
- 在 Javascript 行内创建工作者线程
-
在工作者线程中动态执行脚本
- self.importScripts()
-
委托任务到子工作者线程
- 处理工作者线程的错误
-
与专用工作者线程通信
- 使用 postMessage()
-
使用 MessageChannel
- 实例有两个端口,分别代表两个通信端点。
- 可在两个上下文间建立通信渠道,取代 postMessage()
-
使用 BroadcastChannel
-
工作者线程数据传输
- 结构化克隆算法 (structured clone algorithm)
-
可转移对象 (transferrable objects)
-
ArrayBuffer
- 如果把 ArrayBuffer 指定为可转移对象 (第二个参数),那么缓冲区的内存的引用就会从父上下文中抹去,分配给工作者线程。
- 否则,使用结构化克隆,没有转移。
-
MessagePort
- ImageBitmap
- OffScreenCanvas
-
-
恭喜数组缓冲区 (shared array buffers)
- 即不克隆,也不转移。
- 并行线程竞争资源,意味着 SharedArrayBuffer 是易变 (volatile) 内存。
- 使用 Atomics 解决竞争问题。
-
线程池
- 概念:保持固定数量的线程活动,标记工作者线程为忙碌或空闲的一种任务分派策略。
class TaskWorker extents Worker {}
class WorkerPool {}
共享工作者线程¶
-
共享工作者线程简介
- 场景:多个上下文间共享线程减少计算性能消耗。
- 创建
- 标识与独占
- 使用
-
共享工作者线程生命周期
- 连接数为 0 时,线程才会终止。
-
连接到共享工作者线程
- 触发 connect 事件
服务工作者线程¶
-
基础
-
ServiceWorkerContainer
- 无全局构造函数
- 通过 ServiceWorkerContainer 来管理,实例保存在 navigate.serviceWorker 中。
-
创建:navigate.serviceWorker.register()
-
ServiceWorkerContainer 对象
- 对服务工作者线程生态的顶部封装,管理线程状态和生命周期。
-
ServiceWorkerRegistration 对象
- ServiceWorker 对象
- 安全限制
-
ServiceWorkerGlobalScope
- caches
- clients
- registration
- skipWaiting()
- fetch(): 实际网络请求 (而非返回缓存值)
-
作用域限制
- 只能拦截作用域内的客户端请求。
- scope 指定目录作用域。
-
-
服务工作者线程缓存
-
数据结构
- 双层字典:顶级字典是 CacheStorage 对象,实例为self.caches;每个值为 对象,是request 到 response 的映射。
-
CacheStorage
- Cache
- 最大存储空间: navigator.storage.estimate()
-
-
服务工作者线程客户端
- 跟踪关联的窗口、工作线程或服务者工作线程。
-
服务工作者线程与一致性
- 版本控制
- 代码一性/数据一性
-
服务工作者线程的生命周期
- 已解析 (parsed)
- 安装中 (installing)
- 已安装 (installed)
- 激活中 (activating)
- 已激活 (ativated)
- 已失效 (redundant)
-
控制反转与服务工作者线程持久化
- 遵循控制反转 (IoC)模式,并且是事件驱动的。
- serviceWorker 不依赖工作者线程的全局状态,绝大多数代码应该在事件处理程序中定义。
-
通过 updateViaCache 管理服务文件缓存
- 强制性服务工作者线程操作
- 服务工作者线程消息
- 拦截 fetch 事件
- 推送通知
最佳实践¶
可维护性¶
-
编码规范
- 可读性
- 变量和函数命名
- 变量类型透明化
-
松散耦合
- 解耦 HTML/Javascript
- 解耦 CSS/Javascript
- 解耦应用程序逻辑与事件处理逻辑
-
编码惯例
- 尊重对象所有权
- 不声明全局变量
- 不要比较null
- 使用常量
性能¶
-
作用域意识
-
避免全局查找
- 访问全局变量比访问局部变量慢,因为要遍历作用域链。
-
不使用with语句
- with 语句要创建自己的作用域,加长了其内代码的作用域链。
-
-
选择正确的方法
- 避免不必要的属性查找
- 优化循环
- 展开循环
- 避免重复解释
-
语句最少化
- 多个变量声明
- 插入迭代性值
- 使用数组和对象字面量
-
优化 DOM 交互
- 实时更新最小化
- 使用 innerHTML
- 使用事件委托
-
注意 HTMLCollection
- 只要访问 HTMLCollection,就会触发文档查询,相当耗时。
部署¶
-
构建流程
- 文件结构
- 任务运行器
- Tree Shaking
- 模块打包器
-
验证
- JSLint
-
压缩
- 代码压缩
-
javascript 编译 (compilation)
- 删除未用代码
- 将某些代码转换为更简洁的语法
- 全局函数调用,常量和变量行内化
-
javascript 转译 (transpilation)
- 经高版本语法转译为低版本
-
HTTP 压缩
- Content-Encoding