Skip to content

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 的构成

    • 名称: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