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 时,结尾分号可能没有(最后一项),用 [^;]*.*?; 更稳:

function getCookie(name) {
    const match = document.cookie.match(
        new RegExp(`${name}=([^;]*)`)
    );
    return match ? match[1] : undefined;
}

[^;]* 含义:匹配零个或多个不是分号的字符,不管 cookie 是不是最后一项都能正确提取。