js實現跨域(jsonp, iframe+window.name, iframe+window.domain, iframe+window.postMessage)

1、瀏覽器同源策略javascript

首先咱們須要瞭解一下瀏覽器的同源策略,關於同源策略能夠仔細看看知乎上的一個解釋。傳送門
php

總之:同協議,domain(或ip),同端口視爲同一個域,一個域內的腳本僅僅具備本域內的權限,能夠理解爲本域腳本只能讀寫本域內的資源,而沒法訪問其它域的資源。這種安全限制稱爲同源策略。html

( 現代瀏覽器在安全性和可用性之間選擇了一個平衡點。在遵循同源策略的基礎上,選擇性地爲同源策略"開放了後門"。 例如img script style等標籤,都容許垮域引用資源。)html5

下表給出了相對 http://store.company.com/dir/page.html 同源檢測的示例:java

URL 結果 緣由
http://store.company.com/dir2/other.html 成功  
http://store.company.com/dir/inner/another.html 成功  
https://store.company.com/secure.html 失敗 協議不一樣
http://store.company.com:81/dir/etc.html 失敗 端口不一樣(默認80)
http://news.company.com/dir/other.html 失敗 主機名不一樣

 

 

 

 

 

 

 

 

 

 

因爲瀏覽器同源策略的限制,讓咱們沒法經過js直接在不一樣的域之間進行數據傳輸或通訊,好比用ajax向一個不一樣的域請求數據,或者經過js獲取頁面中不一樣域的框架(iframe)中的數據。git

2、jsonp實現跨域請求數據github

在javascript中,咱們不能直接用ajax請求不一樣域上的數據。可是,在頁面上引入不一樣域上的js腳本文件倒是能夠的,jsonp正是利用這個特性來實現的。web

本地測試利用PHP內置了的Web 服務器來模擬兩個端口不一樣的兩個域,如今在web_test目錄下有9000和9001兩個目錄,分別進入兩個目錄執行ajax

web_test/9000: php -S 127.0.0.1:9000
web_test/9001: php -S 127.0.0.1:9001

執行後:chrome

 

這時候開啓了兩個本地不一樣端口的服務器,如今在兩個目錄下的文件就是在兩個不一樣域。

 在9001目錄下jsonp_test.html中

<!DOCTYPE html>
<html>
<head>
    <title>jsonp-test</title>
</head>
<body>
    <script type="text/javascript">
        function callback_data (data) {
            console.log(data);
        }
    </script>
    <script type="text/javascript" src="http://127.0.0.1:9000/jsonp.php?callback=callback_data"></script>
</body>
</html>

能夠看到咱們在向9000目錄下的jsonp.php文件獲取數據時,地址後面跟了一個callback參數(通常的就是用callback這個參數名,你也能夠用其餘的參數名代替)。

若是你要獲取數據的頁面是你不能控制的,那你只能根據它所提供的接口格式進行獲取。

由於咱們的type規定是當成是一個javascript文件來引入的,因此php文件返回的應該是一個可執行的js文件。

 在9000目錄下jsonp.php中

<?php
    $callback = $_GET['callback'];  // 獲取回調函數名
    $arr = array("name" => "alsy", "age" => "20"); // 要請求的數據
    echo $callback."(". json_encode($arr) .");"; // 輸出
?>

頁面輸出就是這樣的:

callback_data({"name":"alsy","age":"20"}); //執行url參數中指定的函數,同時把咱們須要的json數據做爲參數傳入

 這樣咱們瀏覽器中輸入http://127.0.0.1:9001/jsonp_test.html,控制檯打印出:

這樣咱們就獲取到不一樣域中返回的數據了,同時jsonp的原理也就清楚了:

經過script標籤引入一個js文件,這個js文件載入成功後會執行咱們在url參數中指定的函數,同時把咱們須要的json數據做爲參數傳入。因此jsonp是須要服務器端和客戶端相互配合的。

知道jsonp跨域的原理後咱們就能夠用js動態生成script標籤來進行跨域操做了,而不用特地的手動的書寫那些script標籤。好比jQuery封裝的方法就能很方便的來進行jsonp操做了。

9001目錄下的html中:

//$.getJSON()方法跨域請求
$.getJSON("http://127.0.0.1:9000/jsonp.php?callback=?", function(data){
    console.log(data);
});

原理是同樣的,只不過咱們不須要手動的插入script標籤以及定義回掉函數。jQuery會自動生成一個全局函數來替換callback=?中的問號,以後獲取到數據後又會自動銷燬,實際上就是起一個臨時代理函數的做用。

從請求的url和響應的數據就能夠很明顯的看出來了:

這裏的 jQuery214036133305518887937_1462698255551 就是一個臨時代理函數。

$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。

另外jsonp是沒法post數據的,儘管jQuery.getJSON(url, [data], [callback]); 提供data參數讓你能夠攜帶數據發起請求,但這樣是以get方式傳遞的。好比:

