DOM跨域的三种解决方案:document.domain、window.name、window.postMessage

同域访问

浏览本篇文章之前,需要了解什么是跨域,就需要了解浏览器的同源策略,简单来说就是协议,域名,端口,这三者都一样才称之为同源,详细的浏览器同源策略可以浏览文章:浏览器的同源策略

同域的不同页面之间通信可通过自定义事件和监听事件的方式实现,具体Demo可以参考笔者的另外一篇文章:javascript —— 父页面监听iframe子页面的自定义事件,那么不同域之间的页面怎么进行消息传递呢,本文将介绍三种方式:document.domainwindow.namewindow.postMessage

document.domain

默认情况下,document.domain存放的是载入文档的服务器的主机名,可以手动设置这个属性,设置该属性有两个特点:

  1. 只能设置成当前域名,或者当前域的二级域名(比如document.domain为 www.baidu.com,可以设置成 baidu.com)
  2. 任何对document.domain的赋值操作,包括 document.domain = document.domain 都会导致端口号被重写为 null

通过这两个特点,我们就可以实现二级域名相同的不同域之间的跨域dom访问(就算端口不同也可以)

相同二级域名之间的跨域

举个例子:
http://www.qq.com的document.domain为"qq.com",http://id.qq.com的document.domain为"id.qq.com",默认情况下,id.qq.com是无法对www.qq.com进行跨域操作的:
在这里插入图片描述
但是,如果通过将document.domain设置成"qq.com"就不同了:
在这里插入图片描述

相同域名,不同端口之间的跨域

举个例子:
http://http://localhost:3001/window.html的document.domain为"localhost",http://localhost:3002/b.html的document.domain也为"localhost",但是他们的端口一个是3001,一个是3002,所以默认情况下,window.html是无法对b.html进行跨域操作的:
在这里插入图片描述
现在,我们进行两个操作,分别将window.html的document.domain和b.html的document.domain均设置成"localhost",这样它们的端口都会被重置成null,它们的域名和端口都一致,就可以进行跨域访问了

window.html

document.domain = 'localhost'

b.html

document.domain = 'localhost'

在这里插入图片描述

window.name

在页面在浏览器端展示的时候,我们总能在控制台拿到一个全局变量window,该变量有一个name属性,其有以下 特征:

  1. 每个窗口都有独立的window.name与之对应;
  2. 在一个窗口的生命周期中(被关闭前),窗口载入的所有页面同时共享一个window.name,每个页面对window.name都有读写的权限;
  3. window.name一直存在与当前窗口,即使是有新的页面载入也不会改变window.name的值;
  4. window.name可以存储不超过2M的数据,数据格式按需自定义。

作者:Bennt
链接:https://www.jianshu.com/p/43ff69d076e3
来源:简书

我们来看一个当window重新加载后window.name不变的例子:

window.name = "{\"msg\":\"我是传递的数据\"}"; 
setTimeout(function(){
    window.location.href="b.html"
    alert(JSON.parse(window.name).msg);
}, 2000);

结果:
在这里插入图片描述

现在假如我们在 localhost:3001/window.html页面,需要获取到 localhost:3002/b.html页面上的数据(注意:端口不同,为跨域),我们可以这么操作:

localhost:3002/b.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
</body>
<script type="text/javascript">
    
    window.name = "{\"msg\":\"我是b页面传递的数据\"}"

</script>
</html>

localhost:3001/window.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #inIframe {
            display: none;
        }
    </style>
</head>
<body>
    <h2>主页面</h2>
    <div id="inIframe"></div>
</body>
<script type="text/javascript">

	/**
	* 参考文章:JS跨域--window.name
	* 链接:https://www.jianshu.com/p/43ff69d076e3
	* 作者:Bennt
	*/

    var boo = false;
    var iframe = document.createElement('iframe');
    var loadData = function() {
        if (boo) {
            var data = iframe.contentWindow.name; // 获取window.name
            console.log(data); 
            // 销毁数据   
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
            document.getElementById("inIframe").removeChild(iframe);
        } else {
            boo = true;
            iframe.contentWindow.location = "b.html"; // 设置的代理文件,iframe重新载入
        }  
    };
    iframe.src = 'http://localhost:3002/b.html';
    if (iframe.attachEvent) {
        // 兼容ie7/ie8
        iframe.attachEvent('onload', loadData);
    } else {
        iframe.onload  = loadData;
    }
    // id为inIrame通过css设置为了隐藏,这样获取b.html数据过程中不会出现插入和移除iframe过程中的闪现现象
    document.getElementById("inIframe").appendChild(iframe);
    
</script>

</html>

window.postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,只有同源的页面才能相互通信,window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

语法

otherWindow.postMessage(message, targetOrigin, [transfer]);
变量名说明
otherWindow其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
message将要发送到其他 window的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。
targetOrigin通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串""(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用postMessage传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的origin属性完全一致,来防止密码被恶意的第三方截获。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。
transfer (可选)是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

假如我们通过window.html页面的iframe加载另外一个域的b.html页面,并在b.html页面监听window.html页面发送的消息,在b.html收到消息后,再回消息给window.html,示例代码如下:

loalhost:3001/window.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h2>主页面</h2>
    <iframe id="contentIframe" src="http://localhost:3002/b.html"></iframe>
</body>
<script type="text/javascript">

    window.onload = function() { 
        console.log('window页面发送消息')
        document.getElementById('contentIframe').contentWindow.postMessage(
            {
                code: 1,
                msg: 'Hello b, i am window.html'
            }, 
            "http://localhost:3002"
        ); 
    }; 

    window.addEventListener('message', function(event){  
        // 通过origin属性判断消息来源地址  
        if (event.origin == 'http://localhost:3002') {

            console.log('window页面收到消息:' + event.data.msg)           

        }
    }, false);  
    

</script>

</html>

localhost:3002/b.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <span>我是b页面</span><br>
    <input type="button" value="回消息" onclick="postmsg()">
</body>
<script type="text/javascript">

    let parent = null;
    let origin = null;
    
    window.addEventListener('message', function(event){  
        // 通过origin属性判断消息来源地址  
        if (event.origin == 'http://localhost:3001') {

            parent = event.source
            origin = event.origin

            console.log('b页面收到消息:' + event.data.msg)           

        }
    }, false);  

    /**
     * 回消息方法
     * 
     * 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 
     * 一个很方便的方式就是把event.source作为回信的对象,
     * 并且把event.origin作为targetOrigin
    */
    function postmsg() {
        console.log('b页面回消息')
        parent.postMessage({
            code: 1,
            msg: 'hi, i am b.html'
        }, origin)
    }

</script>
</html>

结果:
在这里插入图片描述
在这里插入图片描述
点击b.html的”回消息“按钮
在这里插入图片描述

注意:
任何窗口可以在任何其他窗口访问此方法,在任何时间,无论文档在窗口中的位置,向其发送消息。 因此,用于接收消息的任何事件监听器必须首先使用origin和source属性来检查消息的发送者的身份。 这不能低估:无法检查origin和source属性会导致跨站点脚本攻击。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值