JS如何无客户端感知执行表达式求值并获取逐行执行结果
静态REPL逐行预校验无副作用实现方案
核心原则非常明确:预校验阶段的代码绝对不能直接在主页面上下文执行,必须跑在完全隔离的沙箱环境里,从根源上避免副作用漏到用户可见的主环境,具体落地可以按下面的步骤做:
- 搭建独立隔离的执行上下文
两种成熟方案二选一即可:- 隐藏同域iframe:创建一个设置了
display: none的空iframe,取它的contentWindow作为预校验的执行上下文。这个上下文的window、document和主页面完全独立,本身就不会直接修改主页面DOM、触发主页面弹窗,兼容性最好。 - 原生ShadowRealm API:如果不需要兼容过低版本浏览器,直接用ES2022标准的
ShadowRealm创建独立JS执行上下文,天生和主环境内存隔离,开销比iframe小很多,安全性更高。
- 隐藏同域iframe:创建一个设置了
- 全量劫持沙箱内的副作用API
光有隔离上下文还不够,要把沙箱里所有会产生可感知副作用的全局API全部替换为空代理函数,注意只拦对外操作的方法,不要改Object、Array、String这类JS内置基础构造函数,避免影响正常语法运算:- 弹窗类:把沙箱内的
alert、confirm、prompt直接替换为() => undefined,执行到相关代码不会真的弹出系统弹窗。 - DOM操作类:如果用iframe做沙箱,直接把
document上的appendChild、removeChild、innerHTML、write等所有修改DOM的方法替换为空函数,不会真的渲染页面内容。 - 日志输出类:把
console对象下的log、warn、error、info等所有输出方法替换为空函数,执行时不会真的往控制台打日志。 - 其他对外API:把
fetch、XMLHttpRequest、localStorage、sessionStorage、window.open、setTimeout、setInterval这类会触发网络请求、存储写入、定时器、新窗口打开的API全部做Mock拦截,替换为空函数或者返回固定Mock值,不准真的触发对外操作。
- 弹窗类:把沙箱内的
- 逐行执行+状态捕获逻辑
预校验逐行跑代码的时候,不要直接裸调eval,要包一层专用执行器:- 先做语法预检:每一行代码先丢到
new Function(codeLine)里做语法校验,语法错误直接捕获返回,根本不进入执行阶段。 - 维护连续作用域:为了支持
let/const/class这类块级作用域声明,不要把每行代码单独丢到全局执行,要维护一个持续拼接的函数作用域,每新增一行代码就把之前所有合法行和当前行拼接起来,在沙箱里重新生成执行函数,保证前一行声明的变量下一行能正常访问,不会出现作用域断裂。 - 变量变更捕获:每执行完一行代码,就把当前沙箱全局/作用域内的变量做一次快照,和执行前的快照做对比,就能拿到这行代码新增、修改的所有变量值。
- 运行时错误捕获:所有执行逻辑用
try/catch包裹,只要出现引用错误、类型错误等运行时异常,立刻捕获错误信息返回给客户端,终止后续行的校验。
- 先做语法预检:每一行代码先丢到
- 正式执行阶段
等所有代码行逐行校验全通过后,不要复用沙箱里的执行状态,直接把整段完整代码放到主页面的正式执行上下文里正常eval即可,这时候不需要拦截任何API,用户可以正常看到代码执行的弹窗、DOM变更、日志输出等预期效果。
避坑提醒:如果用ShadowRealm做沙箱,它和主上下文之间的变量传递是结构化克隆的,无法传递函数、Symbol等不可克隆的值,如果你做的REPL需要支持这类值的校验,优先选iframe方案。
内容的提问来源于stack exchange,提问作者lolBOT V9.17
相关产品推荐
相关产品推荐

