面试题

iframe -- postMessage

2019-04-13  本文已影响0人  人话博客

之前一提到跨域,都是前端到后台的问题.

其实,在网页中嵌套非同源的iframe也存在跨域的问题.

比如,在你自己的页面里利用 iframe 嵌套百度的网页,两个页面存在通信的话,就存在跨域的问题.

对.如果不存在通信,就不存在所谓的跨域问题.


iframe 是干嘛的?

在当前网页中利用 iframe 可以嵌入另一个完整的网页.

这个就是 iframe 干的事情.

这样做的意义在哪呢?为什么我要在我的一个网页里嵌入另外一个网页?

其实都是我瞎诌的.为了让自己能够更快的理解这个玩意,编就编吧.


iframe 嵌入自己的网页.

开发web的时候,使用iframe嵌入自己的网页.

这里嵌入自己的网页潜台词就是两个网页是同源的.

我们自己开发的网页,当然是部署在自己的服务器上.

不出意外的话,那协议+域名+端口号都是一致的.

所以,它们是同源.

iframe 的基本语法如下.

<iframe src="2.html" height="300" width="500" id='demo' name="demo"></iframe>

项目结构:

image.png

同源 3 个静态页面. 1.html 2.html 3.html

image.png

1.html

<body>
  <h1 id="h1">我是1.html网页</h1>
  <iframe src="2.html" height="300" width="500" id='demo' name="demo"></iframe>
</body>

2.html

<body>
  <h1 id="h1">我是2.html网页</h1>
  <iframe src="3.html" width="200" height="200" id="demo2" name="demo2"></iframe>
</body>

3.html

<body>
  <h1 id="h1">我是3.html</h1>
</body>

嵌入完毕之后呢?

光显示的话,也就到这结束了.

如果需要操作同源下嵌套的iframe.
可以按照以下步骤.

1.获取指定的iframe

1.html
<!--同源嵌套2.html-->
<iframe src="2.html" height="300" width="500" id='demo'></iframe>

let iframe = document.getElementById('demo')

接跟获取一个dom元素一样.利用 getElementById('demo') 即可.

2.获取iframe内部一些关键属性

对于一个完整的html页面来说.
它有window,也有document.

对于嵌套的iframe来说,也不例外.

但是指的注意的是,一定要在等待iframe这个嵌套页面加载完毕之后,在去进行获取.

iframe页面是异步加载的.

// 等待iframe嵌套的页面加载完毕
iframe.onload = function () {
    let iframeWindow = iframe.contentWindow
    let iframeDocument = iframe.contentDocument
}

3. 在同源的情况下

iframe 可以等同于一个普通的DOM节点(不过它是异步加载的)
拿到这个iframe的document和window之后

iframe.onload = function () {
    let iframeWindow = iframe.contentWindow
    let iframeDocument = iframe.contentDocument
    
    iframeDocument.getXXXX ==== 获取或修改嵌套iframe的dom结构.就像操作自己的document一样
    iframeWindow.xxxx === 获取嵌套iframe的方法或者属性或者对象.就像操作自己的window一样.
}

几个注意点:

image.png

同源iframe嵌套总结:


跨域的iframe通信

两个页面

1.html -> http://127.0.0.1:12345/1.html
<body>
   <iframe src="http://127.0.0.1:50874/iframe-01/4.html" frameborder="0" id='frame'></iframe>
</body>
4.html ---> http://127.0.0.1:50874/iframe-01/4.html
<body>
    <h1>我是4.html 端口号50874</h1>
</body>
<script>
    // 在 4.html定义的全局对象 window.obj
    var global4Obj = {
    name: '李四'
  }
</script>

它俩的端口号不一致.

12345 | 50874

端口号不一致时,并不影响iframe嵌套.
(经常瞎搞在自己页面里嵌套一个baidu首页)

但是会影响它俩之前的数据跨域请求.

比如,按照同源的方式,去操作iframe的页面,会得到这样一个提示.

1.html -> http://127.0.0.1:12345/1.html
let iframe = document.getElementById('frame')
  iframe.onload = function () {
    const window = iframe.contentWindow
    const document = iframe.contentDocument
    console.log('window',window)
    console.log('document',document)
  }

1.html控制台输出

image.png

发现 document 获取不到.但是获取的到window.

window上有设置了一个全局对象 obj

于是输出:

  let iframe = document.getElementById('frame')
  iframe.onload = function () {
    const window = iframe.contentWindow
    const document = iframe.contentDocument
    console.log('window',window)
    console.log('document',document)
    console.log(window.obj.name)
  }
image.png

很明显的错误提示,操作跨域了.
不能访问跨域iframe的window上的全局属性和方法.
拿不到document(这里为null)了,就更加不能操作dom元素了.

所以,如果嵌套的iframe跨域了,默认情况下只能加载下来看,不能做任何其他的操作.


利用postMessage进行iframe跨域通信

方式一:同一级域名不同二级域名

比如 www.a.com/index.htmlapi.a.com/index.html

