跳到主要内容

存储和缓存✅

浏览器的存储有哪些

答案
存储方式主要特性生命周期容量限制典型用途是否与服务端通信备注/支持API
Cookie小型文本,支持过期可设定/会话/持久约4KB会话、登录、偏好是(随请求发送)document.cookie
sessionStorage本地存储,临时会话窗口关闭即清除约5MB单页会话数据window.sessionStorage
localStorage本地存储,持久需手动清除约5MB持久化本地数据window.localStorage
IndexedDB结构化数据存储持久较大大量数据、离线应用window.indexedDB
Cache Storage缓存静态资源持久较大离线缓存、PWAcaches API
Web SQL Database关系型数据库持久较大结构化数据(已废弃)window.openDatabase

补充说明

  • Service Worker 可通过 Cache API 存储静态资源,通过 IndexedDB 存储结构化数据,内容存储于浏览器专用空间,独立于主线程。
  • Web SQL 已废弃,推荐优先使用 IndexedDB。
  • Cache Storage 主要用于离线缓存和 PWA 场景。

延伸阅读

答案
属性CookieSessionsessionStoragelocalStorage
存储位置客户端(浏览器)服务器端客户端(浏览器)客户端(浏览器)
生命周期可设定/会话/持久会话(超时销毁)窗口/标签页关闭即清除持久,需手动清除
容量限制约4KB较大约5MB约5MB
安全性较低(易被篡改)较高(服务端存储)较高较高
典型用途持久性数据、会话登录、购物车等单页会话数据持久化本地数据
随请求发送
备注支持跨窗口共享需Session ID标识仅当前窗口可见同源窗口共享

补充说明

  • Cookie 适合小数据、需与服务端通信场景,安全性较低,易被劫持或篡改。
  • Session 适合存储敏感、短期数据,安全性高但增加服务器压力。
  • sessionStorage 仅当前窗口/标签页可见,适合临时数据。
  • localStorage 持久化存储,适合长期本地数据,不随请求发送。
提示

敏感信息建议仅存于 Session,避免明文存储在 Cookie 或本地存储。

延伸阅读

localStorage 是同步还是异步

答案

localStorage的操作是同步的。当你使用localStorage.setItem()来存储数据或者localStorage.getItem()来获取数据时,这些操作会立即执行并且不会返回一个 Promise 或者使用回调函数来处理异步操作。

localStorage.setItem('key', 'value')
console.log(localStorage.getItem('key'))

在上面的代码中,设置和获取localStorage中的数据的操作会按顺序立即执行,不会像异步操作那样需要等待一段时间后再执行后续代码。

IndexedDB 存储空间大小是如何约束的?

答案

核心概念:

IndexedDB 存储空间限制由浏览器实现决定,开发者无法直接控制具体大小。浏览器通常基于可用磁盘空间的百分比(如 50%)或固定阈值来动态分配存储配额。不同浏览器有不同的策略:Chrome 基于可用空间,Firefox 有固定限制,Safari 较为保守。配额可能随设备存储情况动态调整。

实际示例:

// 查询存储配额信息
async function checkStorageQuota () {
if ('storage' in navigator && 'estimate' in navigator.storage) {
try {
const estimate = await navigator.storage.estimate()
const used = estimate.usage
const quota = estimate.quota
const available = quota - used

console.log(`已使用: ${(used / 1024 / 1024).toFixed(2)} MB`)
console.log(`总配额: ${(quota / 1024 / 1024).toFixed(2)} MB`)
console.log(`可用空间: ${(available / 1024 / 1024).toFixed(2)} MB`)
console.log(`使用率: ${((used / quota) * 100).toFixed(2)}%`)

return { used, quota, available }
} catch (error) {
console.error('无法获取存储配额信息:', error)
}
} else {
console.log('浏览器不支持 Storage API')
}
}

