KernelRanch
返回文章列表

查询参数里的加号怎么变成空格了?从 URLSearchParams 的两种输入查起

2026-11-13 #web#javascript#url

搜索 C++ 却得到 C 时,检查代码是否先拼成查询字符串,再交给 URLSearchParams 解析。字符串构造走表单式解码,+ 会被读成空格。构造新 URL 时用 set() 或 append() 传值,让序列化器把字面加号编码为 %2B。

1. 比较参数值、重复键和序列化字节

把以下内容保存为 query_lab.mjs,运行 node query_lab.mjs。

import assert from 'node:assert/strict';

const parsed = new URLSearchParams('q=C++&tag=red&tag=blue');
assert.equal(parsed.get('q'), 'C  ');
assert.deepEqual(parsed.getAll('tag'), ['red', 'blue']);
console.log('parsed q:', JSON.stringify(parsed.get('q')));
console.log('tags:', JSON.stringify(parsed.getAll('tag')));

const built = new URLSearchParams();
built.set('q', 'C++');
built.append('tag', 'red');
built.append('tag', 'blue');
assert.equal(built.toString(), 'q=C%2B%2B&tag=red&tag=blue');
console.log('built:', built.toString());

const url = new URL('https://example.test/?q=a%20b~');
const before = url.search;
url.searchParams.sort();
assert.equal(url.searchParams.get('q'), 'a b~');
assert.equal(before, '?q=a%20b~');
assert.equal(url.search, '?q=a+b%7E');
console.log('serialized:', before, '->', url.search);
parsed q: "C  "
tags: ["red","blue"]
built: q=C%2B%2B&tag=red&tag=blue
serialized: ?q=a%20b~ -> ?q=a+b%7E

2. 接口先约定重复键,再处理签名

允许多个筛选值时,读取 getAll() 并保留顺序。约定单值的参数可要求 getAll(name).length === 1,把多次出现视为输入错误。get() 只返回第一个匹配,set() 会替换第一个值并移除后续同名项。调用方和服务端若在同一参数上分别使用首值和末值,可能对同一 URL 得出不同结论。

签名覆盖请求目标的场合,使用签名协议规定的原始字节或规范化算法。样本只有一个参数,排序前后的值相同,序列化仍改写了空格和波浪号。若入口代理会重写路径或查询字符串,要在签名验证接入测试里记录签名方与验证方各自拿到的请求目标,再按协议核对。

小结

按值构造查询字符串,显式定义重复键规则。涉及签名时,用字节级样本验证规范化是否一致。

数据来源与检索时间

来源URL检索日期对应内容
WHATWG URL Standard §5https://url.spec.whatwg.org/#application/x-www-form-urlencoded2026-11-13表单解析与序列化中的加号、空格和百分号编码
WHATWG URLSearchParamshttps://url.spec.whatwg.org/#urlsearchparams2026-11-13get、getAll、set、sort 和 URL 更新行为

核验说明

  • Windows、Node.js 24.19.0 运行标准 URL API 验证三组行为。样本 URL 仅用于本地构造,未访问网站,也未经过浏览器或代理。