← StringDecoder 没有下一篇了 →

Node.js querystring模块教程:查询字符串解析、格式化与URLSearchParams对比

著
原创 2026-10-12 Node.js 已有人查阅

一、查询字符串在HTTP请求中的位置

URL里问号?之后的部分就是查询字符串。它由一组键值对组成,键值之间用=连接,多个键值对之间用&分隔。看一个具体地址:

http://localhost:8080/users?id=1&name=John

id=1和name=John是两个查询参数,&是它们的分隔符。这部分数据通常跟随GET请求发送,用于筛选、分页、排序这类不需要请求体的轻量信息传递。

Node.js提供了两个工具来处理它:内置的querystring模块和标准的URLSearchParams。前者是Node.js早期的实现,后者来自Web标准,现在被Node.js全局支持。官方文档已经把querystring标记为遗留(Legacy)API,新项目推荐使用URLSearchParams。但遗留代码库中querystring仍然大量存在,理解它的行为对维护和迁移都有必要。

二、querystring.parse:把字符串拆成对象

parse()把查询字符串转换成JavaScript对象,键值对一一对应。

const querystring = require('node:querystring');

const query = 'category=technology&author=John';
const result = querystring.parse(query);
console.log(result);

输出:

[Object: null prototype] { category: 'technology', author: 'John' }

输出中[Object:nullprototype]这个标记值得留意。parse()返回的对象没有原型链,也就是说它不继承Object.prototype上的方法,hasOwnProperty、toString这些都不能直接调用。这个设计是为了避免原型污染——如果查询字符串里出现__proto__这样的键,普通对象可能会被污染,而无原型对象天然免疫。

如果要判断某个键是否存在,不能写result.hasOwnProperty('category'),会报错。正确写法是:

if (Object.prototype.hasOwnProperty.call(result, 'category')) {
  console.log('存在');
}

或者用Object.hasOwn(result,'category'),Node.js16.9之后可用。

parse()的完整签名是querystring.parse(str[,sep[,eq[,options]]])。sep自定义键值对分隔符,默认&;eq自定义键值分隔符,默认=;options中可以设置maxKeys限制解析的键数量,默认1000。超过上限的键会被丢弃,这在处理不可信输入时能起到限制作用。

当同一个键出现多次时,parse()会把值聚合成数组:

const result = querystring.parse('tag=js&tag=node&tag=stream');
console.log(result.tag);  // [ 'js', 'node', 'stream' ]

这个行为和URLSearchParams不同,后者保留原始顺序,需要用getAll()获取全部值。

三、querystring.stringify:对象转回字符串

stringify()是parse()的逆操作,把对象序列化成查询字符串。

const querystring = require('node:querystring');

const obj = {
  category: 'technology',
  author: 'John'
};
const query = querystring.stringify(obj);
console.log(query);

输出:

category=technology&author=John

它能处理字符串、数字、布尔值,以及由这些类型组成的数组。数组会被展开成重复的键:

const query = querystring.stringify({ tag: ['js', 'node'] });
console.log(query);  // tag=js&tag=node

值里的特殊字符会被自动编码。空格变成%20,中文会被转成UTF-8的百分号编码。这一点很重要——直接用字符串拼接构造URL时,如果值里含有&或=,会把参数结构破坏掉,而stringify()会替你处理这些。

stringify()签名中的sep和eq参数可以自定义分隔符。比如某些系统用;分隔参数,可以传';'。这类需求不常见,但对接第三方接口时偶尔会遇到。

需要提醒的是,stringify()只序列化对象自身的可枚举属性,原型链上的属性会被忽略。如果传入的对象是类实例,方法不会出现在结果里,这是符合预期的行为。

四、escape与unescape:手动编码解码

escape()把字符串转成URL编码形式,unescape()是它的逆操作。

const querystring = require('node:querystring');

console.log(querystring.escape('Node.js Tutorial'));
console.log(querystring.unescape('Node.js%20Tutorial'));

输出:

Node.js%20Tutorial
Node.js Tutorial

这两个方法在parse和stringify内部已经被调用,日常很少需要单独使用。它们真正的用武之地是在做部分编码或调试时——比如只想编码值而不编码键,或者查看某个编码字符串的原文。

需要注意的是querystring.escape()和全局的encodeURIComponent()行为并不一致。前者对某些字符的处理有细微差别,跨系统传递数据时不要混用,一端用escape编码、另一端用decodeURIComponent解码,可能在某些字符上产生偏差。保持编解码方法成对使用是基本要求。

经验提示:escape和unescape这两个名字容易引起误解,它们和JavaScript早期废弃的全局escape()函数不是一回事。看遗留代码时别把两者混为一谈。

五、querystring与URLSearchParams的取舍

两者都能解析和构造查询字符串,但设计定位不同。

对比项 URLSearchParams querystring(遗留)
状态 现在标准API Node.js遗留API
引入方式 全局可用,无需require 需要引入node:querystring
返回类型 URLSearchParams对象 普通对象(无原型)
重复键 保留顺序,用getAll取全部 聚合成数组
缺失值 返回空字符串 返回空字符串
浏览器支持 现在浏览器支持 仅Node.js
主要操作 get、set、append、delete parse、stringify

URLSearchParams的优势在于它是Web标准,同一套代码在浏览器和Node.js都能跑。它的API更接近操作,增删改查都支持,而querystring只提供整体转换。

const params = new URLSearchParams('category=technology&author=John');
params.append('tag', 'node');
params.set('author', 'Jane');
console.log(params.toString());

输出:

category=technology&author=Jane&tag=node

这种逐项修改的能力在构造请求时很实用。用querystring做同样的事,得先parse成对象、改对象、再stringify,多了一步转换。

个人建议:新项目直接用URLSearchParams。老项目维护时,如果只是读一下参数,querystring.parse也能用,不必为了统一而大改。但如果涉及重复键处理、动态增删参数,迁移到URLSearchParams会让逻辑清晰不少。迁移时注意返回值类型的变化——原来result.tag拿数组,现在要改成params.getAll('tag')。

六、查询字符串的典型应用场景

筛选和搜索是最常见的用途。客户端指定要获取的数据子集:

/products?category=electronics&brand=apple

服务端解析后按条件查询数据库。

分页通过查询参数控制起始位置和数量:

/posts?page=3&limit=10

服务端根据page和limit计算offset,从数据集中取出对应片段。

排序让客户端动态指定排序字段和方向:

/users?sort=createdAt&order=desc

这里有个安全细节:sort和order的值不能直接拼进SQL,必须做白名单校验,只允许预定义的字段名和方向。否则会引入SQL注入风险,这是查询参数处理里容易被忽略的一环。

埋点和分析参数在营销链接中大量出现:

/products?utm_source=google&utm_campaign=sale

这类参数服务端通常只做记录,不参与业务逻辑。

服务端处理这些参数时,类型转换和默认值是必须考虑的问题。查询字符串里的值都是字符串,page=3拿到的是'3'而不是数字3,需要显式转换。缺失的参数要给出默认值,否则parseInt(undefined)会得到NaN,进而引发下游错误。

const page = parseInt(params.get('page'), 10) || 1;
const limit = Math.min(parseInt(params.get('limit'), 10) || 20, 100);

这段代码做了三件事:转数字、给默认值、限制上限。上限尤其重要,不加限制的话客户端传limit=999999就能一次拉走整张表。

七、本节课程知识要点

  • 查询字符串是URL中?之后的部分,由键值对经&连接组成。

  • querystring是遗留API,新项目推荐用URLSearchParams。

  • parse()返回无原型对象,避免原型污染,判断键存在需用Object.hasOwn。

  • 重复键在parse()中聚合为数组,在URLSearchParams中用getAll()获取。

  • stringify()自动处理特殊字符编码,比手动拼接字符串可靠。

  • escape()和unescape()内部已被调用,单独使用时注意与全局编码函数保持一致。

  • 参数值都是字符串,需要显式转数字并设置默认值与上限。

  • 参与SQL或文件路径的排序、筛选参数必须做白名单校验。

  • maxKeys可限制解析数量,处理不可信输入时有用。

八、练习与思考

  1. 写一个函数,接收查询字符串,返回{page,limit,sort},其中缺失值给默认值,limit上限为100。

  2. 用URLSearchParams和querystring分别实现同一段筛选逻辑,对比两种写法的差异。

  3. 构造一个含重复tag参数的查询字符串,分别用两种方式读取全部值,说明返回结构的不同。

  4. 思考:为什么parse()返回无原型对象?如果返回普通对象,__proto__键会带来什么后果。

  5. 设计一个安全的排序参数处理函数,只接受预定义的字段名,其余输入抛出错误或回退到默认值。

九、常见问题排查

解析结果里键名或值出现乱码,检查字符串是否经过了正确的URL解码,不要对已解码的字符串重复解码。result.hasOwnProperty报错,说明拿到的parse()结果是无原型对象,改用Object.hasOwn。数组值被当成字符串处理,确认parse()对重复键返回的是数组,读取时判断类型。构造的URL参数丢失,检查值里是否含有未编码的&或=,改用stringify()生成。分页参数传了非数字,parseInt返回NaN,处理时加上默认值兜底。

查询字符串是Web开发中传递参数的基础形式,理解它的解析规则和编码行为,能避免大量参数丢失、乱码、注入类的隐蔽问题。

← Node.js StringDecoder教程:分块数据下的多字节字符解码 没有下一篇了 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号