面试-CSS

盒模型概念

页面中的一个元素在页面中占有的空间

包括:盒子中的元素内容(content)、内边距(padding)、边框(border)和外边距(margin)所占据的空间

  • 块状盒子
    • 盒子会占据单行
    • 宽度和高度指定有效
    • 内边距、边框和外边距会导致其他元素推离盒子
    • 若宽度未指定,默认盒子宽度会尽可能占据可用空间,通常为父元素宽度的100%
  • 行内盒子
    • 盒子不会占据单行
    • 宽度和高度指定无效
    • 上下内边距、边框和外边距会起效,但不会导致其他行内元素推离盒子
    • 左右内边距、边框和外边距会起效,且会导致其他行内元素推离盒子

面试-HTML

语义化标签作用

  • 优化 SEO,提升页面的搜索引擎的优先级
  • 便于其他设备解析,如屏幕阅读器等,方便残障人员的访问
  • 优化整体代码格式,增强语义化,便于代码的开发与维护
  • 便于用户阅读,特别是针对样式丢失的情形

面试-Vue

Vue diff 算法

  1. 首先比较新老节点是否为同一节点,若不是的话则删除该节点并创建新节点以进行替换
  2. 若为相同节点,则会对节点的子节点进行判断
  3. 先判断是否属于一方无子节点而一方有子节点的情况
  4. 若双方均有子节点,则判断如何对新老节点的子节点进行操作;若找到相同的子节点,则对子节点的子节点也递归进行比较

Vue3 相较于 Vue2 的区别

  • Vue3 的响应式基于 Proxy 实现;Vue2 的响应式基于 Object.defineProperty 实现

  • Vue3 更推荐使用 Composition API;Vue2 更推荐使用 Options API

  • Vue3 相较 Vue2 更利用 TypeScript 开发

  • Vue3 相对于 Vue2 更支持 TreeShaking

组件通信方式

  • props & emits 父=>子 & 子=>父

  • provide & inject 祖=>孙

  • ref|$refs 引用 & expose

  • 事件总线 EventBus & $on|$emit

  • $parent & $root

  • $attrs

<keep-alive> 理解

在组件切换的时候,保存一些组件的状态防止重复渲染 DOM

LRU 缓存策略 - 找出最久未使用的数据并置换新的数据
通常基于链表实现,将新数据插至链表头部,若命中已有数据则将老数据移至链表头部,链表满后则丢弃链表尾部的数据

此时组件会增加 deactivatedactivated 生命周期钩子,而替代 mountedunmounted 生命周期钩子

nextTick() 理解

作为 Vue 内部的异步队列的调用方法同时提供给开发者使用,核心是利用了如 PromiseMutationObserversetImmediate()setTimeout() 等原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是对 JavaScript 执行原理 EventLoop 的一种应用

通常在数据变化后执行的某个操作需要使用随数据变化而变化的 DOM 结构的时候或需在 created 生命周期内修改 DOM 结构时使用

MVVM 概念

Model - 数据模型,定义数据和业务逻辑

View - UI 视图,负责数据展示

ViewModel - 负责监听 Model 数据改变并控制视图更新,同时处理用户交互操作

面试-计算机网络

HTTP2 与 HTTP1 区别

  • HTTP2 是一个二进制协议

HTTP1.1 的报头必须是文本,数据体可以是文本或是二进制
HTTP2 的报头和数据体均为二进制,分别被称为头信息帧和数据帧

  • HTTP2 实现头信息压缩

HTTP2 中头信息使用 gzip 或 compress 压缩后再发送
HTTP2 中客户端和服务器同时维护一张头信息表,所有字段存储在表中并生成索引号,头信息不会重复多次发送,后续头信息利用索引号避免重复发送

  • HTTP2 支持多路复用

HTTP2 支持同时无序的发送多个请求或响应,从而避免队头堵塞的问题

  • HTTP2 使用数据流的概念

因为 HTTP2 的数据包不是按序发送的,同个连接内的数据包可能属于不同的请求;因此 HTTP2 中将每个请求或响应的数据包均作为数据流给出唯一的编号,每个数据包均需标记数据流 ID,以表示其所属的数据流 ID

  • HTTP2 支持服务器推送

HTTP2 允许服务器未经请求,主导向客户端发送资源,可以一定程度上减少延迟时间

HTTP3 与 HTTP2 区别

  • 流量控制、传输可靠性

  • 集成 TLS 加密功能

  • 多路复用 - 同一物理连接上可以有多个独立的逻辑数据流

  • 快速连接 - 基于 UDP,仅需 1~2 RTT 建立连接

TCP 与 UDP 区别

  • TCP 是面向连接;UDP 是面向无连接

TCP 发送数据前必须建立连接后发送数据
UDP 发送数据前不建立连接直接发送

  • TCP 是可靠传输,使用流量控制和拥塞控制;UDP 是不可靠传输,不支持流量控制和拥塞控制

TCP 连接通过给包编号并通过接收到数据后返回的 ACK 判断传输是否成功,从而确定是否重传数据;在出现拥塞时,会通过减小注入网络数据的速率和数量以缓解拥塞
UDP 不会进行检测包的发送成功与否,以恒定速率发送数据,不会根据网络状况调整发送速率

  • TCP 只能是一对一;UDP 可以一对一或一对多或多对一或多对多

TCP 连接只有两个端点,只能进行点对点的数据传输
UDP 支持单播、多播、广播功能

  • TCP 是面向字节流传输;UDP 是面向报文传输

TCP 连接在不保留报文边界情况下以字节流传输数据
UDP 连接在保留报文边界情况下以报文形式传输数据

  • TCP 首部最小 20 字节到 60 字节;UDP 首部为 8 字节

  • TCP 使用要求可靠连接的应用;UDP 适用于实时应用

常见 HTTP 状态码

  • 200 OK 服务器成功处理
  • 301 Moved Permanently 永久重定向
  • 302 Found 临时重定向资源
  • 304 Not Modified 使用客户端缓存
  • 403 Forbidden 服务器拒绝资源访问
  • 404 Not Found 服务器未找到资源或无原因的拒绝访问
  • 500 Internal Server Error 服务器执行出错

TCP/IP五层协议

  • 应用层 - 直接为应用进程提供服务 | HTTP HTTPS DNS FTP SMTP
  • 传输层 - 负责为主机的进程提供通信服务 | TCP UDP
  • 网络层 - 负责为主机提供通信服务 | IP寻址
  • 数据链路层 - 负责将数据封装为帧并在链路间传输 | MAC地址
  • 物理层 - 负责保证数据在物理介质上传输

TCP 三次握手及四次挥手

  • 客户端向服务端发送连接请求报文段

  • 服务端发送同意连接应答

  • 客户端向服务端发送一个确认报文

  • 客户端向服务端发送连接释放请求

  • 服务端不再接收客户端发的数据,仍旧可以发送数据给客户端

  • 服务端向客户端发送连接释放请求

  • 客户端向服务端发送确认应答

HTTPS 过程

  1. 客户端向服务器发起请求,请求中包含使用的协议版本号、生成的一个随机数、以及客户端支持的加密方法
  2. 服务器端接收到请求后,确认双方使用的加密方法、并给出服务器的证书、以及一个服务器生成的随机数
  3. 客户端确认服务器证书有效后,生成一个新的随机数,并使用数字证书中的公钥,加密这个随机数,然后发给服务器。并且还会提供一个前面所有内容的 hash 值,用来供服务器检验。
  4. 服务器使用自己的私钥,来解密客户端发送过来的随机数。并提供前面所有内容的 hash 值来供客户端检验。
  5. 客户端和服务器端根据约定的加密方法使用前面的三个随机数,生成对话秘钥,以后的对话过程都使用这个秘钥来加密信息。

SharedWorker

SharedWorker 是 HTML 标准定义的 Web API 的一部分,是一种特殊的 Worker,支持在多个上下文(例如 window、iframe 甚至 Worker)之间共享

同时,SharedWorker 的全局上下文 SharedWorkerGlobalScope 也与 Worker 不同

创建 SharedWorker

和 Worker 一样,通过调用 SharedWorker() 构造函数来创建

1
const worker = new SharedWorker('./worker.js')

SharedWorker() 构造函数支持传入一组可选的配置项,与 Worker() 构造函数相同
SharedWorker() 构造函数也支持直接传入一个字符串,同配置项的 name 参数;特别的,SharedWorker() 中的 name 参数作为唯一的一个标识符,在创建新的与之前的拥有相同 URL 的 SharedWorker 时有一定作用

SharedWorker 通过脚本文件 URL 和 name 参数确定是否为同一个 SharedWorker

SharedWorker 消息传递

与 Worker 不同,Client 端通过创建的 SharedWorker 实例上的 port 属性暴露的 MessagePort 接口实例,调用其上的 postMessage() 方法实现发送消息

Client 端通过监听 SharedWorker 实例上的 message 事件实现接收到消息

1
2
3
4
5
6
7
8
9
worker.port.start()

worker.port.postMessage('message from client')

worker.port.close()

worker.port.addEventListener('message', (e) => {
console.log('receive message in client: ', e.data)
})

SharedWorker 环境下接收消息,需要监听 connect 事件,从而获取到新的对应的 MessagePort 实例;监听 MessagePort 实例的 message 事件接收消息

SharedWorker 环境下接收消息,同样需要通过调用 MessagePort 实例的 postMessage() 方法实现发送消息

1
2
3
4
5
6
7
8
9
10
11
12
13
self.addEventListener('connect', (e) => {
const port = e.ports.at(0)

port.addEventListener('message', (e) => {
console.log('receive message in worker: ', e.data)
})

port.start()

port.postMessage('message from worker')

port.close()
})

通常,在 connect 事件回调函数内,会把接收到的 port 存储下来,以便之后使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ports = []

self.addEventListener('connect', (e) => {
const port = e.ports.at(0)

port.addEventListener('message', handleReceiveMessage)

port.start()

ports.push(port)
})

function sendMessage() {
for (const port of ports) {
port.postMessage('message from worker')
}
}

卸载 SharedWorker

仅支持在 SharedWorker 环境内调用 close() 方法,来卸载当前 Worker

1
self.close()

SharedWorker 生命周期

SharedWorker 生命周期与 Client 端的生命周期独立,当任一页面创建 SharedWorker 时其生命周期开始,在没有页面使用 SharedWorker 时其生命周期结束

SharedWorker 全局环境

SharedWorker 全局环境通过 SharedWorkerGlobalScope 表示,该接口继承自 WorkerGlobalScope,它与 Worker 全局环境差别不大

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface SharedWorker extends EventTarget, AbstractWorker {
constructor(scriptURL: string | URL, options?: string | WorkerOptions);
readonly port: MessagePort;
}

interface SharedWorkerGlobalScope extends WorkerGlobalScope {
readonly name: string;
close(): void;
onconnect: ((this: SharedWorkerGlobalScope, ev: MessageEvent) => any) | null;
}

interface WorkerOptions {
credentials?: RequestCredentials
name?: string
type?: WorkerType
}

type WorkerType = 'classic' | 'module'

链接

Worker

Web Worker 是 HTML 标准定义的 Web API 的一部分,可以在后台运行一个耗时的任务,避免因长期执行 JS 任务而阻塞用户界面渲染与交互

Web Worker 可以被 Window 环境创建,也可以被其他的 Worker 创建

Web Worker 是独立于主线程的一个线程,具有独立的作用域,其中运行的任务不会阻塞主线程

Web Worker 中的全局作用域 DedicatedWorkerGlobalScope 与 Window 的全局作用域不同,Window 环境中部分 API 在 Worker 环境中不可用或受到一定的限制

Web Worker 线程与主线程之间的通信通过 message 机制实现,传递的数据通过结构化拷贝算法传递,因此通常不存在处理线程安全的需要

创建 Worker

通过调用 Worker() 构造函数,传入 Worker 脚本的 URL,来创建一个 Worker

1
const worker = new Worker('./worker.js')

Worker 脚本需要与 Client 同域

Worker() 构造函数支持传入一组可选的配置项
type 参数指定脚本的类型,值可以是 classicmodule,默认值是 classic
name 参数指定 Worker 的名称,在 debug 时候有一定作用,在 Worker 内可以通过 name 只读属性访问
credentials 参数指定 Worker 的 credentials 选项,允许的值可以是 omitsame-origininclude
若传入的 URL 解析失败,会抛出一个 SyntaxError 错误
若接收到的脚本文件并非 JavaScript 格式,会抛出 NetworkError 错误
若当前文档环境不支持创建 Worker,如未遵守同源策略,会抛出 SecurityError 错误

Worker 消息传递

无论是 Worker 端还是 Client 端,通过调用 postMessage() 方法实现发送消息,通过监听 message 事件实现接收消息

Client 发送消息

1
worker.postMessage('message from client')

Client 接收消息

1
2
3
worker.addEventListener('message', (e) => {
console.log('receive message in client: ', e.data)
})

