js跨域的四种方法(带实例)

js跨域的四种方法(带实例)

一、使用jsonp跨域

1、使用jQuery跨域

html代码

<!DOCTYPE html>
<html>
    <head>
        <title>jsonp跨域一:使用jQuery跨域</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
    </head>
    <body>
        <div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
        <div style="text-align:center;"><input id="getData" type="button" value="获取数据" /></div>
    </body>
</html>

对应的js代码

//jquery入口
$(document).ready(function(){
    $('#getData').click(function(){
        $.ajax({
            type:'get', //这里只能用get,因为jsonp没有post的说法
            url:'http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo1.php',
            // url:'http://45.124.65.84/data.txt',
            dataType:'jsonp',
            data:{
                name:'Bruce',
                mobile:13838383388,
            },
            //指定访问的url的参数名称(用于告诉服务器客户端回调函数的函数名)
            jsonp:'callback', //(如果不写该属性,则默认为callback)
            //指定访问的url的参数值(其实就是回调函数名)
            jsonpCallback:'testfunc', //(如果不写该属性,则默认为jquery自动产生一个随机名)
            //以上jsonp与jsonpCallback两个参数结合起来,代表:你在服务器端用 $_GET['callback'],获取到的值为testfunc,即相当于url上传了这样的参数 ?callback=testfunc。
            success:function(data){
                var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
                +'<br>手机号:'+ data.mobile
                +'<br>年龄:'+ data.age
                +'<br>性别:'+ (data.gender==1 ? '男' : '女')
                +'<br>头像:'+ '<img src="'+data.avatar+'">';
                +'<br></div>';
                $('#screen').append(str);
            }
        });
    });
});

服务器端代码:cross_domain_demo1.php

<?php
    //假设以下数组是从数据库中查询出来的数据
    $userinfo = [
        'id'=>2345,
        'username'=>'Bruce',
        'mobile'=>'13838383388',
        'age'=>18,
        'gender'=>1,
        'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
    ];
    $result = json_encode($userinfo);
    //动态执行回调函数
    $callback = $_GET['callback'];
    sleep(5); //为了测试jquery ajax跨域工作原理,这里设置5秒后再返回数据
    echo $callback."($result)"; //这句是关键

动态插入的<scrip>标签:
Xnip2018-09-22_23-38-11.png
返回的数据:
Xnip2018-09-22_23-38-46.png
动图演示:
ScreenFlow1.gif

总结:
1、这种利用<script>标签的src属性来获取数据的方式我们叫jsonp方式,因为<script>标签加载数据是不受『域』的限制的,所以可以用这种方法来做跨域请求。
2、服务器端需要返回一个对回调函数的调用,比如本例服务器返回的是以下数据,很明显这是在调用一个名字testfunc()的函数,且传入了一个参数,这个参数是一个js对象,不过由于这是在jQuery里,testfunc其实就是jquery的success方法(具体jquery是怎么处理的就不用管了,看下面的原生js方式就明白了)

testfunc({"id":2345,"username":"Bruce","mobile":"13838383388","age":18,"gender":1,"avatar":"http:\/\/wx.qlogo.cn\/mmopen\/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR\/96"})

2、使用原生js跨域

html代码

<!DOCTYPE HTML>
<html>
    <head>
        <title>jsonp跨域二:使用原生js跨域</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <style>
            #screen{word-wrap:break-word;word-break:break-all;}
        </style>
    </head>
    <body>
        <div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
        <div style="text-align:center;"><input id="getData" type="button" value="获取数据" /></div>
    </body>
</html>

对应的js代码

//回调函数
function testfunc(data){
    //删除script标签
    var head = document.getElementsByTagName("head")[0]; //获得head节点
    var script = head.lastChild; //获得head的最后一个元素
    if(script.nodeName=='SCRIPT'){ //如果这个元素是名是SCRIPT,则把它删除
        head.removeChild(script); //删除这个script节点
    }

    //显示数据
    var screen = document.getElementById('screen');
    var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
    +'<br>手机号:'+ data.mobile
    +'<br>年龄:'+ data.age
    +'<br>性别:'+ (data.gender==1 ? '男' : '女')
    +'<br>头像:'+ '<img src="'+data.avatar+'">';
    +'<br></div>';

    screen.innerHTML = screen.innerHTML + str;
}

