• 不同页面通信与跨域


    不同页面通信与跨域

    0. 前言

    相信跨域有什么手段,大家都背得滚瓜烂熟了。现在我们来做一些不在同一个tab页面或者跨域的实践。

    1. localstorage

    1.1 onstorage事件

    localstorage是浏览器同域标签共用的存储空间,所以可以用来实现多标签之间的通信。html5出现了一个事件: onstorage,我们在window对象上添加监听就可以监听到变化: window.addEventListener('storage', (e) => console.log(e))

    需要注意,此事件是非当前页面对localStorage进行修改时才会触发,当前页面修改localStorage不会触发监听函数。如果实在是要,自己重写一个方法吧,要不就在修改的时候把自己改的内容po上去。

    示例: js:

    if(!localStorage.getItem('a')){
    	localStorage.setItem('a',1)
    }else{
    	var s = localStorage.getItem('a')
    	localStorage.setItem('a',+s+1)
    }
    window.addEventListener('storage', (e) => console.log(e))
    复制代码

    我们新建两个html分别叫1.html和2.html,并加上上面的js,于是我们每次打开或者刷新该页面就会给a加上1。需要注意的是,如果是双击打开,是在file://协议下的,而且不会触发storage事件,但是会给a加上1,所以可以做一个功能,计算本地某个文件被打开了多少次。如果我们用服务器打开,我们的不同tab页面通信完成了,而且是实时的。

    2. 玩转iframe

    我们都知道frame可以跨域,那么我们来试一下。下面例子,都是一个html内嵌iframe,当然你直接打开iframe那个文件,没什么意义的

    2.1 利用hash变化传递信息实现父子窗口通信(能跨域)

    父窗口:1.html

    html:

    <iframe name="myIframe" src="http://localhost:1000/2.html"></iframe>
    复制代码

    js:

    var originURL = myIframe.location.href
    var i = document.querySelector('iframe')
    i.onload= function(){//这是异步加载的iframe
      i.src += '#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
    }
    复制代码

    子窗口:2.html

    window.onhashchange = function() {
        console.log(window.location.hash)
    }
    复制代码

    我们打开父窗口,发现子窗口的js已经跑起来了。

    既然能跨域,我们直接双击打开1.html,发现还是可以,这个例子双击打开和服务器打开都能达到目的

    2.2 父调用子页面的js或者反过来调用

    父调子:还是基于前面的条件

    var i = document.querySelector('iframe')
    i.onload= function(){
    myIframe.window.f();
    }
    复制代码

    子:2.html

    function f(){
    	console.log('this is 2.html')
    }
    复制代码

    子调父: 子:2.html

    parent.fn1()
    复制代码

    父:1.html

    function fn1(s){
    	console.log('this is 1.html')
    }
    }
    复制代码

    当然,你直接打开2.html是没意义的而且是报错:Uncaught TypeError: parent.fn1 is not a function

    这个需要注意,不能跨域,所以双击打开以及不同域是报错的:Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.,只能服务器打开

    2.3 window.name (能跨域)

    类似于vue、react的prop父子传值,只要在父窗口设置iframe标签的name,在子窗口就可以读到。

    父窗口:1.html

    <iframe  name="myIframe" src="http://localhost:1000/2.html"></iframe>
    复制代码

    子窗口:2.html

    console.log(window.name)
    复制代码

    少年,放心地双击打开吧,有效果的。

    2.4 postmessage(能跨域)

    H5之后为window新增了window.postMessage()方法,第一个参数是要发送的数据,第二个参数是域名。

    父窗口:1.html

    <iframe id="test" name="myIframe" src="http://localhost:1000/2.html"></iframe>
    
    //js
    var frame = document.querySelector('iframe')
    frame.onload = function(argument) {
    	window.frames[0].postMessage('data from html1', '*');
    }
    复制代码

    子窗口:2.html

    window.onmessage = function (e) {
    	console.log(e.data)
    }
    复制代码

    可以跨域,所以能直接双击打开可以看见效果。

    上面的父子窗口,是指一个html里面的iframe标签引入另一个html。

    3. 非同域的两个tab页面通信

    也就是两个毫无关系的tab页面通信(比如我打开一个baidu和一个github),怎么通?

    当然baidu和github能不能通信,我们不知道,得问他们家的开发。前面我们已经知道,iframe能跨域,localstorage能使得两个tab页面通信。那我们就来试一下,iframe桥接两个互不相干的tab页面。注意,bridge是一个html,其他两个tab是指浏览器打开的两个html文件。你可以另外建立两个不同的html,也可以建立两个一模一样的html,然后双击打开也好、服务器打开也好,有两个就可以了。

    下面,我们把桥接的iframe叫做bridge.html吧。我们用node打开,监听本地的1000端口。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
      </head>
      <body>
        <h1>hi</h1>
      </body>
      <script type="text/javascript">
    window.addEventListener("storage", function(ev){
        if (ev.key == 'info') {
            window.parent.postMessage(ev.newValue,'*');
        }
    });
    
    
    window.addEventListener('message',function(e){
        // 接受到父文档的消息后,广播给其他的同源页面
        localStorage.setItem('info',e.data);
    });
      </script>
    </html>
    复制代码

    我们再来两个页面,内容如下。接着我们可以以n种不同方式分别打开,反正是非同源就可以了

    <!DOCTYPE html>
    <html>
    <head>
    	<title></title>
    </head>
    <body>
        <input id="ipt" type="text" name="">
        <button onclick="sub()">sub</button>
        <p id="cont"></p>
        <iframe src="http://localhost:1000/" style="display: none"></iframe>
    </body>
    <script type="text/javascript">
        var ipt = document.querySelector('#ipt')
        function sub(){
          document.querySelector('iframe').contentWindow.postMessage(ipt.value,'*');
          cont.innerHTML +='我:'+ ipt.value + '<br>'
          ipt.value = ''
        }
        window.addEventListener('message',function(e){
          if(e.data) cont.innerHTML +='对方:'+ e.data + '<br>'
          
        });
    </script>
    </html>
    复制代码

    然后一个简单的聊天就搞定了,试试看。加上websocket的话,还可以非同源聊天呢,其他的可以自己随意设置了。

    1

    从1到2的信息实时传递更新就这样子成功了,反之亦然。

    4.MessageChannel

    顾名思义,信息通道。允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据m,而且在 Web Worker 中可用。可以控制台打印,发现有两个属性,portl1和port2。一个页面内嵌与iframe最常用这种方法。

    就像一条管道,一边出一边进,我们可以给postmessage方法加上第三个参数:

    var channel = new MessageChannel();
    channel.port1.addEventListener("message", function(e){
        window.parent.postMessage(e,'*',[channel.port2]);
        channel = null;
    });
    复制代码

    深拷贝

    n种不同的对象类型?环引用?怎么做到特别容易的深拷?

    然而真的做到了:

    var obj ={a:1,b:2,c:{d:3,e:[{f:1,g:2}]},h:null}
    obj.h = obj
    var res 
    new Promise(resolve => {
        var channel = new MessageChannel();
        channel.port2.onmessage = ev => resolve(ev.data);
        channel.port1.postMessage(obj);
      }).then(data=>{res = data})
    复制代码

    传了数据给管道,管道传回来一个长得一模一样的数据回来,实现了深拷贝。我们叫他结构化克隆,能处理对象循环依赖和大部分的内置对象。比如postMessage发消息给子窗口或者WebWorker的时候就会经常用到,拿到数据进行处理,但不污染原数据。

  • 相关阅读:
    解决margin 外边距合并问题
    tsx 校验 以及写法
    tsx 引入文件找不到
    Ubuntu 16.04安装和卸载软件命令
    Java8 中使用Stream 让List 转 Map使用总结
    Java8 Stream流式编程浅析
    浅析IOC 和 DI
    [ Java面试题 ]Java 开发岗面试知识点解析
    Intellij Idea 常用快捷键总结+实用小技巧
    Intellij Idea基础设置
  • 原文地址:https://www.cnblogs.com/caicaizi/p/13684503.html
Copyright © 2020-2023  润新知