无需调试器/源码修改,如何通过书签脚本修改BoundFunctionObject闭包变量
访问并修改JavaScript闭包变量的迂回方案
针对你提出的场景(无法手动调试、无源码修改权限、不能仅做视觉修改),以下是几个可行的迂回实现方案:
1. 浏览器扩展前置注入脚本
利用浏览器扩展的提前注入能力(设置run_at: document_start),在页面原生脚本执行前拦截并修改代码逻辑,将闭包变量暴露到全局:
实现思路
- 编写Chrome/Firefox扩展,配置content script在文档开始时注入;
- 重写
Function构造函数,捕获目标IIFE的创建过程; - 修改IIFE的函数体,将
internal_variable挂载到全局对象(如window)。
示例代码(扩展content script)
// 保存原生Function构造函数 const originalFunction = window.Function; // 重写Function,拦截目标脚本的函数创建 window.Function = function(...args) { const functionBody = args[0]; // 匹配目标IIFE的特征代码(可根据实际场景调整) if (functionBody.includes('let internal_variable = 0;') && functionBody.includes('_internal_buttonClicked')) { // 修改函数体,把内部变量暴露到全局 const modifiedBody = functionBody.replace( 'let internal_variable = 0;', 'let internal_variable = 0; window.__exposedInternalVar = internal_variable;' ); return originalFunction.apply(this, [modifiedBody, ...args.slice(1)]); } return originalFunction.apply(this, args); };
后续你的书签脚本就可以直接执行window.__exposedInternalVar = -100,实现对闭包变量的修改。
2. 利用Chrome DevTools Protocol(CDP)自动调试
通过CDP实现自动化调试流程,无需手动操作调试器,自动设置断点、修改闭包变量:
实现思路
- 使用Puppeteer/Playwright等工具连接浏览器;
- 自动在
_internal_buttonClicked函数执行处设置断点; - 断点命中时,通过CDP接口获取闭包作用域,修改
internal_variable的值; - 自动恢复脚本执行,完成修改。
示例代码(Puppeteer)
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto('你的目标页面URL'); // 启用CDP调试会话 const cdpSession = await page.target().createCDPSession(); await cdpSession.send('Debugger.enable'); // 监听断点触发事件 cdpSession.on('Debugger.paused', async (event) => { const callFrame = event.callFrames[0]; // 找到闭包作用域 const closureScope = callFrame.scopeChain.find(scope => scope.type === 'closure'); if (closureScope) { // 修改internal_variable的值为-100 await cdpSession.send('Debugger.setVariableValue', { scopeNumber: closureScope.scopeNumber, variableName: 'internal_variable', newValue: { value: -100 } }); // 恢复脚本执行 await cdpSession.send('Debugger.resume'); } }); // 设置断点:匹配_internal_buttonClicked函数的代码位置 await cdpSession.send('Debugger.setBreakpointByUrl', { url: page.url(), lineNumber: 5, // 对应目标函数的行号(需根据实际页面调整) columnNumber: 10 }); // 触发按钮点击,让函数执行到断点 await page.click('#myButton'); // 验证修改结果 const buttonText = await page.$eval('#myButton', el => el.innerText); console.log('修改后按钮文本:', buttonText); // 应为"Clicked -99 times!" await browser.close(); })();
这个方案完全自动化,不涉及手动调试,满足你的要求。
3. 劫持Function.prototype.bind捕获闭包(限未压缩代码)
如果目标页面代码未被压缩混淆,可通过重写bind方法,结合函数toString()提取闭包逻辑:
实现思路
- 页面脚本执行前注入代码,重写
Function.prototype.bind; - 当目标函数被绑定时,通过
toString()获取其源码; - 构造一个可访问闭包的辅助函数,将内部变量暴露出来。
示例代码(前置注入)
const originalBind = Function.prototype.bind; Function.prototype.bind = function(...args) { const boundFn = originalBind.apply(this, args); // 匹配目标函数的特征 if (this.toString().includes('_internal_buttonClicked')) { // 构造可提取闭包的函数 const extractClosure = new Function(` ${this.toString()} return () => ({ internal_variable }); `)(); // 将闭包变量挂载到绑定后的函数上 boundFn.__closure = extractClosure(); } return boundFn; };
之后书签脚本可通过buttonClicked.__closure.internal_variable = -100修改值,但此方法仅适用于未压缩的代码,webpack打包后的压缩代码会失效。
方案局限性说明
- 浏览器扩展方案需要用户安装扩展,拥有浏览器权限;
- CDP自动化方案依赖Node.js环境和自动化工具,适合批量或场景化修改;
- 劫持bind的方案仅对未压缩代码有效,通用性较差。
内容的提问来源于stack exchange,提问作者Kaia
相关产品推荐
相关产品推荐

