美文网首页面试题
iframe -- postMessage

iframe -- postMessage

作者: 人话博客 | 来源:发表于2019-04-13 11:42 被阅读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

    • 在 1.html 中嵌入 2.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一样.
    }
    

    几个注意点:

    • iframe 嵌套获取只能获取一层.(比如1.html 嵌套 iframe(2.html) -> 2.html 嵌套 iframe(3.html).那么 1.html只能获取到2.html 2.html只能获取到3.html

    • window.top 获取当前iframe嵌套层级的最顶级(这里是1.html)

    • window.parent 获取当前iframe的上一层级(2.html就是1.html,3.html就是2.html)

    image.png

    同源iframe嵌套总结:

    • 可以把嵌套的iframe理解成一个普通的大DOM节点.
    • 它是异步加载的必须等待onload执行完成.
    • 指向完成后拿到 contentWindow 和 contentDocument 之后,就可以无缝的操作了.
    • 注意,iframe只能拿一层.

    跨域的iframe通信

    两个页面

    • 一个 1.html 链接地址为: http://127.0.0.1:12345/1.html
    • 一个 4.html 链接地址为: http://127.0.0.1:50874/iframe-01/4.html
    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

    发现报错了.

    回想一下:

    • 1.html
    • 1.html 中利用 iframe 嵌套了 2.html
    • 它俩的属于不同的域(端口号不同)
    • 现在我想从 1.html 传递数据到 2.html.
    • 1.html 里面使用 window.postMessage() 发现报错了.

    问题出在哪?

    重新查看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

    最后总结:

    • 跨域不光是前台都后台的跨域.
    • iframe嵌套不同源的资源也存在跨域.
      • 如果一级域名相同,可以使用document.domain 进行跨域操作.
      • 如果域名完全不同,可以使用 postMessage 进行跨域.
        • postMessage 传递数据,本质上仍然是自己给自己传
        • 在给某个iframe传递数据时,必须首先拿到当前iframe的contentWindow.然后在contentWindow上使用postMessage 传递数据.

    码云code

    相关文章

      网友评论

        本文标题:iframe -- postMessage

        本文链接:https://www.haomeiwen.com/subject/bxslwqtx.html