专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

取代JavaScript库的10个现代Web API及详细实施代码

ins518 2025-07-07 18:57:09 技术文章 1 ℃ 0 评论

为什么浏览器内置的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.innerHTML = `
      <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包体积、更优的性能表现和更强的未来适应性。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表