指南 · 基础
本篇按库讲清各自的机制与常用用法,把「会用」升级到「懂为什么」。7 个库相互独立,可按需跳读。
一、mitt:极简的代价与收益
mitt 返回的 Emitter 只有 on / off / emit 三个方法和一个 all 属性。它的设计哲学是「能砍就砍」:
all是Map:键为事件名(或'*'),值为 handler 数组。它对外暴露,可直接操作,例如bus.all.clear()一次性清空所有监听。- 方法不依赖
this:可以安全解构使用,const { on, emit } = mitt()后单独调用也没问题。 - 通配
'*':on('*', (type, payload) => ...)监听所有事件,回调首参是被触发的事件名。 - 触发顺序:同一次
emit,先调用该事件名的类型化 handler,再调用'*'通配 handler。
const bus = mitt();
bus.on("foo", () => console.log("typed"));
bus.on("*", (t) => console.log("wild:", t));
bus.emit("foo", 1); // 依次输出 "typed" 与 "wild: foo"mitt 没有 once
要实现一次性监听,得在 handler 内部解绑自己:
function onceHandler(payload: unknown) {
// ...处理逻辑
bus.off("ready", onceHandler); // 用完即解绑
}
bus.on("ready", onceHandler);二、qs:嵌套、数组与安全限制
嵌套与数组
qs 用方括号路径表达层级:a[b][c]=d ↔ { a: { b: { c: 'd' } } }。数组解析支持 a[]=x(隐式)与 a[0]=x(显式下标)。
序列化的数组格式(arrayFormat)
| 取值 | 输出({ a: ['b','c'] },encode:false) |
|---|---|
indices(默认) | a[0]=b&a[1]=c |
brackets | a[]=b&a[]=c |
repeat | a=b&a=c |
comma | a=b,c |
默认的安全限制(防 DoS)
qs 的几个默认值都是为防止恶意构造的超大/超深查询拖垮服务:
| 选项 | 默认 | 含义 |
|---|---|---|
depth | 5 | 最大嵌套深度,超出部分并入键名 |
parameterLimit | 1000 | 最大参数(键值对)数,超出默认忽略 |
arrayLimit | 20 | 数组下标超过它就转成对象(避免稀疏大数组) |
qs.parse("a[25]=x"); // { a: { '25': 'x' } }(下标 25 > 20,转对象)这正是 qs 优于原生
URLSearchParams的地方:后者只能处理扁平键值对,且无这些保护。
三、JSZip:异步 I/O 与类型选择
JSZip 的核心 I/O 都是异步、返回 Promise:
zip.generateAsync({ type })—— 生成压缩包;JSZip.loadAsync(data)—— 读取已有 zip;zipObject.async(type)—— 读出单个文件内容。
type 取值要按环境选:
| type | 适用 |
|---|---|
blob | 浏览器下载 / 预览 |
nodebuffer | Node 写文件(fs.writeFile) |
base64 | 嵌入 Data URL / 传输 |
uint8array / arraybuffer | 二进制处理 |
// 浏览器:生成并下载
const blob = await zip.generateAsync({ type: "blob" });
saveAs(blob, "out.zip"); // 配合 FileSaver
// Node:写盘
const buf = await zip.generateAsync({ type: "nodebuffer" });
fs.writeFileSync("out.zip", buf);遍历条目用
zip.forEach((path, entry) => ...)或直接读zip.files(路径 → ZipObject 的对象)。
四、FileSaver.js:它只做「保存」这一步
saveAs(blob, filename, options?) 的底层套路:用 URL.createObjectURL(blob) 造临时 URL,创建带 download 属性的隐藏 <a> 并程序化点击,再释放 URL,并对各浏览器/iOS 做兼容回退。
- 首参可以是 Blob / File / URL 字符串:URL 同源走
<a download>,跨源需服务端 CORS 支持。 autoBom:当 blob 的type含charset=utf-8时,加 UTF-8 BOM,帮助 Excel 等正确识别编码、避免中文乱码(导出 CSV 常用)。- 它不产生内容:内容由
new Blob([...])、canvas.toBlob、JSZip 等准备。
canvas.toBlob((blob) => saveAs(blob!, "shot.png")); // canvas → 图片下载五、qrcode:四种输出 + 纠错级别
qrcode 按「输出目标」提供四组方法:
| 方法 | 输出 | 环境 |
|---|---|---|
toCanvas | 画到 canvas 元素 | 浏览器 / Node |
toDataURL | base64 Data URL(可赋 img.src) | 浏览器 / Node |
toString | 字符串(svg / utf8 / terminal) | 浏览器 / Node |
toFile | 写文件(png / svg / utf8) | 仅 Node |
纠错级别 errorCorrectionLevel(默认 M):
| 级别 | 可恢复污损 | 特点 |
|---|---|---|
L | ~7% | 数据密度最高、最脆弱 |
M | ~15% | 默认,均衡 |
Q | ~25% | 较抗损 |
H | ~30% | 最抗损(可遮挡更多,常用于带 logo 的码) |
所有渲染方法都同时支持「回调」与「Promise」:传
callback即回调式,不传则返回 Promise。
六、chroma.js:构造、转换与色阶
构造与转换
chroma(input) 接受 hex、CSS 名、chroma.rgb(...)、chroma.hsl(...) 等多种输入;再链式取目标表示:.rgb() / .hsl() / .lab() / .lch() / .hex() / .css()。调整类方法 .darken() / .brighten() / .saturate() / .alpha() 都返回新的 chroma 对象,可继续链式。
色阶 scale
chroma.scale([colors]) 返回一个色阶函数,传 0~1 取插值色:
const s = chroma.scale(["white", "red"]);
s(0.5).hex(); // 中间色
s.colors(5); // 取 5 个等距颜色
s.mode("lab"); // 改插值色彩空间(默认 'rgb')注意默认插值 mode 的差异:
chroma.scale默认rgb,而chroma.mix/chroma.average默认lrgb(线性 RGB)。
七、marked:转换与安全模型
marked.parse(md)(或 marked(md))把 Markdown 编译成 HTML,默认同步返回字符串。常用选项:
| 选项 | 默认 | 含义 |
|---|---|---|
gfm | true | GitHub Flavored Markdown(表格、删除线、任务列表等) |
breaks | false | 把单个换行渲染为 <br> |
pedantic | false | 严格遵循原始 markdown.pl |
async | false | 开启后 parse 返回 Promise<string> |
marked 默认不净化 HTML
marked 会原样输出内嵌的 HTML。旧的 sanitize / sanitizer 选项已被移除。渲染不可信内容时,必须自行用 DOMPurify 净化输出:
const html = DOMPurify.sanitize(marked.parse(userInput) as string);
element.innerHTML = html; // 净化必须在写入 DOM 之前进入 指南 · 进阶:mitt 类型化与解绑模式、qs 高级选项、JSZip 流式与压缩级别、chroma 色阶分级、marked 自定义渲染。