//点击获取数据,发起跨域请求
document.getElementById('getData').onclick = function(){
    //动态创建一个script标签并把它加入到head里面
    var script=document.createElement("script"); //创建一个script元素
    script.type="text/javascript"; //给该元素添加type属性
    script.src="http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo1.php?callback=testfunc"; //给该元素添加
    document.getElementsByTagName("head")[0].appendChild(script);
}

服务器端php代码:cross_domain_demo1.php

<?php
    //假设以下数组是从数据库中查询出来的数据
    $userinfo = [
        'id'=>2345,
        'username'=>'Bruce',
        'mobile'=>'13838383388',
        'age'=>18,
        'gender'=>1,
        'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
    ];
    $result = json_encode($userinfo);
    //动态执行回调函数
    $callback = $_GET['callback'];
    sleep(5); //为了测试jquery ajax跨域工作原理,这里设置5秒后再返回数据
    echo $callback."($result)"; //这句是关键

总结:
1、原理与上例一毛一样,也是jsonp跨域,只不过这里是亲自用原生js操作,比用jQuery更加容易理解。
2、很明显,服务器返回的也是一样对testfunc()函数的调用,并传入了js对象作为参数,所以你可以在testfunc()函数里做你要做的事情。
3、服务器怎么知道客户端有个叫testfunc()的函数呢?因为我们在请求链接上告诉它了。

二、使用代理(proxy)方式跨域

1、使用后端语言代理

html代码

<!DOCTYPE HTML>
<html>
    <head>
        <title>使用“代理”的方式跨域</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
        <style>
            #screen{word-wrap:break-word;word-break:break-all;}
        </style>
    </head>
    <body>
        <div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
        <div style="text-align:center;"><input id="getData" type="button" value="获取数据" /></div>
    </body>
</html>

对应的js代码

$(document).ready(function(){
    $('#getData').click(function(){
        var username = 'Bruce';
        var mobile = '13838383388';
        $.ajax({
            type:'post',
            url:'proxy_agent.php',
            data:{
                'username':username,
                'mobile':mobile
            },
            dataType:'json',    //注意:因为是通过本域代理来获取,这里不要再写jsonp了
            success:function(data){
                var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
                +'<br>手机号:'+ data.mobile
                +'<br>年龄:'+ data.age
                +'<br>性别:'+ (data.gender==1 ? '男' : '女')
                +'<br>头像:'+ '<img src="'+data.avatar+'">';
                +'<br></div>';
                $('#screen').append(str);
            }
        });
    });
});

代理php代码:proxy_agent.php

<?php
    //proxy_agent.php
    echo file_get_contents('http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo3.php');

服务器端php代码:cross_domain_demo3.php

<?php
    //假设以下数组是从数据库中查询出来的数据
    $userinfo = [
        'id'=>2345,
        'username'=>'Bruce',
        'mobile'=>'13838383388',
        'age'=>18,
        'gender'=>1,
        'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
    ];
    echo json_encode($userinfo);

总结:
1、这是一种间接的跨域方式,ajax无法直接请求另一个域的服务器,那就不直接请求,改成请求本域的后端的接口,由该接口对应的代码去跨域请求服务器(对服务器端代码来说,发起一个跨域请求太简单了,如PHP的话,可以用file_get_contents方法或者curl去请求),那么这个本域的后端接口,我们称它为『代理』,因为是它代替ajax去获取数据,然后再返回给ajax。

2、直接使用nginx代理

我们知道nginx一大特点是可以设置反向代理,所以对于跨域api,比如前端想请求内网后端同事电脑上的api就可以暂时用nginx代理方式,让ajax请求一个自己定义的本域的url,然后在nginx里把这个url代理到局域网内后端同事的电脑上,这样就可以实现『跨域』了。

三、使用CORS跨域

1、简单请求

html代码

<!DOCTYPE HTML>
<html>
    <head>
        <title>CORS跨域一:简单请求</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
        <style>
            #screen{word-wrap:break-word;word-break:break-all;}
        </style>
    </head>
    <body>
        <div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
        <div style="text-align:center;">
            <input id="getData" type="button" value="获取数据" />
        </div>
    </body>
</html>

对应的js代码

