個人JSONP學習筆記javascript
在談JSONP以前首先要簡單說一說同源政策css
同源政策很簡單,它的含義是指兩個網頁:html
一旦以上三點中有任意一點不一樣,兩個網站都不能稱爲同源。舉例:前端
http://www.example.com/xxx
http://www.example.com/yyy
以上兩個網站是同源的,知足協議,域名,端口都相同(http協議默認端口爲80)
---------------------------
http://example.com/xxx
http://www.example.com/xxx
以上兩個網站是非同源的,由於域名不一樣
---------------------------
http://127.0.0.1:8080/xxx
http://127.0.0.1:8888/xxx
以上兩個網站是非同源的,由於端口號不一樣
複製代碼
同源政策的目的其實就是爲了保證用戶信息的安全,防止惡意的網站數據竊取。 在阮一峯的博客中,在同源政策一節中對其做用描述以下:java
"設想這樣一種狀況: A網站是一家銀行,用戶登陸之後,又去瀏覽其餘網站。 若是其餘網站能夠讀取A網站的 Cookie,會發生什麼? 很顯然,若是 Cookie 包含隱私(好比存款總額),這些信息就會泄漏。 更可怕的是: Cookie 每每用來保存用戶的登陸狀態。 若是用戶沒有退出登陸,其餘網站就能夠冒充用戶,隨心所欲。"
複製代碼
因此自1995起,"同源政策"由網景引入瀏覽器後,全部瀏覽器都開始效仿了這一政策。不過同源政策帶來的安全保障的同時,也帶來了一些限制,其中一個限制就是AJAX 請求不能發送。node
上文說到同源政策的限制之一就是AJAX請求沒法發送,咱們知道AJAX的核心就是XMLHttpRequest,因此藉機我也簡單談一談XMLHttpRequest。先看一個示例:
在個人hosts文件中,我事先已經寫好了ip與域名的映射。
程序員
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
console.log('Please appoint the port number\n Like node server.js 8888')
process.exit(1)
}
var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
var query = parsedUrl.query
var path = parsedUrl.pathname
if(path.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var method = request.method
console.log('HTTP Path:\n'+path)
if(path ==='/'){
// sync是同步,async表明異步
let string = fs.readFileSync('./index.html','utf8');
response.statusCode = 200
response.setHeader('Content-Type','text/html;charset=utf-8')
response.write(string);
response.end();
}else if(path ==='/xxx'){
response.statusCode = 200
response.setHeader('Content-Type','text/json;charset=utf-8')
response.write(`
{
"info":{
"name":"DobbyKim",
"age":"25",
"hobby":"唱跳rap籃球",
"girlfriend":"rightHand"
}
}
`)
response.end();
}
else{
response.statusCode = 404
response.setHeader('Content-Type','text/html;charset=utf-8')
response.write('wrong')
response.end()
}
console.log(method+''+request.url)
})
server.listen(port)
console.log('Listen'+port+'Success\n Please open http://localhost:'+port)
複製代碼
前端代碼:面試
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>你咬我啊</title>
</head>
<body>
<button id="btn">你咬我啊</button>
<script>
btn.addEventListener('click',()=>{
// 建立XMLHttpRequest對象
let request = new XMLHttpRequest();
// 初始化
request.open('POST','http://dobby.com:8888/xxx');
// 發送請求
request.send();
request.onreadystatechange = ()=>{
// 請求及響應均成功
if(request.readyState === 4){
if(request.status>=200 && request.status<300){
let string = request.responseText;
let obj = window.JSON.parse(string);
console.log(string);
console.log(obj);
}else{console.log('fail');}
}
}
})
</script>
</body>
</html>
複製代碼
在前端script代碼中,咱們爲按鈕添加了事件,當按鈕被click,當前頁面就會向服務端發起請求,咱們再來回想一下request.readyState的五個狀態值:ajax
0 :代理被建立,但還沒有調用open()方法
1 : open()方法已經被調用
2 : send()方法已經被調用
3 : 響應數據下載中
4 : 響應數據下載已完成
複製代碼
首先咱們開啓兩個node-server,它們指定的端口號分別爲:8888和8889。咱們在瀏覽器分別輸入URL:dobby.com:8888
以及frank.com:8889
。當咱們在dobby.com:8888
下點擊按鈕時,在瀏覽器的控制檯上打印出了咱們接收到的JSON數據。
數據庫
frank.com:8889
下點擊按鈕,在控制檯上則會報錯:
else if(path ==='/xxx'){
response.statusCode = 200
response.setHeader('Content-Type','text/json;charset=utf-8')
// 添加了這句話之後,任何網站均可以請求dobbykim.com:8888
// response.setHeader('Access-Control-Allow-Origin','*')
response.setHeader('Access-Control-Allow-Origin','http://frank.com:8889')
response.write(` { "info":{ "name":"DobbyKim", "age":"25", "hobby":"唱跳籃球rap", "girlfriend":"rightHand" } } `)
response.end();
}
複製代碼
上面咱們實際上用到了CORS機制,CORS即Cross-Origin-Resource-Sharing,翻譯成跨域資源共享,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。有了CORS機制,可使AJAX進行跨域請求,AJAX同時也支持多種請求方式:get,post,put,delete等等。那麼在沒有AJAX以前,咱們是怎樣進行跨域請求的呢?這就要引出咱們今天的主角JSONP了,可是在談JSONP以前,咱們還要再聊一聊歷史~
假設咱們有一個文件db,這個文件db暫時做爲咱們的數據庫進行數據的存儲,文件存儲着當前金額的數量100。 後臺程序以下:
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
console.log('Please appoint the port number\n Like node server.js 8888')
process.exit(1)
}
var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
var query = parsedUrl.query
var path = parsedUrl.pathname
if(path.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var method = request.method
console.log('HTTP Path:\n'+path)
if(path == '/'){
var string = fs.readFileSync('./index.html','utf8')
var amount = fs.readFileSync('./db','utf-8')
string = string.replace('&amount',amount);
response.setHeader('Content-Type','text/html;charset=utf8')
response.write(string)
response.end()
}else if(path==='/pay' && method.toUpperCase()==='POST'){
var amount = fs.readFileSync('./db','utf8')
var newAmount = parseInt(amount) - 1;
fs.writeFileSync('./db',newAmount);
response.write('success');
response.end()
} else{
response.statusCode = 404
response.setHeader('Content-Type','text/html;charset=utf-8')
response.write('找不到對應的路徑')
response.end()
}
console.log(method+''+request.url)
})
server.listen(port)
console.log('Listen'+port+'Success\n Please open http://localhost:'+port)
複製代碼
前端代碼以下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<h5>您的帳餘額是 <span id="amount">&amount</span></h5>
<form action="/pay" method="post">
<input type="submit" value="付款">
</form>
</body>
</html>
複製代碼
form表單的核心功能就是提交。如本例:當咱們點擊submit進行提交時,瀏覽器會跳轉到pay這個路徑下 若是path==='/pay' && method.toUpperCase()==='POST'
,咱們就會將db文件存儲的金額-1,而後返回一個"success"。開啓server後,程序運行的結果以下:
response.write('success');
,在頁面上咱們看到了success的字樣,後退至index.html頁面,並點擊刷新,咱們能夠看到,金額減小了一元錢。
<form action="/pay" method="post" target="result">
<input type="submit" value="付款">
</form>
<iframe name="result" src="about:blank" frameborder="0" height="200"></iframe>
複製代碼
form表單最大的問題就是會刷新頁面或打開新的頁面,不過form表單卻有一個特性即:沒有跨域的問題。在上面的程序中,咱們若是將form標籤變爲<form action="http://www.baidu.com/pay" method="get">
。實際上這個請求是能夠發送的。在知乎上有一個問題:爲何form表單提交沒有跨域問題,可是ajax提交有跨域問題?我在這裏面借用下方老師的答案 :-)
a標籤能夠發起get請求,不過也會刷新或打開頁面,img標籤會發起get請求,可是隻能以圖片形式進行展現,通過多方面考慮,因而乎,當時的前端程序員決定使用script標籤,由於script標籤不只能發起請求,同時也能做爲腳本執行,最重要的是,script標籤支持跨域請求。接下來,咱們來看一個示例:
首先,在個人hosts文件中,我已經寫好了ip與域名的映射。
http://dobby.com:8888
以及
http://frank.com:8889
,模擬dobby.com向frank.com發起跨域請求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h5>您的帳餘額是 <span id="amount">&amount</span></h5>
<button id="btn">付款</button>
<script> btn.addEventListener('click',()=>{ // 動態建立script標籤 let script = document.createElement('script'); // 隨機生成函數名 let functionName = 'dobby'+parseInt(Math.random()*10000,10); window[functionName] = (result)=>{ if(result === 'success'){ amount.innerText = amount.innerText - 1; }else{ alert('fail'); } } // 指定發起請求的地址 script.src = 'http://frank.com:8889/pay?callback='+functionName; // 必定要將script加進去 document.body.appendChild(script); script.onload = (e)=>{ // 每次動態建立script標籤以後,都將script標籤刪掉 e.currentTarget.remove(); // 不管script標籤加載成功或失敗都須要將window[functionName]屬性刪除 delete window[functionName]; } script.onerror = ()=>{ alert('fail'); delete window[functionName]; } }) </script>
</body>
</html>
複製代碼
對於frank.com的後端來說,只須要這樣作便可:
else if(path==='/pay'){
var amount = fs.readFileSync('./db','utf8')
var newAmount = parseInt(amount) - 1;
fs.writeFileSync('./db',newAmount);
response.setHeader('Content-Type','application/javascript')
response.statusCode = 200
// query爲path後面的查詢參數
response.write(` ${query.callback}.call(undefined,'success'); `)
response.end()
}
複製代碼
frank.com的後端程序員只須要拿到查詢參數中的callback的值,並調用此方法,而前端程序員經過後端傳入的參數進行判斷,這樣就作到了低耦合高複用的代碼。實際上,這就是JSONP。
JSONP是一種動態script標籤跨域請求技術。指的是請求方動態建立script標籤,src指向響應方的服務器,同時傳一個參數callback,callback後面是一個隨機生成的functionName,當請求方向響應方發起請求時,響應方根據傳過來的參數callback,構造並調用形如:xxx.call(undefined,'你要的數據'),其中'你要的數據'的傳入格式是以JSON格式傳入的,由於傳入的JSON數據具備左右padding,於是得名JSONP。後端代碼構造並調用了xxx,瀏覽器接收到了響應,就會執行xxx.call(undefined,'你要的數據'),因而乎,請求方就知道了他要的數據,這就是JSONP。在知乎上,看到了有關於JSONP的回答:
咱們首先須要引入jQuery,而後將代碼中script標籤裏面的內容變爲這樣便可:
btn.addEventListener('click',function () {
$.ajax({
url: "http://jack.com:8001/pay",
// The name of the callback parameter, as specified by the YQL service
jsonp: "callback",
// Tell jQuery we're expecting JSONP
dataType: "jsonp",
// Tell YQL what we want and that we want JSON
data: {
q: "select title,abstract,url from search.news where query=\"cat\"",
format: "json"
},
// Work with the response
success: function( response ) {
if(response === 'success'){
amount.innerText = amount.innerText - 1;
}
}
});
})
複製代碼
值得吐槽的一點是:調用jQuery的JSONP API裏面出現了ajax這樣的字眼,實際上JSONP和Ajax毛關係都沒有。
這是一道大機率會出現的面試題,回答以下:
回答完畢~
文章若是出現問題,歡迎指出與批評。