JSONP(跨域)原理

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

image.png

返回剛才的首頁(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)擊付錢,成功,并刷新


image.png

付錢失敗時(shí),彈框后瀏覽器就不刷新了:


image.png

也可局部刷新

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ǔ)句

先執(zhí)行請(qǐng)求里的script
在執(zhí)行html中的script

每次請(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)入失敗分支:
image.png

重要的一點(diǎn):
就是每一次請(qǐng)求都會(huì)創(chuàng)建一個(gè)script標(biāo)簽郑兴,如何修改請(qǐng)看下一節(jié)優(yōu)化體驗(yàn)

image.png
優(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


image.png

可以引入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
image.png

添加代碼,那么如果我在本地訪問(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
image.png
image.png

這樣我們?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):

image.png

上圖中的pay是請(qǐng)求的路徑嘿辟,jack.comhe 127.0.0.1:8002 分別是jack的地址和端口

這是成功的時(shí)候,結(jié)果如圖所示

image.png

可以看到,兩個(gè)完全不同的網(wǎng)站,他們兩個(gè)是可以互相調(diào)script的

image.png

這時(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é)果


image.png
image.png
image.png
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;
        }
    }
image.png
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)造形如

      1. xxx.call('undefined','你要的數(shù)據(jù)')
      1. xxx("你要的數(shù)據(jù)")
        這樣的響應(yīng)
      1. 請(qǐng)求方瀏覽器接收到響應(yīng), 就會(huì)執(zhí)行xxx.call('undefined','你要的數(shù)據(jù)')
      1. 這樣請(qǐng)求方就得到了想要的數(shù)據(jù)

這就是jsonp

行業(yè)約定

jsonp沒有標(biāo)準(zhǔn)的規(guī)范,但是為了統(tǒng)一,有了行業(yè)約定

  1. callbackName一律用callback
  2. 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}
})`)
  1. 隨機(jī)生成的查詢字符串
  2. 返回的根據(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'}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市商源,隨后出現(xiàn)的幾起案子车份,更是在濱河造成了極大的恐慌,老刑警劉巖牡彻,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扫沼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡庄吼,警方通過(guò)查閱死者的電腦和手機(jī)缎除,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)总寻,“玉大人器罐,你說(shuō)我怎么就攤上這事〗バ校” “怎么了轰坊?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)祟印。 經(jīng)常有香客問(wèn)我肴沫,道長(zhǎng),這世上最難降的妖魔是什么旁理? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任樊零,我火速辦了婚禮,結(jié)果婚禮上孽文,老公的妹妹穿的比我還像新娘驻襟。我一直安慰自己,他們只是感情好芋哭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布沉衣。 她就那樣靜靜地躺著,像睡著了一般减牺。 火紅的嫁衣襯著肌膚如雪豌习。 梳的紋絲不亂的頭發(fā)上存谎,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音肥隆,去河邊找鬼既荚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛栋艳,可吹牛的內(nèi)容都是我干的恰聘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼吸占,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晴叨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起矾屯,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兼蕊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后件蚕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孙技,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年排作,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绪杏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纽绍,死狀恐怖蕾久,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拌夏,我是刑警寧澤僧著,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站障簿,受9級(jí)特大地震影響盹愚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜站故,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一皆怕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧西篓,春花似錦愈腾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吮成,卻和暖如春橱乱,著一層夾襖步出監(jiān)牢的瞬間辜梳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工泳叠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留作瞄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓危纫,卻偏偏與公主長(zhǎng)得像粉洼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叶摄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)安拟。 注意:講述HT...
    kismetajun閱讀 27,449評(píng)論 1 45
  • 什么是JSONP蛤吓? 先說(shuō)說(shuō)JSONP是怎么產(chǎn)生的: 其實(shí)網(wǎng)上關(guān)于JSONP的講解有很多,但卻千篇一律糠赦,而且云里霧里...
    cjg520閱讀 340評(píng)論 0 0
  • 背景: 一個(gè)眾所周知的問(wèn)題会傲,Ajax直接請(qǐng)求普通文件存在跨域無(wú)權(quán)限訪問(wèn)的問(wèn)題,甭管你是靜態(tài)頁(yè)面拙泽、動(dòng)態(tài)網(wǎng)頁(yè)淌山、web服...
    16manman閱讀 142評(píng)論 0 0
  • 同源策略是由Netscape提出的著名安全策略,是瀏覽器最核心顾瞻、基本的安全功能,它限制了一個(gè)源(origin)中加...
    never997閱讀 207評(píng)論 0 0
  • 前言 原文地址 倉(cāng)庫(kù)地址 jsonp(JSON with padding)你一定不會(huì)陌生泼疑,前端向后端拿數(shù)據(jù)的方式之...
    謙龍閱讀 588評(píng)論 0 4