js iframe 简易代码沙箱

iframe 实现代码沙箱是一个非常优质的选择,不仅能隔绝代码,最主要的还能隔绝样式。

实际上我们只要实现一件事情,即 在 iframe 中加载我们想要的代码

我想到两种实现方式:

  • 把代码转为一个可以访问的地址,让 iframe 指向这个地址
  • 解析代码的 dom 结构,替换 iframe 中的 dom

实现 1 - 把代码转为一个可以访问的地址,让 iframe 指向这个地址

第一个实现需要使用 Blob,在要加载的代码改变时,生成 blob 的 url 访问,然后重写 iframe 的地址:

 function getCodeTextarea() {
   return document.getElementsByTagName("textarea")[0];
 }
 
 function onCodeChange() {
   const iframe = document.getElementsByTagName("iframe")[0];

   const blob = new Blob([getCodeTextarea().value], { type: "text/html;charset=UTF-8" });

   iframe.src = URL.createObjectURL(blob);
 }

注意要写字符集,不然就炸了。这一刻突然了解了 Content-Type 请求头为什么可以设置字符集。

实现 2 - 解析代码的 dom 结构,替换 iframe 中的 dom

第二种实现是解析 DOM 并替换,粗暴点,我们可以直接替换根节点。

 function createCodeDom() {
   const parser = new DOMParser();
   return parser.parseFromString(getCodeTextarea().value, "text/html");
 }

 function onCodeChange() {
   const iframe = document.getElementsByTagName("iframe")[0];
   const iframeDom = iframe.contentWindow.document;

   iframeDom.replaceChild(createCodeDom().documentElement, iframeDom.documentElement);
 }

小结

上面的实现方式中,【把代码转为一个可以访问的地址,让 iframe 指向这个地址】更加干净,每个生命周期不会互相污染,但需要消费额外的 iframe 生命周期计算,不过这其实很快。

第二种【解析代码的 dom 结构,替换 iframe 中的 dom】更快,因为直接替换 dom,不会在跑一个 iframe 生命周期,坏处就是上次代码的定时器会在下次代码接着跑,沙箱内部会污染。

附录 - 在 iframe 中跑了 bootstrap

<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <title>index</title>
    <style>
      .root {
        display: grid;
        height: 100vh;
        grid-template-columns: 1fr 1fr;
      }
      textarea {
        height: 100%;
        background-color: transparent;
      }
      iframe {
        height: 100%;
        width: 100%;
        background-color: transparent;
      }
    </style>
  </head>
  <body>
    <div class="root">
      <textarea class="textarea">
        <!DOCTYPE html>
        <html>
          <head>
            <link
              rel="stylesheet"
              href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
              integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu"
              crossorigin="anonymous"
            />
          </head>

          <body>
            <!-- Standard button -->
            <button type="button" class="btn btn-default">(默认样式)Default</button>

            <!-- Provides extra visual weight and identifies the primary action in a set of buttons -->
            <button type="button" class="btn btn-primary">(首选项)Primary</button>

            <!-- Indicates a successful or positive action -->
            <button type="button" class="btn btn-success">(成功)Success</button>

            <!-- Contextual button for informational alert messages -->
            <button type="button" class="btn btn-info">(一般信息)Info</button>

            <!-- Indicates caution should be taken with this action -->
            <button type="button" class="btn btn-warning">(警告)Warning</button>

            <!-- Indicates a dangerous or potentially negative action -->
            <button type="button" class="btn btn-danger">(危险)Danger</button>

            <!-- Deemphasize a button by making it look like a link while maintaining button behavior -->
            <button type="button" class="btn btn-link">(链接)Link</button>
          </body>
        </html>
      </textarea>

      <iframe></iframe>
    </div>

    <script>
      function getCodeTextarea() {
        return document.getElementsByTagName("textarea")[0];
      }
      
      // 实现 1 - 把代码转为一个可以访问的地址,让 iframe 指向这个地址
      function onCodeChange() {
        const iframe = document.getElementsByTagName("iframe")[0];

        const blob = new Blob([getCodeTextarea().value], { type: "text/html;charset=UTF-8" });

        iframe.src = URL.createObjectURL(blob);
      }

      // function createCodeDom() {
      //   const parser = new DOMParser();
      //   return parser.parseFromString(getCodeTextarea().value, "text/html");
      // }

      // 实现 2 - 解析代码的 dom 结构,替换 iframe 中的 dom
      // function onCodeChange() {
      //   const iframe = document.getElementsByTagName("iframe")[0];
      //   const iframeDom = iframe.contentWindow.document;

      //   iframeDom.replaceChild(createCodeDom().documentElement, iframeDom.documentElement);
      // }

      function listenCodeChange() {
        onCodeChange();
        getCodeTextarea().addEventListener("input", onCodeChange);
        return () => void getCodeTextarea().removeEventListener("input", onCodeChange);
      }

      function main() {
        listenCodeChange();
      }

      main();
    </script>
  </body>
</html>

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值