面试必考的跨域问题你还是不会?别怕,这里有最全的总结!!!

最全跨域方案总结

Posted by tinypoint on September 28, 2017

本文首次发布于 tinypoint`s blog, 作者 @张小点(tinypoint) ,转载请保留原文链接.

前言

最近正是招聘季节,面试前端的小伙伴们难免会在跨域问题上被面试官们’刁难’,不过别怕,这篇文章将会详细的讲解常用的跨域解决方案,帮你在面试中披荆斩棘。

正文

通过XHR实现AJAX通信的一个主要限制,来源于跨域安全策略。默认情况下XHR对象只能访问与包含它的页面位于同一个域中的资源,即同源策略。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序是至关重要的。

同源策略

同源是指

  • 协议相同(FTP、HTTP等)
  • 域名相同(包括每一级域名, domain.com和www.domain.com不同)
  • 端口相同

不符合以上任意一条都不属于同源, 跨域的请求将会受到限制

解决方案

所有具有src的标签

  • 比如<img><script><link>
  • 只能进行GET请求
  • 需要注意的是img标签在设置src属性的时候就会进行发送请求,而link,script,iframe等则是在被添加到页面中才会发送请求
var img = new Image()
img.onload = function () {
    console.log('成功加载');
}
img.src = 'http://www.example.com';

var script = document.createElement('script');
script.src = 'http://www.example.com'
document.body.appendChild(script);

jsonp

  • jsonp其实利用了
  • 回调函数必须是全局函数
  • 同样,只能用于GET方法
function dataResolver (data) {
    console.log('This is' + data);
}

var script = docuemnt.createElement('script');
script.src = 'http://www.example.com?callback=daraResolver'
docuemnt.body.appendChild(script);

document.domain

  • 只适用于主域相同,子域不同的页面交流,如从http://subdomain.domain.comhttp://domain.com之间的通信
  • 原理:利用document.domain允许同时也是只允许从低级域名到高级域名设置,手动设置document的domain属性,让页面同域
<script>
    //  http://domain.com/example 页面
    var iframe = document.createElement('iframe');
    iframe.src = 'http://subdomain.domain.com';
    
    iframe.onload = function() {
        var iframeDocuemnt = iframe.contentDocument || iframe.contentWindow.document;
        alert(iframeDocuemnt.getElementById("foo").innerHTML));
    };
    
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
<script>

设置document.domain与父页面保持一致

<script>
    //  http://subdomain.domain.com/example 页面
    document.domain = 'domain.com';
</script>

window.name跨域

  • 在一个窗口(window)的生命周期内,窗口载入的所有的页面共享一个window.name的,每个页面对window.name都有读写的权限
  • window支持多达2m的name属性

a.com/index.html要从b.com/data.html获取数据时,需要借助与a同域的a.com/proxy.html作为代理

a.com/index.html

// load事件会触发两次,每个时机都有不同的任务
var proxy = function (url, cb) {
    var isFirst = true;
    
    var iframe = document.createElement('iframe');
    iframe.src = url;
    
    iframe.onload = function () {
        if (isFirst) {
            // 切换到代理页面
            iframe.contentWindow.location = 'http://a.com/proxy.html';
            isFirst = false
        } else {
            // 读取window.name
            cb(iframe.contentWindow.name);
            destoryFrame();
        }
    }
}

// iframe用完要删除,释放内存,同时保证了安全
function destoryFrame () {
    iframe.contentWindow.document.write('');
    iframe.contentWindow.close();
    document.body.removeChild(iframe);
}

//想要发送数据,可以将数据放在window.name中

window.name = '12';

proxy('http://b.com/data.html', function (data) {
    console.log(data);
});

a.com/proxy.html代理页面与主页面同域,内容为空即可

b.com/data.html

var id = window.name;
window.name = id + 'is tinypoint';

location.hash

  • 不同域利用location.hash来传值
  • 通过hashchange事件来监听hash变化

a.com下的a.html想要与b.com下的b.html通信,需要中间页a.com下的c.html来实现

a.html

<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
 
    // 要发送的数据放在b页面的hash上
    iframe.src = iframe.src + '#name';
    
    // 回调方法
    function dataResolver(data) {
        alert('This is ' + data);
    }
</script>

b.html

<iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
 
    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        var name = location.hash
        iframe.src = iframe.src + '#tinypoint';
    };
</script>

c.html

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.dataResolver('name is ' + location.hash);
    };
</script>

postMassage

HTML5中XMLHttpRequest 2级中的API,可解决以下问题

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的iframe消息传递
  4. 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数 data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。 origin: 协议+主机+端口号,也可以设置为”*”,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/”。

a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>      
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };
 
    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>

b.html:(http://www.domain2.com/b.html)

<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
    // e.data中保存了传递的数据
        alert('data from domain1 ---> ' + e.data);
 
        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;
 
            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

跨域资源共享(CORS)

  • 使用自定义的http头部要让浏览器与服务器进行通信
  • 在后端设置Access-Control-Allow-Origin: 当前域名(公共资源,可设置为*)
IE使用XDR(XDomainRequest)对象实现CORS

XDR与XHR类似

  • 有open,send,abort方法,但是open只接受两个参数,即所有请求都是异步的
  • 支持load,error,timeout事件
  • 支持timeout

XDR与XHR有一些不同之处

  • cookie不会随请求发送,也不会随相应返回
  • 只能设置请求头部信息的Content-Type字段
  • 不能访问响应头部信息
  • 只支持GET和POST请求
  • 不能访问status和statusText属性
var xdr = new XDomainRequest();
xdr.onload = function () {
    console.log(xdr.responseText);
}

xdr.onerror = function () {
    console.log('An error occurred');
}

xdr.open('get', 'http://www.a.com');
xdr.send();
其他浏览器对CORS的支持
  • 只需要在open方法中传入绝对路径即可
  • 存在一些限制
    • 不能使用setRequestHeader()设置自定义头部
    • 不能收发和接受cookie
    • getAllResponseHeaders()总是返回空字符串

Web Sockets

ws协议是HTML5新协议。实现了浏览器与服务器全双工通信,同时允许跨域通讯。 支持的事件

  • message 收到服务器传来的信息时触发
  • open 成功建立里连接时触发
  • error 发生错误时触发,连接不能持续
  • close 连接关闭时触发 ```html

socket.send(‘some text’);

socket.onmessage = function (e) { console.log(e.data) } </script> ```

参考