由于它们的的一级域名一一致.

可以利用 document.domain 进行跨域操作.

a.html
document.domain = 'a.com'
b.html
document.domain = 'b.com'

双方都设置同样的域之后,就可以像同源非跨域的iframe那样操作了.

方式二.使用postMessage

看了很多博客关于postMessage方法的使用.

大致说的都是:

如果嵌套的iframe存在跨域,那么就可以使用postMessage进行通信.

于是心想,这也太简单了吧.

就开始吭哧吭哧写代码.

image.png
1.html -> http://127.0.0.1:12345/1.html
<body>
  <!-- <b>12345</b> -->
  <h1>我是1.html</h1>
  <p></p>
  <iframe src="http://127.0.0.1:50874/iframe-01/4.html" frameborder="0" id='frame'></iframe>
</body>

4.html ->http://127.0.0.1:50874/iframe-01/4.html

<body>
  <h2>我是4.html</h2>
  <p></p>
</body>

现在,我想让 1.html 跨域的给 2.html 传递数据.

于是在 1.html 中.

window.postMessage('1.html的数据','http://127.0.0.1:50874/')

在 4.html 中

  window.addEventListener('message', function (e) {
    console.log(e.data)
  },false)

想着非常完美,也太简单了.

执行浏览器.

image.png

发现报错了.

回想一下:

问题出在哪?

重新查看API文档之后,发现理解是错的.

本质上利用 postMessage 跨域,不是 1.html2.html 发数据.

应该是是 2.html2.html 发数据

体现在代码上应该是就是:

1.html -> http://127.0.0.1:12345/1.html
window.onload = function () {
    let frame = document.getElementById('frame')
    // 相当于还是自己在给自己传啊!!!
    document.getElementById('text').addEventListener('input', function () {
      frame.contentWindow.postMessage(this.value, 'http://127.0.0.1:50874/')
    }, false)
  }

传递数据的是 frame.contentWindow.postMessage
而不是想当然的 window.postMessage

测试一下想法.

1.html中 --> http://127.0.0.1:12345/1.html

1.html -> http://127.0.0.1:12345/1.html

<body>
  <!-- <b>12345</b> -->
  <h1>我是1.html</h1>
  <p></p>
  <iframe src="http://127.0.0.1:56434/iframe-01/4.html" width="500" height="300" id='frame'></iframe>
  <!-- 设置一个按钮,点击按钮往跨域的4.html发送数据 -->
  <button class="postMessage">postMessage</button>
  <!-- 用于接受4.html跨域提交过来的数据 -->
  <p class="result"></p>
</body>

1.html -> http://127.0.0.1:12345/1.html
<script>
// 第一步,要拿到iframe,这里主要是拿到iframe.contentWindow
  let iframe = document.getElementById('frame')
  let iframeWindow = null
  iframe.addEventListener('load', function () {
    console.log('iframe loaded')
    iframeWindow = iframe.contentWindow // 利用iframe.contentWindow 也就是4.html 的window对象.
  },false)
  
// 给4.html利用postMessage跨域发送数据
let postMessageButton = document.querySelector('.postMessage')
  postMessageButton.addEventListener('click', function () {
    // 这里是使用iframeWindow.也就是iframe自己的window发送数据.
    // 而不是使用window.
    // 就相当于使用 postMessage 其实本质上还是自己在给自己发数据.
    iframeWindow.postMessage('1.html发送过来的数据','http://127.0.0.1:56434') // 第二个参数,指定4.html的域名
  }, false)
  
  // 用于接收 4.html提交回来的数据
  const result = document.querySelector('.result')
  window.addEventListener('message', function (event) {
    result.innerText = event.data
    console.log(event.source)
    console.log(event)
  }, false)
</script>

在4.html中 --> ->http://127.0.0.1:50874/iframe-01/4.html

4.html -> http://127.0.0.1:50874/iframe-01/4.html

<body>
  <h2>我是4.html</h2>
  <p></p>
  <!-- 用于接受 1.html 使用 postMessage 发送过来的数据 -->
  <p class="result"></p>
  <!-- 设置一个按钮给 1.html 发送数据 -->
  <button class="postMessage">postMessage</button>
</body>

4.html->http://127.0.0.1:50874/iframe-01/4.html

<script>
   let result = document.querySelector('.result')
  window.addEventListener('message', function (e) {
    result.innerText = e.data
  },false)

  let postMessageButton = document.querySelector('.postMessage')
  postMessageButton.addEventListener('click', function () {
    window.parent.postMessage('4.html发送过来的数据','http://127.0.0.1:12345/') // 注意,这里由于我们知道 4.html 被 1.html 嵌套了,所以使用 window.parent 可以拿到 1.html 环境中的window.
    
    // 由于我们也知道 1.html 是当前iframe嵌套层级中的最顶层,也可以使用window.top.postMessage() ....
  }, false)   
</script>

查看结果:

image.png

最后总结:

码云code

上一篇 下一篇

猜你喜欢

热点阅读