JSONP
從這一部分開始了解一下前后端分離的思想:
javascript高級(jí)部分:前后端聯(lián)動(dòng),瀏覽器+服務(wù)器
數(shù)據(jù)庫(kù)是什么
文件系統(tǒng)是一種數(shù)據(jù)庫(kù)
MySQL 是一種數(shù)據(jù)庫(kù),也是一個(gè)軟件
只要能長(zhǎng)久地存數(shù)據(jù),就是數(shù)據(jù)庫(kù)
前后端如何配合?
接下來(lái)我們用一個(gè)文件充當(dāng)數(shù)據(jù)庫(kù)(實(shí)際上數(shù)據(jù)庫(kù)的存儲(chǔ)內(nèi)容本質(zhì)就是一個(gè)帶有結(jié)構(gòu)的文件),捋一捋前后端交互的過(guò)程.
首先寫一個(gè)首頁(yè)
<title>首頁(yè)</title>
<link rel="stylesheet" href="/style.css">
<h5>我的余額為<span id="amount">&&&amount&&&</span></h5>
<!-- &&&amount&&&只是前臺(tái)的占位符,會(huì)將從后臺(tái)讀取的數(shù)據(jù)顯示在這里,并替換這個(gè)我們假設(shè)的占位符 -->
<form action="/pay" method="POST">
<input type="submit" value="付款一元">
</form>
再寫服務(wù)器的代碼 server.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.env.PORT || 8888;
var server = http.createServer(function (request, response) {
var temp = url.parse(request.url, true)
var path = temp.pathname
var query = temp.query
var method = request.method
//從這里開始看,上面不要看
if (path === '/') { // 如果用戶請(qǐng)求的是 / 路徑
var string = fs.readFileSync('./index.html') // 就讀取 index.html 的內(nèi)容
var amount = fs.readFileSync('./db','utf-8')//****從數(shù)據(jù)庫(kù)(db文件)同步讀取數(shù)據(jù),放到amount變量里
string = string.toString().replace('&&&amount&&&',amount)//將后臺(tái)的amount替換為前臺(tái)的占位符&&&amount&&&
response.setHeader('Content-Type', 'text/html;charset=utf-8') // 設(shè)置響應(yīng)頭 Content-Type
response.write(string) // 設(shè)置響應(yīng)消息體
response.end();
} else if (path === '/style.css') { // 如果用戶請(qǐng)求的是 /style.css 路徑
var string = fs.readFileSync('./style.css')
response.setHeader('Content-Type', 'text/css')
response.write(string)
response.end()
} else if (path === '/main.js') { // 如果用戶請(qǐng)求的是 /main.js 路徑
var string = fs.readFileSync('./main.js')
response.setHeader('Content-Type', 'application/javascript')
response.write(string)
response.end()
} else if(path === '/pay' && method.toUpperCase() === 'POST'){//如果請(qǐng)求的路徑是pay且方法為post
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
fs.writeFileSync('./db',newAmount);
response.write('success')
response.end()
}else { // 如果上面都不是用戶請(qǐng)求的路徑
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8') // 設(shè)置響應(yīng)頭 Content-Type
response.write('找不到對(duì)應(yīng)的路徑,你需要自行修改 index.js')
response.end()
}
// 代碼結(jié)束键菱,下面不要看
console.log(method + ' ' + request.url)
})
server.listen(port)
console.log('監(jiān)聽 ' + port + ' 成功瓷炮,請(qǐng)用在空中轉(zhuǎn)體720度然后用電飯煲打開 http://localhost:' + port)
最后在創(chuàng)建一個(gè)名為db的文本文件充當(dāng)數(shù)據(jù)庫(kù)
接著我們開啟服務(wù)器
node server.js 8888
接著在瀏覽器中輸入地址http://localhost:8888
當(dāng)用戶打開瀏覽器輸入ip地址,就會(huì)向后臺(tái)發(fā)送請(qǐng)求,服務(wù)器發(fā)現(xiàn)地址是/之后執(zhí)行if else的path==='/'分支,執(zhí)行下面的代碼:
if (path === '/') { // 如果用戶請(qǐng)求的是 / 路徑
var string = fs.readFileSync('./index.html') // 就讀取 index.html 的內(nèi)容
var amount = fs.readFileSync('./db','utf-8')//****從數(shù)據(jù)庫(kù)(db文件)同步讀取數(shù)據(jù),放到amount變量里
string = string.toString().replace('&&&amount&&&',amount)//將后臺(tái)的amount替換為前臺(tái)的占位符&&&amount&&&
response.setHeader('Content-Type', 'text/html;charset=utf-8') // 設(shè)置響應(yīng)頭 Content-Type
response.write(string) // 設(shè)置響應(yīng)消息體
response.end();
}
接著點(diǎn)擊提交按鈕,就會(huì)向后臺(tái)發(fā)起post請(qǐng)求,請(qǐng)求路徑為/pay,于是進(jìn)入path === '/pay'分支,執(zhí)行下面的代碼,使數(shù)據(jù)庫(kù)(這里數(shù)據(jù)庫(kù)用一個(gè)文本文件代替)中的金額減一
else if(path === '/pay' && method.toUpperCase() === 'POST'){//如果請(qǐng)求的路徑是pay且方法為post
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
fs.writeFileSync('./db',newAmount);
response.write('success')
response.end()
}
然后會(huì)發(fā)現(xiàn)/pay頁(yè)面返回了success
返回剛才的首頁(yè),刷新后,會(huì)發(fā)現(xiàn)金額減少了1.
也可以模擬后臺(tái)修改數(shù)據(jù)庫(kù)成功或失敗
else if(path === '/pay' && method.toUpperCase() === 'POST'){//如果請(qǐng)求的路徑是pay且方法為post
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
if(Math.random()>0.5){//模擬成功或失敗
fs.writeFileSync('./db',newAmount);
response.write('success')
}else{
response.write('fail')
}
response.end()
}
優(yōu)化體驗(yàn):用image和script發(fā)送請(qǐng)求
但是剛才那樣體驗(yàn)非常不好,因?yàn)橐祷?要刷新.在2005年以前,網(wǎng)頁(yè)的前后端交互都是這樣.但現(xiàn)在是9012年了,我們需要優(yōu)化一下用戶體驗(yàn)
不光form表單可以發(fā)送請(qǐng)求,a標(biāo)簽(需要點(diǎn)擊),link標(biāo)簽,script,圖片,都可以發(fā)送請(qǐng)求
用image發(fā)送請(qǐng)求
那我們先試試用img發(fā)送一個(gè)請(qǐng)求
當(dāng)瀏覽器發(fā)現(xiàn)html里面有一個(gè)img時(shí),會(huì)自動(dòng)發(fā)送請(qǐng)求,請(qǐng)求路徑就是img的src.但是有一個(gè)缺點(diǎn).發(fā)送的請(qǐng)求只能是get,沒辦法改成post.
使用img.onload和img.onerror方法判斷請(qǐng)求是成功還是失敗,具體操作看下方代碼
復(fù)習(xí)一下返回的狀態(tài)碼:
2開頭:成功
3開頭:重定向
4開頭:客戶端錯(cuò)誤
5開頭:服務(wù)器錯(cuò)誤
修改首頁(yè)
<title>首頁(yè)</title>
<link rel="stylesheet" href="/style.css">
<h5>我的余額為<span id="amount">&&&amount&&&</span></h5>
<!-- &&&amount&&&知識(shí)前臺(tái)的占位符,會(huì)將從后臺(tái)讀取的數(shù)據(jù)顯示在這里,并替換這個(gè)占位符 -->
<button id="button">付錢一元</button>
<script>
let btn = document.getElementById("button");
btn.onclick = function(){
let img = document.createElement("img");
img.src = "/pay";
img.onload = function(){//如果圖片請(qǐng)求成功(返回碼以2開頭)
alert("付錢成功")
window.location.reload();//彈框并刷新
}
img.onerror = function(){//返回碼是4開頭
alert("付錢失敗")
}
}
</script>
修改/pay路徑的響應(yīng) 這里面的post已經(jīng)去掉了
這個(gè)里面 必須放一個(gè)真的圖片,不然瀏覽器還是會(huì)認(rèn)為是失敗
else if(path === '/pay'){//如果請(qǐng)求的路徑是pay,接受圖片的請(qǐng)求
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
if(Math.random()>0.5){//模擬成功或失敗
fs.writeFileSync('./db',newAmount);//成功了就把數(shù)據(jù)寫入數(shù)據(jù)庫(kù)
response.setHeader('Content-Type','image/jpg')//設(shè)置返回文件類型為jpg
response.statusCode = 200;//返回碼為200,說(shuō)明成功
response.write(fs.readFileSync('./dog.jpg'))//必須返回一個(gè)真的圖片,不然瀏覽器還是會(huì)認(rèn)為是失敗
}else{
response.statusCode = 400;//否則返回碼為400,說(shuō)明失敗
response.write('fail')
}
response.end()
}
然后開啟服務(wù)器,瀏覽器輸入地址
點(diǎn)擊付錢,成功,并刷新
付錢失敗時(shí),彈框后瀏覽器就不刷新了:
也可局部刷新
img.onload = function(){//如果圖片請(qǐng)求成功(返回碼以2開頭)
alert("付錢成功")
amount.innerText = amount.innerText-1;//局部刷新/因?yàn)橐呀?jīng)知道數(shù)據(jù)庫(kù)修改成功,所以無(wú)需再進(jìn)行刷新,直接修改數(shù)值即可
}
還有一個(gè)需要注意的點(diǎn)
在下面這段代碼中
response.setHeader('Content-Type','image/jpg')//設(shè)置返回文件類型為jpg,
response.statusCode = 200;//返回碼為200,說(shuō)明成功
response.write(fs.readFileSync('./dog.jpg'))//必須返回一個(gè)真的圖片,不然瀏覽器還是會(huì)認(rèn)為是失敗
看看回復(fù)的響應(yīng)
response.write(fs.readFileSync('./dog.jpg'))
這句代碼中fs.readFileSync('./dog.jpg')
參數(shù)里只有路徑,沒有加別的,那么讀出來(lái)的數(shù)據(jù)類型為2進(jìn)制數(shù)據(jù),然后'Content-Type','image/jpg'
這句代碼表示以jpg圖片的形式讀取這段二進(jìn)制代碼.
復(fù)習(xí)一下響應(yīng)的四個(gè)部分:
1 協(xié)議/版本號(hào) 狀態(tài)碼 狀態(tài)解釋
2 Key1: value1
2 Key2: value2
2 Content-Length: 17931
2 Content-Type: text/html
3
4 響應(yīng)的內(nèi)容
response.write(fs.readFileSync('./dog.jpg'))
這句代碼就是響應(yīng)的第四部分,即響應(yīng)的內(nèi)容
用script發(fā)送請(qǐng)求
那么我們也可以用script動(dòng)態(tài)的發(fā)送請(qǐng)求.原理和img類似,但是必須得把script標(biāo)簽加入到body里面,才會(huì)發(fā)送請(qǐng)求,而img標(biāo)簽只要?jiǎng)?chuàng)建,就會(huì)發(fā)送請(qǐng)求.
接下來(lái)看代碼
首頁(yè)的發(fā)送請(qǐng)求代碼
<script>
let btn = document.getElementById("button");
let amount = document.getElementById("amount")
btn.onclick = function(){
let script = document.createElement("script");
script.src = "/pay";
document.body.appendChild(script);//這句話一定要加上,這是與使用圖片請(qǐng)求不一樣的地方
script.onload = function(){//如果script請(qǐng)求成功(返回碼以2開頭)
alert("付錢成功");
}
script.onerror = function(){//返回碼是4開頭
alert("付錢失敗");
}
}
</script>
服務(wù)器端的代碼:
else if(path === '/pay'){//如果請(qǐng)求的路徑是pay,接受script的請(qǐng)求
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
if(Math.random()>0.5){//模擬成功或失敗
fs.writeFileSync('./db',newAmount);//成功了就把數(shù)據(jù)寫入數(shù)據(jù)庫(kù)
response.setHeader('Content-Type','applacation/javascript')//設(shè)置返回文件類型為javascript
response.statusCode = 200;//返回碼為200,說(shuō)明成功
response.write('alert("我是創(chuàng)建的script請(qǐng)求里面響應(yīng)的內(nèi)容")')//響應(yīng)內(nèi)容為創(chuàng)建的script標(biāo)簽里面的內(nèi)容
}else{
response.statusCode = 400;//否則返回碼為400,說(shuō)明失敗
response.write('fail')
}
response.end()
}
當(dāng)我點(diǎn)擊打錢的時(shí)候,他會(huì)動(dòng)態(tài)的創(chuàng)建一個(gè)script,這個(gè)script會(huì)根據(jù)src發(fā)起請(qǐng)求
進(jìn)入成功分支:
先執(zhí)行請(qǐng)求使用的script里面的語(yǔ)句
每次請(qǐng)求就會(huì)創(chuàng)建一個(gè)新的script標(biāo)簽,如果響應(yīng)成功了,就執(zhí)行響應(yīng)里面的javascript語(yǔ)句,這個(gè)script里面的語(yǔ)句就是返回的響應(yīng)的第四部分的內(nèi)容,執(zhí)行完之后才會(huì)觸發(fā)onload事件
進(jìn)入失敗分支:
重要的一點(diǎn):
就是每一次請(qǐng)求都會(huì)創(chuàng)建一個(gè)script標(biāo)簽郑兴,如何修改請(qǐng)看下一節(jié)優(yōu)化體驗(yàn)
優(yōu)化用戶體驗(yàn)
那么既然請(qǐng)求返回的響應(yīng)可以執(zhí)行,我們就不需要寫onload了,直接把onload里面的代碼寫在響應(yīng)的第四部分不就可以了?沒錯(cuò)
接來(lái)下修改一下代碼,刪掉onload代碼,將onload里面的代碼寫在服務(wù)器端.將前臺(tái)請(qǐng)求成功要做的事情交給后臺(tái)來(lái)做.
btn.onclick = function(){
let script = document.createElement("script");
script.src = "/pay";
document.body.appendChild(script);//這句話一定要加上,這是與使用圖片請(qǐng)求不一樣的地方
//刪除onload代碼,交給后臺(tái)來(lái)做
script.onerror = function(){//返回碼是4開頭
alert("付錢失敗");
}
}
后臺(tái)只需要將金額數(shù)量減1即可,不需要刷新,增加用戶體驗(yàn)
else if(path === '/pay'){//如果請(qǐng)求的路徑是pay,接受script的請(qǐng)求
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
if(Math.random()>0.5){//模擬成功或失敗
fs.writeFileSync('./db',newAmount);//成功了就把數(shù)據(jù)寫入數(shù)據(jù)庫(kù)
response.setHeader('Content-Type','applacation/javascript')//設(shè)置返回文件類型為javascript
response.statusCode = 200;//返回碼為200,說(shuō)明成功
response.write('amount.innerText = amount.innerText-1')//響應(yīng)內(nèi)容為創(chuàng)建的script標(biāo)簽里面的內(nèi)容,返回后會(huì)立即執(zhí)行
}else{
response.statusCode = 400;//否則返回碼為400,說(shuō)明失敗
response.write('fail')
}
response.end()
}
最后在做一件事情腥泥,因?yàn)槊看尾还艹晒κ?都會(huì)創(chuàng)建一個(gè)script標(biāo)簽,那么可否在請(qǐng)求結(jié)束后,將標(biāo)簽刪除?
答: 可以.
修改前臺(tái)onload和onerror代碼即可,不管請(qǐng)求成功失敗,都移除script標(biāo)簽
script.onload = function(e) {
e.currentTarget.remove(); //添加移除代碼
};
script.onerror = function(e) {
e.currentTarget.remove();
};
總結(jié) SRJ(server rendered javascript--服務(wù)器返回的javascript)
以上方法就叫做SRJ,服務(wù)器返回的javascript,即不是在前臺(tái)寫的javascript
這是在ajax出現(xiàn)之前,后端程序員想出來(lái)的前后臺(tái)無(wú)刷新,局部更新頁(yè)面內(nèi)容的交互方法
請(qǐng)求另個(gè)一網(wǎng)站的script
script請(qǐng)求的內(nèi)容不受域名限制.當(dāng)前網(wǎng)站的可以請(qǐng)求其他網(wǎng)站的js代碼,例如jquery
可以引入jquery
所以剛才的SRJ很危險(xiǎn),假如只用了get方法,那么像付錢這種操作很容易被偽裝成功,導(dǎo)致別人的惡意攻擊
例如我進(jìn)入一個(gè)釣魚網(wǎng)站
那個(gè)網(wǎng)站直接請(qǐng)求
script.src = "http://alipay.com/pay?usname=yourName&amount=100000"
這樣只要進(jìn)入網(wǎng)站執(zhí)行這個(gè)請(qǐng)求,豈不是可以隨意給自己的賬戶打錢?
所以只用get不安全.
所以很多危險(xiǎn)操作都得用POST請(qǐng)求,例如打錢必須驗(yàn)證用戶,做各種支付相關(guān)的安全防范,手機(jī)驗(yàn)證碼之類的.
下面我們模仿一下不同的網(wǎng)站請(qǐng)求對(duì)方的后臺(tái)的過(guò)程
首先修改一下本地的hosts文件,將兩個(gè)域名都指向127.0.0.1,假裝成兩個(gè)不同的網(wǎng)站
hosts文件相當(dāng)于一個(gè)個(gè)人的DNS岩梳,當(dāng)你訪問(wèn)某個(gè)域名時(shí)唆樊,是先通過(guò)hosts進(jìn)行解析的宛琅,沒有找到才進(jìn)一步通過(guò)外網(wǎng)DNS服務(wù)器進(jìn)行解析。
如果是windows系統(tǒng):
windows系統(tǒng)hosts文件位置及操作
如果是linux或者Unix
地址為/etc/hosts
打開文件之后逗旁,在末尾添加
127.0.0.1 chenhuan.com
127.0.0.1 jack.com
添加代碼,那么如果我在本地訪問(wèn)chenhuan.com和jack.com,他首先會(huì)進(jìn)127.0.0.1,而不是去外網(wǎng)解析dns
這樣我們就假裝成為了兩個(gè)網(wǎng)站,實(shí)際上兩個(gè)網(wǎng)站的內(nèi)容是一樣的,只是用來(lái)做實(shí)驗(yàn)
然后開兩個(gè)服務(wù)器,使用兩個(gè)不同的端口來(lái)監(jiān)聽(模擬兩個(gè)不同的網(wǎng)站都開啟了服務(wù)器,都在監(jiān)聽)
監(jiān)聽8001
PORT =8001 node index
監(jiān)聽8002
PORT =8002 node index
這樣我們?cè)诘刂窓谳斎隿henhuan.com:8001就進(jìn)入了第一個(gè)網(wǎng)站,在地址欄輸入jack.com:8002
就進(jìn)入了第二個(gè)網(wǎng)站.
這樣兩個(gè)個(gè)網(wǎng)站都可以接受請(qǐng)求,都可以訪問(wèn),只不過(guò)內(nèi)容一樣而已
那么接下來(lái)我們來(lái)模仿不同網(wǎng)站之間的請(qǐng)求互動(dòng)
修改前端SRJ請(qǐng)求代碼的請(qǐng)求路徑,改為script.src = "http://jack.com:8002/pay";
那么我們打開chenhuan.com:8001就進(jìn)入了第一個(gè)網(wǎng)站,點(diǎn)擊按鈕后,意思就是請(qǐng)求http://jack.com:8002/pay這個(gè)第二個(gè)網(wǎng)站的地址.我們來(lái)試一試
server.js
index.html
<title>首頁(yè)</title>
<link rel="stylesheet" href="/style.css" />
<h5>我的余額為<span id="amount">&&&amount&&&</span></h5>
<!-- &&&amount&&&知識(shí)前臺(tái)的占位符,會(huì)將從后臺(tái)讀取的數(shù)據(jù)顯示在這里,并替換這個(gè)占位符 -->
<button id="button">付錢一元</button>
<script>
let btn = document.getElementById("button");
btn.onclick = function() {
let script = document.createElement("script");
script.src = "http://jack.com:8002/pay";
document.body.appendChild(script); //這句話一定要加上,這是與使用圖片請(qǐng)求不一樣的地方
//刪除onload代碼,交給后臺(tái)來(lái)做
script.onload = function(e) {
e.currentTarget.remove(); //添加移除代碼
};
script.onerror = function(e) {
e.currentTarget.remove();
};
// let img = document.createElement("img");
// img.src = "/pay";
// img.onload = function() {
// //如果圖片請(qǐng)求成功(返回碼以2開頭)
// alert("付錢成功");
// // window.location.reload(); //彈框并刷新
// amount.innerText = amount.innerText - 1; //局部刷新
// };
// img.onerror = function() {
// //返回碼是4開頭
// alert("付錢失敗");
// };
};
</script>
在chenhuan.com:8080中點(diǎn)擊付錢
失敗的狀態(tài):
上圖中的pay是請(qǐng)求的路徑嘿辟,jack.comhe 127.0.0.1:8002 分別是jack的地址和端口
這是成功的時(shí)候,結(jié)果如圖所示
可以看到,兩個(gè)完全不同的網(wǎng)站,他們兩個(gè)是可以互相調(diào)script的
這時(shí)候的意思是:我是用第一個(gè)網(wǎng)站(chenhuan.com)打開了第二個(gè)網(wǎng)站(jack.com)的付錢功能!
(由于我為了演示,這兩個(gè)網(wǎng)站用了同樣的代碼,同一個(gè)數(shù)據(jù)庫(kù),我們假設(shè)第一個(gè)網(wǎng)站和第二個(gè)網(wǎng)站代碼不同)
jsonp
解耦(解決耦合問(wèn)題)
解決方法:
后臺(tái)程序員:我只把我后臺(tái)應(yīng)該改的數(shù)據(jù)庫(kù),然后我寫一個(gè)callback函數(shù),將成功和失敗的狀態(tài)傳遞給前臺(tái).這個(gè)函數(shù)就是我后端程序員做完這些事情成功之后要前端程序員去做的事情.然后執(zhí)行這個(gè)函數(shù),這個(gè)函數(shù)的內(nèi)容交給前端去寫.
前端程序員:我不管后端如何修改數(shù)據(jù)庫(kù),我事先寫好成功和失敗的函數(shù),成功了就返回給我一個(gè)成功的提示,然后我根據(jù)提示執(zhí)行成功的函數(shù),如果給我的提示是失敗,就執(zhí)行失敗的函數(shù).
接下來(lái)寫代碼理解:
修改服務(wù)器端的代碼:
response.write(`${query.callbackName}.call(undefined,'success')`)//先獲取查詢字符串里的callbackName即從前臺(tái)傳過(guò)來(lái)的回調(diào)函數(shù)的函數(shù)名,然后再執(zhí)行他,并把函數(shù)的參數(shù)定為success
添加前臺(tái)index.html的代碼
<script>
// 這三行是添加的
window.xxx = function(result){
alert("我是前端程序員寫的函數(shù),經(jīng)過(guò)后端程序員調(diào)用才執(zhí)行")
alert(`后端返回回來(lái)的函數(shù)參數(shù)是:${result}`)
}
// 這三行是添加的
let btn = document.getElementById("button");
let amount = document.getElementById("amount")
btn.onclick = function(){
let script = document.createElement("script");
//這行是修改的
script.src = "http://jack.com:8002/pay?callbackName=xxx";//請(qǐng)求時(shí)帶上查詢參數(shù),指定callbackName為XXX,以便給后臺(tái)程序員調(diào)用
//這行是修改的
document.body.appendChild(script);//這句話一定要加上,這是與使用圖片請(qǐng)求不一樣的地方
script.onload = function(e){//如果script請(qǐng)求成功(返回碼以2開頭)
e.currentTarget.remove();//添加移除代碼
}
script.onerror = function(e){//返回碼是4開頭
alert("失敗")
e.currentTarget.remove();
}
}
</script>
添加的這幾句代碼的解釋:
首先
script.src = "http://ceshi2.com:8002/pay?callbackName=xxx";
在請(qǐng)求時(shí)帶上查詢參數(shù),指定callbackName為xxx,以便給后臺(tái)程序員調(diào)用
接著
response.write(`${query.callbackName}.call(undefined,'success')`)
后臺(tái)程序員獲得前臺(tái)程序員的查詢參數(shù),并執(zhí)行名為查詢參數(shù)的函數(shù)xxx
執(zhí)行xxx,并且將相應(yīng)的內(nèi)容作為回調(diào)函數(shù)的第一個(gè)參數(shù)傳回去,這個(gè)參數(shù)為字符串'success'
執(zhí)行結(jié)果
window.xxx = function(result){
alert("我是前端程序員寫的函數(shù),經(jīng)過(guò)后端程序員調(diào)用才執(zhí)行")
alert(`后端返回回來(lái)的函數(shù)參數(shù)是:${result}`)
}
也可以根據(jù)后臺(tái)傳過(guò)來(lái)的參數(shù),修改前臺(tái)的邏輯
window.xxx = function(result){
if(result === "success"){
amount.innerText = amount.innerText - 1;
}
}
那么如果我們返回的參數(shù)不是字符串,而是對(duì)象呢?
這也是可以的,例如我們修改一下代碼
else if(path === '/pay'){//如果請(qǐng)求的路徑是pay,接受script的請(qǐng)求
var amount = fs.readFileSync('./db','utf-8')
var newAmount = amount-1;
if(Math.random()>0.5){//模擬成功或失敗
fs.writeFileSync('./db',newAmount);//成功了就把數(shù)據(jù)寫入數(shù)據(jù)庫(kù)
response.setHeader('Content-Type','applacation/javascript')//設(shè)置返回文件類型為javascript
response.statusCode = 200;//返回碼為200,說(shuō)明成功
response.write(`${query.callbackName}.call(undefined,{
"success":true,
"left":${newAmount}
})`)//把函數(shù)的參數(shù)定為一個(gè)對(duì)象
}else{
response.statusCode = 400;//否則返回碼為400,說(shuō)明失敗
response.write('fail')
}
response.end()
}
前端:
window.xxx = function(result){
if(result.success === true){
amount.innerText = amount.innerText - 1;
}
}
jsonp的含義和理解
我請(qǐng)求一個(gè)script,script調(diào)用一個(gè)函數(shù)的同時(shí),把第一個(gè)參數(shù)設(shè)置為要返回的數(shù)據(jù),不管這個(gè)數(shù)據(jù)是對(duì)象還是字符串.然后人們把這種調(diào)用script并返回字符串或者對(duì)象的使用方法叫做是jsonp,這就是jsonp!.只是名字比較奇怪,其實(shí)真名的名字應(yīng)該是:動(dòng)態(tài)標(biāo)簽跨域請(qǐng)求!即利用動(dòng)態(tài)標(biāo)簽進(jìn)行跨域請(qǐng)求的技術(shù)
這種方法的好處是可以解決跨域的問(wèn)題.例如剛才的例子,兩個(gè)網(wǎng)站如果域名不一樣,那么第一個(gè)網(wǎng)址的前臺(tái)和另一個(gè)網(wǎng)址后臺(tái)的服務(wù)器無(wú)法進(jìn)行數(shù)據(jù)交換(由于瀏覽器的同源策略).但是使用script標(biāo)簽就可以避免這個(gè)問(wèn)題.利用script請(qǐng)求,可以請(qǐng)求道第二個(gè)網(wǎng)站的json或字符串?dāng)?shù)據(jù),這種方法就叫做jsonp
JSONP(JSON with Padding)是JSON的一種“使用模式”片效,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問(wèn)的問(wèn)題红伦。由于同源策略,一般來(lái)說(shuō)位于 server1.example.com 的網(wǎng)頁(yè)無(wú)法與不是 server1.example.com的服務(wù)器溝通淀衣,而 HTML 的<script> 元素是一個(gè)例外昙读。利用 <script> 元素的這個(gè)開放策略,網(wǎng)頁(yè)可以得到從其他來(lái)源動(dòng)態(tài)產(chǎn)生的 JSON 資料膨桥,而這種使用模式就是所謂的 JSONP蛮浑。----百度百科
jsonp要解決的是兩個(gè)不同域名的網(wǎng)站如何交流
,答案是用script標(biāo)簽就可以交流了,因?yàn)閟cript標(biāo)簽是不受域名限制的,而Ajax是受域名限制的
什么是jsonp總結(jié)
jsonp
請(qǐng)求方:chenhuan.com的前端(瀏覽器), 一個(gè)網(wǎng)站的前端
響應(yīng)方:jack.com的后端(服務(wù)器), 另一個(gè)網(wǎng)站的后端
請(qǐng)求方創(chuàng)建一個(gè)script,src指向響應(yīng)方,同時(shí)傳一個(gè)查詢參數(shù)?callbackName=xxx
-
響應(yīng)方根據(jù)查詢參數(shù)構(gòu)造形如
- xxx.call('undefined','你要的數(shù)據(jù)')
- xxx("你要的數(shù)據(jù)")
這樣的響應(yīng)
- xxx("你要的數(shù)據(jù)")
- 請(qǐng)求方瀏覽器接收到響應(yīng), 就會(huì)執(zhí)行xxx.call('undefined','你要的數(shù)據(jù)')
- 這樣請(qǐng)求方就得到了想要的數(shù)據(jù)
這就是jsonp
行業(yè)約定
jsonp沒有標(biāo)準(zhǔn)的規(guī)范,但是為了統(tǒng)一,有了行業(yè)約定
- callbackName一律用callback
- xxx函數(shù)一律使用形如chenhuan123124234的字符加上隨機(jī)數(shù)的形式(這樣的好處是不用取函數(shù)名了,而且調(diào)用完就delete,不會(huì)污染全局變量)
下面用行業(yè)約定來(lái)改寫代碼,加*的行為修改的代碼
<script>
let btn = document.getElementById("button");
let amount = document.getElementById("amount")
btn.onclick = function () {
let script = document.createElement("script");
let functionName = 'taotao' + parseInt(Math.random() * 10000, 10);//***隨機(jī)生成一個(gè)函數(shù)名
window[functionName] = function (result) {//***必須使用window[functionName]這樣的形式
if (result.success === true) {
amount.innerText = amount.innerText - 1;
}
}
script.src = "http://jack.com:8002/pay?callback=" + functionName;//***將隨機(jī)生成的函數(shù)名放到拼接到查詢字符串上
document.body.appendChild(script);//這句話一定要加上,這是與使用圖片請(qǐng)求不一樣的地方
script.onload = function (e) {//如果script請(qǐng)求成功(返回碼以2開頭)
e.currentTarget.remove();//添加移除代碼
delete window[functionName];//***不管成功失敗,最后都移除functionName
}
script.onerror = function (e) {//返回碼是4開頭
alert("失敗")
e.currentTarget.remove();
delete window[functionName];//***不管成功失敗,最后都移除functionName
}
}
</script>
response.write(`${query.callback}.call(undefined,{
"success":true,
"left":${newAmount}
})`)
- 隨機(jī)生成的查詢字符串
- 返回的根據(jù)隨機(jī)生成的函數(shù)名的數(shù)據(jù)
使用jQuery中的jsonp
jQuery使用jsonp非常簡(jiǎn)單
只要這樣修改前臺(tái)的代碼.后臺(tái)不用改 url不需要寫callback查詢參數(shù),因?yàn)閖Query會(huì)自動(dòng)給你生成
btn.onclick = function () {
$.ajax({
url: "http://jack.com:8002/pay",
jsonp: "callback",
dataType: "jsonp",
success: function (response) {
if (response.success) {
amount.innerText = amount.innerText - 1;
}
}
});
}
需要注意的一點(diǎn)是,jsonp不是ajax中的一種.不要背jquery誤導(dǎo)
面試題:jsonp為什么不能用post請(qǐng)求
答: jsonp是通過(guò)動(dòng)態(tài)創(chuàng)建script實(shí)現(xiàn)的
動(dòng)態(tài)創(chuàng)建script的時(shí)候只能用get,沒有辦法用post
什么json?
JSON 語(yǔ)法
JSON 語(yǔ)法規(guī)則
在 JS 語(yǔ)言中只嚣,一切都是對(duì)象沮稚。因此,任何支持的類型都可以通過(guò) JSON 來(lái)表示册舞,例如字符串蕴掏、數(shù)字、對(duì)象调鲸、數(shù)組等盛杰。但是對(duì)象和數(shù)組是比較特殊且常用的兩種類型:
- 對(duì)象表示為鍵值對(duì)
- 數(shù)據(jù)由逗號(hào)分隔
- 花括號(hào)保存對(duì)象
- 方括號(hào)保存數(shù)組
JSON 鍵/值對(duì)
JSON 鍵值對(duì)是用來(lái)保存 JS 對(duì)象的一種方式,和 JS 對(duì)象的寫法也大同小異藐石,鍵/值對(duì)組合中的鍵名寫在前面并用雙引號(hào) "" 包裹即供,使用冒號(hào) : 分隔,然后緊接著值:
{"firstName": "Json"}
這很容易理解于微,等價(jià)于這條 JavaScript 語(yǔ)句:
{firstName : "Json"}
JSON 與 JS 對(duì)象的關(guān)系
很多人搞不清楚 JSON 和 Js 對(duì)象的關(guān)系逗嫡,甚至連誰(shuí)是誰(shuí)都不清楚办素。其實(shí),可以這么理解:
JSON 是 JS 對(duì)象的字符串表示法祸穷,它使用文本表示一個(gè) JS 對(duì)象的信息,本質(zhì)是一個(gè)字符串勺三。
如
var obj = {a: 'Hello', b: 'World'};
//這是一個(gè)對(duì)象雷滚,注意鍵名也是可以使用引號(hào)包裹的
var json = '{"a": "Hello", "b": "World"}'; //這是一個(gè) JSON 字符串,本質(zhì)是一個(gè)字符串
JSON 和 JS 對(duì)象互轉(zhuǎn)
要實(shí)現(xiàn)從對(duì)象轉(zhuǎn)換為 JSON 字符串吗坚,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'}); //結(jié)果是 '{"a": "Hello", "b": "World"}'
要實(shí)現(xiàn)從 JSON 轉(zhuǎn)換為對(duì)象祈远,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //結(jié)果是 {a: 'Hello', b: 'World'}