Skip to content

浏览器API和Web API

Web API是浏览器提供的一组接口,允许JavaScript与浏览器和设备功能进行交互。掌握这些API对于构建现代、交互式的Web应用至关重要。本文将介绍几个重要的浏览器和Web API及其实际应用。

Fetch API和网络请求

Fetch API提供了一个更强大、更灵活的网络请求接口,用于替代传统的XMLHttpRequest。

基本用法

javascript
// 基本GET请求
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json(); // 或 response.text(), response.blob() 等
  })
  .then(data => {
    console.log('Data received:', data);
  })
  .catch(error => {
    console.error('Fetch error:', error);
  });

// 使用async/await
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    const data = await response.json();
    console.log('Data received:', data);
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

配置请求选项

javascript
// POST请求
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({
    name: '张三',
    email: 'zhangsan@example.com'
  })
})
.then(response => response.json())
.then(data => console.log('Created user:', data));

// 上传文件
const formData = new FormData();
const fileInput = document.querySelector('#file-input');
formData.append('file', fileInput.files[0]);
formData.append('user', 'zhangsan');

fetch('https://api.example.com/upload', {
  method: 'POST',
  body: formData
})
.then(response => response.json())
.then(data => console.log('Upload successful:', data));

处理响应

javascript
fetch('https://api.example.com/data')
  .then(response => {
    // 检查响应状态
    console.log('Status:', response.status);
    console.log('OK:', response.ok);
    
    // 获取响应头
    console.log('Content-Type:', response.headers.get('Content-Type'));
    
    // 响应类型
    if (response.headers.get('Content-Type').includes('application/json')) {
      return response.json();
    } else if (response.headers.get('Content-Type').includes('text/html')) {
      return response.text();
    } else if (response.headers.get('Content-Type').includes('image/')) {
      return response.blob();
    }
  })
  .then(data => {
    // 处理不同类型的数据
    if (typeof data === 'object') {
      // JSON数据
      console.log('JSON data:', data);
    } else if (typeof data === 'string') {
      // 文本数据
      console.log('Text data:', data);
    } else if (data instanceof Blob) {
      // Blob数据
      const url = URL.createObjectURL(data);
      const img = document.createElement('img');
      img.src = url;
      document.body.appendChild(img);
    }
  });

请求控制

javascript
// 超时控制
const controller = new AbortController();
const { signal } = controller;

// 设置超时
setTimeout(() => controller.abort(), 5000);

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted due to timeout');
    } else {
      console.error('Fetch error:', error);
    }
  });

// 取消请求
const fetchButton = document.querySelector('#fetch-button');
const cancelButton = document.querySelector('#cancel-button');
let controller;

fetchButton.addEventListener('click', () => {
  controller = new AbortController();
  
  fetch('https://api.example.com/large-data', { 
    signal: controller.signal 
  })
    .then(/* ... */)
    .catch(/* ... */);
});

cancelButton.addEventListener('click', () => {
  if (controller) {
    controller.abort();
    console.log('Request cancelled');
  }
});

实际应用场景

javascript
// 实现搜索功能
const searchInput = document.querySelector('#search-input');
let controller;

searchInput.addEventListener('input', async (e) => {
  const searchTerm = e.target.value;
  
  // 取消上一次请求
  if (controller) {
    controller.abort();
  }
  
  // 创建新的控制器
  controller = new AbortController();
  
  if (searchTerm.length < 3) return;
  
  try {
    const response = await fetch(`/api/search?q=${encodeURIComponent(searchTerm)}`, {
      signal: controller.signal
    });
    
    const results = await response.json();
    displayResults(results);
  } catch (error) {
    if (error.name !== 'AbortError') {
      console.error('Search error:', error);
    }
  }
});

// 带缓存的数据加载
const cache = new Map();

async function fetchWithCache(url, options = {}) {
  // 如果在缓存中,直接返回
  if (cache.has(url)) {
    return cache.get(url);
  }
  
  const response = await fetch(url, options);
  const data = await response.json();
  
  // 存入缓存
  cache.set(url, data);
  
  return data;
}

本地存储

浏览器提供了多种本地存储机制,用于在客户端保存数据。

localStorage 和 sessionStorage

javascript
// localStorage - 持久化存储,没有过期时间
// 存储数据
localStorage.setItem('username', '张三');
localStorage.setItem('preferences', JSON.stringify({
  theme: 'dark',
  fontSize: 'medium'
}));