Worker 发送消息

1
self.postMessage('message from worker')

Worker 发送消息

1
2
3
self.addEventListener('message', (e) => {
console.log('receive message in worker: ', e.data)
})

此外,可以选择传入一组数组或包含 transfer 参数的配置项,定义需要转移所有权的对象

所有权被转移后,对应对象在原环境内不再可用,而是仅在新环境内可用

普通消息

当然,传递的消息可以不仅仅是 string 类型,可以是其他任何可以被结构化拷贝算法执行的数据,包括:

  • number
  • string
  • boolean
  • null
  • undefined
  • bigint
  • 普通 object
  • Array
  • RegExp
  • Date
  • Error
  • Set
  • Map
  • Blob
  • ArrayBuffer
  • TypedArray
  • 等等

结构化拷贝算法,严格来说,与 JSON.stringfy()JSON.parse() 行为上不同。在结构化拷贝算法中,试图复制 Function 参数会抛出异常;但结构化拷贝算法支持复制包含循环对象的对象

可转移对象

可以转移的对象可以是:

  • ArrayBuffer
  • MessagePort
  • ReadableStream
  • WritableStream
  • TransformStream
  • WebTransportReceiveStream
  • AudioData
  • ImageBitmap
  • VideoFrame
  • OffscreenCanvas
  • RTCDataChannel

可共享对象

SharedArrayBuffer 可以用于多个线程之间的共享数据,并利用 Atomics 实现线程同步与线程锁功能。

启用该 API 需要 secure context,并且需要 cross-origin isolate,可以通过检测 isSecureContext 全局变量和 crossOriginIsolated 全局变量来确定是否可以使用 SharedArrayBuffer

卸载 Worker

通过调用 worker 实例的 terminate() 方法,来卸载一个 Worker

1
worker.terminate()

或者调用 Worker 环境中的 close() 方法,来卸载当前的 Worker

1
self.close()

卸载是立即执行的,不会等待 worker 内部任务的完成

Worker 全局环境

Worker 全局环境通过 DedicatedWorkerGlobalScope 表示,该接口继承自 WorkerGlobalScope

Worker 全局环境的 messageerror 事件会在传递的消息无法解析时触发,可用用于监听发送失败的消息(Worker 对象上同样存在)

Worker 全局环境的 importScripts() 方法可以导入一组同源的脚本文件,并在 Worker 全局环境下执行

示例

类型

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

interface Worker extends EventTarget, AbstractWorker {
constructor(scriptURL: string | URL, options?: WorkerOptions)
postMessage(message: any, transfer: Transferable[]): void
postMessage(message: any, options?: StructuredSerializeOptions): void
terminate(): void
}

interface DedicatedWorkerGlobalScope extends WorkerGlobalScope {
readonly name: string
close(): void
onmessage: ((this: DedicatedWorkerGlobalScope, ev: MessageEvent) => any) | null
onmessageerror: ((this: DedicatedWorkerGlobalScope, ev: MessageEvent) => any) | null
postMessage(message: any, transfer: Transferable[]): void
postMessage(message: any, options?: StructuredSerializeOptions): void
}

interface StructuredSerializeOptions {
transfer?: Transferable[]
}

interface WorkerOptions {
credentials?: RequestCredentials
name?: string
type?: WorkerType
}

type WorkerType = 'classic' | 'module'

链接

ServiceWorker II

ServiceWorker 全局上下文

ServiceWorkerGlobalScope 接口代表 ServiceWorker 的全局上下文,在 ServiceWorker 内通过 self 全局变量或者 globalThis 全局变量访问(该接口继承自 WorkerGlobalScope)。

ServiceWorkerGlobalScope

以下代表在 ServiceWorkerGlobalScope 接口本身的属性、方法和事件

  • ServiceWorkerGlobalScope 接口的 clients 属性代表一个 Clients 实例,可用于获取 Client (可执行上下文)实例。

  • ServiceWorkerGlobalScope 接口的 registration 属性代表一个 ServiceWorkerRegistration 实例,即当前 ServiceWorker 注册的引用。

  • ServiceWorkerGlobalScope 接口的 serviceWorker 属性代表一个 ServiceWorker 实例,即当前 ServiceWorker 实例的引用。

  • ServiceWorkerGlobalScope 接口的 skipWaiting 方法强制当前 ServiceWorker 从等待状态变成激活状态,返回一个该 ServiceWorker 激活后完成的 Promise。其在 install 事件的回调中调用才具有实际意义。

WorkerGlobalScope

以下代表继承自 WorkerGlobalScope 接口的属性、方法和事件

  • WorkerGlobalScope 接口的 location 属性代表一个 WorkerLocation 实例,是 Location 的字集。

  • WorkerGlobalScope 接口的 navigator 属性代表一个 WorkerNavigator 实例,是 Navigator 的字集。

  • WorkerGlobalScope 接口的 self 属性代表 WorkerGlobalScope 接口本身。

  • WorkerGlobalScope 接口的 importScripts 方法同步导入一组脚本文件并执行,接受一组参数,代表脚本文件的 URL,其可以为绝对路径或相对路径(相对文档路径)。

  • WorkerGlobalScope 接口的 error 事件在 ServiceWorker 内发生脚本错误时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 languagechange 事件在用户的首选语言更改时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 online 事件在浏览器获得网络访问权限并且 navigator.onLine 值切换到 true 时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 offline 事件在浏览器获得网络访问权限并且 navigator.onLine 值切换到 false 时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 rejectionhandled 事件在 ServiceWorker 内处理的 Promise 拒绝事件时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 unhandledrejection 事件在 ServiceWorker 内未处理的 Promise 拒绝事件时触发,返回一个 Event 实例。

以下代表暴露在全局的属性、方法和事件

1
2
3
4
5
6
7
8
self.fonts
self.caches
self.crossOriginIsolated
self.crypto
self.indexedDB
self.isSecureContext
self.origin
self.performance
1
2
3
4
5
6
7
8
9
10
11
self.atob()
self.btoa()
self.clearInterval()
self.clearTimeout()
self.createImageBitmap()
self.fetch()
self.queueMicrotask()
self.reportError()
self.setInterval()
self.setTimeout()
self.structuredClone()

示例

类型

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
49
50
51
interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
readonly clients: Clients;
readonly registration: ServiceWorkerRegistration;
readonly serviceWorker: ServiceWorker;
onactivate: ((this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any) | null;
onfetch: ((this: ServiceWorkerGlobalScope, ev: FetchEvent) => any) | null;
oninstall: ((this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any) | null;
onmessage: ((this: ServiceWorkerGlobalScope, ev: ExtendableMessageEvent) => any) | null;
onmessageerror: ((this: ServiceWorkerGlobalScope, ev: MessageEvent) => any) | null;
onnotificationclick: ((this: ServiceWorkerGlobalScope, ev: NotificationEvent) => any) | null;
onnotificationclose: ((this: ServiceWorkerGlobalScope, ev: NotificationEvent) => any) | null;
onpush: ((this: ServiceWorkerGlobalScope, ev: PushEvent) => any) | null;
onpushsubscriptionchange: ((this: ServiceWorkerGlobalScope, ev: Event) => any) | null;
skipWaiting(): Promise<void>;
}

interface WorkerGlobalScope extends EventTarget, FontFaceSource, WindowOrWorkerGlobalScope {
readonly location: WorkerLocation;
readonly navigator: WorkerNavigator;
readonly self: WorkerGlobalScope & typeof globalThis;
onerror: ((this: WorkerGlobalScope, ev: ErrorEvent) => any) | null;
onlanguagechange: ((this: WorkerGlobalScope, ev: Event) => any) | null;
onoffline: ((this: WorkerGlobalScope, ev: Event) => any) | null;
ononline: ((this: WorkerGlobalScope, ev: Event) => any) | null;
onrejectionhandled: ((this: WorkerGlobalScope, ev: PromiseRejectionEvent) => any) | null;
onunhandledrejection: ((this: WorkerGlobalScope, ev: PromiseRejectionEvent) => any) | null;
importScripts(...urls: (string | URL)[]): void;
}

interface WindowOrWorkerGlobalScope {
readonly caches: CacheStorage;
readonly crossOriginIsolated: boolean;
readonly crypto: Crypto;
readonly fonts: FontFaceSet;
readonly indexedDB: IDBFactory;
readonly isSecureContext: boolean;
readonly origin: string;
readonly performance: Performance;
atob(data: string): string;
btoa(data: string): string;
clearInterval(id: number | undefined): void;
clearTimeout(id: number | undefined): void;
createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise<ImageBitmap>;
createImageBitmap(image: ImageBitmapSource, sx: number, sy: number, sw: number, sh: number, options?: ImageBitmapOptions): Promise<ImageBitmap>;
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
queueMicrotask(callback: VoidFunction): void;
reportError(e: any): void;
setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
structuredClone<T = any>(value: T, options?: StructuredSerializeOptions): T;
}

链接

Local Font Access API

Local Font Access API 向开发者提供了获取用户本地安装的字体的信息的方式,包括字体名称、字体样式及字体族等等

获取本地字体

调用 window.queryLocalFonts() 方法来获取本地安装的字体

方法允许传入一组可选的配置项,其 postscriptNames 参数允许传入一组字符串数组,代表希望筛选的 postscriptName 名称

方法会返回 Promise 的 FontData 数组,表示本地安装的字体的列表

方法可能抛出 NotAllowedError 异常,表示用户拒绝授予开发者 'local-fonts' 权限

方法可能抛出 SecurityError 异常,表示该 API 受 Permissions Policy 的限制无法被调用或调用该方法并非缘于用户交互行为

1
2
3
window.queryLocalFonts().then((fonts) => {
// to do something
})

处理本地字体

字体信息使用 FontData 接口表示

FontData 接口的 family 属性表示字体的字体族,可以用于 CSS 的 font-family 属性或者 @font-face 规则中的 local() 函数等;

FontData 接口的 fullName 属性表示字体的全名,通常是一个用户可辨识的名称,可以用于向用户展示;

FontData 接口的 postscriptName 属性表示字体的 PostScript 名称,可以用于唯一地辨识字体;

FontData 接口的 style 属性表示字体的样式,可以用于 CSS 的 font-style 属性;

1
2
3
4
5
6
7
8
9
10
async function logFonts() {
const fonts = await window.queryLocalFonts()

for (const data of fonts) {
console.log(data.postscriptName)
console.log(data.fullName)
console.log(data.family)
console.log(data.style)
}
}

FontData 接口的 blob() 方法以 Blob 形式返回字体的源数据;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function getFontFormat(font) {
const data = await fontData.blob()

const version = await data.slice(0, 4).text()

let format: 'unknown' | 'truetype' | 'cff' = 'unknown'

switch (format) {
case "\x00\x01\x00\x00":
case "true":
case "typ1":
format = "truetype"
break
case "OTTO":
format = "cff"
break
}

return format
}

权限策略

该 API 调用受到 local-fonts 权限策略的控制,可以通过 Permissions-Policy 响应头指定,或通过 <iframe> 标签的 allow 属性指定

默认为 self,即允许在当前上下文或内嵌的其他同源上下文中使用

权限 API

该 API 调用需要用户授予 local-fonts 权限,可以调用 Permission.query() 方法检查用户是否已授予了该权限

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Window {
queryLocalFonts: (options?: QueryOptions) => Promise<FontData[]>
}

interface QueryOptions {
postscriptNames?: string[]
}

interface FontData {
readonly family: string
readonly fullName: string
readonly postscriptName: string
readonly style: string
blob: () => Promise<Blob>
}

链接

性能优化

构建相关

路由懒加载

最主要在于降低首屏加载资源大小,仅加载所需的页面资源文件,加快页面的显示

bad
1
2
3
4
5
import C from 'c'

{
component: C,
}
good
1
2
3
{
component: () => import('c'),
}

原理即将导航中的路由组件从静态 import 导入改为动态 import() 导入

组件懒加载

原理同路由懒加载

worse
1
2
3
4
5
6
7
import C from 'c'

export default {
components: {
C,
},
}
better
1
2
3
4
5
6
7
const C = () => import('c')

export default {
components: {
C,
},
}

做组件懒加载一般在某些特别条件下使用,如组件仅在特定条件下才展示、当前页面文件过大、组件复用性较强

外部依赖懒加载

较大外部依赖可动态导入

worse
1
import * as THREE from 'three'
better
1
import('three').then((THREE) => { /* do */ })

但建议谨慎采取此方式

Tree Shaking 和 SideEffects

依赖 ESM 的静态特性,进行静态分析,在生成产物中去除无用的模块或代码,从而降低生成产物的大小

webpack 默认在构建阶段会启用 Tree Shaking,在开发阶段需手动配置

webpack.config.js
1
2
3
4
5
6
module.exports = {
mode: 'development',
optimization: {
usedExports: true,
},
}
webpack.config.js
1
2
3
module.exports = {
mode: 'production',
}

使用副作用

package.json
1
2
3
{
"sideEffects": false
}

某些情况下需手动标记 /*#__PURE__*/ 以标记代码,以标记语句是可执行 Tree Shaking 的

vite 原生基于 rollup 支持在构建阶段启用 Tree Shaking

构建产物压缩

webpack 可以使用 terser-webpack-plugin 插件来执行代码构建产物的压缩

webpack.config.js
1
2
3
4
5
6
7
8
const TerserPlugin = require("terser-webpack-plugin")

module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
}