$(document).ready(function(){
    $('#getData').click(function(){
        var username = 'Bruce';
        var mobile = '13838383388';
        $.ajax({
            type:'get',
            url:'http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo4.php',
            data:{
                'username':username,
                'mobile':mobile
            },
            dataType:'json',
            success:function(data){
                var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
                +'<br>手机号:'+ data.mobile
                +'<br>年龄:'+ data.age
                +'<br>性别:'+ (data.gender==1 ? '男' : '女')
                +'<br>头像:'+ '<img src="'+data.avatar+'">';
                +'<br></div>';
                $('#screen').append(str);
            }
        });
    });
});

后端php代码:cross_domain_demo4.php
代码与代理跨域方式的后端代码几乎一样,唯一不同的是,该方式在返回数据前,使用header('Access-Control-Allow-Origin:*');发送了header给浏览器,告诉浏览器,我允许任何人访问我,你就不要再拦截我返回的数据了。

<?php
    //允许所有服务器上的文件使用ajax请求该文件
    //通过启用CORS(跨域资源共享,Cross-Origin Resource Sharing)来允许所有域名使用ajax请求该文件
    header('Access-Control-Allow-Origin:*');

    //只允许域名为www.baidu.com的服务器上的文件使用ajax请求该文件
    //header('Access-Control-Allow-Origin:https://www.xiebruce.top');

    //假设以下数组是从数据库中查询出来的数据
    $userinfo = [
        'id'=>2345,
        'username'=>'Bruce',
        'mobile'=>'13838383388',
        'age'=>18,
        'gender'=>1,
        'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
    ];
    echo json_encode($userinfo);

总结:
这种方式,是真正的从源头上允许Ajax跨域,前面的是jsonp和代理方式。

2、非简单请求

html代码

<!DOCTYPE HTML>
<html>
    <head>
        <title>CORS跨域二:非简单请求</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
        <style>
            #screen{word-wrap:break-word;word-break:break-all;}
        </style>
    </head>
    <body>
        <div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
        <div style="text-align:center;">
            <input id="getData" type="button" value="获取数据" />
        </div>
    </body>
</html>

对应的js代码

$(document).ready(function(){
    $('#getData').click(function(){
        var username = 'Bruce';
        var mobile = '13838383388';
        $.ajax({
            type:'post',
            url:'http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo4.php',
            beforeSend:function(xhr){
                xhr.setRequestHeader("custom-header", "this is a custom header");
            },
            data:{
                'username':username,
                'mobile':mobile
            },
            dataType:'json',
            success:function(data){
                var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
                +'<br>手机号:'+ data.mobile
                +'<br>年龄:'+ data.age
                +'<br>性别:'+ (data.gender==1 ? '男' : '女')
                +'<br>头像:'+ '<img src="'+data.avatar+'">';
                +'<br></div>';
                $('#screen').append(str);
            }
        });
    });
});

非简单请求会发起两次请求,第一次请求的请求方法是OPTIONS方法,目的是『探路』,通过这次请求,客户端可以知道服务器是否允许本次请求的请求方法(REQUEST METHOD)、请求头以及请求域,如果都允许,那么客户端(即浏览器)才会发出第二次请求,第二次请求是真正请求数据的请求。由于发起两次请求,相当于请求量多了一倍,会给服务器造成较大压力,一般情况下不建议使用非简单请求。
Xnip2018-09-23_02-36-54.png

后端php代码:

<?php
    /**
     * Created by PhpStorm.
     * User: bruce
     * Date: 10/01/2018
     * Time: 16:15
     * corss_domain_demo4.php
     */

    //允许所有服务器上的文件使用ajax请求该文件
    //通过启用CORS(跨域资源共享,Cross-Origin Resource Sharing)来允许所有域名使用ajax请求该文件
    header('Access-Control-Allow-Origin:*');
    header('Access-Control-Allow-Methods:PUT,POST,DELETE,GET');
    header('Access-Control-Allow-Headers:custom-header');

    //只允许域名为http://www.xiebruce.top的服务器上的文件使用ajax请求该文件
    //header('Access-Control-Allow-Origin:http://www.xiebruce.top');

    //假设以下数组是从数据库中查询出来的数据
    $userinfo = [
        'id'=>2345,
        'username'=>'Bruce',
        'mobile'=>'13838383388',
        'age'=>18,
        'gender'=>1,
        'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
    ];
    echo json_encode($userinfo);