// 读取数据
const username = localStorage.getItem('username');
const preferences = JSON.parse(localStorage.getItem('preferences'));

// 删除数据
localStorage.removeItem('username');

// 清空所有数据
localStorage.clear();

// sessionStorage - 会话级存储,关闭标签页后清除
sessionStorage.setItem('tempData', JSON.stringify({id: 123}));
const tempData = JSON.parse(sessionStorage.getItem('tempData'));

IndexedDB

IndexedDB是一个大型的、支持索引的客户端存储系统,适合存储大量结构化数据。

javascript
// 打开数据库
const request = indexedDB.open('MyDatabase', 1);

// 创建架构
request.onupgradeneeded = event => {
  const db = event.target.result;
  
  // 创建对象仓库
  const store = db.createObjectStore('users', { keyPath: 'id' });
  
  // 创建索引
  store.createIndex('name', 'name', { unique: false });
  store.createIndex('email', 'email', { unique: true });
};

// 处理成功
request.onsuccess = event => {
  const db = event.target.result;
  
  // 存储数据
  function addUser(user) {
    const transaction = db.transaction(['users'], 'readwrite');
    const store = transaction.objectStore('users');
    const request = store.add(user);
    
    request.onsuccess = () => {
      console.log('User added successfully');
    };
    
    transaction.oncomplete = () => {
      console.log('Transaction completed');
    };
    
    transaction.onerror = event => {
      console.error('Transaction error:', event.target.error);
    };
  }
  
  // 读取数据
  function getUser(id) {
    const transaction = db.transaction(['users']);
    const store = transaction.objectStore('users');
    const request = store.get(id);
    
    request.onsuccess = event => {
      const user = event.target.result;
      console.log('Got user:', user);
    };
  }
  
  // 使用索引查询
  function getUsersByName(name) {
    const transaction = db.transaction(['users']);
    const store = transaction.objectStore('users');
    const index = store.index('name');
    const request = index.getAll(name);
    
    request.onsuccess = event => {
      const users = event.target.result;
      console.log('Users with name:', users);
    };
  }
  
  // 更新数据
  function updateUser(user) {
    const transaction = db.transaction(['users'], 'readwrite');
    const store = transaction.objectStore('users');
    const request = store.put(user);
    
    request.onsuccess = () => {
      console.log('User updated');
    };
  }
  
  // 删除数据
  function deleteUser(id) {
    const transaction = db.transaction(['users'], 'readwrite');
    const store = transaction.objectStore('users');
    const request = store.delete(id);
    
    request.onsuccess = () => {
      console.log('User deleted');
    };
  }
  
  // 使用游标遍历
  function listAllUsers() {
    const transaction = db.transaction(['users']);
    const store = transaction.objectStore('users');
    const request = store.openCursor();
    
    const users = [];
    
    request.onsuccess = event => {
      const cursor = event.target.result;
      if (cursor) {
        users.push(cursor.value);
        cursor.continue();
      } else {
        console.log('All users:', users);
      }
    };
  }
};

// 错误处理
request.onerror = event => {
  console.error('Database error:', event.target.error);
};

Cookies

虽然相对较老,但Cookie仍然是在浏览器和服务器间传递数据的重要方式。

