1. JSONP
首先要介紹的跨域方法必然是 JSONP。
現(xiàn)在你想要獲取其他網(wǎng)站上的 JavaScript 腳本,你非常高興的使XMLHttpRequest 對(duì)象來(lái)獲取。但是瀏覽器一點(diǎn)兒也不配合你,無(wú)情的彈出了下面的錯(cuò)誤信息:
XMLHttpRequest cannot load http://x.com/main.dat. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://y.com' is therefore not allowed access.
你心里肯定會(huì)想,我難道要用后臺(tái)做個(gè)爬蟲(chóng)來(lái)獲取這個(gè)數(shù)據(jù)嗎?!為了避免這種事情發(fā)生,JSONP 就派上用場(chǎng)了。
<script>標(biāo)簽是不受同源策略的限制的,它可以載入任意地方的 JavaScript 文件,而并不要求同源。所以 JSONP 的理念就是,我和服務(wù)端約定好一個(gè)函數(shù)名,當(dāng)我請(qǐng)求文件的時(shí)候,服務(wù)端返回一段 JavaScript。這段 JavaScript 調(diào)用了我們約定好的函數(shù),并且將數(shù)據(jù)當(dāng)做參數(shù)傳入。非常巧合的一點(diǎn)(其實(shí)并不是),JSON 的數(shù)據(jù)格式和JavaScript 語(yǔ)言里對(duì)象的格式正好相同。所以在我們約定的函數(shù)里面可以直接使用這個(gè)對(duì)象。光說(shuō)不練假把式,讓我們來(lái)看一個(gè)例子:
你需要獲取數(shù)據(jù)的頁(yè)面 index.html:
<script>
function getWeather(data) {
console.log(data);
}
</script>
<script src="http://x.y.com/xx.js">
http://x.y.com/xx.js 文件內(nèi)容:
getWeather({
"城市": "北京",
"天氣": "大霧"
});
我們可以看到,在我們定義了 getWeather(data)這個(gè)函數(shù)后,直接載入了 xx.js。在這個(gè)腳本中,執(zhí)行了 getWeather函數(shù),并傳入了一個(gè)對(duì)象。然后我們?cè)谶@個(gè)函數(shù)中將這個(gè)對(duì)象輸出到 console 中。
這就是整個(gè) JSONP 的流程。
2. document.domain
使用條件:
- 有其他頁(yè)面 window對(duì)象的引用
- 二級(jí)域名相同
- 協(xié)議相同
- 端口相同
document.domain默認(rèn)的值是整個(gè)域名,所以即使兩個(gè)域名的二級(jí)域名一樣,那么他們的 document.domain也不一樣。使用方法就是將符合上述條件頁(yè)面的 document.domain設(shè)置為同樣的二級(jí)域名。這樣我們就可以使用其他頁(yè)面的 window對(duì)象引用做我們想做的任何事情了。
補(bǔ)充知識(shí):
x.one.example.com 和 y.one.example.com 可以將 document.domain設(shè)置為 one.example.com,也可以設(shè)置為example.com。document.domain只能設(shè)置為當(dāng)前域名的一個(gè)后綴,并且包括二級(jí)域名或以上(.edu.cn這種整個(gè)算頂級(jí)域名)。我們直接操刀演示,用兩個(gè)網(wǎng)站 http://wenku.baidu.com/ 和http://zhidao.baidu.com/。這兩個(gè)網(wǎng)站都是 http 協(xié)議,端口都是 80, 且二級(jí)域名都是 baidu.com。打開(kāi)http://wenku.baidu.com/,在 console 中輸入代碼:
document.domain = 'baidu.com';
var otherWindow = window.open('http://zhidao.baidu.com/');
我們現(xiàn)在已經(jīng)發(fā)現(xiàn)百度知道的網(wǎng)頁(yè)已經(jīng)打開(kāi)了,在百度知道網(wǎng)頁(yè)的 console 中輸入以下代碼:
document.domain = 'baidu.com';
現(xiàn)在回到百度文庫(kù)的網(wǎng)頁(yè),我們就可以使用百度知道網(wǎng)頁(yè)的 window對(duì)象來(lái)操作百度知道的網(wǎng)頁(yè)了。例如:
var divs = otherWindow.document.getElementsByTagName('div');
上面這個(gè)例子的使用方法并不常見(jiàn),但是非常詳細(xì)的說(shuō)明了這種方法的原理。這種方法主要用在控制 <iframe>的情況中。
比如我的頁(yè)面(http://one.example.com/index.html)中內(nèi)嵌了一個(gè) <iframe>:
<iframe id="iframe" src="http://two.example.com/iframe.html"></iframe>
我們?cè)?iframe.html 中使用 JavaScript 將 document.domain設(shè)置好,也就是 example.com。在 index.html 執(zhí)行以下腳本:
var iframe = document.getElementById('iframe');
document.domain = 'example.com';
iframe.contentDocument; // iframe的 document 對(duì)象
iframe.contentWindow; // iframe的 window 對(duì)象
這樣,我們就可以獲得對(duì)iframe的完全控制權(quán)了。
補(bǔ)充知識(shí):
當(dāng)兩個(gè)頁(yè)面不做任何處理,但是使用了iframe或者 window.open() 得到了某個(gè)頁(yè)面的 window 對(duì)象的引用,我們可以直接訪問(wèn)的屬性有哪些?
| 方法 |
|---|
| window.blur |
| window.close |
| window.focus |
| window.postMessage |
| window.location.replace |
| 屬性 | 權(quán)限 |
|---|---|
| window.closed | 只讀 |
| window.frames | 只讀 |
| window.length | 只讀 |
| window.location.href | 只寫 |
| window.opener | 只讀 |
| window.parent | 只讀 |
| window.self | 只讀 |
| window.top | 只讀 |
| window.window | 只讀 |
3. window.name
我們來(lái)看以下一個(gè)場(chǎng)景:
隨意打開(kāi)一個(gè)頁(yè)面,輸入以下代碼:
window.name = "My window's name";
location.;
再檢測(cè) window.name :
window.name; // My window's name
可以看到,如果在一個(gè)標(biāo)簽里面跳轉(zhuǎn)網(wǎng)頁(yè)的話,我們的 window.name是不會(huì)改變的?;谶@個(gè)思想,我們可以在某個(gè)頁(yè)面設(shè)置好 window.name的值,然后跳轉(zhuǎn)到另外一個(gè)頁(yè)面。在這個(gè)頁(yè)面中就可以獲取到我們剛剛設(shè)置的 window.name了。由于安全原因,瀏覽器始終會(huì)保持 window.name 是 string類型。這個(gè)方法也可以應(yīng)用到與 <iframe> 的交互上來(lái)。我的頁(yè)面(http://one.example.com/index.html)中內(nèi)嵌了一個(gè) <iframe>:
<iframe id="iframe" src="http://omg.com/iframe.html"></iframe>
在 iframe.html 中設(shè)置好了 window.name為我們要傳遞的字符串。我們?cè)?index.html 中寫了下面的代碼:
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
data = iframe.contentWindow.name;
};
定睛一看,為毛線報(bào)錯(cuò)?細(xì)心的讀者們肯定已經(jīng)發(fā)現(xiàn)了,兩個(gè)頁(yè)面完全不同源啊!由于 window.name 不隨著 URL 的跳轉(zhuǎn)而改變,所以我們使用一個(gè)暗黑技術(shù)來(lái)解決這個(gè)問(wèn)題:
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
iframe.onload = function() {
data = iframe.contentWindow.name;
}
iframe.src = 'about:blank';
};
或者將里面的 about:blank 替換成某個(gè)同源頁(yè)面(最好是空頁(yè)面,減少加載時(shí)間)。
補(bǔ)充知識(shí):
about:blank,javascript: 和 data:中的內(nèi)容,繼承了載入他們的頁(yè)面的源。
這種方法與 document.domain方法相比,放寬了域名后綴要相同的限制,可以從任意頁(yè)面獲取 string類型的數(shù)據(jù)。
4. [HTML5] postMessage
在 HTML5 中, window 對(duì)象增加了一個(gè)非常有用的方法:
windowObj.postMessage(message, targetOrigin);
windowObj: 接受消息的 Window 對(duì)象。
message: 在最新的瀏覽器中可以是對(duì)象。
targetOrigin: 目標(biāo)的源,* 表示任意。
這個(gè)方法非常強(qiáng)大,無(wú)視協(xié)議,端口,域名的不同。
下面是烤熟的栗子:
var windowObj = window; // 可以是其他的 Window 對(duì)象的引用
var data = null;
addEventListener('message', function(e) {
if(e.origin == 'http://qiaohongshen.github.io/foo') {
data = e.data;
e.source.postMessage('Got it!', '*');
}
});
message事件就是用來(lái)接收 postMessage發(fā)送過(guò)來(lái)的請(qǐng)求的。函數(shù)參數(shù)的屬性有以下幾個(gè):
origin: 發(fā)送消息的 window的源。
data: 數(shù)據(jù)。
source: 發(fā)送消息的 Window對(duì)象。