+
24
-

回答

这个问题出现的核心原因是:浏览器的 Global Scope(全局作用域)没有被彻底重置

尽管你使用了 doc.open() 和 doc.write(),但这仅仅是清空了 iframe 的 DOM 结构。在某些现代浏览器环境下,复用同一个 iframe 元素时,其挂载的 Window 对象(以及挂载在上面的全局变量)可能并没有被立即销毁或完全重置。

当你第一次运行代码时:const scene = ... 在 iframe 的全局作用域中声明了 scene。

当你第二次运行代码(没有销毁 iframe 标签,只是重写内容)时:浏览器试图再次在同一个作用域执行 const scene = ...。由于 const 和 let 不允许在同一作用域下重复声明(Re-declaration),所以抛出了 SyntaxError。

解决方案

要解决这个问题,最稳妥的方法不是重写 iframe 的内容,而是彻底销毁旧的 iframe,并创建一个新的 iframe。这样可以保证拥有一个全新的、纯净的 Window 上下文。

既然你使用了 nextTick,我推测你是在使用 Vue (或类似框架)。以下是几种解决方案:

方案一:利用 Vue 的 :key 强制重新渲染 (推荐)

这是最简单且最符合 Vue 逻辑的方法。给 iframe 绑定一个唯一的 key。每次你需要更新代码时,改变这个 key 的值。Vue 会检测到 key 变化,从而自动销毁旧组件(iframe),并挂载一个新的 iframe。

HTML:

<!-- 给 iframe 绑定一个动态的 key -->
<iframe id="previewFrame" :key="iframeKey"></iframe>

JavaScript:

data() {
  return {
    iframeKey: 0, // 初始 key
    // ...
  };
},
methods: {
  runCode() {
    // 1. 更新 key,强制 Vue 销毁旧 iframe 创建新 iframe
    this.iframeKey++; 

    // 2. 等待 DOM 更新完毕(新 iframe 创建完成)
    this.$nextTick(() => {
        const iframe = document.getElementById('previewFrame');
        if(iframe) {
            const doc = iframe.contentDocument || iframe.contentWindow.document;
            doc.open();
            doc.write(html);
            doc.close();
        }
    });
  }
}
方案二:原生 JS 移除并重新创建 DOM

如果你不方便使用 key,可以在 nextTick 里手动操作 DOM:

nextTick(() => {
    const container = document.getElementById('iframe-container'); // iframe 的父容器
    const oldIframe = document.getElementById('previewFrame');

    // 1. 移除旧 iframe
    if (oldIframe) {
        container.removeChild(oldIframe);
    }

    // 2. 创建新 iframe
    const iframe = document.createElement('iframe');
    iframe.id = 'previewFrame';
    // 设置其他属性,如样式等
    iframe.style.width = '100%';
    iframe.style.height = '100%';

    // 3. 挂载新 iframe
    container.appendChild(iframe);

    // 4. 写入内容
    const doc = iframe.contentDocument || iframe.contentWindow.document;
    doc.open();
    doc.write(html);
    doc.close();
});

重要提示:关于 Three.js 的 WebGL 上下文丢失问题

你在做 Three.js 的编辑器/预览,除了上述的变量报错,还必定会遇到另一个问题:"Too many active WebGL contexts"

浏览器对同时存在的 WebGL 上下文数量有限制(通常是 16 个左右)。即使你清空了 iframe,旧的 WebGL Context 有时不会立即释放显存。如果用户多次点击“运行”,浏览器最终会崩溃或 WebGL 报错无法显示。

建议做法:

在你的注入代码(html 字符串)中,或者在销毁 iframe 之前,最好能显式调用 Three.js 的资源释放逻辑。虽然销毁 iframe 理论上浏览器会回收,但在高频操作下,手动清理更安全。

如果你无法控制用户写的代码,方案一(强制销毁 iframe DOM) 是最能确保 WebGL 上下文被浏览器回收的方式,因为 DOM 节点被移除通常会触发浏览器的垃圾回收机制。

总结

不要复用 iframe,用完即弃。每次运行代码时,确保是一个全新的 <iframe> 标签。

网友回复

我知道答案,我要回答