vite 内部默认集成 esbuild 进行代码构建产物的压缩,同时支持配置为使用 terser 来执行压缩

vite.config.js
1
2
3
4
5
6
7
8
9
10
11
export default {
esbuild: {},
build: {
cssMinify: 'esbuild',
minify: 'esbuild',
terserOptions: {},
},
optimizeDeps: {
esbuildOptions: {},
},
}

静态资源构建产物(特别是图片)的压缩可以使用一些插件实现,如 compression-webpack-plugin

外部库按需加载

外部库(特别是 UI 组件库)使用插件(如 babel-plugin-import 等)进行按需加载

代码分割

可以适当进行代码分割,避免一次性加载过大的资源文件,阻碍页面的展示;也需要避免过度分割,一次性执行过多的资源获取请求

vite.config.js
1
2
3
4
5
6
7
8
9
10
11
export default {
build: {
rollupOptions: {
output: {
manualChunks: {},
// or: manualChunks: (id) => id,
},
},
cssCodeSplit: {},
},
}

内联代码文件

部分小体量的 JS 文件或 CSS 文件,可以内联到 HTML 文件中,减少请求的数量

分析外部依赖

可通过 webpack-bundle-analyzer 插件或 rollup-plugin-visualizer 插件来分析查看

webpack.config.js
1
2
3
4
5
6
7
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')

module.exports = {
plugins: [
new BundleAnalyzerPlugin(),
],
}
vite.config.js
1
2
3
4
5
6
7
import { visualizer } from 'rollup-plugin-visualizer'

export default {
plugins: [
visualizer(),
],
}

检查项目的依赖包是否有重复引用的情况,避免出现使用同样名称不同版本的依赖包引用的情况

渲染相关

骨架屏

主要应用于缩短白屏时长,特别是 SPA 单页应用

原理是直接把展示骨架屏的内容放在 html 文件内,在真正内容加载完后再隐藏骨架屏的内容

虚拟滚动

只渲染可视区域的列表项,非可见区域的不渲染

原理为计算列表的总高度,并在触发滚动事件时根据滚动高度更新起始下标和结束下标,从而取出相应的数据渲染元素

Worker 长任务优化

将一些长任务逻辑移入到 Worker 中,避免长任务的执行阻碍 UI 渲染而影响用户体验

是否使用 Worker,需要比较 Worker 通信时长与运算时长相比是否具有足够的优势

利用 requestAnimationFrame 周期任务

可以利用 requestAnimationFrame 处理周期任务

特别是需要较严格固定周期频率执行的情况(setInterval 和 setTimeout 无法保证准确的时间间隔)

同时 requestAnimationFrame 支持在页面隐藏或最小化时暂停执行周期任务,以节省性能(setInterval 和 setTimeout 不会因页面隐藏或最小化等因素暂停执行)

使用 CSS 动画过渡变换替代 JS

CSS 动画 Animation、过渡 Transition、变换 Transform 相较于 JS 性能通过上更具优势,并且浏览器更易于针对性地做优化

简化 CSS 选择器

避用通配符选择器 *

减少使用标签选择器

优先使用默认的样式继承

避免层数过大的选择器

代码复用及代码封装

提升代码复用率,进行功能代码封装等

CSS 的样式简化,可以使用 TailwindCSSUnoCSS 等方案

使用防抖节流

防抖,使得指定函数至少间隔 n 秒才会执行一次

节流,使得指定函数在 n 秒中最多执行一次

对于容易连续触发的事件,如 mousemovepointermovescrolltouchmovewheelresize 等,通过将事件处理方法绑定为防抖节流版本的方法,避免持续多次重复执行方法

使用 will-change 优化动态效果

预先将执行动画的元素设置 will-change CSS 属性,以便浏览器引擎将其视为单独图层来进行优化

注意点是,避免过度应用 will-change 属性;建议仅在需要时候 JS 动态设置该属性

减少页面重排重绘

重绘指元素的非几何样式改变引起的浏览器重新渲染目标元素的现象

重绘指元素的几何样式改变引起的浏览器重新渲染整个渲染树或的现象

使用 GPU 渲染

CSS中可使用如下一些方式将目标元素独立为合成层,从而进行独立渲染,以触发 GPU 渲染

  • 指定 will-change 属性
  • 3D 或者透视变换 perspective transform
  • 使用加速视频解码的 video 元素
  • 拥有 3D 上下文(WebGL)或者加速 2D 上下文的 canvas 元素
  • 使用 opacity、filter 实现 CSS 动画或使用一个动画 webkit 变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点
  • 元素有一个兄弟元素在复合图层渲染,且具备较低的 z-index

避免无效请求

避免出现无效请求,例如表单提交频繁点击的问题,或路由切换时还有未完成的请求;对于服务器和用户来说,会造成不必要的困扰

网络相关

使用 HTTP2

HTTP2 支持头部压缩,能够减少数据传输量,节省消息投占用的网络的流量

且 HTTP2 支持多路复用、服务器推送等功能

gzip 压缩

HTTP 头部及资源启用 gzip 压缩,能够大大减少网络传输的数据量

可以使用 compression-webpack-pluginvite-plugin-compression 压缩打包资源至 gzip

webpack.config.js
1
2
3
4
5
const compression = require('compression-webpack-plugin')

module.exports = {
plugins: [new compression()],
}
vite.config.js
1
2
3
4
5
import compression from 'vite-plugin-compression'

export default {
plugins: [compression()]
}

对资源服务开启 gzip 支持

nginx.conf
1
2
3
4
5
6
7
8
9
gzip on;
gzip_min_length 1k;
gzip_comp_level 5;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
gzip_disable "MSIE [1-6]\.";
gzip_proxied any;
gzip_vary on;

一般推荐提前处理完成 gzip 文件,再直接交由 nginx 服务

执行网络请求时,浏览器会自动带上同源的 Cookie 信息,一定程度上会增大请求头的大小

可以通过精简 Cookie 的内容,来降低请求信息的大小