9001目錄下的html中:

//$.getJSON()方法
$.getJSON("http://127.0.0.1:9000/jsonp.php?callback=?", {u:'abc', p: '123'}, function(jsonData){
    console.log(jsonData);
});

或者是調用$.ajax()方法指定type爲post,它仍是會轉成get方式請求。

9001目錄下的html中:

$.ajax({
type: 'post',
url: "http://127.0.0.1:9000/jsonp_post.php",
crossDomain: true,
data: {u: 'alsy', age: 20},
dataType: "jsonp",
success: function(r){
console.log(r);
}
});

以get形式的話是能夠攜帶少許數據,可是數據量一大就不行了。

若是想post大量數據,就能夠嘗試用CORS(跨域資源共享,Cross-Origin Resource Sharing)。傳送門

CORS定義一種跨域訪問的機制,可讓AJAX實現跨域訪問。CORS 容許一個域上的網絡應用向另外一個域提交跨域 AJAX 請求。實現此功能很是簡單,只需由服務器發送一個響應標頭便可。

即服務器響應頭設置

    header('Access-Control-Allow-Origin: *'); // "*"號表示容許任何域向服務器端提交請求;也能夠設置指定的域名,那麼就容許來自這個域的請求:
    header('Access-Control-Allow-Methods: POST');
    header('Access-Control-Max-Age: 1000');

好比:

9001目錄下的一個html文件:

$.ajax({
    type: 'post',
    url: "http://127.0.0.1:9000/jsonp_post.php",
    crossDomain: true,
    data: {u: 'alsy', age: 20},
    dataType: "json",
    success: function(r){
        console.log(r);
    }
});

9000目錄下的php文件:

<?php
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: POST');
    header('Access-Control-Max-Age: 1000');
    if($_POST){
        $arr = array('name' => $_POST['u'], 'age' => $_POST['age']);
        echo json_encode($arr);
    } else {
        echo json_encode([]);
    }
?>

瀏覽器顯示:

這樣也是能夠實現跨域post數據的。

  1. 兼容性。CORS是W3C中一項較新的方案,因此部分瀏覽器尚未對其進行支持或者完美支持,詳情可移至 http://www.w3.org/TR/cors/
  2. 安全問題。CORS提供了一種跨域請求方案,但沒有爲安全訪問提供足夠的保障機制,若是你須要信息的絕對安全,不要依賴CORS當中的權限制度,應當使用更多其它的措施來保障。

3、iframe+window.domain 實現跨子域

 如今9001目錄下有一個iframe-domain.html文件:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-domain</title>
</head>
<body>
<h1>127.0.0.1:9001/iframe-domain.html</h1>
<script>  
function test(){ var obj = document.getElementById("iframe").contentWindow; //獲取window對象 console.log(obj); } </script> <iframe id="iframe" src="http://127.0.0.1:9000/domain.html" onload="test()"></iframe> </body> </html>

在這個頁面中存在一個不一樣域的框架(iframe),而iframe載入的頁面是和目標域在同一個域的,是可以向目標域發起ajax請求獲取數據的。

那麼就想能不能控制這個iframe讓它去發起ajax請求,但在同源策略下,不一樣域的框架之間也是不可以進行js的交互。

雖然不一樣的框架之間(父子或同輩),是可以獲取到彼此的window對象的,可是卻不能獲取到的window對象的屬性和方法(html5中的postMessage方法是一個例外,還有些瀏覽器好比ie6也可使用top、parent等少數幾個屬性),總之就是獲取到了一個無用的window對象。

在這個時候,document.domain就派上用場了,咱們只要兩個域的頁面的document.domain設置成同樣了,這個例子中因爲端口不一樣,兩邊的document.domain也要從新設置成"127.0.0.1",才能正常通訊。

要注意的是,document.domain的設置是有限制的,咱們只能把document.domain設置成自身更高一級的父域主域必須相同。

另舉例:a.b.example.com 中某個文檔的document.domain 能夠設成a.b.example.com、b.example.com 、example.com中的任意一個;

可是不能夠設成c.a.b.example.com,由於這是當前域的子域,也不能夠設成baidu.com,由於主域已經不相同了。

經過設置document.domain爲相同,如今已經可以控制iframe載入的頁面進行ajax操做了。

9001目錄下的iframe-domain.html文件改成:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-domain</title>
</head>
<body>
<h1>127.0.0.1:9001/iframe-domain.html</h1>
<script> document.domain = '127.0.0.1'; //設置domain function test(){
        var obj = document.getElementById("iframe").contentWindow;
        console.log(obj);
        obj.getData('http://127.0.0.1:9000/json_domain.php', '{u: "alsy-domain", age: "20"}', function(r){
            console.log(  eval("("+ r +")") );
        });
    }