响应header必须与客户器端对应,否则客户端不会发起获取数据的请求
Xnip2018-09-23_02-39-12.png

四、使用iframe+postMessage跨域

如果iframe里请求的是非本域url,因为同源策略,用下边这种方法是无法获取iframe里返回的数据的:

$('#_iframe').on('load', function (e){
    var responseData = this.contentDocument.body.textContent || this.contentWindow.document.body.textContent;
    console.log(responseData);
})

利用iframe+postMessage跨域
html代码

<!DOCTYPE HTML>
<html>
<head>
    <title>利用iframe+postMessage跨域</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
    <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
    <style>
        #screen{word-wrap:break-word;word-break:break-all;}
    </style>
</head>
<body>
    <div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
    <div style="text-align:center;">
        <input id="getData" type="button" value="获取数据" />
    </div>
    </body>
</html>

对应的js代码

$(document).ready(function(){
    var iframeId = '_iframe';
    $('#getData').on('click', function (){
        //动态创建iframe
        var iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        iframe.id = '_iframe';
        iframe.src = 'https://www.xiebruce.top:8080/cross_domain_demo/cross_domain_demo5.php?username=Bruce&mobile=13838383388';
        $('body').append(iframe);

        //iframe内容加载完成时触发onload事件
        $('#'+iframeId).on('load', function (){
            //当iframe加载完成后(此时iframe中其实已经是http://xiebruce.top:8080页面)
            //用iframe的window对象中的postMessage方法向iframe发送消息,消息内容无所谓
            //目的是为了触发iframe中的onmessage事件,这样子页面才能把数据传出来
            //第二个参数接收消息的窗口域名(*任何域名都可以接收)
            $('#'+iframeId).get(0).contentWindow.postMessage('aaa','https://www.xiebruce.top:8080');
        });
    });

    //监听onmessage事件,当有其他窗口(这里是iframe)用postMessage方法往本窗口发送消息时,会触发该事件
    $(window).on('message', function (e){
        var data = eval("("+e.originalEvent.data+")");
        var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
        +'<br>手机号:'+ data.mobile
        +'<br>年龄:'+ data.age
        +'<br>性别:'+ (data.gender==1 ? '男' : '女')
        +'<br>头像:'+ '<img src="'+data.avatar+'">';
        +'<br></div>';
        $('#screen').append(str);
        $('#'+iframeId).remove();
    });
});

后端php代码

<?php
    //假设以下数组是从数据库中查询出来的数据
    $userinfo = [
        'id'=>2345,
        'username'=>'Bruce',
        'mobile'=>'13838383388',
        'age'=>18,
        'gender'=>1,
        'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
    ];
    $json = json_encode($userinfo);
    $str = <<<EOF
    <script>
        window.addEventListener('message', function (ev){
            //向父窗体发送消息,这里ev.source=window.parent,随便用其中一个即可
            ev.source.postMessage('$json', ev.origin);
         }, false);
    </script>
EOF;
    echo $str;

postMessage支持跨域源通信,所以我们可以利用它把iframe页面中的数据传到父窗口,进而使用这些服务器返回数据。

服务器返回的是监听message事件的js代码,当我们访问页面后,页面内的iframe同时也跳转到了指定的地址(该地址即为跨域的地址,比如api的接口,这时其实接口数据已经返回了),因为iframe加载了api的数据,所以父窗体的iframe onload事件被触发,然后往ifame中发送消息,iframe此时虽然处于另一个域,但由于postMessage支持跨域,所以iframe中的onmessage被触发,进而给父窗体发消息并把服务器返回的数据通过消息带出去,父窗体的onmessage事件接收到该消息后就能拿到服务器返回的数据,进而使用这些数据。

五、总结

1、跨域的方法目前我知道的就是这四种方法:jsonp、CORS、proxy、iframe+postMessage
2、最理想的跨域当然是CORS了,jsonp也常用,proxy方式不建议使用,毕竟有两次请求,而iframe+postMessage方式由于我不是很了解,不是特别清楚它与jsonp之间哪个好。

打赏

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
Notify of

扫码在手机查看
iPhone请用自带相机扫
安卓用UC/QQ浏览器扫

js跨域的四种方法(带实例)