同时可以对静态资源单独部署,避免请求携带不必要的 Cookie 信息

启用 Keep-Alive

通过给请求头或响应头设置 Keep-Alive 头,通常用于提示连接超时时间和最大请求量

1
2
Connection: Keep-Alive
Keep-Alive: timeout=5, max=1000

减少预检请求发起

可以在跨域请求设置 Access-Control-Max-Age 响应头指定预检请求的缓存期限,从而在指定期限内的跨域请求无需进行预检请求可以直接发起请求

有效期 10min
1
Access-Control-Max-Age: 600

资源相关

script 加载方式

  • 正常模式

JS 会阻碍 DOM 渲染

<script src="main.js"></script>

  • async 模式

异步加载 JS,执行无顺序,加载完成后立即执行

可以用于加载与 DOM 无关的 JS 资源,如埋点统计等

<script async src="main.js"></script>

  • defer 模式

异步加载 JS,执行有顺序,加载完成后统一在 DOMContentLoaded 事件触发前执行

一般情况均可使用 defer 优化 JS 资源的加载,避免 JS 脚本加载与执行阻碍网页的渲染

<script defer src="main.js"></script>

  • module 模式

行为上会类似于 defer 模式

<script type="module" src="main.js"></script>

  • fetchpriority 资源加载优先级

可以利用 fetchpriority HTML 属性指定 script 脚本加载的优先级,优先加载级别高的脚本,延后加载级别低的脚本

资源预加载

需要避免 preloadprefetch 的混用,以避免不必要的二次自由加载

  • preload

预先下载当前页面将使用的资源并缓存(不会执行),会提升资源的优先级

需同时指定 as 属性与 href 属性

<link rel="preload" href="style.css" as="style" />

<link rel="preload" href="main.js" as="script" />

建议指定 type 属性,以避免浏览器下载格式不支持的资源

建议同时指定 crossorigin 属性

  • prefetch

预加载未来页面将使用的资源,并保存在缓存内一段时间,会降低资源的优先级

要求当前页面需为安全上下文

<link rel="prefetch" href="main.js" />

  • modulepreload

类似于 preload

预加载当前页面将使用的模块脚本资源,并进行解析与执行

<link rel="modulepreload" href="main.js" />

  • prerender

预加载目标资源并提前在后台处理执行

仅部分浏览器支持该非标准特性

网络预连接

一般情况下,dns-prefetchpreconnect 都是配对使用

但不建议过度使用 preconnect,仅用于未来一段时间极可能访问或请求的 origin;否则仅应用 dns-prefetch

同时 dns-prefetch 的浏览器兼容性优于 preconnect

建议使用以上两属性的同时指定 crossorigin 属性

  • dns-prefetch

提前执行目标 origin 的 DNS 解析,可以加快未来将访问或请求的 origin 的处理速度(直接使用已预先解析的 DNS 缓存)

<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />

  • preconnect

提前执行目标 origin 的连接 —— DNS 解析、TCP 连接(及 TLS 握手),可以加快未来将访问或请求的 origin 的处理速度

<link rel="preconnect" href="https://fonts.googleapis.com/" />

避用外部依赖

尽量减少对非必要的外部依赖的使用,使用轻量级别替代方案或者自行实现

常见的如:

使用轻量级 day.js 替代 moment.js

使用 ESM 的 lodash-es 替代 CJS 的 lodash

使用 CDN 服务静态资源

CDN 即 Content Delivery Network,其具有分布于多个地域的服务器阵列

CDN 可以降低私有服务器的访问压力

地理位置的距离可能相对更近,一定程度上可以降低网络资源加载的时延

CDN 保证了比较正确的缓存配置

合理配置缓存策略

服务器在响应资源时,通过指定 Expires 响应头或 Cache-Control 响应头来控制浏览器该资源的缓存策略;若被指定为强缓存并且在有效期内直接使用缓存;反之若为被禁止使用缓存,则进行协商缓存,通过 If-Modified-Since 头向服务器提供浏览器缓存的资源的修改时间(在获取资源时服务器通过 Last-Modified 头指定)或通过 If-None-Match 头向服务器提供浏览器缓存的资源的标识符(在获取资源时服务器通过 ETag 头指定)

  • Expires(推荐使用 Cache-Control 代替,其优先级更高)【响应头】

    指定缓存失效的时间

  • Cache-Control【响应头,请求头】

    配置缓存的策略及有效期

    • no-store 不允许缓存

    • no-cache 允许缓存,但使用前需进行服务端验证

    • must-revalidate 允许缓存,有效期内直接使用缓存,超出有效期需进行服务端验证,通常结合 max-age=N 使用

    • max-age=N 指定缓存的有效期

  • Last-Modified【响应头】/ If-Modified-Since【请求头】

    资源最近修改时间

  • ETag【响应头】/ If-None-Match【请求头】

    资源唯一标识符

ServiceWorker 实现可控缓存

利用 ServiceWorker 结合 CacheStorage 实现可控缓存,原理是基于受 ServiceWorker 控制的上下文会在 ServiceWorker 全局触发 fetch 事件,可以通过调用返回的 FetchEventrespondWith() 方法自定义响应

图片字体相关

webp 图片

webp 格式图片大小通常比同等情况下的其他格式图片大小有较大优势,因此若浏览器支持 webp 格式图片,优先使用 webp 格式图片

可以利用离线或在线 webp 图片格式转换工具转换图片格式为 webp

图片懒加载

  • JS 手动控制

    初始不指定图片标签的 src 属性,直到图片需要展示时再指定其 src 属性,避免图片的自动预加载

    1
    <img data-src="/img/png" />
    1
    2
    3
    4
    // 适当情况下调用该方法
    function loadImg(el) {
    el.src = el.getAttribute('data-src')
    }
  • 利用 img 标签特性(更推荐)

    可以设置 img 标签的 loading 属性实现懒加载功能,将属性值指定为 lazy 以惰性加载图片

    同时可以指定 img 标签的 fetchpriority 属性,以控制获取图片资源的优先级,设定为 high 以提升获取的优先级,设定为 low 以降低获取的优先级

    同时可以指定 img 标签的 decoding 属性,以设定解码图片的模式(是否允许在图片解码完成前展示图片),设定为 sync 以同步解码图片,设定为 async 以异步解码图片

    1
    <img src="/img/png" alt="" loading="lazy" fetchpriority="auto" decoding="auto" />

字体图标

将小图标利用字体形式加载,如 IconFont

1
@import url('//at.alicdn.com/t/font_8d5l8fzk5b87iudi.css');
1
<i class="iconfont icon-xxx"></i>