// 监控 IndexedDB 数据大小
async function getIndexedDBSize () {
return new Promise((resolve, reject) => {
const request = indexedDB.open('myDatabase')

request.onsuccess = () => {
const db = request.result
const transaction = db.transaction(['myStore'], 'readonly')
const store = transaction.objectStore('myStore')
const countRequest = store.count()

countRequest.onsuccess = () => {
console.log(`数据库记录数: ${countRequest.result}`)
// 注意:无法直接获取精确的存储字节数
resolve(countRequest.result)
}
}

request.onerror = () => reject(request.error)
})
}

// 数据清理策略
class IndexedDBManager {
constructor (dbName, version) {
this.dbName = dbName
this.version = version
}

async cleanupOldData (maxAge = 7 * 24 * 60 * 60 * 1000) { // 7天
const db = await this.openDB()
const transaction = db.transaction(['data'], 'readwrite')
const store = transaction.objectStore('data')
const cutoffTime = Date.now() - maxAge

const request = store.index('timestamp').openCursor(
IDBKeyRange.upperBound(cutoffTime)
)

request.onsuccess = (event) => {
const cursor = event.target.result
if (cursor) {
cursor.delete()
cursor.continue()
}
}
}

async compressData (data) {
// 使用 JSON 压缩或其他压缩算法
return JSON.stringify(data)
}
}

面试官视角:

要点清单:

  • 理解IndexedDB配额由浏览器控制,非开发者可调
  • 了解不同浏览器的存储策略差异
  • 掌握Storage API查询配额的方法

加分项:

  • 提及持久化存储权限(persistent storage)
  • 了解数据清理和压缩策略
  • 知道如何监控和优化存储使用

常见失误:

  • 认为可以通过API直接设置存储大小
  • 不了解浏览器间配额策略的差异
  • 忽略存储配额耗尽的错误处理

延伸阅读:

WebWorker、SharedWorker 和 ServiceWorker 有哪些区别?

答案

核心概念:

Web Workers 是 HTML5 提供的多线程解决方案,让 JavaScript 可以在后台线程执行,避免阻塞主线程和 UI 渲染。三种类型各有特点:

  • Worker - 专用线程,与创建它的页面一对一通信,适合CPU密集型任务
  • SharedWorker - 共享线程,多个页面可同时访问,适合跨标签页数据共享
  • ServiceWorker - 服务代理,拦截网络请求,适合离线缓存和推送通知

实际示例:

// 1. Worker - 专用线程
// main.js
const worker = new Worker('./worker.js')
worker.postMessage({ cmd: 'calculate', data: [1, 2, 3, 4, 5] })
worker.onmessage = (e) => {
console.log('计算结果:', e.data)
}

// worker.js
self.onmessage = (e) => {
if (e.data.cmd === 'calculate') {
const result = e.data.data.reduce((sum, num) => sum + num, 0)
self.postMessage(result)
}
}

// 2. SharedWorker - 共享线程
// page1.js & page2.js
const sharedWorker = new SharedWorker('./shared-worker.js')
const port = sharedWorker.port

port.postMessage({ type: 'subscribe', data: 'user123' })
port.onmessage = (e) => {
console.log('收到共享数据:', e.data)
}

// shared-worker.js
const connections = []
self.onconnect = (e) => {
const port = e.ports[0]
connections.push(port)

port.onmessage = (event) => {
// 广播给所有连接的页面
connections.forEach(p => p.postMessage(event.data))
}
}

// 3. ServiceWorker - 服务代理
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', { scope: '/' })
.then(reg => console.log('SW注册成功'))
.catch(err => console.log('SW注册失败', err))
}

// sw.js
const CACHE_NAME = 'app-v1'
const urlsToCache = ['/app.js', '/style.css', '/offline.html']

self.addEventListener('install', (e) => {
e.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
)
})

self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request)
.then(response => response || fetch(e.request))
)
})

核心对比:

特性WorkerSharedWorkerServiceWorker
作用域单页面多页面共享全局代理
通信方式postMessageport.postMessage事件监听
生命周期页面控制浏览器控制独立运行
主要用途CPU密集计算跨页面数据共享离线缓存、推送
网络拦截
调试工具DevTools→Sourceschrome://inspectchrome://inspect

面试官视角:

要点清单:

  • 理解三种Worker的不同作用域和用途
  • 掌握基本的创建和通信方式
  • 了解ServiceWorker的网络拦截能力

加分项:

  • 提及Worker的线程安全和性能优势
  • 了解SharedWorker的跨页面状态管理
  • 知道ServiceWorker在PWA中的核心作用

常见失误:

  • 混淆不同Worker的适用场景
  • 不了解ServiceWorker需要HTTPS环境
  • 忽略Worker无法访问DOM的限制

延伸阅读:

Service Worker 是如何缓存 http 请求资源的?

答案

核心概念:

Service Worker 通过拦截网络请求实现资源缓存,作为浏览器与网络之间的代理层。它在独立线程运行,可以控制页面的网络请求,实现缓存策略、离线访问和后台同步。核心机制包括注册、安装、激活和网络拦截四个阶段,通过 Cache API 管理缓存资源。

实际示例:

// 1. 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW 注册成功:', registration.scope)
})
.catch(error => {
console.log('SW 注册失败:', error)
})
})
}

// 2. Service Worker 文件 (sw.js)
const CACHE_NAME = 'app-cache-v1'
const urlsToCache = [
'/',
'/index.html',
'/styles.css',
'/script.js',
'/offline.html'
]

// 安装阶段:预缓存关键资源
self.addEventListener('install', (event) => {
console.log('SW 安装中')
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('缓存已打开')
return cache.addAll(urlsToCache)
})
.then(() => self.skipWaiting()) // 立即激活新版本
)
})

// 激活阶段:清理旧缓存
self.addEventListener('activate', (event) => {
console.log('SW 激活中')
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => cacheName !== CACHE_NAME)
.map(cacheName => caches.delete(cacheName))
)
}).then(() => self.clients.claim()) // 立即控制所有页面
)
})

// 拦截网络请求:实现缓存策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存优先策略
if (response) {
console.log('从缓存返回:', event.request.url)
return response
}

// 网络请求并缓存新资源
return fetch(event.request).then(response => {
// 只缓存有效响应
if (!response || response.status !== 200 || response.type !== 'basic') {
return response
}

const responseToCache = response.clone()
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache)
})

return response
})
})
.catch(() => {
// 离线回退页面
if (event.request.destination === 'document') {
return caches.match('/offline.html')
}
})
)
})

// 不同缓存策略示例
function cacheFirst (request) {
return caches.match(request)
.then(response => response || fetch(request))
}

function networkFirst (request) {
return fetch(request)
.catch(() => caches.match(request))
}

function staleWhileRevalidate (request) {
const cached = caches.match(request)
const fetched = fetch(request).then(response => {
const responseClone = response.clone()
caches.open(CACHE_NAME).then(cache => {
cache.put(request, responseClone)
})
return response
})

return cached.then(response => response || fetched)
}

面试官视角:

要点清单:

  • 理解Service Worker的生命周期和缓存机制
  • 掌握Cache API的基本使用方法
  • 了解不同缓存策略的适用场景

加分项:

  • 提及缓存版本管理和更新策略
  • 了解PWA中的离线体验实现
  • 知道如何处理缓存失效和错误情况

常见失误:

  • 不理解Service Worker的异步特性
  • 忽略缓存更新和版本控制
  • 缓存策略选择不当影响用户体验

延伸阅读:

HTML5的离线储存怎么使用,工作原理能不能解释一下?

答案

核心概念:

HTML5 离线存储主要通过 Application Cache (已废弃) 和现代的 Service Worker + Cache API 实现。现代方案使用 Service Worker 拦截网络请求,配合 Cache API 存储资源,实现离线访问。核心原理是在用户在线时预缓存关键资源,离线时从缓存中提供内容,确保应用的基本功能可用。

