当我们在前端开发中处理 URL 时,可能会用到字符串拼接、ES6 模板语法 (template literals) 或者使用 new URL() 构造函数。这三者各有优劣,适用于不同的场景。

1. 字符串拼接与 ES6 模板语法

字符串拼接ES6 模板语法 都是将不同的字符串片段组合在一起生成 URL。区别在于语法上:

  • 字符串拼接 使用 + 号:
const baseUrl = "https://example.com";
const path = "/api/data";
const query = "?id=123";
const fullUrl = baseUrl + path + query;
  • 1.
  • 2.
  • 3.
  • 4.
  • ES6 模板语法 使用反引号和 ${} 插入变量:
const baseUrl = "https://example.com";
const path = "/api/data";
const query = "?id=123";
const fullUrl = `${baseUrl}${path}${query}`;
  • 1.
  • 2.
  • 3.
  • 4.

优点:

  • 简单、直观,适合拼接简单的字符串。

缺点:

  • 对于复杂的 URL(如需要处理相对路径、不同的协议、查询参数编码等),容易出错且难以维护。
  • 手动拼接可能会忽略一些细节,比如自动编码特殊字符等需要enUrlcode。
2. new URL() 构造函数

new URL() 是一个更强大的方式,用于生成和解析 URL。它不仅可以拼接 URL,还可以处理 URL 的各个组成部分(协议、主机、路径、查询参数等),并且可以自动处理编码、路径规范化等问题。

const baseUrl = "https://example.com";
const path = "/api/data";
const query = "?id=123";
const fullUrl = new URL(path + query, baseUrl);

console.log(fullUrl.toString()); // "https://example.com/api/data?id=123"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

优点:

  • 自动处理 URL 的规范化和编码。
  • 允许直接操纵 URL 的各个部分(如更改查询参数、路径等)。
  • 适用于处理复杂 URL 或需要确保 URL 结构正确的场景。

缺点:

  • 相对来说,写法稍显复杂,尤其在处理简单 URL 时。
什么时候使用哪种方法?
  • 简单拼接:对于简单的 URL,字符串拼接或模板语法通常就足够了,尤其是在你非常确定路径和参数不会产生问题时。
  • 复杂 URL 操作:如果需要处理 URL 的不同部分,或要确保生成的 URL 是有效的(尤其是在处理用户输入或多个路径时),使用 new URL() 更加安全和可靠。
总结
  • 简单场景:字符串拼接或 ES6 模板语法。
  • 复杂场景new URL() 构造函数。

这两种方法没有绝对的优劣,关键是选择适合场景的工具。


详解new URL()参数使用

new URL() 构造函数在处理 URL 时非常强大和灵活,它不仅可以帮助我们构建 URL,还能解析和操作 URL 的各个部分。下面是对它的使用参数和能力的详解。

1. new URL() 构造函数的参数

new URL() 可以接收两个参数:

  1. 第一个参数(必选):表示目标 URL 的路径或完整的 URL 字符串。
  • 如果是完整的 URL,则直接使用。
  • 如果是相对路径,则需要结合第二个参数来解析为完整的 URL。
  1. 第二个参数(可选):表示基准 URL(base URL)。如果第一个参数是相对路径,那么会基于这个 URL 解析出完整的 URL。
示例 1:使用完整 URL
const url = new URL('https://example.com/path?name=value#hash');
console.log(url.toString()); // "https://example.com/path?name=value#hash"
  • 1.
  • 2.
示例 2:使用相对路径和基准 URL
const url = new URL('/path?name=value#hash', 'https://example.com');
console.log(url.toString()); // "https://example.com/path?name=value#hash"
  • 1.
  • 2.
2. new URL() 的能力与方法

一旦使用 new URL() 创建了 URL 对象,它会暴露出一些属性和方法,这些可以用来操作和查询 URL 的不同部分。

2.1. 属性
  • href:整个 URL 字符串。
console.log(url.href); // "https://example.com/path?name=value#hash"
  • 1.
  • origin:协议、域名和端口号的组合。
console.log(url.origin); // "https://example.com"
  • 1.
  • protocol:URL 的协议部分(包括结尾的冒号 :)。
console.log(url.protocol); // "https:"
  • 1.
  • host:主机部分(域名和端口号,如果有)。
console.log(url.host); // "example.com"
  • 1.
  • hostname:主机名部分(不包括端口号)。