通常加载资源大小会更小,并且能够避免重复加载图片并降低请求数量,且支持修改各类字体样式

内联图片

将小图片转换为 base64 编码内联入 html 文档,可以减少请求数量

webpack 中可以使用 url-loader 插件自动转换内联图片

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 1024 * 8,
},
},
],
},
],
},
}

vite 原生支持内联图片,默认在图片大小小于 4KB 时启用

可以通过 build.assetsInlineLimit 选项配置启用的阈值

vite.config.js
1
2
3
4
5
export default {
build: {
assetsInlineLimit: 4096,
}
}

图片裁剪

对图片生成多个尺寸的备用图片,使用时根据需要加载不同尺寸的图片,减少不必要的资源流量

图片单独部署

将图片等静态资源部署在单独的静态资源服务器或是 CDN 上,避免直接打包到项目中

图片尺寸指定

设置图片标签的尺寸大小,防止图片加载中导致页面布局抖动,影响 CLS 指标

字体按需生成

使用第三方字体库时,尽可能按需生成,避免不必要的全量引入字体库

代码相关

JSON 字符串使用

对于大对象数据,尽量采用 JSON 格式而非 JS 对象格式,因为 JSON 语法比 JS 简单,解析速度更快

如 vite 支持将 JSON 文件打包为 JSON 字符串而非 JS 对象

vite.config.js
1
2
3
4
5
export default {
json: {
stringify: true,
},
}

if 逻辑提前跳出

提前结束的逻辑利于编译器的优化

bad
1
2
3
4
5
6
7
8
9
10
11
function () {
if (A) {
if (B) {
return 'good'
} else {
return 'bad'
}
} else {
return 'bad'
}
}
good
1
2
3
4
5
6
7
function () {
if (A && B) {
return 'good'
}

return 'bad'
}

逻辑能提前结束就提前结束

switch 连续值优化

switch 对于连续值会处理成数组,而数组会具有更高效率的随机访问性能

bad
1
2
3
4
5
6
7
function get(/* @type {1 - 10} */ level) {
if (level >= 10) return 100
if (level >= 9) return 80
if (level >= 6) return 50
if (level >= 1) return 20
return 10
}
good
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getPrice(level) {
switch(level) {
case 10: return 100
case 9: return 80
case 8:
case 7:
case 6: return 50
case 5:
case 4:
case 3:
case 2:
case 1: return 20
default: return 10
}
}

若条件可以处理成连续的数字,可以使用 switch 来进行优化

循环减少执行次数

对于循环,满足条件或完成任务后即刻跳出,避免不必要的执行损耗

bad
1
2
3
4
5
6
7
8
9
function find(data) {
let result = null
for (let i = 0; i < data.length; i++) {
if (data[i].key === KEY) {
result = data[i]
}
}
return result
}
good
1
2
3
4
5
6
7
function find(data) {
for (let i = 0; i < data.length; i++) {
if (data[i].key === KEY) {
return data[i]
}
}
}

提取集合数组长度

在循环处理数组、集合等容器的元素,若可以保证容器的容量不会发生变化,可以提前提取容器的容量,避免在循环中重复获取

适当使用位运算

对于一些和 2 相关的乘除法或者条件相关的,可以用位运算替代

bad
1
const A = 2 ** 8
good
1
2
const A = 2 << 3
const isPowerOfTwo = n => (n > 0) && (n & (n - 1) === 0)

ServiceWorker III

ServiceWorker 中 Client 与 Worker

在 Worker 中获取 Client

在 ServiceWorker 中,会对应着多个 Client 实例,代表着受 ServiceWorker 控制的上下文,可以是网页、Worker 乃至 SharedWorker 等,而 Client 实例的访问与控制通过 Clients 实例实现,通过 clients 属性获取

同时,在 ServiceWorker 中,通过 registration 属性暴露 ServiceWorkerRegistration 实例,即当前 ServiceWorker 注册;通过 serviceWorker 属性暴露 ServiceWorker 实例,即当前 ServiceWorker 实例

Clients

