JS 模板字符串里包含反引号:三种不转义方案
模板字符串内部出现反引号时,必须转义成 \`。内容量大或来自外部时,转义很烦。 方案一:数组 join(最稳) 完全不需要考虑转义,任意字符都行: const sql = [ "SELECT * FROM `users`", "WHERE `status` = 'active'", "AND `name` LIKE `%test%`", ].join('\n');大段 HTML、SQL、Prompt 推荐这种写法——可读性好,又不怕特殊字符。 方案二:String.raw(不处理转义序列) const pattern = String.raw`\d+\.\d+`; // 等价于 "\\d+\\.\\d+" // 不需要手动双反斜杠在 String.raw 里,反斜杠不被解释为转义符,但反引号仍然需要转义,所以它主要解决的是 \n、\t 这类转义序列问题,而不是反引号嵌套问题。 const py = String.raw` print("hello") # 反引号仍需 \` `;方案三:外部文件(大段文本首选) // Vite / webpack 环境 import template from './prompt.txt?raw'; import html from './template.html?raw';// Node.js 环境 const template = fs.readFileSync('./prompt.txt', 'utf8');AI Prompt、HTML 模板、大段代码存外部文件,完全不需要处理引号或转义问题,也方便单独编辑和版本追踪。 油猴脚本里 hook window 属性 油猴脚本里经常需要拦截页面的某个全局变量或 API,Object.defineProperty 比整体 Proxy 更可靠: // hook 单个属性 const rawFetch = window.fetch;Object.defineProperty(window, 'fetch', { configurable: true, get() { return function(...args) { console.log('fetch called:', args[0]); return rawFetch.apply(this, args); }; } });整体 new Proxy(window, ...) 只是创建了一个代理对象,不会改变其他代码里访问的真实 window,所以通常没有实际效果。Object.defineProperty 是直接修改全局对象,才能真正拦截到。 hook document.cookie const cookieSetter = Document.prototype.__lookupSetter__('cookie'); const cookieGetter = Document.prototype.__lookupGetter__('cookie');Object.defineProperty(Document.prototype, 'cookie', { configurable: true, get() { return cookieGetter.call(this); }, set(val) { console.log('cookie set:', val); cookieSetter.call(this, val); } });安全读取 cookie 值 正则匹配 cookie 时,结尾分号可能没有(最后一项),用 [^;]* 比 .*?; 更稳: function getCookie(name) { const match = document.cookie.match( new RegExp(`${name}=([^;]*)`) ); return match ? match[1] : undefined; }[^;]* 含义:匹配零个或多个不是分号的字符,不管 cookie 是不是最后一项都能正确提取。
JS 模板字符串含反引号的处理与 Object.defineProperty Hook window 属性
模板字符串里包含反引号 反引号是模板字符串的开始/结束符,中间再出现反引号会直接语法错误: // ❌ 语法错误 ` asdasd`` `方案一:转义(最标准) const str = `asd\`asd`;方案二:String.raw(不处理转义) const str = String.raw`asd\`asd`;String.raw 是标签模板,适合存放正则、shell 命令、SQL、Prompt 等原始文本,不会解释 \n、\t 等转义序列。 方案三:数组 join(不用转义,推荐) const s = [ '`', 'asdasd``', '`' ].join('\n');大型项目里代码生成器常用这种方式,完全不需要考虑转义问题,任意字符都行。 方案四:外部文件 内容包含大量反引号、${} 或换行的情况,最推荐直接存外部文件: // Vite / 现代构建工具 import txt from './prompt.txt?raw';// Node.js const txt = fs.readFileSync('./prompt.txt', 'utf8');AI prompt、HTML 模板、大段 SQL 都适合这种做法。 unsafeWindow 与 Proxy 油猴脚本里直接 window = unsafeWindow 会在某些环境报错,推荐用 Proxy: const windowProxy = new Proxy(unsafeWindow, { get(target, prop) { return target[prop]; }, set(target, prop, value) { target[prop] = value; return true; } });关键限制:new Proxy(window) 只影响局部变量 windowProxy,其他代码里的 window.xxx 仍然访问原生 window,不会走代理。真正要"全局 hook"必须用 Object.defineProperty。 Object.defineProperty Hook window 属性 这是浏览器里最常用的 hook 方式,直接修改全局对象的属性描述符: 监听自定义属性的 get/set let value;Object.defineProperty(window, 'myToken', { configurable: true, get() { console.log('GET myToken'); return value; }, set(v) { console.log('SET myToken', v); value = v; } });其他脚本只要访问 window.myToken 就会触发上面的拦截器。 Hook fetch const rawFetch = window.fetch;Object.defineProperty(window, 'fetch', { configurable: true, get() { return function (...args) { console.log('fetch called:', args[0]); return rawFetch.apply(this, args); }; } });Hook document.cookie const rawSetter = Document.prototype.__lookupSetter__('cookie');Document.prototype.__defineSetter__('cookie', function (val) { console.log('cookie set:', val); rawSetter.call(this, val); });通用 hook 工具函数 function hookGlobal(name) { let value; Object.defineProperty(window, name, { configurable: true, get() { console.log('GET', name); return value; }, set(v) { console.log('SET', name, v); value = v; } }); }hookGlobal('abc');操作前先检查 configurable 部分内置属性(location、top、self、document)不可重定义,直接操作会抛错: const desc = Object.getOwnPropertyDescriptor(window, 'fetch'); console.log(desc.configurable, desc.writable);configurable: true 才能用 defineProperty 重新定义;writable: true 才能直接赋值覆盖。 Proxy vs Object.defineProperty 选哪个场景 推荐方式Hook 单个全局 API(fetch、XHR) Object.defineProperty局部作用域内代理对象 Proxy监控任意属性读写 Proxy(但不适用于真正的 window)跨越沙箱访问页面 window unsafeWindow + Proxy 或 script 注入整体代理 window 在浏览器里基本行不通,原生 window 是宿主对象,有大量不可配置属性。工程上的最佳实践是 hook 关键 API(fetch、XMLHttpRequest、WebSocket、JSON.parse)而不是整体代理。
JavaScript 模板字符串进阶:反引号转义、String.raw、标签模板
反引号转义 模板字符串用反引号包裹,内部出现反引号需要转义: // 错误:会产生语法错误 const str = `hello `world``;// 正确:用反斜杠转义 const str = `hello \`world\``; // 输出:hello `world`String.raw:禁止转义处理 String.raw 是内置标签函数,使反斜杠不被解释为转义字符: // 普通模板:\n 被处理为换行 const a = `line1\nline2`;// String.raw:\n 保持原样 const b = String.raw`line1\nline2`; // 输出:line1\nline2(字面量)适合场景: // Windows 路径(不用写 \\) const path = String.raw`C:\Users\admin\Desktop`;// 正则(不用双重转义) const pattern = new RegExp(String.raw`\d{4}-\d{2}-\d{2}`);// Prompt / Python 代码片段 const prompt = String.raw` 请用 Python 处理:print("hello\nworld") `;数组 join:最稳的多行字符串 当内容包含大量特殊字符时,数组 join 完全避免转义问题: const html = [ '<script>', 'console.log(`hello`)', '</script>' ].join('\n');缺点是没有插值语法,需要手动拼接变量。 标签模板(Tagged Template) 模板字符串前加函数名即为标签模板,函数接收字符串片段和插值变量: function highlight(strings, ...values) { return strings.reduce((result, str, i) => result + str + (values[i] !== undefined ? `<b>${values[i]}</b>` : ''), '' ); }const name = 'World'; const msg = highlight`Hello ${name}!`; // 输出:Hello <b>World</b>!标签模板的 strings.raw 属性包含未处理转义的原始字符串片段,这是 String.raw 的实现原理。 实际应用:库 用途styled-components CSS-in-JSgraphql-tag (gql) GraphQL 查询lit-html (html) Web Components 模板sql (SQL 防注入) 参数化查询外部文件替代超长模板 模板字符串不适合存放超长内容(HTML、SQL、Prompt)。更好的方式: // Node.js:读取外部文件 const template = fs.readFileSync('./templates/email.html', 'utf8');// Vite/Webpack:作为原始文本导入 import template from './templates/email.html?raw';规则:代码逻辑放 JS,内容放文件,不要混在一起。
macOS 上 nc -l -p 报错:BSD netcat 的参数和 Linux 不一样
Linux 上抄来的教程: nc -l -p 1337在 macOS 直接报错。因为 macOS 自带的是 BSD 版 netcat,参数和 Linux 上常见的 GNU/OpenBSD 版有几处不兼容。 正确写法 nc -l 1337或者加日志: nc -lv 1337-l:监听 -v:verbose,显示连接信息 端口号直接跟在后面,不用 -p客户端连过来: nc 127.0.0.1 1337持续监听多个连接 单次 -l 处理完一个连接就退出。想常驻用 -k(keep-open): nc -lk 1337老版本不支持 -k 的话,套一层循环: while true; do nc -l 1337; doneLinux vs macOS 差异速查目的 Linux (nmap-netcat 或 nc.openbsd) macOS BSD nc监听端口 nc -l -p 1337 nc -l 1337短参组合 nc -lp 1337 不支持 -p 监听持续监听 nc -k -l 1337 nc -lk 1337UDP 监听 nc -u -l -p 1337 nc -u -l 1337端口扫描 nc -zv host 20-30 一样指定源端口发送 nc -p 1234 host 80 一样-p 在 macOS 里只用于发送端指定源端口,监听时不能用。 检查你的 nc 是哪一版 which nc # 通常 /usr/bin/nc nc -h 2>&1 | head -3BSD 版的帮助会显示 usage: nc [-46...。GNU 版会显示 Ncat: Version 7.xx。 换成 ncat(可选) 如果不想记两套参数,直接装 nmap 自带的 ncat: brew install nmap ncat -l 1337 # 和 Linux 上写法一样 ncat -lk 1337ncat 参数在所有平台一致,还额外支持 SSL、SOCKS 代理,取代 BSD nc 很合适。 常见踩坑权限:监听 1024 以下端口需要 sudo 防火墙:macOS 系统偏好设置里的防火墙可能拦掉外部连接 AirDrop / IPv6:BSD nc -l 8080 可能只绑 IPv6(::)不绑 IPv4,测试时改用 nc -4 -l 8080 强制 v4一句话总结 macOS 的 nc 是 BSD 版,监听不用 -p:nc -l 1337 或 nc -lk 1337。要跨平台无差别用就装 ncat。
Nginx 反向代理开启 CORS:Access-Control 配置与 OPTIONS 预检处理
前后端分离时,在 Nginx 反向代理层统一添加 CORS 头,比在每个后端框架里单独配置更集中可控。 基础配置 server { listen 80; server_name api.example.com; location / { proxy_pass http://backend-server:8002; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # CORS 响应头 add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always; add_header Access-Control-Allow-Headers "Authorization, Content-Type, Accept, X-Requested-With" always; # OPTIONS 预检请求直接返回 204 if ($request_method = OPTIONS) { return 204; } } }always 参数确保 CORS 头在 4xx/5xx 错误响应中也会附加。 带 Credentials 时不能用通配符 Access-Control-Allow-Origin: * 不能与 Access-Control-Allow-Credentials: true 同时使用,浏览器会拒绝: # 错误:浏览器拒绝 add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Credentials true always;需要改为动态匹配请求来源: add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Vary Origin always;Vary: Origin 告知缓存层不同 Origin 的响应不同,防止缓存污染。 多域名白名单:map 方案 允许特定域名列表访问(带 Credentials): map $http_origin $cors_origin { default ""; "~^https?://localhost(:\d+)?$" $http_origin; "~^https://app\.example\.com$" $http_origin; "~^https://admin\.example\.com$" $http_origin; }server { location /api/ { proxy_pass http://backend-server:8002; add_header Access-Control-Allow-Origin $cors_origin always; add_header Access-Control-Allow-Credentials true always; add_header Vary Origin always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; if ($request_method = OPTIONS) { return 204; } } }非白名单来源 $cors_origin 为空字符串,不会添加 CORS 头,浏览器拒绝跨域请求。 常见坑 proxy_pass 末尾斜杠影响路径 proxy_pass http://backend:8002; # /api/users → /api/users proxy_pass http://backend:8002/; # /api/users → //users(多了斜杠)后端已有 CORS 头导致重复 如果后端也返回了 CORS 头,Nginx 再叠加会导致响应中出现两个 Access-Control-Allow-Origin,浏览器拒绝。需要在后端关闭 CORS,统一由 Nginx 管理: proxy_hide_header Access-Control-Allow-Origin; add_header Access-Control-Allow-Origin $cors_origin always;