javascript
// 设置Cookie
function setCookie(name, value, days) {
  let expires = '';
  
  if (days) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    expires = `; expires=${date.toUTCString()}`;
  }
  
  document.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=/`;
}

// 获取Cookie
function getCookie(name) {
  const nameEQ = `${name}=`;
  const cookies = document.cookie.split(';');
  
  for (let i = 0; i < cookies.length; i++) {
    let cookie = cookies[i];
    
    while (cookie.charAt(0) === ' ') {
      cookie = cookie.substring(1);
    }
    
    if (cookie.indexOf(nameEQ) === 0) {
      return decodeURIComponent(cookie.substring(nameEQ.length));
    }
  }
  
  return null;
}

// 删除Cookie
function deleteCookie(name) {
  setCookie(name, '', -1);
}

// 使用示例
setCookie('user', '张三', 7); // 存储7天
const user = getCookie('user');
deleteCookie('user');

Cache API

Cache API主要用于PWA(渐进式Web应用)中,可以缓存网络请求和响应。

javascript
// 打开缓存
async function openCache() {
  return await caches.open('my-cache-v1');
}

// 缓存资源
async function cacheResources(urls) {
  const cache = await openCache();
  await cache.addAll(urls);
}

// 从缓存或网络获取资源
async function fetchWithCache(request) {
  const cache = await openCache();
  
  // 先查找缓存
  const cachedResponse = await cache.match(request);
  
  if (cachedResponse) {
    return cachedResponse;
  }
  
  // 如果没有缓存,从网络获取
  try {
    const networkResponse = await fetch(request);
    
    // 复制响应并存入缓存
    if (networkResponse.ok) {
      const clone = networkResponse.clone();
      cache.put(request, clone);
    }
    
    return networkResponse;
  } catch (error) {
    console.error('Fetch failed:', error);
    // 可以返回一个备用响应
  }
}

// 清除旧缓存
async function clearOldCaches() {
  const cacheList = await caches.keys();
  
  const deletions = cacheList
    .filter(name => name !== 'my-cache-v1')
    .map(name => caches.delete(name));
  
  await Promise.all(deletions);
}

// 在Service Worker中使用
self.addEventListener('install', event => {
  event.waitUntil(
    cacheResources([
      '/',
      '/index.html',
      '/styles.css',
      '/script.js',
      '/images/logo.png'
    ])
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    fetchWithCache(event.request)
  );
});

实际应用场景

javascript
// 用户设置的持久化
function saveUserSettings(settings) {
  localStorage.setItem('userSettings', JSON.stringify(settings));
}

function loadUserSettings() {
  try {
    const settings = JSON.parse(localStorage.getItem('userSettings'));
    return settings || getDefaultSettings();
  } catch (error) {
    console.error('Error loading settings:', error);
    return getDefaultSettings();
  }
}

function getDefaultSettings() {
  return {
    theme: 'light',
    fontSize: 'medium',
    notifications: true
  };
}

// 应用状态的恢复
function saveApplicationState(state) {
  sessionStorage.setItem('appState', JSON.stringify(state));
}

function restoreApplicationState() {
  try {
    return JSON.parse(sessionStorage.getItem('appState')) || {};
  } catch (error) {
    console.error('Error restoring state:', error);
    return {};
  }
}

// 表单数据的自动保存
const form = document.querySelector('#contact-form');

// 监听输入变化,自动保存
form.addEventListener('input', event => {
  const formData = new FormData(form);
  const data = {};
  
  for (const [key, value] of formData.entries()) {
    data[key] = value;
  }
  
  localStorage.setItem('savedForm', JSON.stringify(data));
});

// 页面加载时恢复表单数据
window.addEventListener('load', () => {
  try {
    const savedData = JSON.parse(localStorage.getItem('savedForm'));
    
    if (savedData) {
      // 填充表单
      Object.entries(savedData).forEach(([key, value]) => {
        const field = form.elements[key];
        if (field) {
          field.value = value;
        }
      });
    }
  } catch (error) {
    console.error('Error restoring form:', error);
  }
});

// 离线应用数据同步
class DataSynchronizer {
  constructor() {
    this.offlineActions = [];
    this.isOnline = navigator.onLine;
    
    // 监听在线状态变化
    window.addEventListener('online', this.handleOnline.bind(this));
    window.addEventListener('offline', () => {
      this.isOnline = false;
    });
    
    // 从IndexedDB加载未同步的操作
    this.loadPendingActions();
  }
  
  async handleOnline() {
    this.isOnline = true;
    await this.synchronize();
  }
  
  async addAction(action) {
    this.offlineActions.push(action);
    
    // 保存到IndexedDB
    await this.savePendingActions();
    
    // 如果在线,立即同步
    if (this.isOnline) {
      await this.synchronize();
    }
  }
  
  async synchronize() {
    if (!this.isOnline || this.offlineActions.length === 0) {
      return;
    }
    
    const actionsToSync = [...this.offlineActions];
    
    try {
      // 发送到服务器
      const response = await fetch('/api/sync', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ actions: actionsToSync })
      });
      
      if (response.ok) {
        // 同步成功,清除已同步的操作
        this.offlineActions = this.offlineActions.filter(
          action => !actionsToSync.includes(action)
        );
        
        // 更新存储
        await this.savePendingActions();
      }
    } catch (error) {
      console.error('Sync failed:', error);
    }
  }
  
  async savePendingActions() {
    // 保存到IndexedDB...
  }
  
  async loadPendingActions() {
    // 从IndexedDB加载...
  }
}

用知识点燃技术的火山