在 ServiceWorker 中,Clients 接口提供对 Client 接口的访问

  • Clients 接口的 get() 方法根据给定的 id 获取对应的 Client 实例

    接收一个字符串,代表 Client 的 id

    返回一个 Promise 的 Client(或 undefined

  • Clients 接口的 matchAll() 方法获取所有匹配的 Client 实例

    接收一组可选的配置项

    配置项的 type 参数指定需匹配的 Client 类型,允许的值为 "window", "worker", "sharedworker""all",默认为 "window"

    配置项的 includeUncontrolled 参数指定是否返回未受控制的 Client,默认为 false

    返回一个 Promise 的 Client 数组

  • Clients 接口的 claim() 方法将所有匹配的 Client 实例置于当前 ServiceWorker 的控制之下

    返回一个 Promise

  • Clients 接口的 openWindow() 方法创建新的顶层上下文并加载给定的 URL

    要求该 URL 需与 ServiceWorker 同源,并且要求调用前一段时间用户需发生交互行为

    Firefox 中要求该方法必须在通知点击回调方法中调用

    返回一个 Promise 的 WindowClient 实例或 null

Client

在 ServiceWorker 中,Client 接口表示受 ServiceWorker 控制的执行上下文

其中 WindowClient 接口继承自 Client 接口,表示受 ServiceWorker 控制的 Window 执行上下文

受 ServiceWorker 控制的各执行上下文可以是 Worker、Shared Worker 以及 Window

  • Client 接口的 id 属性返回一个字符串,代表对应的执行上下文的 ID

  • Client 接口的 type 属性返回一个字符串枚举,可能为 "window""worker""sharedworker" 之一,代表对应的执行上下文的类型

  • Client 接口的 url 属性返回一个字符串,代表对应的执行上下文的 URL

  • Client 接口的 frameType 属性返回一个字符串枚举,可能为 "auxiliary""top-level""nested""none" 之一,代表对应的执行上下文的类型

  • WindowClient 接口的 focused 属性返回一个布尔值,代表对应的 Window 执行上下文是否处于聚焦状态

  • WindowClient 接口的 visibilityState 属性返回一个字符串枚举,可能的值为 "hidden""visible""prerender",代表对应的 Window 执行上下文的可见性类型

  • WindowClient 接口的 focus() 方法控制对应的执行上下文聚焦

    返回一个 Promise 的 WindowClient 实例

  • WindowClient 接口的 navigate() 方法控制对应的执行上下文加载指定 URL

    方法接收一个字符串或 URL 实例,代表 URL

    若 ServiceWorker 执行上下文与 URL 同源,返回 Promise 的 WindowClient;否则返回 Promise 的 null

在 Client 中获取 Worker

在 Client 端中,通过 ready 属性暴露控制当前页面的 ServiceWorkerRegistration 实例,通过 controller 属性暴露控制当前页面的 ServiceWorker 实例

ServiceWorkerContainer

在 ServiceWorker 中,ServiceWorkerContainer 接口包含 ServiceWorker 的相关状态与相关控制方法,用于实现对 ServiceWorker 的管理

  • ServiceWorkerContainer 接口的 ready 属性返回一个 Promise 的 ServiceWorkerRegistration,表示控制当前页面的 ServiceWorker 的注册;该属性与 ServiceWorkerGlobalScope 接口的 registration 属性类似

  • ServiceWorkerContainer 接口的 controller 属性返回一个 ServiceWorkernull,表示是否存在控制当前页面的 ServiceWorker 的实例;该属性与 ServiceWorkerGlobalScope 接口的 serviceWorker 属性类似

  • ServiceWorkerContainer 接口的 getRegistration() 方法根据给定的 URL (默认使用当前 Client 的 URL)返回与之匹配的 ServiceWorkerRegistration 对象

    方法可以接受一个参数

    方法返回一个 Promise 的 ServiceWorkerRegistration 或者 undefined,根据是否存在对应的注册对象

  • ServiceWorkerContainer 接口的 getRegistrations() 方法获取所有与当前 Client 相关的 ServiceWorkerRegistration 对象

    返回一个 ServiceWorkerRegistration 的数组

  • ServiceWorkerContainer 接口的 startMessages() 方法强制当前上下文提前开始接收发送自 ServiceWorker 的消息

  • ServiceWorkerContainer 接口的 controllerchange 事件在控制当前 Client 的 ServiceWorker 变化时触发,返回一个 Event 事件

ServiceWorkerRegistration

在 ServiceWorker 中,ServiceWorkerRegistration 接口表示 ServiceWorker 的注册对象

  • ServiceWorkerRegistration 接口的 active 属性返回一个 ServiceWorker 或者 null,代表最新注册的 state 属性为 activatingactivated 的 ServiceWorker

  • ServiceWorkerRegistration 接口的 installing 属性返回一个 ServiceWorker 或者 null,代表最新注册的 state 属性为 installing 的 ServiceWorker

  • ServiceWorkerRegistration 接口的 waiting 属性返回一个 ServiceWorker 或者 null,代表最新注册的 state 属性为 installed 的 ServiceWorker

通常而言,active 属性、installing 属性、waiting 属性三个中最多只有一个是非 null 值。

  • ServiceWorkerRegistration 接口的 scope 属性返回一个字符串,代表 ServiceWorker 的注册域

  • ServiceWorkerRegistration 接口的 updateViaCache 属性返回一个字符串枚举,可能的值为 'all''imports''none',指定 HTTP 缓存的脚本如何应用于 ServiceWorker

以上两个属性的值在注册 ServiceWorker 时指定

  • ServiceWorkerRegistration 接口的 updatefound 事件在新的 ServiceWorker 开始下载时触发,返回一个 Event 事件

ServiceWorker

在 ServiceWorker 中,ServiceWorker 接口表示 ServiceWorker 对象

  • ServiceWorker 接口的 scriptURL 属性返回一个字符串,表示 ServiceWorker 的注册脚本 URL

  • ServiceWorker 接口的 state 属性返回一个字符串枚举,可能的值包括 "parsed""installing""installed""activating""activated""redundant",表示 ServiceWorker 的状态

    • "parsed" ServiceWorker 在下载完成并且验证可运行后的初始值
    • "installing" ServiceWorker 在安装中
    • "installed" ServiceWorker 安装完成
    • "activating" ServiceWorker 在激活中
    • "activated" ServiceWorker 激活完成
    • "redundant" ServiceWorker 被替代或安装失败
  • ServiceWorker 接口的 statechange 事件在 ServiceWorker 的状态更新时触发,返回一个 Event 事件

示例

类型

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
49
50
51
52
53
54
55
56
57
interface Client {
readonly frameType: FrameType;
readonly id: string;
readonly type: ClientTypes;
readonly url: string;
postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: StructuredSerializeOptions): void;
}

interface Clients {
claim(): Promise<void>;
get(id: string): Promise<Client | undefined>;
matchAll<T extends ClientQueryOptions>(options?: T): Promise<ReadonlyArray<T["type"] extends "window" ? WindowClient : Client>>;
openWindow(url: string | URL): Promise<WindowClient | null>;
}

interface WindowClient extends Client {
readonly focused: boolean;
readonly visibilityState: DocumentVisibilityState;
focus(): Promise<WindowClient>;
navigate(url: string | URL): Promise<WindowClient | null>;
}

interface ServiceWorker extends EventTarget, AbstractWorker {
readonly scriptURL: string;
readonly state: ServiceWorkerState;
postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: StructuredSerializeOptions): void;
onstatechange: ((this: ServiceWorker, ev: Event) => any) | null;
}

interface ServiceWorkerContainer extends EventTarget {
readonly controller: ServiceWorker | null;
readonly ready: Promise<ServiceWorkerRegistration>;
getRegistration(clientURL?: string | URL): Promise<ServiceWorkerRegistration | undefined>;
getRegistrations(): Promise<ReadonlyArray<ServiceWorkerRegistration>>;
register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>;
startMessages(): void;
oncontrollerchange: ((this: ServiceWorkerContainer, ev: Event) => any) | null;
onmessage: ((this: ServiceWorkerContainer, ev: MessageEvent) => any) | null;
onmessageerror: ((this: ServiceWorkerContainer, ev: MessageEvent) => any) | null;
}

interface ServiceWorkerRegistration extends EventTarget {
readonly active: ServiceWorker | null;
readonly installing: ServiceWorker | null;
readonly navigationPreload: NavigationPreloadManager;
readonly pushManager: PushManager;
readonly scope: string;
readonly updateViaCache: ServiceWorkerUpdateViaCache;
readonly waiting: ServiceWorker | null;
getNotifications(filter?: GetNotificationOptions): Promise<Notification[]>;
showNotification(title: string, options?: NotificationOptions): Promise<void>;
unregister(): Promise<boolean>;
update(): Promise<void>;
onupdatefound: ((this: ServiceWorkerRegistration, ev: Event) => any) | null;
}

链接


:D 一言句子获取中...