</script>
<iframe id="iframe" src="http://127.0.0.1:9000/domain.html" onload="test()"></iframe>
</body>
</html>

 

9000目錄下有一個domain.html,和一個json_domain.php文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>domain</title>
</head>
<body>
<h1>127.0.0.1/9000/domain.html</h1>
<script>
    window.onload = function(){
        document.domain = '127.0.0.1'; //設置domain
        window.getData =  function(url, data, cb) {
            var xhr = null;
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            } else {
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
            }
            xhr.open('POST', url, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    cb(xhr.responseText);
                }
            }
            xhr.send(data);
        }
    }
</script>
</body>
</html>
<?php
    $str = file_get_contents('php://input');
    echo json_encode($str);
?>

瀏覽器打印:

這樣就能夠實現跨域,固然你也能夠動態建立這麼一個iframe,獲取完數據後再銷燬。

4、iframe+window.name 實現跨域

window對象有個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限。(window.name的值只能是字符串的形式,這個字符串的大小最大能容許2M左右甚至更大的一個容量,具體取決於不一樣的瀏覽器,通常夠用了。)

9001目錄下的iframe-window.name.html:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-window.name</title>
</head>
<body>
<h1>127.0.0.1:9001/iframe-window.name.html</h1>
<script>
    function test(){
        var obj = document.getElementById("iframe");
        obj.onload = function(){
            var message = obj.contentWindow.name;
            console.log(message);
        }
        obj.src = "http://127.0.0.1:9001/a.html";
    }
</script>
<iframe id="iframe" src="http://127.0.0.1:9000/window.name.html" onload="test()"></iframe>
</body>
</html>

9000目錄下的window.name.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>window.name</title>
</head>
<body>
<h1>127.0.0.1/9000/window.name.html</h1>
<script>
    //todo
    window.name = "This is message!";
</script>
</body>
</html>

瀏覽器輸出:

整個跨域的流程就是:如今9000/window.name.html中經過一些操做將數據存入window.name中了,而9001/iframe-window.name.html想要獲取到window.name的值就須要依靠iframe做爲中間代理,首先把iframe的src設置成http://127.0.0.1:9000/window.name.html,這樣就至關於要獲取iframe的window.name,而要想獲取到iframe中的window.name,就須要把iframe的src設置成當前域的一個頁面地址"http://127.0.0.1:9001/a.html",否則根據前面講的同源策略,window.name.html是不能訪問到iframe裏的window.name屬性的。

5、iframe+window.postMessage 實現跨域

html5炫酷的API之一:跨文檔消息傳輸。高級瀏覽器Internet Explorer 8+, chrome,Firefox , Opera  和 Safari 都將支持這個功能。這個功能實現也很是簡單主要包括接受信息的"message"事件和發送消息的"postMessage"方法。

發送消息的"postMessage"方法:

向外界窗口發送消息:

otherWindow.postMessage(message, targetOrigin);

otherWindow:  指目標窗口,也就是給哪一個window發消息。

message: 要發送的消息,類型爲 String、Object (IE八、9 不支持)

targetOrigin: 是限定消息接收範圍,不限制請使用 '*'

接受信息的"message"事件

var onmessage = function (event) {
    var data = event.data;
    var origin = event.origin;
    //do someing
};
if (typeof window.addEventListener != 'undefined') {
    window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
    //for ie
    window.attachEvent('onmessage', onmessage);
}

回調函數第一個參數接收 event 對象,有三個經常使用屬性:

  • data:  消息
  • origin:  消息來源地址
  • source:  源 DOMWindow 對象

 9000目錄下的 postmessage.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>postmessage</title>
</head>
<body>
<h1>127.0.0.1/9000/postmessage.html</h1>
<script>
    window.onload = function () {
        if (typeof window.postMessage === undefined) {
            alert("瀏覽器不支持postMessage!");
        } else {
            window.top.postMessage({u: "alsy"}, "http://127.0.0.1:9001");
        }
    }
</script>
</body>
</html>

 9001目錄下的 iframe-postmessage.html:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-postmessage</title>
</head>
<body>
<h1>127.0.0.1:9001/iframe-postmessage.html</h1>
<script>
    function test(){
        if (typeof window.postMessage === undefined) {
            alert("瀏覽器不支持postMessage!");
        } else {
            window.addEventListener("message", function(e){
                if (e.origin == "http://127.0.0.1:9000") { //只接收指定的源發來的消息
                    console.log(e.data);
                };
            }, false);
        }
    }
</script>
<iframe id="iframe" src="http://127.0.0.1:9000/postmessage.html" onload="test()"></iframe>
</body>
</html>

瀏覽器打印:

 6、說明

參考自 http://www.cnblogs.com/2050/p/3191744.html

若有錯誤或不一樣觀點請指出,共同交流。

完整代碼:傳送門

相關文章
相關標籤/搜索