如何获取iframe中eval内console.log的执行代码行号?
解决iframe中eval执行代码时console.log无法获取真实行号的问题
我通过将JavaScript代码传入iframe并在eval中执行,来规避引入React、ReactDOM等库时的代码量限制。已经重定义了内置的console.log方法用于向父窗口发送消息,但无法获取该console.log调用对应的真实代码行号——使用new Error().stack只能拿到eval所在的行号,而非用户代码里的实际行号。
现有iframe内脚本代码:
<script> const _log = console.log; console.log = function (...args) { // Send to parent window.parent.postMessage( { id: '${id}', source: 'code-preview', message: args, }, '*', ); _log.apply(console, args); }; window.addEventListener('message', (event) => { const handleError = (error) => { const root = document.getElementById('root'); root.innerHTML = '<div style="color: red;">' + error + '</div>'; console.error(error); }; window.addEventListener('error', (event) => { event.preventDefault(); handleError(event.error) }); try { eval(event.data) } catch(error){ if(error instanceof Error){ return { code: '', error: error.message } } else { throw error; } } }, false); </script>
可行解决方案
方案1:解析Error栈信息结合用户代码行号映射
修改console.log的重写逻辑,通过生成Error对象解析栈帧,过滤掉无关信息后提取用户代码对应的行号:
const _log = console.log; // 存储用户传入的原始代码,用于后续行号校验 let userCode = ''; console.log = function (...args) { const err = new Error(); // 拆分栈信息,过滤掉当前console.log和eval相关的栈帧 const stackFrames = err.stack.split('\n').filter(frame => !frame.includes('console.log') && !frame.includes('eval') ); if (stackFrames.length > 0) { // 提取行号(以Chrome栈帧格式为例,如"at <anonymous>:3:13") const lineMatch = stackFrames[0].match(/:(\d+):(\d+)/); if (lineMatch) { // eval中行号直接对应用户代码的行号 const lineNumber = parseInt(lineMatch[1], 10); // 向父窗口发送消息时携带行号 window.parent.postMessage( { id: '${id}', source: 'code-preview', message: args, line: lineNumber }, '*' ); } } _log.apply(console, args); }; window.addEventListener('message', (event) => { const handleError = (error) => { const root = document.getElementById('root'); root.innerHTML = '<div style="color: red;">' + error + '</div>'; console.error(error); }; window.addEventListener('error', (event) => { event.preventDefault(); handleError(event.error) }); try { userCode = event.data; eval(event.data); } catch(error){ if(error instanceof Error){ return { code: '', error: error.message } } else { throw error; } } }, false);
方案2:用Function替代eval并指定自定义文件名
使用new Function()替代eval,通过构造函数最后一个参数指定自定义文件名,这样栈信息会明确标识该文件名,行号直接对应用户代码的真实行号,可靠性更高:
const _log = console.log; console.log = function (...args) { const err = new Error(); const stackFrames = err.stack.split('\n'); // 查找自定义文件名对应的栈帧(这里指定为"user-code.js") const userFrame = stackFrames.find(frame => frame.includes('user-code.js')); if (userFrame) { const lineMatch = userFrame.match(/user-code.js:(\d+):(\d+)/); if (lineMatch) { const lineNumber = parseInt(lineMatch[1], 10); window.parent.postMessage( { id: '${id}', source: 'code-preview', message: args, line: lineNumber }, '*' ); } } _log.apply(console, args); }; window.addEventListener('message', (event) => { const handleError = (error) => { const root = document.getElementById('root'); root.innerHTML = '<div style="color: red;">' + error + '</div>'; console.error(error); }; window.addEventListener('error', (event) => { event.preventDefault(); handleError(event.error) }); try { // 用Function替代eval,最后一个参数传入自定义文件名 const userFunction = new Function(event.data); userFunction(); } catch(error){ if(error instanceof Error){ return { code: '', error: error.message } } else { throw error; } } }, false);
注意事项
- 不同浏览器的Error栈信息格式存在差异,需要针对Chrome、Firefox、Safari等主流浏览器做兼容调整。
- 使用
Function替代eval不仅能优化行号获取的准确性,还能避免eval带来的作用域污染问题,性能表现也更优。
内容的提问来源于stack exchange,提问作者Francisco Martin
相关产品推荐
相关产品推荐