实际示例:

// 现代离线存储方案:Service Worker + Cache API

// 1. 注册 Service Worker (main.js)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('离线存储注册成功')

// 检查更新
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 新版本可用,提示用户更新
if (confirm('发现新版本,是否立即更新?')) {
window.location.reload()
}
}
})
})
})
})
}

// 2. Service Worker 离线缓存逻辑 (sw.js)
const CACHE_NAME = 'offline-app-v1'
const OFFLINE_URL = '/offline.html'

// 预缓存关键资源
const PRECACHE_URLS = [
'/',
'/index.html',
'/app.css',
'/app.js',
'/manifest.json',
OFFLINE_URL,
'/images/logo.png'
]

// 安装时预缓存
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('预缓存离线资源')
return cache.addAll(PRECACHE_URLS)
})
.then(() => self.skipWaiting())
)
})

// 激活时清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => cacheName !== CACHE_NAME)
.map(cacheName => caches.delete(cacheName))
)
}).then(() => self.clients.claim())
)
})

// 拦截请求实现离线功能
self.addEventListener('fetch', event => {
// 只处理同源请求
if (event.request.url.startsWith(self.location.origin)) {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response // 从缓存返回
}

return fetch(event.request)
.then(response => {
// 缓存新的成功响应
if (response.status === 200) {
const responseClone = response.clone()
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, responseClone))
}
return response
})
.catch(() => {
// 网络失败时的降级策略
if (event.request.destination === 'document') {
return caches.match(OFFLINE_URL)
}

// 对于图片等资源,返回占位符
if (event.request.destination === 'image') {
return new Response('离线模式', {
headers: { 'Content-Type': 'text/plain' }
})
}
})
})
)
}
})

// 3. 离线状态检测和用户提示
class OfflineManager {
constructor () {
this.isOnline = navigator.onLine
this.init()
}

init () {
window.addEventListener('online', () => {
this.isOnline = true
this.showStatus('已连接网络')
this.syncOfflineData()
})

window.addEventListener('offline', () => {
this.isOnline = false
this.showStatus('离线模式')
})
}

showStatus (message) {
const statusDiv = document.getElementById('network-status')
if (statusDiv) {
statusDiv.textContent = message
statusDiv.className = this.isOnline ? 'online' : 'offline'
}
}

async syncOfflineData () {
// 同步离线期间产生的数据
const offlineData = JSON.parse(localStorage.getItem('offlineData') || '[]')

for (const data of offlineData) {
try {
await fetch('/api/sync', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
})
} catch (error) {
console.error('同步失败:', error)
}
}

localStorage.removeItem('offlineData')
}
}

// 初始化离线管理器
new OfflineManager()

面试官视角:

要点清单:

  • 理解现代离线存储与Application Cache的区别
  • 掌握Service Worker的离线缓存机制
  • 了解离线状态检测和数据同步策略

加分项:

  • 提及PWA的离线体验设计
  • 了解不同缓存策略的权衡
  • 知道如何处理离线数据同步问题

常见失误:

  • 仍然使用已废弃的Application Cache
  • 不考虑离线状态下的用户体验
  • 忽略缓存更新和版本管理

延伸阅读:

有用过剪切板么,简要讲解下

答案
方法/接口主要特性典型用法/代码示例兼容性/限制
document.execCommand('copy')同步,需选中内容document.execCommand('copy')需用户交互,部分浏览器已限制
navigator.clipboard API异步,安全性高navigator.clipboard.writeText('内容')需HTTPS,部分浏览器需授权

补充说明

  • execCommand('copy') 适合兼容性要求高的场景,但逐步被淘汰,需选中内容或操作可编辑元素。
  • navigator.clipboard 推荐用于现代浏览器,支持异步复制,安全性更高,适合用户主动操作(如按钮点击)。
  • 剪切板操作通常需用户交互触发,防止恶意脚本窃取或篡改剪切板内容。
