解析 URL Params 为对象

将 URL 查询参数字符串解析为 JavaScript 对象,支持数组、重复参数等场景

问题

在前端开发中,我们经常需要从 URL 中提取查询参数。例如,给定 URL https://example.com?name=张三&age=25&hobby=reading&hobby=coding,需要将查询参数解析为一个对象:

{
  name: '张三',
  age: '25',
  hobby: ['reading', 'coding']
}

这道题要求实现一个函数,能够将 URL 的查询参数部分解析为 JavaScript 对象,并正确处理重复参数、特殊字符编码等情况。

解答

/**
 * 解析 URL 参数为对象
 * @param {string} url - 完整的 URL 或查询字符串
 * @returns {object} 解析后的参数对象
 */
function parseUrlParams(url) {
  // 提取查询字符串部分
  const queryString = url.includes('?') ? url.split('?')[1] : url;
  
  // 如果没有查询参数,返回空对象
  if (!queryString) {
    return {};
  }
  
  const params = {};
  
  // 按 & 分割参数对
  const pairs = queryString.split('&');
  
  pairs.forEach(pair => {
    // 按 = 分割键值对
    const [key, value] = pair.split('=');
    
    // 解码键和值(处理中文和特殊字符)
    const decodedKey = decodeURIComponent(key);
    const decodedValue = decodeURIComponent(value || '');
    
    // 如果键已存在,转换为数组
    if (params.hasOwnProperty(decodedKey)) {
      // 如果已经是数组,直接添加
      if (Array.isArray(params[decodedKey])) {
        params[decodedKey].push(decodedValue);
      } else {
        // 如果不是数组,转换为数组
        params[decodedKey] = [params[decodedKey], decodedValue];
      }
    } else {
      // 首次出现,直接赋值
      params[decodedKey] = decodedValue;
    }
  });
  
  return params;
}

/**
 * 使用 URLSearchParams API 的现代实现方式
 * @param {string} url - 完整的 URL 或查询字符串
 * @returns {object} 解析后的参数对象
 */
function parseUrlParamsModern(url) {
  const queryString = url.includes('?') ? url.split('?')[1] : url;
  
  if (!queryString) {
    return {};
  }
  
  const params = {};
  const searchParams = new URLSearchParams(queryString);
  
  // 遍历所有参数
  for (const [key, value] of searchParams.entries()) {
    if (params.hasOwnProperty(key)) {
      if (Array.isArray(params[key])) {
        params[key].push(value);
      } else {
        params[key] = [params[key], value];
      }
    } else {
      params[key] = value;
    }
  }
  
  return params;
}

使用示例

// 示例 1: 基本用法
const url1 = 'https://example.com?name=张三&age=25&city=北京';
console.log(parseUrlParams(url1));
// 输出: { name: '张三', age: '25', city: '北京' }

// 示例 2: 处理重复参数
const url2 = 'https://example.com?hobby=reading&hobby=coding&hobby=gaming';
console.log(parseUrlParams(url2));
// 输出: { hobby: ['reading', 'coding', 'gaming'] }

// 示例 3: 处理特殊字符和编码
const url3 = 'https://example.com?name=张%20三&email=test%40example.com';
console.log(parseUrlParams(url3));
// 输出: { name: '张 三', email: 'test@example.com' }

// 示例 4: 只传查询字符串
const queryString = 'page=1&size=10&sort=desc';
console.log(parseUrlParams(queryString));
// 输出: { page: '1', size: '10', sort: 'desc' }

// 示例 5: 空值处理
const url5 = 'https://example.com?name=&age=25';
console.log(parseUrlParams(url5));
// 输出: { name: '', age: '25' }

// 示例 6: 使用现代 API
const url6 = 'https://example.com?tag=js&tag=css&tag=html';
console.log(parseUrlParamsModern(url6));
// 输出: { tag: ['js', 'css', 'html'] }

关键点

  • 提取查询字符串:使用 split('?') 方法分离 URL 和查询参数部分,兼容传入完整 URL 或纯查询字符串

  • 参数分割:先用 & 分割各个参数对,再用 = 分割键值对

  • URL 解码:使用 decodeURIComponent() 解码键和值,正确处理中文、空格、特殊字符等编码内容

  • 重复参数处理:当同一个键出现多次时,将值转换为数组存储,保留所有值

  • 边界情况:处理空查询字符串、空值、缺少值等边界情况

  • 现代 API:可以使用 URLSearchParams API 简化实现,但需注意浏览器兼容性

  • 性能优化:使用 hasOwnProperty 检查属性存在性,避免原型链查找