取代JavaScript库的10个现代Web API及详细实施代码
为什么浏览器内置的API你还在用某个臃肿的Javascript库呢?用内置的API有什么好处呢?
Web平台经历了巨大演进,引入了强大的原生API,不再需要臃肿的JavaScript库。现代浏览器现已支持以往需要第三方依赖的复杂功能,从而带来更快的加载速度、更好的性能表现和更小的代码体积。
在分析了数百个生产环境应用后,我总结了10个最具影响力的原生Web API,它们能替代流行JavaScript库,同时提供更优的性能和用户体验。
1.Web Components API:替代React/Vue实现简单组件
无需框架依赖即可创建可复用组件

class CustomButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.render(); this.addEventListener('click', this.handleClick); } disconnectedCallback() { this.removeEventListener('click', this.handleClick); } static get observedAttributes() { return ['variant', 'disabled']; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue) { this.render(); } } render() { const variant = this.getAttribute('variant') || 'primary'; const disabled = this.hasAttribute('disabled'); this.shadowRoot[xss_clean] = ` <style> button { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 14px; transition: all 0.2s ease; } .primary { background: #007bff; color: white; } .secondary { background: #6c757d; color: white; } button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); } button:disabled { opacity: 0.6; cursor: not-allowed; } </style> <button class="${variant}" ${disabled ? 'disabled' : ''}> <slot></slot> </button> `; } handleClick = (event) => { if (!this.hasAttribute('disabled')) { this.dispatchEvent(new CustomEvent('custom-click', { detail: { originalEvent: event }, bubbles: true })); } }}customElements.define('custom-button', CustomButton);// 用法// <custom-button variant="primary">点击我</custom-button>性能优势:相比React组件,简单UI元素代码体积减少80%
2.Intersection Observer API:替代滚动事件库
高效检测元素可见性,无需滚动事件监听器
class LazyImageLoader { constructor(options = {}) { this.options = { rootMargin: '50px 0px', threshold: 0.1, ...options }; this.observer = new IntersectionObserver( this.handleIntersection.bind(this), this.options ); this.loadingImages = new Set(); } observe(img) { if (img.dataset.src && !this.loadingImages.has(img)) { this.observer.observe(img); } } handleIntersection(entries) { entries.forEach(entry => { if (entry.isIntersecting) { this.loadImage(entry.target); this.observer.unobserve(entry.target); } }); } async loadImage(img) { this.loadingImages.add(img); try { // 预加载图片 const imageLoader = new Image(); imageLoader.src = img.dataset.src; await new Promise((resolve, reject) => { imageLoader.onload = resolve; imageLoader.onerror = reject; }); // 为加载图片实施丝滑过度 img.style.opacity = '0'; img.src = img.dataset.src; img.onload = () => { img.style.transition = 'opacity 0.3s ease'; img.style.opacity = '1'; img.classList.add('loaded'); }; } catch (error) { console.error('Failed to load image:', error); img.classList.add('error'); } finally { this.loadingImages.delete(img); } } disconnect() { this.observer.disconnect(); this.loadingImages.clear(); }}// 无限滚动实施class InfiniteScroll { constructor(container, loadMore, options = {}) { this.container = container; this.loadMore = loadMore; this.loading = false; this.sentinel = document.createElement('div'); this.sentinel.style.height = '1px'; container.appendChild(this.sentinel); this.observer = new IntersectionObserver( this.handleIntersection.bind(this), { rootMargin: '100px', ...options } ); this.observer.observe(this.sentinel); } async handleIntersection(entries) { const entry = entries[0]; if (entry.isIntersecting && !this.loading) { this.loading = true; try { const hasMore = await this.loadMore(); if (!hasMore) { this.observer.disconnect(); } } catch (error) { console.error('Failed to load more content:', error); } finally { this.loading = false; } } }}3.Resize Observer API:替代窗口缩放库
高效监控元素尺寸变化
class ResponsiveComponent { constructor(element) { this.element = element; // 需要响应式处理的DOM元素 // 定义响应式断点(单位:像素) this.breakpoints = { small: 320, // 小屏幕(手机竖屏) medium: 768, // 中等屏幕(平板) large: 1024, // 大屏幕(笔记本) xlarge: 1440 // 超大屏幕(桌面显示器) }; // 创建ResizeObserver实例监听元素尺寸变化 this.resizeObserver = new ResizeObserver( this.handleResize.bind(this) // 绑定this上下文 ); // 开始观察目标元素 this.resizeObserver.observe(element); } // 处理元素尺寸变化的回调函数 handleResize(entries) { entries.forEach(entry => { // 从内容矩形中解构获取当前宽度和高度 const { width, height } = entry.contentRect; // 根据当前宽度获取对应的断点类型 const currentBreakpoint = this.getBreakpoint(width); // 更新CSS自定义属性(可在CSS中通过var()引用) this.element.style.setProperty('--element-width', `${width}px`); this.element.style.setProperty('--element-height', `${height}px`); // 更新data-size属性(用于CSS选择器定位) this.element.dataset.size = currentBreakpoint; // 触发自定义事件(允许外部监听响应式变化) this.element.dispatchEvent(new CustomEvent('element-resize', { detail: { width, // 当前元素宽度 height, // 当前元素高度 breakpoint: currentBreakpoint // 当前断点类型 } })); // 调用断点变化处理函数 this.handleBreakpointChange(currentBreakpoint, width, height); }); } // 根据宽度确定当前断点类型 getBreakpoint(width) { if (width < this.breakpoints.small) return 'xsmall'; // 超小屏幕 if (width < this.breakpoints.medium) return 'small'; // 小屏幕 if (width < this.breakpoints.large) return 'medium'; // 中等屏幕 if (width < this.breakpoints.xlarge) return 'large'; // 大屏幕 return 'xlarge'; // 超大屏幕 } // 处理不同断点的响应式行为 handleBreakpointChange(breakpoint, width, height) { // 首先移除所有可能存在的布局类 this.element.classList.remove( 'mobile-layout', 'tablet-layout', 'desktop-layout' ); // 根据断点类型添加对应的布局类 switch (breakpoint) { case 'small': this.element.classList.add('mobile-layout'); // 移动端布局 break; case 'medium': this.element.classList.add('tablet-layout'); // 平板布局 break; default: this.element.classList.add('desktop-layout'); // 桌面端布局 } } // 停止监听并清理资源 disconnect() { this.resizeObserver.disconnect(); // 停止所有观察 }}4.支持流式传输的Fetch API:替代Axios等HTTP库
用原生能力处理HTTP请求
class AdvancedHTTP { constructor(baseURL = '', options = {}) { this.baseURL = baseURL; this.defaultOptions = { headers: { 'Content-Type': 'application/json' }, ...options }; // 初始化拦截器对象,包含请求拦截器和响应拦截器数组 this.interceptors = { request: [], response: [] }; } addRequestInterceptor(interceptor) { // 添加请求拦截器到拦截器数组 this.interceptors.request.push(interceptor); } addResponseInterceptor(interceptor) { // 添加响应拦截器到拦截器数组 this.interceptors.response.push(interceptor); } async request(url, options = {}) { // 合并默认配置和传入的配置选项 let config = { ...this.defaultOptions, ...options, headers: { ...this.defaultOptions.headers, ...options.headers } }; // 应用所有请求拦截器,按顺序执行 for (const interceptor of this.interceptors.request) { config = await interceptor(config); } try { // 拼接完整的请求URL const fullURL = this.baseURL + url; // 发送fetch请求 let response = await fetch(fullURL, config); // 应用所有响应拦截器,按顺序执行 for (const interceptor of this.interceptors.response) { response = await interceptor(response); } return response; } catch (error) { // 请求失败时抛出错误 throw new Error(`HTTP请求失败: ${error.message}`); } } // 流式响应处理 async streamingRequest(url, onData, options = {}) { // 发送请求获取响应 const response = await this.request(url, { ...options, headers: { ...options.headers, 'Accept': 'text/plain' } }); // 检查响应是否支持流式读取 if (!response.body) { throw new Error('不支持流式传输'); } // 获取响应体的读取器 const reader = response.body.getReader(); // 创建文本解码器 const decoder = new TextDecoder(); try { // 循环读取流数据 while (true) { // 读取一块数据 const { done, value } = await reader.read(); if (done) break; // 如果读取完成则退出循环 // 解码数据块 const chunk = decoder.decode(value, { stream: true }); // 调用回调函数处理数据块 await onData(chunk); } } finally { // 释放读取器锁 reader.releaseLock(); } } // 带进度跟踪的文件上传 async uploadWithProgress(url, file, onProgress) { return new Promise((resolve, reject) => { // 创建XMLHttpRequest对象 const xhr = new XMLHttpRequest(); // 监听上传进度事件 xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { // 计算上传进度百分比 const percentComplete = (event.loaded / event.total) * 100; // 调用进度回调函数 onProgress(percentComplete); } }); // 监听加载完成事件 xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { // 请求成功,解析响应数据 resolve(JSON.parse(xhr.responseText)); } else { // 请求失败,抛出错误 reject(new Error(`上传失败: ${xhr.statusText}`)); } }); // 监听错误事件 xhr.addEventListener('error', () => { // 发生错误,抛出错误 reject(new Error('上传失败')); }); // 创建FormData对象并添加文件 const formData = new FormData(); formData.append('file', file); // 打开连接并发送请求 xhr.open('POST', this.baseURL + url); xhr.send(formData); }); } // 便捷的GET请求方法 get(url, options) { return this.request(url, { ...options, method: 'GET' }); } // 便捷的POST请求方法 post(url, data, options) { return this.request(url, { ...options, method: 'POST', body: JSON.stringify(data) }); } // 便捷的PUT请求方法 put(url, data, options) { return this.request(url, { ...options, method: 'PUT', body: JSON.stringify(data) }); } // 便捷的DELETE请求方法 delete(url, options) { return this.request(url, { ...options, method: 'DELETE' }); }}5.Web Animations API:替代动画库
无需外部依赖创建流畅动画
class AnimationManager { constructor() { // 使用Map存储单个动画实例,键为自动生成的ID,值为Animation对象 this.animations = new Map(); // 使用Map存储动画序列,键为自动生成的ID,值为Animation对象数组 this.sequences = new Map(); } // 基础动画方法,支持缓动效果 animate(element, keyframes, options = {}) { // 创建动画实例,设置默认参数:300ms时长、ease-out缓动、forwards填充模式 const animation = element.animate(keyframes, { duration: 300, easing: 'ease-out', fill: 'forwards', ...options // 合并传入的自定义选项 }); // 生成唯一ID:元素ID(若无则用'element') + 时间戳 const id = `${element.id || 'element'}_${Date.now()}`; // 将动画实例存入Map this.animations.set(id, animation); // 动画结束时自动清理Map中的记录 animation.addEventListener('finish', () => { this.animations.delete(id); }); return animation; // 返回动画实例以便链式调用 } // 淡入动画效果 fadeIn(element, duration = 300) { // 定义关键帧:从透明且下移20px到完全不透明且回到原位 return this.animate(element, [ { opacity: 0, transform: 'translateY(20px)' }, { opacity: 1, transform: 'translateY(0)' } ], { duration }); // 应用传入的时长参数 } // 滑动动画效果 slideIn(element, direction = 'left', duration = 300) { // 定义不同方向的滑动起始和结束位置 const transforms = { left: ['translateX(-100%)', 'translateX(0)'], // 从左侧滑入 right: ['translateX(100%)', 'translateX(0)'], // 从右侧滑入 up: ['translateY(-100%)', 'translateY(0)'], // 从上方滑入 down: ['translateY(100%)', 'translateY(0)'] // 从下方滑入 }; // 使用对应方向的transform关键帧创建动画 return this.animate(element, [ { transform: transforms[direction][0] }, { transform: transforms[direction][1] } ], { duration }); } // 弹性动画效果 spring(element, keyframes, options = {}) { // 定义弹性缓动曲线 const springEasing = 'cubic-bezier(0.68, -0.55, 0.265, 1.55)'; // 创建带有弹性效果的动画 return this.animate(element, keyframes, { duration: 600, // 默认600ms时长 easing: springEasing,// 应用弹性缓动 ...options // 合并其他自定义选项 }); } // 动画序列执行 async sequence(animations) { // 生成唯一序列ID const id = `sequence_${Date.now()}`; const sequence = []; // 存储序列中的动画实例 // 遍历动画配置数组 for (const animationConfig of animations) { const { element, keyframes, options, delay = 0 } = animationConfig; // 如果有延迟设置,等待指定时间 if (delay > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } // 创建并执行动画 const animation = this.animate(element, keyframes, options); sequence.push(animation); // 将动画加入序列 // 如果配置要求等待动画完成(默认行为),则等待animation.finished Promise if (options?.wait !== false) { await animation.finished; } } // 将完成的序列存入Map this.sequences.set(id, sequence); return sequence; // 返回序列数组以便外部控制 } // 交错动画效果 stagger(elements, keyframes, options = {}) { // 从选项中提取交错延迟时间(默认100ms),其余选项保留 const { staggerDelay = 100, ...animOptions } = options; // 为每个元素创建带动画的Promise,按索引计算延迟时间 return elements.map((element, index) => { return this.animate(element, keyframes, { ...animOptions, // 合并基础动画选项 delay: index * staggerDelay // 每个元素递增延迟 }); }); } // 视差滚动效果 parallax(elements, scrollContainer = window) { // 定义更新视差效果的函数 const updateParallax = () => { // 获取当前滚动位置(兼容window和自定义容器) const scrollTop = scrollContainer === window ? window.pageYOffset : scrollContainer.scrollTop; // 遍历所有需要视差效果的元素 elements.forEach(({ element, speed = 0.5 }) => { // 获取元素位置信息 const rect = element.getBoundingClientRect(); // 计算偏移量 = 滚动距离 * 速度系数 const offset = scrollTop * speed; // 应用transform实现位移效果 element.style.transform = `translateY(${offset}px)`; }); }; // 监听滚动事件(使用passive模式提升性能) scrollContainer.addEventListener('scroll', updateParallax, { passive: true }); // 初始化时立即执行一次 updateParallax(); // 返回清理函数,用于移除事件监听 return () => { scrollContainer.removeEventListener('scroll', updateParallax); }; } // 停止所有动画 stopAll() { // 取消所有单个动画 this.animations.forEach(animation => animation.cancel()); // 取消所有序列中的动画 this.sequences.forEach(sequence => sequence.forEach(animation => animation.cancel()) ); // 清空两个Map this.animations.clear(); this.sequences.clear(); }}6.Broadcast Channel API:替代消息通信库
无需第三方依赖实现跨标签页通信
class CrossTabCommunication { constructor(channelName = 'app-channel') { // 创建BroadcastChannel实例,用于跨标签页通信 this.channel = new BroadcastChannel(channelName); // 使用Map存储消息类型与处理函数的映射关系 this.handlers = new Map(); // 监听channel的消息事件 this.channel.addEventListener('message', this.handleMessage.bind(this)); // 处理页面可见性变化,用于标签页状态同步 document.addEventListener('visibilitychange', () => { if (document.hidden) { // 当前标签页隐藏时广播通知 this.broadcast('tab-hidden', { tabId: this.tabId }); } else { // 当前标签页变为可见时广播通知 this.broadcast('tab-visible', { tabId: this.tabId }); } }); // 生成唯一的标签页ID,结合时间戳和随机字符串确保唯一性 this.tabId = `tab_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 广播当前标签页的连接通知 this.broadcast('tab-connected', { tabId: this.tabId }); } // 向所有其他标签页发送消息 broadcast(type, data = {}) { const message = { type, // 消息类型 data, // 消息数据 timestamp: Date.now(), // 时间戳 sender: this.tabId // 发送者标签页ID }; this.channel.postMessage(message); // 通过BroadcastChannel发送消息 } // 监听特定类型的消息 on(type, handler) { // 如果该消息类型尚未注册处理函数,初始化一个Set集合 if (!this.handlers.has(type)) { this.handlers.set(type, new Set()); } // 将处理函数添加到对应消息类型的集合中 this.handlers.get(type).add(handler); // 返回取消订阅的函数,便于后续移除监听 return () => { const handlers = this.handlers.get(type); if (handlers) { handlers.delete(handler); // 移除指定处理函数 if (handlers.size === 0) { this.handlers.delete(type); // 如果没有处理函数了,删除该消息类型的注册 } } }; } // 处理接收到的消息 handleMessage(event) { // 从事件中解构出消息类型、数据和发送者ID const { type, data, sender } = event.data; // 忽略自己发送的消息,避免循环处理 if (sender === this.tabId) return; // 获取该消息类型的所有处理函数 const handlers = this.handlers.get(type); if (handlers) { // 依次执行所有处理函数 handlers.forEach(handler => { try { handler(data, sender); // 调用处理函数并传入数据和发送者ID } catch (error) { console.error('消息处理函数出错:', error); // 捕获并打印处理函数中的错误 } }); } } // 跨标签页同步状态 syncState(key, value) { // 广播状态同步消息 this.broadcast('state-sync', { key, value }); } // 请求其他标签页的状态 requestState(key) { return new Promise((resolve) => { // 设置超时处理,1秒后自动resolve为null const timeout = setTimeout(() => { unsubscribe(); // 取消订阅 resolve(null); // 解析为null表示超时 }, 1000); // 定义取消订阅函数 const unsubscribe = this.on('state-response', ({ key: responseKey, value }) => { if (responseKey === key) { clearTimeout(timeout); // 收到响应后清除超时 unsubscribe(); // 取消订阅 resolve(value); // 解析响应值 } }); // 广播状态请求消息 this.broadcast('state-request', { key }); }); } // 协调单标签页操作(获取锁) async acquireLock(lockName, timeout = 5000) { return new Promise((resolve, reject) => { let acquired = false; // 标记是否成功获取锁 // 设置获取锁的超时处理 const timeoutId = setTimeout(() => { if (!acquired) { cleanup(); // 清理监听 reject(new Error('获取锁超时')); // 超时后拒绝Promise } }, timeout); // 定义清理函数 const cleanup = this.on('lock-response', ({ lock, holder }) => { if (lock === lockName) { if (holder === null) { // 如果锁未被持有,则成功获取 acquired = true; clearTimeout(timeoutId); // 清除超时 cleanup(); // 清理监听 // 声明自己持有锁 this.broadcast('lock-claimed', { lock: lockName, holder: this.tabId }); // 返回释放锁的函数 resolve(() => this.releaseLock(lockName)); } } }); // 广播锁请求消息 this.broadcast('lock-request', { lock: lockName }); }); } // 释放锁 releaseLock(lockName) { this.broadcast('lock-released', { lock: lockName, holder: this.tabId }); } // 清理资源 close() { // 广播标签页断开连接通知 this.broadcast('tab-disconnected', { tabId: this.tabId }); // 关闭BroadcastChannel this.channel.close(); // 清空所有消息处理函数 this.handlers.clear(); }}7.文件系统访问API:替代文件上传库
用浏览器原生能力处理文件操作
class FileManager { constructor() { // 使用Map存储文件句柄,键为文件名,值为FileSystemFileHandle对象 this.fileHandles = new Map(); // 使用Map存储目录句柄,键为目录名,值为FileSystemDirectoryHandle对象 this.directoryHandles = new Map(); } // 检查浏览器是否支持文件系统访问API static isSupported() { return 'showOpenFilePicker' in window && 'showSaveFilePicker' in window && 'showDirectoryPicker' in window; } // 打开单个文件选择器 async openFile(options = {}) { try { // 调用文件选择器API,默认配置为单选模式 const [fileHandle] = await window.showOpenFilePicker({ multiple: false, // 禁止多选 excludeAcceptAllOption: false, // 显示"所有文件"选项 types: [{ description: 'All files', accept: { '*/*': [] } // 接受所有文件类型 }], ...options // 合并自定义选项 }); // 获取文件对象 const file = await fileHandle.getFile(); // 存储文件句柄 this.fileHandles.set(file.name, fileHandle); // 返回文件对象和句柄 return { file, handle: fileHandle }; } catch (error) { // 忽略用户取消操作产生的错误 if (error.name !== 'AbortError') { throw error; } return null; } } // 打开多个文件选择器 async openFiles(options = {}) { try { // 启用多选模式 const fileHandles = await window.showOpenFilePicker({ multiple: true, ...options }); // 并行获取所有文件对象 const files = await Promise.all( fileHandles.map(async (handle) => { const file = await handle.getFile(); this.fileHandles.set(file.name, handle); return { file, handle }; }) ); return files; } catch (error) { if (error.name !== 'AbortError') { throw error; } return []; // 用户取消时返回空数组 } } // 保存文件 async saveFile(content, options = {}) { try { // 调用文件保存对话框 const fileHandle = await window.showSaveFilePicker({ suggestedName: 'untitled.txt', // 默认文件名 types: [{ description: 'Text files', accept: { 'text/plain': ['.txt'] } // 默认保存为文本文件 }], ...options }); // 获取可写流 const writable = await fileHandle.createWritable(); // 支持直接写入Blob或字符串内容 if (content instanceof Blob) { await writable.write(content); } else { await writable.write(new Blob([content], { type: 'text/plain' })); } // 关闭流完成写入 await writable.close(); return fileHandle; } catch (error) { if (error.name !== 'AbortError') { throw error; } return null; } } // 打开目录选择器 async openDirectory(options = {}) { try { const directoryHandle = await window.showDirectoryPicker(options); // 存储目录句柄 this.directoryHandles.set(directoryHandle.name, directoryHandle); return directoryHandle; } catch (error) { if (error.name !== 'AbortError') { throw error; } return null; } } // 读取目录内容 async readDirectory(directoryHandle) { const entries = []; // 使用异步迭代器遍历目录项 for await (const [name, handle] of directoryHandle.entries()) { entries.push({ name, // 条目名称 handle, // 文件/目录句柄 kind: handle.kind, // 类型标识 isDirectory: handle.kind === 'directory', // 是否为目录 isFile: handle.kind === 'file' // 是否为文件 }); } return entries; } // 在指定目录中创建文件 async createFileInDirectory(directoryHandle, fileName, content) { try { // 获取文件句柄(自动创建) const fileHandle = await directoryHandle.getFileHandle(fileName, { create: true }); // 写入内容 const writable = await fileHandle.createWritable(); await writable.write(content); await writable.close(); return fileHandle; } catch (error) { throw new Error(`创建文件失败: ${error.message}`); } } // 监听文件变化(实验性功能) async watchFile(fileHandle, callback) { let lastModified = null; // 记录最后修改时间 const check = async () => { try { const file = await fileHandle.getFile(); if (lastModified === null) { // 首次检查,记录初始修改时间 lastModified = file.lastModified; } else if (file.lastModified !== lastModified) { // 检测到修改时间变化 lastModified = file.lastModified; callback(file); // 触发回调 } } catch (error) { console.error('文件监听出错:', error); } }; // 每秒检查一次(可根据需要调整间隔) const interval = setInterval(check, 1000); // 返回清理函数用于停止监听 return () => clearInterval(interval); } // 设置拖放区域 setupDropZone(element, options = {}) { const handleDrop = async (event) => { event.preventDefault(); // 阻止默认行为 // 获取拖放项并转换为文件系统句柄 const items = Array.from(event.dataTransfer.items); const entries = []; for (const item of items) { if (item.kind === 'file') { const entry = await item.getAsFileSystemHandle(); entries.push(entry); } } // 触发自定义回调 if (options.onDrop) { options.onDrop(entries); } }; const handleDragOver = (event) => { event.preventDefault(); // 必须阻止默认行为才能触发drop事件 if (options.onDragOver) { options.onDragOver(event); } }; // 绑定事件监听器 element.addEventListener('drop', handleDrop); element.addEventListener('dragover', handleDragOver); // 返回清理函数用于移除监听 return () => { element.removeEventListener('drop', handleDrop); element.removeEventListener('dragover', handleDragOver); }; }}8.支付请求API:替代支付处理库
原生处理支付流程
class PaymentManager { constructor() { // 初始化支持的支付方式(基础信用卡支付) this.supportedMethods = [ { supportedMethods: 'basic-card', // 支付方式标识 data: { supportedNetworks: ['visa', 'mastercard', 'amex'], // 支持的卡组织 supportedTypes: ['debit', 'credit'] // 支持借记卡/信用卡 } } ]; } // 检查浏览器是否支持支付请求API static isSupported() { return 'PaymentRequest' in window; } // 创建支付请求 async createPaymentRequest(details, options = {}) { if (!PaymentManager.isSupported()) { throw new Error('当前浏览器不支持支付请求API'); } // 默认支付详情(会被传入的details参数覆盖) const paymentDetails = { total: { label: '总计', // 总金额标签 amount: { currency: 'USD', value: '0.00' } // 默认美元货币 }, displayItems: [], // 显示项目列表 ...details // 合并自定义支付详情 }; // 默认支付选项(会被传入的options参数覆盖) const paymentOptions = { requestPayerName: true, // 要求提供付款人姓名 requestPayerEmail: true, // 要求提供付款人邮箱 requestPayerPhone: false, // 不要求提供电话 requestShipping: false, // 不要求配送地址 ...options // 合并自定义选项 }; // 创建并返回PaymentRequest实例 return new PaymentRequest( this.supportedMethods, // 支持的支付方式 paymentDetails, // 支付详情 paymentOptions // 支付选项 ); } // 处理支付流程 async processPayment(details, options = {}) { try { // 创建支付请求 const request = await this.createPaymentRequest(details, options); // 检查用户是否可以使用当前支付方式 const canMakePayment = await request.canMakePayment(); if (!canMakePayment) { throw new Error('用户无法使用可用支付方式进行付款'); } // 显示支付界面并获取用户响应 const response = await request.show(); // 验证支付信息(需实现自定义验证逻辑) const isValid = await this.validatePayment(response); if (isValid) { // 支付成功,完成交易 await response.complete('success'); return { success: true, response, // 支付响应对象 paymentMethod: response.methodName, // 使用的支付方式 details: response.details // 支付详情 }; } else { // 支付验证失败 await response.complete('fail'); return { success: false, error: '支付验证失败' }; } } catch (error) { // 捕获并返回错误信息 return { success: false, error: error.message }; } } // 更新配送选项(根据地址变化动态计算) updateShippingOptions(request, shippingOptions) { // 监听配送地址变化事件 request.addEventListener('shippingaddresschange', async (event) => { const shippingAddress = request.shippingAddress; // 根据地址计算配送选项 const options = await this.calculateShippingOptions(shippingAddress); // 更新支付请求的配送选项 event.updateWith({ shippingOptions: options }); }); } // 计算配送选项和费用 async calculateShippingOptions(address) { // 实现您的配送计算逻辑 // 基础配送选项 const baseShipping = { id: 'standard', // 选项ID label: '标准配送', // 显示标签 amount: { currency: 'USD', value: '5.00' } // 配送费用 }; // 加急配送选项 const expressShipping = { id: 'express', // 选项ID label: '加急配送', // 显示标签 amount: { currency: 'USD', value: '15.00' } // 配送费用 }; return [baseShipping, expressShipping]; } // 验证支付信息(通常需要服务器端验证) async validatePayment(response) { // 实现您的支付验证逻辑 // 这里示例调用服务器API进行验证 try { const validationResult = await fetch('/api/validate-payment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ methodName: response.methodName, // 支付方式 details: response.details, // 支付详情 payerName: response.payerName, // 付款人姓名 payerEmail: response.payerEmail // 付款人邮箱 }) }); // 返回验证结果(根据API响应状态) return validationResult.ok; } catch (error) { console.error('支付验证错误:', error); return false; } } // 处理订阅支付 async createSubscription(planDetails, options = {}) { // 构建订阅支付详情 const subscriptionDetails = { total: { label: `${planDetails.name} - 月付`, // 显示标签 amount: { currency: 'USD', value: planDetails.price } // 订阅金额 }, displayItems: [ { label: planDetails.name, // 订阅计划名称 amount: { currency: 'USD', value: planDetails.price } // 计划价格 } ] }; // 处理支付(强制要求提供邮箱) const result = await this.processPayment(subscriptionDetails, { ...options, requestPayerEmail: true }); // 支付成功后存储订阅信息 if (result.success) { await this.storeSubscription({ planId: planDetails.id, // 计划ID payerEmail: result.response.payerEmail, // 付款人邮箱 paymentMethod: result.paymentMethod // 支付方式 }); } return result; } // 存储订阅信息到服务器 async storeSubscription(subscriptionData) { // 实现您的订阅存储逻辑 return fetch('/api/subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscriptionData) // 订阅数据 }); }}9.屏幕捕捉API:替代屏幕录制库
原生实现屏幕内容捕获
class ScreenCaptureManager { constructor() { this.mediaRecorder = null; // 用于存储媒体录制器实例 this.recordedChunks = []; // 存储录制视频的数据块 this.stream = null; // 存储屏幕捕获的媒体流 } // 检查浏览器是否支持屏幕捕获API static isSupported() { return 'getDisplayMedia' in navigator.mediaDevices; } // 开始屏幕捕获 async startCapture(options = {}) { if (!ScreenCaptureManager.isSupported()) { throw new Error('当前浏览器不支持屏幕捕获API'); } try { // 配置屏幕捕获选项 const displayMediaOptions = { video: { cursor: 'always', // 始终显示鼠标光标 displaySurface: 'browser', // 默认捕获浏览器窗口 ...options.video // 合并自定义视频选项 }, audio: { echoCancellation: true, // 启用回声消除 noiseSuppression: true, // 启用噪音抑制 sampleRate: 44100, // 设置音频采样率 ...options.audio // 合并自定义音频选项 } }; // 获取屏幕共享媒体流 this.stream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions); // 监听视频轨道结束事件(用户停止共享时触发) this.stream.getVideoTracks()[0].addEventListener('ended', () => { this.stopCapture(); }); return this.stream; } catch (error) { throw new Error(`屏幕捕获启动失败: ${error.message}`); } } // 开始录制屏幕 startRecording(options = {}) { if (!this.stream) { throw new Error('没有活动的屏幕捕获流'); } // 配置录制选项 const recordingOptions = { mimeType: 'video/webm;codecs=vp9', // 默认使用VP9编码 videoBitsPerSecond: 2500000, // 设置视频比特率 ...options // 合并自定义选项 }; // 支持的视频格式列表 const supportedTypes = [ 'video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm', 'video/mp4' ]; // 检测浏览器实际支持的视频格式 const mimeType = supportedTypes.find(type => MediaRecorder.isTypeSupported(type) ) || 'video/webm'; // 默认回退到webm格式 // 创建媒体录制器实例 this.mediaRecorder = new MediaRecorder(this.stream, { ...recordingOptions, mimeType // 使用检测到的支持格式 }); // 初始化录制数据块数组 this.recordedChunks = []; // 数据可用事件处理 this.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { this.recordedChunks.push(event.data); // 存储视频数据块 } }; // 录制停止事件处理 this.mediaRecorder.onstop = () => { // 将所有数据块合并为一个Blob对象 const blob = new Blob(this.recordedChunks, { type: mimeType }); this.onRecordingComplete(blob); // 处理录制完成的视频 }; // 开始录制,每1000毫秒收集一次数据 this.mediaRecorder.start(1000); return this.mediaRecorder; } // 停止录制 stopRecording() { // 如果录制器存在且处于活动状态 if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') { this.mediaRecorder.stop(); // 停止录制 } } // 停止屏幕捕获 stopCapture() { // 停止所有媒体轨道 if (this.stream) { this.stream.getTracks().forEach(track => track.stop()); this.stream = null; } // 停止录制器 if (this.mediaRecorder) { this.stopRecording(); } } // 处理录制完成的视频 onRecordingComplete(blob) { // 创建视频下载链接 const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; // 隐藏下载链接 a.href = url; a.download = `screen-recording-${Date.now()}.webm`; // 设置带时间戳的文件名 document.body.appendChild(a); a.click(); // 触发下载 // 清理DOM和释放内存 setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); // 释放对象URL }, 100); } // 截取屏幕画面 async takeScreenshot() { // 如果没有活动流,则自动启动无音频的屏幕捕获 if (!this.stream) { await this.startCapture({ audio: false }); } // 创建视频元素用于捕获画面 const video = document.createElement('video'); video.srcObject = this.stream; video.play(); // 必须调用play()才能获取有效画面 return new Promise((resolve) => { // 等待视频元数据加载完成 video.addEventListener('loadedmetadata', () => { // 创建画布用于截图 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置画布尺寸与视频一致 canvas.width = video.videoWidth; canvas.height = video.videoHeight; // 将视频画面绘制到画布 ctx.drawImage(video, 0, 0); // 将画布转换为PNG格式的Blob对象 canvas.toBlob((blob) => { resolve(blob); // 返回截图数据 }, 'image/png'); }); }); } // 创建实时预览窗口 createPreview(container) { if (!this.stream) { throw new Error('没有活动的屏幕捕获流'); } // 创建并配置预览视频元素 const video = document.createElement('video'); video.srcObject = this.stream; video.autoplay = true; // 自动播放 video.muted = true; // 静音避免回声 video.style.width = '100%'; video.style.height = 'auto'; // 将预览添加到指定容器 container.appendChild(video); return video; } // 启用画中画模式 async enablePictureInPicture(video) { // 检查浏览器是否支持画中画 if ('pictureInPictureEnabled' in document) { try { await video.requestPictureInPicture(); // 请求进入画中画模式 } catch (error) { console.error('启用画中画失败:', error); } } }}10.地理定位API:替代位置服务库
原生获取设备位置信息
class LocationManager { constructor(options = {}) { // 默认配置选项(可被传入的options覆盖) this.options = { enableHighAccuracy: true, // 启用高精度定位 timeout: 10000, // 超时时间(10秒) maximumAge: 300000, // 位置缓存有效期(5分钟) ...options // 合并自定义配置 }; this.watchId = null; // 位置监听器ID this.lastKnownPosition = null; // 最后已知位置 this.listeners = new Set(); // 使用Set存储监听回调函数 } // 检查浏览器是否支持地理定位API static isSupported() { return 'geolocation' in navigator; } // 获取当前位置(单次请求) async getCurrentPosition(options = {}) { if (!LocationManager.isSupported()) { throw new Error('浏览器不支持地理定位功能'); } return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition( (position) => { this.lastKnownPosition = position; // 缓存最新位置 resolve(this.formatPosition(position)); // 返回格式化后的位置数据 }, (error) => { reject(this.handleError(error)); // 处理并返回错误信息 }, { ...this.options, ...options } // 合并配置选项 ); }); } // 持续监听位置变化 startWatching(callback, options = {}) { if (!LocationManager.isSupported()) { throw new Error('浏览器不支持地理定位功能'); } // 添加回调到监听器集合 this.listeners.add(callback); // 如果尚未启动监听,则创建监听器 if (!this.watchId) { this.watchId = navigator.geolocation.watchPosition( (position) => { this.lastKnownPosition = position; // 更新最后已知位置 const formattedPosition = this.formatPosition(position); // 格式化位置数据 // 通知所有监听者 this.listeners.forEach(listener => { try { listener(formattedPosition); } catch (error) { console.error('位置监听回调出错:', error); } }); }, (error) => { const formattedError = this.handleError(error); // 格式化错误信息 // 通知所有监听者(携带错误) this.listeners.forEach(listener => { try { listener(null, formattedError); } catch (error) { console.error('错误监听回调出错:', error); } }); }, { ...this.options, ...options } // 合并配置选项 ); } // 返回取消监听的函数 return () => { this.listeners.delete(callback); // 移除当前回调 // 如果没有监听者了,停止位置监听 if (this.listeners.size === 0) { this.stopWatching(); } }; } // 停止监听位置变化 stopWatching() { if (this.watchId) { navigator.geolocation.clearWatch(this.watchId); // 清除监听器 this.watchId = null; } this.listeners.clear(); // 清空所有监听回调 } // 格式化位置数据为简洁对象 formatPosition(position) { return { latitude: position.coords.latitude, // 纬度 longitude: position.coords.longitude, // 经度 accuracy: position.coords.accuracy, // 水平精度(米) altitude: position.coords.altitude, // 海拔高度 altitudeAccuracy: position.coords.altitudeAccuracy, // 高度精度 heading: position.coords.heading, // 行进方向(度) speed: position.coords.speed, // 速度(米/秒) timestamp: position.timestamp // 时间戳 }; } // 处理地理定位错误 handleError(error) { const errorMessages = { 1: '用户拒绝位置访问权限', // PERMISSION_DENIED 2: '无法获取位置信息', // POSITION_UNAVAILABLE 3: '位置请求超时' // TIMEOUT }; return { code: error.code, message: errorMessages[error.code] || '未知位置错误' }; } // 计算两点间距离(使用Haversine公式) calculateDistance(pos1, pos2) { const R = 6371; // 地球半径(公里) const dLat = this.toRadians(pos2.latitude - pos1.latitude); // 纬度差转弧度 const dLon = this.toRadians(pos2.longitude - pos1.longitude); // 经度差转弧度 // Haversine公式计算 const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRadians(pos1.latitude)) * Math.cos(this.toRadians(pos2.latitude)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // 返回距离(公里) } // 角度转弧度 toRadians(degrees) { return degrees * (Math.PI / 180); } // 反向地理编码(需要OpenCageData服务) async reverseGeocode(latitude, longitude) { try { const response = await fetch( `https://api.opencagedata.com/geocode/v1/json?q=${latitude}+${longitude}&key=YOUR_API_KEY` ); const data = await response.json(); if (data.results && data.results.length > 0) { return { address: data.results[0].formatted, // 格式化地址 components: data.results[0].components // 地址组成部分 }; } return null; // 无结果 } catch (error) { console.error('反向地理编码失败:', error); return null; } } // 设置地理围栏(进入/离开指定区域触发回调) async setupGeofence(center, radius, callback) { // 启动位置监听 const unsubscribe = this.startWatching((position, error) => { if (error) return; // 忽略错误情况 // 计算当前位置与围栏中心的距离 const distance = this.calculateDistance(center, position); const isInside = distance <= radius; // 是否在围栏内 // 触发回调 callback({ position, // 当前位置 distance, // 与围栏中心的距离 isInside, // 是否在围栏内 center, // 围栏中心点 radius // 围栏半径 }); }); // 返回取消监听的函数 return unsubscribe; }}实施策略与性能优势
这些原生Web API相比第三方库具有显著优势:
代码体积缩减:
- Web Components:比React小80-90%
- Fetch API:比Axios小95%
- Web Animations:基础动画比GSAP小70%
性能提升:
- 零依赖包开销
- 直接浏览器级优化
- 更低内存占用
- 更快执行速度
维护优势:
- 无需依赖更新
- 自动随浏览器升级
- 减少安全漏洞
- 更好的长期稳定性
浏览器支持考量:
- 多数API支持率超过90%
- 支持渐进增强
- 旧版浏览器可提供polyfill
利用这些web API, 让你未来的应用实现更小的JavaScript包体积、更优的性能表现和更强的未来适应性。
文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有