// 现代推荐写法
async function copyToClipboard (text) {
try {
await navigator.clipboard.writeText(text)
console.log('复制成功')
} catch (e) {
console.error('复制失败', e)
}
}
提示

优先使用 navigator.clipboard,如需兼容老浏览器可降级为 execCommand('copy')

延伸阅读

如何通过设置失效时间清除本地存储的数据?

答案

核心概念:

localStorage原生不支持过期时间,但可以通过封装实现。核心思路是在存储数据时同时记录过期时间戳,读取时检查是否过期,过期则自动删除。实现方式包括:数据结构设计(包含value和expiration)、定时清理机制、懒清理策略等。

实际示例:

以下是一个封装了支持过期时间的localStorage的示例代码:

class EnhancedLocalStorage {
constructor () {
this.prefix = 'enhanced_storage_'
}

setItem (key, value, expirationInSeconds) {
const item = {
value,
expirationTime: expirationInSeconds ? Date.now() + expirationInSeconds1000 : null
}
localStorage.setItem(this.prefix + key, JSON.stringify(item))
}

getItem (key) {
const itemStr = localStorage.getItem(this.prefix + key)
if (!itemStr) return null
const item = JSON.parse(itemStr)
if (item.expirationTime && item.expirationTime < Date.now()) {
localStorage.removeItem(this.prefix + key)
return null
}
return item.value
}

removeItem (key) {
localStorage.removeItem(this.prefix + key)
}
}

const enhancedStorage = new EnhancedLocalStorage()
export default enhancedStorage

使用方法如下:

// 设置带有过期时间的存储项
enhancedStorage.setItem('myKey', 'myValue', 60) // 60 秒后过期

// 获取存储项
const value = enhancedStorage.getItem('myKey')
console.log(value)

// 一段时间后,存储项过期
setTimeout(() => {
const expiredValue = enhancedStorage.getItem('myKey')
console.log(expiredValue) // null
}, 65000)

在这个封装中,使用了一个自定义的前缀来避免与普通的localStorage键冲突。设置项时,会记录一个过期时间,如果有过期时间且当前时间超过了过期时间,在获取项时会返回null并自动删除该项。

要清除本地存储的数据,可以通过设置失效时间来实现。以下是一种常见的方法:

  1. 将数据存储到本地存储中,例如使用localStorage或sessionStorage。

  2. 在存储数据时,同时设置一个失效时间。可以将失效时间存储为一个时间戳或特定的日期时间。

  3. 在读取数据时,检查当前时间是否超过了失效时间。如果超过了失效时间,则认为数据已过期,需要清除。

  4. 如果数据已过期,则使用localStorage.removeItem(key)或sessionStorage.removeItem(key)方法删除该数据。

以下是一个示例代码:

// 存储数据
function setLocalStorageData (key, data, expiration) {
const item = {
data,
expiration
}
localStorage.setItem(key, JSON.stringify(item))
}

// 读取数据
function getLocalStorageData (key) {
let item = localStorage.getItem(key)
if (item) {
item = JSON.parse(item)
if (item.expiration && new Date().getTime() > item.expiration) {
// 数据已过期,清除数据
localStorage.removeItem(key)
return null
}
return item.data
}
return null
}

// 示例用法
const data = { name: 'John', age: 30 }
const expiration = new Date().getTime() + 36001000 // 设置失效时间为当前时间后的1小时
setLocalStorageData('user', data, expiration)

const storedData = getLocalStorageData('user')
console.log(storedData)

在示例代码中,setLocalStorageData函数用于存储数据,并接受一个失效时间参数。getLocalStorageData函数用于读取数据,并检查失效时间是否已过期。如果数据已过期,则清除数据。示例中的失效时间设置为当前时间后的1小时。

面试官视角:

