Ajax,Asynchronous JavaScript and XML,字面意思:異步的 JavaScript 和 XML,是指一種建立交互式網頁應用的網頁開發技術。
用於異步地去獲取XML做爲數據交換的格式,固然,如今的 ajax 並不只僅侷限於XML做爲數據交換格式,還能夠像純文本、XML、HTML、JSON 等格式都可。javascript
Ajax 的 A 就是 asynchronous 的簡寫,表示異步。php
同步和異步:css
同步,按照代碼書寫的順序,一個任務一個任務的來執行。 異步,並非按照代碼書寫的順序,一般會結合回調和事件來執行相應代碼。 在同步中,若是有一個任務耗時較長,整個的後面任務都須要等待。 在異步中,能夠將耗時較長先放起來,執行其餘的,其餘的執行完畢,回頭再執行這個。 同步:提交請求->等待服務器處理->處理完畢返回 阻塞模式。 異步:請求經過事件觸發->服務器處理->處理完畢。非阻塞模式。
首先,咱們都瞭解軟件的兩種架構形態,C/S(Client/Server)和B/S(Browser/Server)。它們的區別以下:html
C/S,在客戶端安裝了客戶端軟件以後,整個程序的運行,分擔到客戶端和服務器端。用戶在操做的時候,體驗更好,響應速度很是快。java
B/S,所務的服務都放在服務器端,經過瀏覽器使用服務器,用戶在操做的時候,體驗不太好,響應速度特別慢。node
B/S的好處,就是不須要安裝客戶端,軟件維護和更新比較方便,用戶體驗很差。ajax
C/S的好處,用戶體驗夠好,響應及時,可是軟件的維護和更新比較麻煩。chrome
隨着互聯網的發展,愈來愈多的C/S應用慢慢 轉成的B/S,這也是將來的趨勢。可是矛盾很突出,B/S的響應速度慢。須要B/S模式具有C/S模式的快速響應特色。
Ajax的出現就是爲了解決這個問題-----異步刷新。
在異步刷新機制,整個頁面不用跳轉,只須要更新須要變化的地方。數據庫
宗旨:提高頁面的訪問速度,提升用戶體驗。express
2005年2月,Adaptive Path公司的Jesse James Garrett最先提出這個概念。它出如今Garrett的文章「Ajax: A New Approach to Web Applications」中。這篇文章描述了混合使用XHTML、CSS、JavaScript、DOM、XMLHttpRequest進行Web開發將會成爲一種新的趨勢。
同年,Google在三大產品中使用了Ajax技術:
實際上,Ajax發展經歷了三個時代:
2005年,沒有 Ajax 的概念,可是異步刷新的須要仍是很是多。經常使用的方式就是 iframe。
第一,iframe有一個src屬性,經過設置src屬性,發出請求,發出請求的時候不會跳轉;
第二,iframe其實就是指向單獨的頁面,能夠在其中進行任意的操做,能夠在iframe中書寫js代碼,能夠去操做父頁面。
優勢,實現起來特別簡單,兼容性好。
缺點,功能比較弱,在服務端須要寫大量的代碼。
簡單示例:
前臺代碼:
<body> <h2>用戶註冊</h2> <form action="/reg" method="post"> <p> <label for="username">用戶名:</label> <input type="text" id="username" name="username"> <span id="msg"></span> </p> <p> <label for="password">密 碼:</label> <input type="password" id="password" name="password"> </p> <p><input type="submit" value="註冊"></p> </form> <iframe src="" frameborder="0" width="0" height="0" id="iframe1"></iframe> <script> var iframe1 = document.getElementById('iframe1'); var username = document.getElementById('username'); username.addEventListener('blur',function(){ iframe1.src = "/check?username=" + this.value; }); </script> </body>
服務器端代碼:
const express = require('express'); const bodyParser = require('body-parser'); const path = require('path'); const app = express(); app.get('/',(req,res)=>{ res.sendFile(path.join(__dirname,'login.html')); }); app.post('/reg',(req,res)=>{ let username = req.body.username; let password = req.body.password; // 檢測用戶名是否可用,典型的作法須要去鏈接數據庫,取出全部用戶名,進行查詢比較 // TODO // 這種方式,能夠知足需求,可是用戶體驗極差 // 每次都是填寫完全部的表單內容,才能作一些檢測 // 整個頁面已經發生刷新跳轉 }); // 模仿數據庫 let users = ['admin','test']; app.get('/check',(req,res)=>{ let username = req.query.username; if (inArray(username,users)) { res.send("<script>window.parent.document.getElementById('msg').innerHTML='對不起,用戶名已被佔用,請從新註冊!'</script>"); } else { res.send("<script>window.parent.document.getElementById('msg').innerHTML='恭喜,用戶名可用!'</script>"); } }); app.listen(3000,()=>{ console.log('server is listening in port 3000...'); }); // 簡單封裝一個方法,用於檢測一個值是否在數組中存在 function inArray(str,arr){ for(let i=0; i<arr.length; i++){ if(str == arr[i]){ return true; break; } } return false; }
1.首先要建立一個 Ajax 對象
var xhr = new XMLHttpRequest();
狀態:readyState:
status:
responseText:
2.初始化
狀態:readyState:
status:
responseText:
3.調用 open()
方法,開啓一個請求,但沒有向服務器端發起請求
xhr.open(method, url [,async = true]);
狀態:readyState: 1
status:
responseText:
4.調用 send()
方法,正式向服務器端發起請求
get
xhr.send([data=null]);
post
xhr.setRequestHeader(header,value);
header:content-type
表單value:application/x-www-form-urlencoded
文件上傳value:multipart/form-data
狀態:readyState:2
status:
responseText:
5.當服務器端返回數據,瀏覽器端接收數據時
狀態:readyState:3
status:
responseText:
6.當瀏覽器端結束請求的時候
xhr.onreadystatechange = function(callback){ if(xhr.readyState == 4){ if( (xhr.status >= 200 && xhr.status < 300) || xhr.status==304 ){ callback(xhr.responseText); } else { alert('request was unsuccessful:' + xhr.status); } } }
狀態:readyState:4
status:200
responseText:<!DOCTYPE html>響應返回值
onreadystatechange* 指定當readyState屬性改變時的事件處理句柄。只寫 readyState 返回當前請求的狀態,只讀. responseBody 將回應信息正文以unsigned byte數組形式返回.只讀 responseStream 以Ado Stream對象的形式返回響應信息。只讀 responseText 將響應信息做爲字符串返回.只讀 responseXML 將響應信息格式化爲Xml Document對象並返回,只讀 status 返回當前請求的http狀態碼.只讀 statusText 返回當前請求的響應行狀態,只讀
abort 取消當前請求 getAllResponseHeaders 獲取響應的全部http頭 getResponseHeader 從響應信息中獲取指定的http頭 open 建立一個新的http請求,並指定此請求的方法、URL以及驗證信息(用戶名/密碼) send 發送請求到http服務器並接收回應 setRequestHeader 單獨指定請求的某個http頭
Microsoft最先把XMLHttpRequest對象引入到IE5中,且在IE5和IE6中它只是一個ActiveX對象。IE7以前的版本不支持非標準的XMLHttpRequest()構造函數,兼容以下:
function createXHR(){ var xhr = null; try{ xhr = new XMLHttpRequest(); }catch(e1){ try{ xhr = new ActiveXObject("Microsoft.XMLHTTP"); }catch(e2){ try{ xhr = new ActiveXObject("Msxml2.XMLHTTP"); }catch(e3){ throw new Error("XMLHttpRequest is not supported"); } } } return xhr; }
兩種請求方式,有所不一樣。
get 請求
&
連接?
跟上xhr.send([data=null])
post請求
form
表單的數據給請求出來以xml形式傳遞給服務器send
方法,其中數據以鍵值對的形式來傳遞xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
xhr.send(Formdata);
post請求注意事項
get請求中,若是參數是對象,那麼咱們須要對它進行轉換,編碼以後,不須要解碼,瀏覽器會自動解碼;
var url = "example.json?" + serialize(formdata); xhr.open('get', url, true); xhr.send(null);
post請求中,有特殊符號(=和&)和中文須要編碼,encodeURIComponent
方法
xhr.open('post', 'example.json', true); xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded'); xhr.send(serialize(formdata));
序列化方法:
function serialize(data){ if(!data) return ''; var pairs = []; for(var name in data){ if(!data.hasOwnProperty(name)) continue; if(typeof data[name] === 'function') continue; var value = data[name].toString(); name = encodeURIComponent(name); value = encodeURIComponent(value); pairs.push(name + '=' + value); } return pairs.join('&'); }
0(未初始化)
此階段確認xhr對象是否建立成功,爲調用open方法作好準備,值爲0表示對象已經存在,不然瀏覽器報錯
1(載入)
對xhr進行初始化,調用open方法,根據參數完成對象狀態的設置,並調用send開始向服務器端發送請求,值爲1表示正在向服務器端發送請求
2(載入完成)
接收服務器端的響應數據,但得到的仍是服務器響應的原始數據,並不能在客戶端使用。值爲2表示已經接收徹底部響應數據,併爲下一階段對數據進行解析作好準備
3(交互)
解析接收到的服務器端響應數據,根據服務器頭部返回的MIME把數據轉換成對應格式,爲在客戶端調用作好準備。3表示正在解析數據
4(完成)
確認所有數據已經解析爲客戶端可用的格式,解析已經完成。值爲4表示數據解析完畢,能夠經過xhr對象的屬性取得數據
xml responseXML
和 text responseText
做爲文本返回時,返回的是字符串,但有些字符串比較特殊,如html代碼、json對象,因此有以下幾種形式
兩個頁面擁有相同的協議(protocol)端口(port)和主機(host),那麼這兩個頁面就屬於同一個源(origin)
不知足同源策略的資源訪問,叫跨域資源訪問
若是是協議和端口形成的跨域問題「前臺」是無能爲力的
在跨域問題上,域僅僅是經過「URL的首部」來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。
(「URL的首部」指window.location.protocol +window.location.host,也能夠理解爲「Domains, protocols and ports must match」。)
CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。
CORS背後的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功仍是失敗。
目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。
對於開發者來講,CORS通訊與同源的Ajax通訊沒有差異,代碼徹底同樣。
瀏覽器一旦發現Ajax請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。
參考阮老師的文章:http://www.ruanyifeng.com/blog/2016/04/cors.html
JSONP(JSON with Padding)是一個非官方的協議,它容許在服務器端集成Script tags返回至客戶端,經過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。單向的數據請求。
JSONP的原理與實現思路
<script><img><iframe>
。由於經過script標籤引入的js是不受同源策略的限制的。因此咱們能夠經過script標籤引入一個js或者是一個其餘後綴形式(如php,jsp等)的文件,此文件返回一個js函數的調用
示例以下:(服務器端用node.js)
瀏覽器端代碼:
動態生成script
標籤,建立一個callback
,以查詢字符串形式跟在src
的url
後邊
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSONP</title> </head> <body> <button id="btn">JSONP</button> <script> var btn = document.getElementById('btn'); function show(data){ console.log(data); } btn.onclick = function(){ var url = "http://localhost:4000/test?callback=show"; var script = document.createElement('script'); script.setAttribute('src',url); document.getElementsByTagName('head')[0].appendChild(script); } </script> </body> </html>
服務器端代碼:
const express = require('express'); const path = require('path'); const app = express(); app.get('/',(req,res)=>{ res.sendFile(path.join(__dirname,'index.html')); }); app.listen(3000,()=>{ console.log('http server is listening in port 3000'); });
跨域服務器端代碼:獲取請求url
中的callback
,把callback
和json
數據組合後返回,這裏須要注意,node.js返回數據的時候,設置res.type("text/javascript");
,
const express = require('express'); const path = require('path'); const app = express(); app.get('/test',(req,res)=>{ let test = require('./test.json'); const callback = req.query.callback; res.type("text/javascript"); res.send(callback+'('+JSON.stringify(test)+')'); }); app.listen(4000,()=>{ console.log('http server is listening in port 4000'); });
json數據:
{ "title" : "JSONP", "time" : "2017-1-1" }
結果:
圖1
圖2
優缺點
JSONP的優勢:它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制;
它的兼容性更好,在更加古老的瀏覽器中均可以運行,不須要XMLHttpRequest或ActiveX的支持;
而且在請求完畢後能夠經過調用callback的方式回傳結果
JSONP的缺點:它只支持GET請求而不支持POST等其它類型的HTTP請求;
它只支持跨域HTTP請求這種狀況,不能解決不一樣域的兩個頁面之間如何進行JavaScript調用的問題
一種用 CSS 跨域傳輸文本的方案
優勢:相比 JSONP 更爲安全,不須要執行跨站腳本。
缺點:沒有 JSONP 適配廣,CSST 依賴支持 CSS3 的瀏覽器。
原理:經過讀取 CSS3 content 屬性獲取傳送內容。經過建立一個 link 請求到 css 文件,而後經過 computedStyle = window.getComputedStyle
獲取到指定元素的 style 對象,再經過 computedStyle .content
獲取到內容