console.log(url.hostname); // "example.com"
  • 1.
  • port:端口号(如果有)。
const urlWithPort = new URL('https://example.com:8080/path');
console.log(urlWithPort.port); // "8080"
  • 1.
  • 2.
  • pathname:路径部分。
console.log(url.pathname); // "/path"
  • 1.
  • search:查询字符串部分(包括开头的问号 ?)。
console.log(url.search); // "?name=value"
  • 1.
  • searchParamsURLSearchParams 对象,允许操作查询参数。
console.log(url.searchParams.get('name')); // "value"
url.searchParams.append('age', '25');
console.log(url.search); // "?name=value&age=25"
  • 1.
  • 2.
  • 3.
  • hash:哈希部分(包括开头的 #)。
console.log(url.hash); // "#hash"
  • 1.
2.2. 方法
  • toString():返回 URL 的字符串表示,等同于 href 属性。
console.log(url.toString()); // "https://example.com/path?name=value#hash"
  • 1.
  • toJSON():返回 URL 的字符串表示,适合 JSON 序列化。
console.log(url.toJSON()); // "https://example.com/path?name=value#hash"
  • 1.
3. 常见用法示例
3.1. 动态构建 URL
const baseUrl = 'https://example.com';
const url = new URL('/api/data', baseUrl);
url.searchParams.append('id', '123');
url.searchParams.append('filter', 'active');
console.log(url.toString()); // "https://example.com/api/data?id=123&filter=active"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
3.2. 操作查询参数
const url = new URL('https://example.com/api/data?id=123&filter=active');
console.log(url.searchParams.get('id')); // "123"
url.searchParams.set('id', '456'); // 修改参数
url.searchParams.delete('filter'); // 删除参数
console.log(url.toString()); // "https://example.com/api/data?id=456"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
3.3. 处理相对路径
const baseUrl = 'https://example.com/some/path/';
const url = new URL('../other/path', baseUrl);
console.log(url.toString()); // "https://example.com/some/other/path"
  • 1.
  • 2.
  • 3.
4. 使用场景
  • 处理动态查询参数:可以轻松添加、修改或删除查询参数。
  • 解析和操作 URL:从 URL 中提取信息或修改其中的部分。
  • 构建完整的 URL:基于基准 URL 生成新的完整 URL,适合 RESTful API 或页面导航等场景。

通过 new URL(),我们可以更好地管理和操作 URL,使代码更健壮和易于维护。比如以下实际场景中的使用


拼接数组参数
假设我们有一个URL,需要将一个数组作为查询参数添加到URL中。

const baseUrl = 'https://example.com';
const url = new URL(baseUrl);

const arrayParam = ['value1', 'value2', 'value3'];
// 将数组转换为逗号分隔的字符串
url.searchParams.set('array', arrayParam.join(','));

console.log(url.toString()); // https://example.com/?array=value1,value2,value3
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

解析数组参数
当我们获取URL并需要解析其中的数组参数时,可以使用URLSearchParams对象进行解析。

const urlString = 'https://example.com/?array=value1,value2,value3';
const url = new URL(urlString);

const arrayParamString = url.searchParams.get('array');
// 将逗号分隔的字符串转换回数组
const arrayParam = arrayParamString ? arrayParamString.split(',') : [];

console.log(arrayParam); // ['value1', 'value2', 'value3']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

处理多个同名参数
有时我们可能会遇到需要处理多个同名参数的情况,例如?array=value1&array=value2&array=value3。可以使用URLSearchParamsgetAll方法:

// 拼接多个同名参数到URL
const url = new URL(baseUrl);

const arrayParam = ['value1', 'value2', 'value3'];
arrayParam.forEach(value => url.searchParams.append('array', value));

console.log(url.toString()); // https://example.com/?array=value1&array=value2&array=value3

// 解析多个同名参数从URL
const urlString = url.toString();
const parsedUrl = new URL(urlString);

const parsedArrayParam = parsedUrl.searchParams.getAll('array');

console.log(parsedArrayParam); // ['value1', 'value2', 'value3']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

但实际情况往往比上面的示例更复杂,比如参数是一个对象、根据实际情况来设置参数的值、要处理undefined0'0'Boolean'true'NaN等不同类型和异常的值,每次使用时都去处理显然是不合理的,这时候就可以将拼接和移除参数的函数封装成方法来使用。

/**
 * 获取URL查询参数并返回一个对象,支持数组
 * @param {string} urlString - 需要解析的URL字符串
 * @returns {Object} - 包含查询参数的对象
 */
function getURLParams(urlString) {
    const url = new URL(urlString);
    const params = new URLSearchParams(url.search);
    const result = {};

    for (const [key, value] of params.entries()) {
        if (result[key]) {
            if (Array.isArray(result[key])) {
                result[key].push(value);
            } else {
                result[key] = [result[key], value];
            }
        } else {
            result[key] = value;
        }
    }

    return result;
}

/**
 * 设置URL的查询参数,支持对象和数组
 * @param {string} urlString - 基础URL字符串
 * @param {Object} params - 需要设置的查询参数对象
 * @returns {string} - 带有查询参数的URL字符串
 */
function setURLParams(urlString, params) {
    const url = new URL(urlString);
    const searchParams = new URLSearchParams();

    for (const key in params) {
        if (params.hasOwnProperty(key)) {
            const value = params[key];
            if (Array.isArray(value)) {
                value.forEach(val => {
                    if (val !== undefined && !Number.isNaN(val)) {
                        searchParams.append(key, val);
                    } else {
                        console.warn(`Warning: The value of "${key}" is ${val}, which is invalid and will be ignored.`);
                    }
                });
            } else if (value !== undefined && !Number.isNaN(value)) {
                searchParams.append(key, value);
            } else {
                console.warn(`Warning: The value of "${key}" is ${value}, which is invalid and will be ignored.`);
            }
        }
    }

    url.search = searchParams.toString();
    return url.toString();
}

// 测试用例
const baseUrl = 'https://example.com';

// 测试 getURLParams 方法
const testUrl = 'https://example.com/?param1=value1¶m2=value2¶m2=value3';
const parsedParams = getURLParams(testUrl);
console.log(parsedParams); // { param1: 'value1', param2: ['value2', 'value3'] }

// 测试 setURLParams 方法
const params = {
    param1: 'value1',
    param2: ['value2', 'value3'],
    param3: undefined,
    param4: NaN,
    param5: 'value5',
    param6: 0,
};

const newUrl = setURLParams(baseUrl, params);
console.log(newUrl); // 'https://example.com/?param1=value1¶m2=value2¶m2=value3¶m5=value5'
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.

实现一个url解析参数和拼接参数的功能。这时候一个简单的解析和拼接的函数就可以搞定。
当然方法有多种实现方式,下面还有一种通过正则来实现的,但下面拼接的时候会忽略数字0,所以参数一定要用字符串。

/**
 * 合并查询参数到 URL 的函数
 * 将给定的查询对象 Query 合并到指定的 URL 中
 *
 * @param {Object} query - 要合并到 URL 中的查询对象
 * @param {string} url - 作为基础的 URL,默认为当前页面的 URL
 * @returns {string} 生成的合并查询参数后的新 URL
 */
export function getUrlMergeQuery(query = {}, url) {
  url = url || window.location.href
  const _orgQuery = getQueryObject(url)
  const _query = {..._orgQuery,...query }
  let _arr = []
  for (let key in _query) {
    const value = _query[key]
    if (value) _arr.push(`${key}=${encodeURIComponent(_query[key])}`)
  }
  return `${url.split('?')[0]}${_arr.length > 0? `?${_arr.join('&')}` : ''}`
}

/**
 * 从 URL 中提取查询参数对象
 *
 * @param {string} [url=window.location.href] - 要解析的 URL 字符串。如果未提供,则使用当前页面的 URL
 * @returns {Object} - 包含提取的查询参数的对象
 */
export function getQueryObject(url = window.location.href) {
  const search = url.substring(url.lastIndexOf('?') + 1);
  const obj = {};
  const reg = /([^?&=]+)=([^?&=]*)/g;
  search.replace(reg, (rs, $1, $2) => {
    const name = decodeURIComponent($1);
    let val = decodeURIComponent($2);
    val = String(val);
    obj[name] = val;
    return rs;
  });
  return obj;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

当然也可以用qs
npmjs www.npmjs.com/package/qs

它是开源免费项目,每周下载量将近7千万,支持任意字符,对象进行解析和拼接,支持@types/qs,导入后11.3k。