要点清单:

  • 理解localStorage原生不支持过期,需要手动实现
  • 掌握数据结构设计和过期检查逻辑
  • 了解定时清理和懒清理的区别

加分项:

  • 实现批量过期清理和存储空间管理
  • 提供完整的错误处理和兼容性方案
  • 结合业务场景优化存储策略

常见失误:

  • 忘记在读取时检查过期状态
  • 没有处理JSON序列化异常
  • 缺乏定期清理机制导致存储空间浪费

延伸阅读:

在页面关闭时执行方法,该如何做

答案

核心概念:

页面关闭时执行方法主要通过监听浏览器的生命周期事件实现。主要事件包括:beforeunload(页面卸载前)、unload(页面卸载时)、pagehide(页面隐藏时)。现代推荐使用navigator.sendBeacon()发送数据,因为它能保证在页面卸载过程中可靠地发送请求。需要注意不同事件的执行时机和浏览器兼容性差异。

实际示例:

在页面关闭时执行特定的方法,你可以使用 window 对象的 beforeunloadunload 事件。不过,这两个事件有一些微妙的区别和适用场景。

使用 beforeunload 事件

beforeunload 事件在窗口、文档或其资源即将卸载时触发,这一点让它成为在页面关闭前提示用户保存未保存更改的理想选择。在绑定到该事件的处理函数中,你可以执行特定的逻辑,但请注意,按照现代浏览器的安全策略,除非你设置了 event.returnValue,否则不会显示自定义的离开提示信息。

window.addEventListener('beforeunload', (event) => {
// 在这里执行你的清理逻辑或者其他操作
// 例如,发送一个统计日志
navigator.sendBeacon('/log', '用户即将离开页面')

// 显示离开提示(大多数现代浏览器不支持自定义文本)
event.returnValue = '您确定要离开此页面吗?'
})

使用 unload 事件

unload 事件在用户即将从页面导航走,或关闭页面时触发。你可以在这个事件的处理函数中执行不能阻止页面卸载的清理逻辑。不过需要注意,这个事件的执行时间非常短,某些操作(例如异步操作)可能无法完成。

window.addEventListener('unload', (event) => {
// 执行简短的同步操作,例如发送统计信息
// 注意:这种情况下 navigator.sendBeacon 是更好的选择
})

使用 navigator.sendBeacon

对于在页面卸载时需要发送数据到服务器的情况,使用 navigator.sendBeacon 方法是一种更可靠的方式。它有效地解决了通过异步 AJAX 请求可能导致的数据不被送出的问题。

window.addEventListener('unload', (event) => {
navigator.sendBeacon('/log-out', '用户离开')
})

注意事项

  • 不是所有浏览器都完全一样地支持这些事件和 navigator.sendBeacon 方法。实施时应当考虑兼容性。
  • beforeunloadunload 事件中执行大量的同步操作或长时间运行的脚本可能会导致用户体验下降。推荐尽量使用简洁快速的逻辑。
  • beforeunload 事件可以控制是否提示用户离开页面的确认对话框,但自定义的确认对话框信息可能不被所有浏览器支持。
  • 使用 navigator.sendBeacon 来发送数据是因为它能在请求中携带足够的数据量,且即使页面卸载过程中也能确保数据被发送。

根据你的应用需求,选择合适的事件和方法,确保页面关闭时能够执行你的逻辑。

面试官视角:

要点清单:

  • 理解beforeunload和unload事件的区别和适用场景
  • 掌握navigator.sendBeacon()的使用和优势
  • 了解页面生命周期事件的执行顺序

加分项:

  • 提及Page Visibility API和pagehide事件
  • 了解不同浏览器的兼容性差异
  • 实现健壮的数据发送和错误处理机制

常见失误:

  • 在unload事件中执行异步操作
  • 过度依赖beforeunload的自定义提示文本
  • 忽略移动端浏览器的生命周期差异

延伸阅读: