模板字符串里包含反引号
反引号是模板字符串的开始/结束符,中间再出现反引号会直接语法错误:
// ❌ 语法错误
`
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)而不是整体代理。
