孤獨的象形文
中秋節,當一個人在宿舍,朋友工作的工作,回家的回家。反正沒事,寫篇關於跨域的博客吧。
跨域的背景
1.為了安全我們的瀏覽器有同源策略。使我們不方便跨域訪問。
2.出於種種原因我們就是要跨域訪問。
3.前輩們通過鑽空子想出來的方法,和后來新的API等都讓我們能夠跨域訪問。
關鍵字:同源策略、跨域訪問。
跨域訪問的方法
1.JSONP
2.window.name + iframe
3.hash + iframe
4.postMessage
5.CORS
6.WebSockets
PS:我寫一些常用的方法,沒有講的要么是新技術要么是。。。我懶得寫。求你打我。
CORS
PS: 我使用node作為后台語言,開啟兩個服務器localhost:3000和localhost:1234 來模擬跨域訪問
這個是最簡單無腦的,不過跟前端沒什么關系,這是后台的寫。后台在返回的頭部中寫 Access-Control-Allow-Origin:xxxx(xxxx 為被允許訪問的源)這樣就搞定啦。
router.get('/cors', function (req, res) {
res.set('Access-Control-Allow-Origin','http://localhost:1234');
res.send('Mid-Autumn is a lonely day');
});
// 跨域訪問: localhost:1234 頁面下訪問localhost:3000/cors
$.ajax({
url: "http://localhost:3000/cors",
success: function (data) {
alert(data);
}
});
缺點:不能攜帶cookie信息,不能使用自定義請求頭部,需要兼容IE瀏覽器。最重要的是不能裝逼,后特么給后台去干了,前端沒逼可裝。
JSONP
也是個簡單的方法,這個是前輩們鑽空子想出來的方法。
大概原理是這樣的:
1.在全局定義一個funName函數 2.在頁面中創建一個script,src格式為:url+?cb=funName&key1=value1。
3.后台定義一個接口來接受cb,key 等參數,邏輯計算,返回格式為字符串:funName({prop:value});
$(function () {
var btn = $('button');
btn.on('click', function () {
JSONP({
url: 'http://localhost:3000/jsonp',
data: {
name: 'ahole',
goddness: 'GillianChung'
},
jsonp:'ahole',
success: function (data) {
alert(data.name + '\'s Goddness is ' + data.goddness);
}
})
})
function JSONP(data) {
var script = $('<script>'),
src = data.url,
callback = data.success,
randomName = 'Ahole',
query = data.data;
src = src + '?' + data.jsonp + '=' + randomName + '&';
for(var key in query){
src= src + key + '=' + query[key] + '&';
}
// 最后的src格式
// 'http://localhost:3000/jsonp?ahole=Ahole&name=ahole&goddness=GillianChung&';
script.attr('src',src);
document.body.appendChild(script[0]);
// 定義全局函數,方便jsonp得到的函數執行
window[randomName] = function(arg){
data.success(arg)
}
}
})
// node.js
router.get('/jsonp', function (req, res) {
var functionName = req.query.ahole;
var data = {
name:req.query.name,
goddness:req.query.goddness
};
// 這里需要轉為string,別忘了我要的是一份拼接的字符串,看做是你用node來寫.js。
data = JSON.stringify(data);
res.send(functionName+'('+data+')');
res.end();
})
測試成功。輸出為:ahole's goddness is ...;
補充:
1.script的src不一定要是.js結尾。比如本例子。
2.抱歉!我的JSONP是模仿jQuery寫的,但有很多細節沒有處理,比如在windows下添加變量,但其實jQuery也是這么做的,只是他在執行完回調之后把值換回去了,所以原理是對的。有空可以去看下jQuery的源碼,還是挺不錯的。
window.name + iframe
很明顯,JSONP的致命缺點就是只能使用GET,這個改變不了,就像我是個廢材是事實。沒錯window.name + iframe 的組合能實現post版本的JSONP。這個比較繞。
原理:
1.可以通過js操作沒有設置src的iframe,通過他來發送跨域請求。
2.window.name 這個值通過window.href = xxx 之后也不會刪除或者改變,可以通過這個特點來傳遞跨域請求后的返回值。
關鍵代碼與解析
var form = $('form');
// 個人定義的函數接口
form.on('submit',function (e) {
// 利用serialize方法來得到即將提交表單的數據
var data = $(this).serialize();
e.preventDefault();
postJSONP({
url:'http://localhost:1234/postjsonp',
// dataString 的格式為 key=value&key2=value2
dataString:data,
success:function(data){
alert(data);
}
});
})
function postJSONP(data){
var query = data.dataString,
$iframe = $('<iframe style="display:none">'),
index,$input,
$form = $('<form>');
// 初始化iframe
$('body').append($iframe);
$form.attr({
'action':data.url,
'method':'POST',
});
// 創建表單
if(query){
query = query.split('&');
for(index in query){
$input = $('<input>');
$input.attr({
type:'input',
name:query[index].split('=')[0],
value:query[index].split('=')[1]
})
$form.append($input);
}
$form.append($('<input type="submit" value="submit">'))
}
$iframe.contents().find('body').append($form);
// 提交表單 ,這里要注意的是如果請求成功那么,我們就失去了對iframe內部元素的訪問權利。
$iframe.contents().find('form').submit();
/*
* state 變量用於標示是否跳轉到同源頁面
* onload 事件每次我們改動iframe的地址重新下載一次iframe,成功下載即觸發,本例順利的話觸發兩次
*
* /
$iframe.state = 0;
$iframe.on('load',function () {
if($iframe.state === 0){
$iframe.state = 1;
// 跳轉到同源頁面,為了避免不必要的流量,最好下載一個同源的空白頁面
this.contentWindow.location.href = 'http://localhost:3000';
}else if($iframe.state === 1){
// 調用成功回調函數,以name為參數。
data.success(this.contentWindow.name);
}
})
}
node.js
router.post('/postjsonp',function(req,res){
// 我是后台,window.name 我想寫啥就寫啥
res.send('<script>window.name="Ahole is a good boy"</script>');
res.end();
})
結語
中秋快樂,我去搬磚求offer了。