javascript高級部分:先後端聯動,瀏覽器+服務器javascript
文件系統是一種數據庫
MySQL 是一種數據庫,也是一個軟件
只要能長久地存數據,就是數據庫php
接下來咱們用一個文件充當數據庫(實際上數據庫的存儲內容本質就是一個帶有結構的文件),捋一捋先後端交互的過程.css
代碼在這裏
幾回代碼的變動在歷史commit裏html
首先寫一個首頁前端
<title>首頁</title> <link rel="stylesheet" href="/style.css"> <h5>個人餘額爲<span id="amount">&&&amount&&&</span></h5> <!-- &&&amount&&&知識前臺的佔位符,會將從後臺讀取的數據顯示在這裏,並替換這個咱們假設的佔位符 --> <form action="/pay" method="POST"> <input type="submit" value="付款一元"> </form>
再寫服務器的代碼java
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 === '/') { // 若是用戶請求的是 / 路徑 var string = fs.readFileSync('./index.html') // 就讀取 index.html 的內容 var amount = fs.readFileSync('./db','utf-8')//****從數據庫(db文件)同步讀取數據,放到amount變量裏 string = string.toString().replace('&&&amount&&&',amount)//將後臺的amount替換爲前臺的佔位符&&&amount&&& response.setHeader('Content-Type', 'text/html;charset=utf-8') // 設置響應頭 Content-Type response.write(string) // 設置響應消息體 response.end(); } else if (path === '/style.css') { // 若是用戶請求的是 /style.css 路徑 var string = fs.readFileSync('./style.css') response.setHeader('Content-Type', 'text/css') response.write(string) response.end() } else if (path === '/main.js') { // 若是用戶請求的是 /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'){//若是請求的路徑是pay且方法爲post var amount = fs.readFileSync('./db','utf-8') var newAmount = amount-1; fs.writeFileSync('./db',newAmount); response.write('success') response.end() }else { // 若是上面都不是用戶請求的路徑 response.statusCode = 404 response.setHeader('Content-Type', 'text/html;charset=utf-8') // 設置響應頭 Content-Type response.write('找不到對應的路徑,你須要自行修改 index.js') response.end() } // 代碼結束,下面不要看 console.log(method + ' ' + request.url) }) server.listen(port) console.log('監聽 ' + port + ' 成功,請用在空中轉體720度而後用電飯煲打開 http://localhost:' + port)
最後在建立一個名爲db的文本文件充當數據庫node
接着咱們開啓服務器node index.js 8888
jquery
接着在瀏覽器中輸入地址http://localhost:8888
當用戶打開瀏覽器輸入ip地址,就會向後臺發送請求,服務器發現地址是/
以後執行if else
的path==='/'
分支,執行下面的代碼:linux
if (path === '/') { // 若是用戶請求的是 / 路徑 var string = fs.readFileSync('./index.html') // 就讀取 index.html 的內容 var amount = fs.readFileSync('./db','utf-8')//****從數據庫(db文件)同步讀取數據,放到amount變量裏 string = string.toString().replace('&&&amount&&&',amount)//將後臺的amount替換爲前臺的佔位符&&&amount&&& response.setHeader('Content-Type', 'text/html;charset=utf-8') // 設置響應頭 Content-Type response.write(string) // 設置響應消息體 response.end(); }
接着點擊提交按鈕,就會向後臺發起post請求,請求路徑爲/pay
,因而進入path === '/pay'
分支,執行下面的代碼,使數據庫(這裏數據庫用一個文本文件代替)中的金額減一git
else if(path === '/pay' && method.toUpperCase() === 'POST'){//若是請求的路徑是pay且方法爲post var amount = fs.readFileSync('./db','utf-8') var newAmount = amount-1; fs.writeFileSync('./db',newAmount); response.write('success') response.end() }
而後會發現/pay頁面返回了success
返回剛纔的首頁,刷新後,會發現金額減小了1.
也能夠模擬後臺修改數據庫成功或失敗
else if(path === '/pay' && method.toUpperCase() === 'POST'){//若是請求的路徑是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() }
可是剛纔那樣體驗很是很差,由於要返回,要刷新.在2005年之前,網頁的先後端交互都是這樣.但如今是8012年了,咱們須要優化一下用戶體驗
不光form表單能夠發送請求,a標籤(須要點擊),link標籤,script,圖片,均可以發送請求
那咱們先試試用img發送一個請求
當瀏覽器發現html裏面有一個img時,會自動發送請求,請求路徑就是img的src.可是有一個缺點.發送的請求只能是get,沒辦法改爲post.
使用img.onload和img.onerror方法判斷請求是成功仍是失敗,具體操做看下方代碼
複習一下返回的狀態碼:
2開頭:成功
3開頭:重定向
4開頭:客戶端錯誤
5開頭:服務器錯誤
修改首頁
<title>首頁</title> <link rel="stylesheet" href="/style.css"> <h5>個人餘額爲<span id="amount">&&&amount&&&</span></h5> <!-- &&&amount&&&知識前臺的佔位符,會將從後臺讀取的數據顯示在這裏,並替換這個佔位符 --> <button id="button">付錢一元</button> <script> let btn = document.getElementById("button"); btn.onclick = function(){ let img = document.createElement("img"); img.src = "/pay"; img.onload = function(){//若是圖片請求成功(返回碼以2開頭) alert("付錢成功") window.location.reload();//彈框並刷新 } img.onerror = function(){//返回碼是4開頭 alert("付錢失敗") } } </script>
修改/pay
路徑的響應
else if(path === '/pay'){//若是請求的路徑是pay,接受圖片的請求 var amount = fs.readFileSync('./db','utf-8') var newAmount = amount-1; if(Math.random()>0.5){//模擬成功或失敗 fs.writeFileSync('./db',newAmount);//成功了就把數據寫入數據庫 response.setHeader('Content-Type','image/jpg')//設置返回文件類型爲jpg response.statusCode = 200;//返回碼爲200,說明成功 response.write(fs.readFileSync('./dog.jpg'))//必須返回一個真的圖片,否則瀏覽器仍是會認爲是失敗 }else{ response.statusCode = 400;//不然返回碼爲400,說明失敗 response.write('fail') } response.end() }
而後開啓服務器,瀏覽器輸入地址
點擊付錢,成功,並刷新
付錢失敗時,彈框後瀏覽器就不刷新了:
也可局部刷新
img.onload = function(){//若是圖片請求成功(返回碼以2開頭) alert("付錢成功") amount.innerText = amount.innerText-1;//局部刷新/由於已經知道數據庫修改爲功,因此無需再進行刷新,直接修改數值便可 }
還有一個須要注意的點
在下面這段代碼中
response.setHeader('Content-Type','image/jpg')//設置返回文件類型爲jpg, response.statusCode = 200;//返回碼爲200,說明成功 response.write(fs.readFileSync('./dog.jpg'))//必須返回一個真的圖片,否則瀏覽器仍是會認爲是失敗
看看回復的響應response.write(fs.readFileSync('./dog.jpg'))
這句代碼中fs.readFileSync('./dog.jpg')
參數裏只有路徑,沒有加別的,那麼讀出來的數據類型爲2進制數據,而後'Content-Type','image/jpg'
這句代碼表示以jpg圖片的形式讀取這段二進制代碼.
複習一下響應的四個部分:
1 協議/版本號 狀態碼 狀態解釋 2 Key1: value1 2 Key2: value2 2 Content-Length: 17931 2 Content-Type: text/html 3 4 響應的內容
response.write(fs.readFileSync('./dog.jpg'))
這句代碼就是響應的第四部分,即響應的內容
那麼咱們也能夠用script動態的發送請求.原理和img相似,可是必須得把script標籤加入到body裏面,纔會發送請求,而img標籤只要建立,就會發送請求.
接下來看代碼
首頁的發送請求代碼
<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);//這句話必定要加上,這是與使用圖片請求不同的地方 script.onload = function(){//若是script請求成功(返回碼以2開頭) alert("付錢成功"); } script.onerror = function(){//返回碼是4開頭 alert("付錢失敗"); } } </script>
服務器端的代碼:
else if(path === '/pay'){//若是請求的路徑是pay,接受script的請求 var amount = fs.readFileSync('./db','utf-8') var newAmount = amount-1; if(Math.random()>0.5){//模擬成功或失敗 fs.writeFileSync('./db',newAmount);//成功了就把數據寫入數據庫 response.setHeader('Content-Type','applacation/javascript')//設置返回文件類型爲javascript response.statusCode = 200;//返回碼爲200,說明成功 response.write('alert("我是建立的script請求裏面響應的內容")')//響應內容爲建立的script標籤裏面的內容 }else{ response.statusCode = 400;//不然返回碼爲400,說明失敗 response.write('fail') } response.end() }
當我點擊打錢的時候,他會動態的建立一個script,這個script會根據src發起請求
每次請求就會建立一個新的script標籤,若是響應成功了,就執行響應裏面的javascript語句,這個script裏面的語句就是返回的響應的第四部分的內容,執行完以後纔會觸發onload事件
那麼既然請求返回的響應能夠執行,咱們就不須要寫onload了,直接把onload裏面的代碼寫在響應的第四部分不就能夠了?沒錯
接來下修改一下代碼,刪掉onload代碼,將onload裏面的代碼寫在服務器端.將前臺請求成功要作的事情交給後臺來作.
btn.onclick = function(){ let script = document.createElement("script"); script.src = "/pay"; document.body.appendChild(script);//這句話必定要加上,這是與使用圖片請求不同的地方 //刪除onload代碼,交給後臺來作 script.onerror = function(){//返回碼是4開頭 alert("付錢失敗"); } }
後臺只須要將金額數量減1便可,不須要刷新,增長用戶體驗
else if(path === '/pay'){//若是請求的路徑是pay,接受script的請求 var amount = fs.readFileSync('./db','utf-8') var newAmount = amount-1; if(Math.random()>0.5){//模擬成功或失敗 fs.writeFileSync('./db',newAmount);//成功了就把數據寫入數據庫 response.setHeader('Content-Type','applacation/javascript')//設置返回文件類型爲javascript response.statusCode = 200;//返回碼爲200,說明成功 response.write('amount.innerText = amount.innerText-1')//響應內容爲建立的script標籤裏面的內容,返回後會當即執行********* }else{ response.statusCode = 400;//不然返回碼爲400,說明失敗 response.write('fail') } response.end() }else
最後在作一件事情
由於每次無論成功失敗,都會建立一個script標籤,那麼能否在請求結束後,將標籤刪除?
能夠.
修改前臺onload和onerror代碼便可,無論請求成功失敗,都移除script標籤
script.onload = function(e){ e.currentTarget.remove();//添加移除代碼 } script.onerror = function(e){ e.currentTarget.remove(); }
以上方法就叫作SRJ,服務器返回的javascript,即不是在前臺寫的javascript
這是在ajax出現以前,後端程序員想出來的先後臺無刷新,局部更新頁面內容的交互方法
script請求的內容不受域名限制.當前網站的能夠請求其餘網站的js代碼,例如jquery
能夠引入jquery
因此剛纔的SRJ很危險,假如只用了get方法,那麼像付錢這種操做很容易被假裝成功,致使別人的惡意攻擊
例如我進入一個釣魚網站
那個網站直接請求script.src = "http://alipay.com/pay?usname=mataotao&amount=100000"
這樣只要進入網站執行這個請求,豈不是能夠隨意給本身的帳戶打錢?
因此只用get不安全.
因此不少危險操做都得用POST請求,例如打錢必須驗證用戶,作各類支付相關的安全防範,手機驗證碼之類的.
下面咱們模仿一下不一樣的網站請求對方的後臺的過程
首先修改一下本地的hosts文件,將兩個域名都指向127.0.0.1,僞裝成兩個不一樣的網站
hosts文件至關於一個我的的DNS,當你訪問某個域名時,是 先經過hosts進行解析的,沒有找到才進一步經過外網DNS服務器進行解析。
若是是windows系統:
windows系統hosts文件位置及操做
若是是linux或者Unix
地址爲/etc/hosts
添加代碼,那麼若是我在本地訪問mataotao.com和jack.com,他首先會進127.0.0.1,而不是去外網解析dns
這樣咱們就僞裝成爲了兩個網站,實際上兩個網站的內容是同樣的,知識用來作實驗
而後開兩個服務器,使用兩個不一樣的端口來監聽(模擬兩個不一樣的網站都開啓了服務器,都在監聽)
監聽8001
這樣咱們在地址欄輸入mataotao.com:8001
就進入了第一個網站,在地址欄輸入jack.com:8002
就進入了第二個網站.
這樣兩個個網站均可以接受請求,均可以訪問,只不過內容同樣而已
那麼接下來咱們來模仿不一樣網站之間的請求互動
修改前端SRJ請求代碼的請求路徑,改成script.src = "http://jack.com:8002/pay";
那麼咱們打開mataotao.com:8001
就進入了第一個網站,點擊按鈕後,意思就是請求http://jack.com:8002/pay
這個第二個網站的地址.咱們來試一試
能夠看到,兩個徹底不一樣的網站,他們兩個是能夠互相調script的
這時候的意思是:我是用第一個網站(mataotao.com)打開了第二個網站(jack.com)的付錢功能!
(因爲我爲了演示,這兩個網站用了一樣的代碼,同一個數據庫,咱們假設第一個網站he第二個網站代碼不一樣)
這時候出現一個問題:
jack.com的後端程序員須要對mataotao.com的頁面細節瞭解的很清楚.
這樣的缺點是後臺對前臺須要瞭解得太深刻了.好比說你是node或者php騰訊後端程序員,但是我居然還要對阿里巴巴網站的一個按鈕的一個細節很是瞭解.耦合度過高,先後端彼此之間關係太緊密了.若是後端程序員對頁面細節不瞭解,代碼就寫不下去了.這樣對先後端分離不友好
解耦(解決耦合問題)
解決方法:
後臺程序員:我只把我後臺應該改的數據庫,而後我寫一個callback函數,將成功和失敗的狀態傳遞給前臺.這個函數就是我後端程序員作完這些事情成功以後要前端程序員去作的事情.而後執行這個函數,這個函數的內容交給前端去寫.
前端程序員:我無論後端如何修改數據庫,我事先寫好成功和失敗的函數,成功了就返回給我一個成功的提示,而後我根據提示執行成功的函數,若是給個人提示是失敗,就執行失敗的函數.
接下來寫代碼理解:
修改服務器端的代碼:
response.write(`${query.callbackName}.call(undefined,'success')`)//先獲取查詢字符串裏的callbackName即從前臺傳過來的回調函數的函數名,而後再執行他,並把函數的參數定爲success
添加前臺index.html的代碼
<script> // 這三行是添加的 window.xxx = function(result){ alert("我是前端程序員寫的函數,通過後端程序員調用才執行") alert(`後端返回回來的函數參數是:${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";//請求時帶上查詢參數,指定callbackName爲XXX,以便給後臺程序員調用 //這行是修改的 document.body.appendChild(script);//這句話必定要加上,這是與使用圖片請求不同的地方 script.onload = function(e){//若是script請求成功(返回碼以2開頭) e.currentTarget.remove();//添加移除代碼 } script.onerror = function(e){//返回碼是4開頭 alert("失敗") e.currentTarget.remove(); } } </script>
添加的這幾句代碼的解釋:
首先script.src = "http://jack.com:8002/pay?callbackName=xxx";
在請求時帶上查詢參數,指定callbackName爲xxx,以便給後臺程序員調用
接着
response.write(`${query.callbackName}.call(undefined,'success')`)
後臺程序員得到前太程序員的查詢參數,並執行名爲查詢參數的函數xxx
執行xxx,而且將相應的內容做爲回調函數的第一個參數傳回去,這個參數爲字符串'success'
window.xxx = function(result){ alert("我是前端程序員寫的函數,通過後端程序員調用才執行") alert(`後端返回回來的函數參數是:${result}`) }
也能夠根據後臺傳過來的參數,修改前臺的邏輯
window.xxx = function(result){ if(result === "success"){ amount.innerText = amount.innerText - 1; } }
那麼若是咱們返回的參數不是字符串,而是對象呢?
這也是能夠的,例如咱們修改一下代碼
else if(path === '/pay'){//若是請求的路徑是pay,接受script的請求 var amount = fs.readFileSync('./db','utf-8') var newAmount = amount-1; if(Math.random()>0.5){//模擬成功或失敗 fs.writeFileSync('./db',newAmount);//成功了就把數據寫入數據庫 response.setHeader('Content-Type','applacation/javascript')//設置返回文件類型爲javascript response.statusCode = 200;//返回碼爲200,說明成功 response.write(`${query.callbackName}.call(undefined,{ "success":true, "left":${newAmount} })`)//把函數的參數定爲一個對象 }else{ response.statusCode = 400;//不然返回碼爲400,說明失敗 response.write('fail') } response.end() }
前端:
window.xxx = function(result){ if(result.success === true){ amount.innerText = amount.innerText - 1; } }
我請求一個script,script調用一個函數的同時,把第一個參數設置爲要返回的數據,無論這個數據是對象仍是字符串.而後人們把這種調用script並返回字符串或者對象的使用方法叫作是jsonp,這就是jsonp!.只是名字比較奇怪,其實真名的名字應該是:動態標籤跨域請求!即利用動態標籤進行跨域請求的技術
這種方法的好處是能夠解決跨域的問題.例如剛纔的例子,兩個網站若是域名不同,那麼第一個網址的前臺和另外一個網址後臺的服務器沒法進行數據交換(因爲瀏覽器的同源策略).可是使用script標籤就能夠避免這個問題.利用script請求,能夠請求道第二個網站的json或字符串數據,這種方法就叫作jsonp
JSONP(JSON with Padding)是JSON的一種「使用模式」,可用於解決主流瀏覽器的跨域數據訪問的問題。因爲同源策略,通常來講位於 server1.example.com 的網頁沒法與不是 server1.example.com的服務器溝通,而 HTML 的<script> 元素是一個例外。利用 <script> 元素的這個開放策略,網頁能夠獲得從其餘來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。----百度百科
jsonp要解決的是兩個不一樣域名的網站如何交流
,答案是用script標籤就能夠交流了,由於script標籤是不受域名限制的,而Ajax是受域名限制的
jsonp
請求方:mataotao.com的前端(瀏覽器),一個網站的前端
響應方:jack.com的後端(服務器)另外一個網站的後端
響應方根據查詢參數構造形如
xxx.call('undefined','你要的數據')
xxx("你要的數據")
這樣的響應
xxx.call('undefined','你要的數據')
這就是jsonp
jsonp沒有標準的規範,可是爲了統一,有了行業約定
callback
mataotao123124234
的字符加上隨機數的形式(這樣的好處是不用取函數名了,並且調用完就delete,不會污染全局變量)下面用行業約定來改寫代碼,加*的行爲修改的代碼
<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);//***隨機生成一個函數名 window[functionName] = function (result) {//***必須使用window[functionName]這樣的形式 if (result.success === true) { amount.innerText = amount.innerText - 1; } } script.src = "http://jack.com:8002/pay?callback=" + functionName;//***將隨機生成的函數名放到拼接到查詢字符串上 document.body.appendChild(script);//這句話必定要加上,這是與使用圖片請求不同的地方 script.onload = function (e) {//若是script請求成功(返回碼以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} })`)
jQuery使用jsonp很是簡單
只要這樣修改前臺的代碼.後臺不用改 url不須要寫callback查詢參數,由於jQuery會自動給你生成
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; } } }); }
須要注意的一點是,jsonp不是ajax中的一種.不要背jquery誤導
答:
JSON 語法
在 JS 語言中,一切都是對象。所以,任何支持的類型均可以經過 JSON 來表示,例如字符串、數字、對象、數組等。可是對象和數組是比較特殊且經常使用的兩種類型:
JSON 鍵值對是用來保存 JS 對象的一種方式,和 JS 對象的寫法也大同小異,鍵/值對組合中的鍵名寫在前面並用雙引號 "" 包裹,使用冒號 : 分隔,而後緊接着值:
{"firstName": "Json"}
這很容易理解,等價於這條 JavaScript 語句:
{firstName : "Json"}
不少人搞不清楚 JSON 和 Js 對象的關係,甚至連誰是誰都不清楚。其實,能夠這麼理解:
JSON 是 JS 對象的字符串表示法,它使用文本表示一個 JS 對象的信息,本質是一個字符串。
如
var obj = {a: 'Hello', b: 'World'}; //這是一個對象,注意鍵名也是可使用引號包裹的
var json = '{"a": "Hello", "b": "World"}'; //這是一個 JSON 字符串,本質是一個字符串
要實現從對象轉換爲 JSON 字符串,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'}); //結果是 '{"a": "Hello", "b": "World"}'
要實現從 JSON 轉換爲對象,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //結果是 {a: 'Hello', b: 'World'}