javascript 跨域总结

什么情况会跨域

  1. 主域不同
  2. 主域相同,子域不同
  3. 域名相同,协议不同(http / https)
  4. 域名相同,端口不同

跨域解决办法

  1. document.domain + iframe

    对于主域相同而子域不同的例子,可以通过设置 document.domain 的办法来解决。具体的做法如下:

    是可以在 a.com/a.html 和 1.a.com/b.html 两个文件中分别加上 document.domain = 'a.com'; 然后通过 a.html 文件中创建一个 iframe,去控制 iframe 的 contentDocument,当然这种办法只能解决主域相同而二级域名不同的情况。

    1
    2
    3
    4
    5
    6
    <!-- a.com/a.html -->
    <iframe id='i' src="1.a.com" onload="do()"></iframe>
    <script>
    document.domain = 'a.com';
    document.getElementById('i').contentWindow;
    </script>
    1
    2
    3
    4
    <!-- 1.a.com/b.html -->
    <script>
    document.domain = 'a.com';
    </script>

    这样,就可以解决问题了。值得注意的是:document.domain 的设置是有限制的,只能设置为页面本身或者更高一级的域名。利用这种方法是极其方便的,但是如果一个网站被攻击之后另外一个网站很可能会引起安全漏洞

  2. 动态创建 script (jsonp)

    JSONP 的全称是 “JSON With Padding”, 词面意思上理解就是 “填充式的JSON”。由于浏览器的同源策略,使得在网页端出现了这个“跨域”的问题,然而我们发现,所有的 src 属性并没有受到相关的限制,比如 img / script 等,jsonp 就是利用了 script 可以执行其它域的 js 函数,比如这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!-- a.com/a.html -->
    ...
    <script>
    function callback(data) {
    console.log(data.url)
    }
    </script>
    <script src='http://b.com/b.js'></script>
    ...
    <!-- b.com/b.js -->
    callback({url: 'http://www.rccoder.net'})

    利用这一点,假如 b.js 里面的内容不是固定的,而是根据一些东西自动生成的, 嗯,这就是 JSONP 的主要原理了。回调函数+数据就是 JSON With Padding 了,回调函数用来响应应该在页面中调用的函数,数据则用来传入要执行的回调函数,至于这个数据是怎么产生的,无非就是服务端的字符串拼接了。

    以下是 jsonp 请求的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    var loadJsonp = (function() {
    var seq = new Date() * 1;
    return function(url, params, callback) {
    var funName = 'XYJsonp' + seq++,
    head = document.getElementsByTagName('head')[0],
    script = document.createElement('script');
    for (var key in params) {
    url += (/\?/.test(url) ? '&': '?') + key + '=' + encodeURIComponent(params[key]);
    }
    url += '&callback=' + funName;
    window[funName] = function(data) {
    window[funName] = undefined;
    try {
    delete window[funName];
    }
    catch(e) {}
    if (head) {
    head.removeChild(script);
    }
    callback(data);
    };
    script.charset = "UTF-8";
    script.src = url;
    head.appendChild(script);
    };
    } ());

    注意:JSONP的这种实现方式不受同源策略的影响,兼容性也很好;但是它之支持 GET 方式的请求,只支持 HTTP 请求这种特殊的情况,对于两个不同域之间两个页面的互相调用也是无能为力。

  3. 利用 iframe 和 location.hash

    这个办法比较绕,把数据的变化显示在 url 的 hash 里面。原理是利用 location.hash 来进行传值。但是由于 chrome 和 IE 不允许修改 parent.location.hash 的值,所以需要再加一层。

    a.html 和 b.html 进行数据交换:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- a.com/a.html -->
    <script>
    function startRequest() {
    var ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = 'http://b.com/b.html#paramdo';
    document.body.appendChild(ifr);
    }
    function checkHash() {
    try {
    var data = location.hash ? location.hash.substring(1) : '';
    if (console.log) {
    console.log('Now the data is '+data);
    }
    }
    catch(e) {};
    }
    setInterval(checkHash, 2000);
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!-- b.com/b.html -->
    <script>
    // 模拟一个简单的参数处理操作
    switch(location.hash){
    case '#paramdo':
    callBack();
    break;
    case '#paramset':
    //do something……
    break;
    }
    function callBack() {
    try {
    parent.location.hash = 'somedata';
    }
    catch (e) {
    // ie、chrome 的安全机制无法修改 parent.location.hash,
    // 所以要利用一个中间域下的代理 iframe
    var ifrproxy = document.createElement('iframe');
    ifrproxy.style.display = 'none';
    ifrproxy.src = 'http://a.com/c.html#somedata'; // 注意该文件在 a.com 域下
    document.body.appendChild(ifrproxy);
    }
    }
    </script>
    1
    2
    3
    4
    5
    <!-- a.com/c.html -->
    <script>
    // 因为 parent.parent 和自身属于同一个域,所以可以改变其 location.hash 的值
    parent.parent.location.hash = self.location.hash.substring(1);
    </script>
  4. window.name 实现跨域数据传输

    window.name 在一个窗口(标签)的生命周期之内是共享的,利用这点结合 iframe:当在 iframe 中加载新页面时,name 的属性值依旧保持不变。

    总结起来即:iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

    需要3个文件:a.com/a.html, a.com/proxy.html, b.com/b.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!-- a.com/a.html -->
    <script type="text/javascript">
    var state = 0,
    iframe = document.createElement('iframe'),
    loadfn = function() {
    if (state === 1) {
    var data = iframe.contentWindow.name; // 读取数据
    alert(data); //弹出'I was there!'
    }
    else if (state === 0) {
    state = 1;
    iframe.contentWindow.location = "http://a.com/proxy.html"; // 设置的代理文件
    }
    };
    iframe.src = 'http://b.com/b.html';
    if (iframe.attachEvent) {
    iframe.attachEvent('onload', loadfn);
    }
    else {
    iframe.onload = loadfn;
    }
    document.body.appendChild(iframe);
    </script>
    1
    2
    3
    4
    5
    6
    <!-- b.com/b.html -->
    <script type="text/javascript">
    window.name = 'I was there!';
    // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
    // 数据格式可以自定义,如json、字符串
    </script>

    proxy 是一个代理文件,空的就可以,需要和 a 在同一域下

  5. 利用 HTML5 postMessage

    window.postMessage 是 HTML5 新增 API 之一,可以用来向其他所有的 window 对象发送消息。需要注意的是我们必须要保证所有的脚本执行完才发送 MessageEvent,如果在函数执行的过程中调用了他,就会让后面的函数超时无法执行。

    下一代浏览器支持这个功能 Chrome 2.0+, Internet Explorer 8.0+, Firefox 1.0+, Opera 9.6+, 和 Safari 4.0+ 。

    参考资料:

  6. CORS

    CORS 的全称是 Cross-Origin Resource Sharing,即跨域资源共享。他的原理就是使用自定义的 HTTP 头部,让服务器与浏览器进行沟通,主要是通过设置响应头的 Access-Control-Allow-Origin 来达到目的,这样,XMLHttpRequest 就能跨域了。

    参考资料: