instanceof 在 iframe 中懵圈

起因

由于 iframe 的特殊性很适合作为一个沙箱环境来使用,除了内嵌网页外,还可以用于预览动态生成 HTML 片段,由于 HTML 片段可能来自用户输入,放在 iframe 是一种比较安的全处理方式。

实际上我们也是这样用的,最近在进行在线网页编辑器的开发,需要所见即所得响应用户的操作,iframe 用于页面实时预览。最终我们会生成一份完整的页面 HTML 文件与一张预览图。这次问题也就是出在这个预览图上。

用户保存时我们需要实时生成页面的预览图,前端出图是个老生常谈的问题了,趁手的工具库不多,这里推荐一个出图库 html-to-image,也是这次故事的主角。

html-to-image 的实现是将 HTML 片段嵌入 svg 的 <foreignObject> 中生成一张 svg 图片后绘制到 canvas 实现的,内嵌 HTML 片段时还需要处理各种依赖样式与静态资源,需要将图片字体一类的资源转成 base64 的内联形式。

在使用 html-to-image 遇到了一个问题,针对 iframe 中 HTML 片段进行前端出图时图片元素是空白的,但父级窗口的图片元素导出又是正常的。所以肯定是 iframe 哪些差异引起的......

instanceof

这里也不卖关子了,排查完发现是 html-to-image 使用了 instanceof 做 node 节点检查导致的,很隐蔽的一个 BUG,分享下 instanceof 在 iframe 中使用的一些注意点。

先来复习下 instanceof,MDN 上的描述如下:

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

很好理解:

function Test() {}
const test = new Test();
console.log(test instanceof Test);
// expected output: true
console.log(test instanceof Object);
// expected output: true
复制代码

使用 instanceof 我们可以判断一个元素是否是个 node 节点。

const node = document.createElement('div');
node.innerText = '扶桑若木';
console.log(node instanceof HTMLDivElement); // true
console.log(node instanceof HTMLElement); // true
console.log(node instanceof Element); // true
// node.childNodes[0] 是个 Text 不是一个 Element 节点
console.log(node.childNodes[0] instanceof Element); // false
复制代码

通过这种方式我们可以区分出普通节点与文本节点,当然这里更推荐使用 nodeType 来区分各种 node 节点,使用 nodeType 就没这一堆问题了~

html-to-image 使用 instanceof 来判断 node 节点类型:

image.png

github.com/bubkoo/html…

咋一看没啥问题,但在 iframe 就会有问题,看个示例:

<!DOCTYPE html>
<html lang="en">

<body>
    <div id="a">A</div>
    <iframe></iframe>
    <script>
        const iframe = document.querySelector('iframe');
        iframe.srcdoc = `<body><div id="b">B</div></body>`;
        iframe.addEventListener('load', () => {
            const node = document.createElement('div');
            node.setAttribute('id', 'c');
            node.innerText = 'C';
            iframe.contentDocument.body.appendChild(node);
        });
    </script>
</body>

</html>
复制代码

image.png

  • A 是父级容器节点
  • B 是 iframe 中节点
  • C 是由父级容器创建插入到 iframe 中的节点

问下面的结果是?

const aNode = document.querySelector('#a');
const bNode = iframe.contentDocument.querySelector('#b');
const cNode = iframe.contentDocument.querySelector('#c');

console.log('aNode instanceof HTMLDivElement', aNode instanceof HTMLDivElement);
console.log('bNode instanceof HTMLDivElement', bNode instanceof HTMLDivElement);
console.log('cNode instanceof HTMLDivElement', cNode instanceof HTMLDivElement);
复制代码

image.png

Emmmm.... why ?

image.png

很好理解,iframe 和父级窗口环境是独立的,其创建的对象自然是不一样的

image.png

而 C 节点是由父级容器创建的,所以其 HTMLDivElement 还是属于父级容器的。

如果想获取正确的结果则需要用 node 节点自身的宿主环境判断才行。

bNode instanceof bNode.ownerDocument.defaultView.HTMLDivElement
复制代码

image.png

避坑指南

  • 在 iframe 使用 instanceof 之类的 api 需要关注当前宿主,需要使用 node 所属的宿主属性进行判断。
  • 在 iframe 创建资源使用 iframe 自己的 api 进行操作,使用父级容器的 api 会有原型链指向混乱的问题。
  • 核心就是 iframe 和父级容器对象是完全独立的,除了传递数据之外不要进行引用数据比较

over~

猜你喜欢

转载自juejin.im/post/7050413117641064484