XMLHttpRequest
JSON
AJAX
CORS
四個名詞來開會
如何發請求
在前端的世界裏也逛蕩了很多日子了,目前已經get到大約5種發起請求的方式,主流的、非主流的。javascript
何種方式 | 請求方法 | |
---|---|---|
最多見的form 表單 |
默認GET ,多用POST ,只此兩種 |
會刷新頁面或者新開頁面 |
a 標籤 |
GET 請求 |
也會刷新頁面或者新開頁面 |
img 的src 屬性 |
GET |
只能以圖片的形式展示 |
link 標籤 |
GET |
只能以CSS 、favicon 的形式展示 |
script 標籤 |
GET |
只能以腳本的形式運行 |
但是css
- 咱們可能想用
GET
POST
PUT
DELETE
方法 - 不想刷新整個頁面,想用一種更易於理解的方式來響應
AJAX出現
瀏覽器和服務器交互模式 V1.0
在AJAX
未出現以前,瀏覽器想從服務器得到資源,注意是獲取資源,會通過以下一個過程html
- 瀏覽器發起請求->服務器接到請求響應給你HTML文檔->瀏覽器收到資源,刷新頁面,加載得到的的HTML。簡略的過程
我稱這種交互方式是 V1.0,此時仍是以獲取資源爲導向。後來隨着時代的發展,人們日益增加的文化需求成爲了社會的主要矛盾……有一天,小明看了一篇報道,他只是想在下面評論一下,發表對實事的親切問候,問候完了,唉,你給我刷新頁面幹啥,我只是想評論一下啊。前端
大概那是網民們第一次對 良好的用戶體驗 提出了要求。後來的蘋果爸爸,把你們慣壞了,每天嚷着 "你這產品用戶體驗太差了"……java
彼時,微軟仍是對web作出了很大的貢獻的。node
交互模式2.0
大約1999年,微軟發佈IE 5.0
版本,它容許JavaScript腳本向服務器發起HTTP請求。不過很遺憾,當時也沒有火起來,直到2004年Gmail發佈和2005年Google Map發佈,才引發普遍重視。2005年,一個叫Jesse James Garrett的人提出了一個新術語----AJAX
,它是一系列技術的組合體,全稱是 Asynchronous JavaScript + XML
(異步的JS和XML)能夠阻止頁面總體刷新,只是動態響應用戶的操做,快速顯示到局部,用戶就能夠很愉快的繼續上網了。git
AJAX程序員
能夠看出IE當時仍是很猛的,隨着IE 6.0 市場份額進一步擴大,IE已經把火狐整的半死不活,放眼整個瀏覽器市場,微軟是當之無愧的王者,後來微軟就把瀏覽器團隊解散了……不得不說這是一波神操做,能與之媲美的操做大概只有殘血我能反殺
塔下我能秀他
了。微軟強行爲後續各家瀏覽器的發展提供了優秀的工程師,尤爲是0八、09年出生的谷歌瀏覽器,再看現在的IE……es6
既然AJAX
是一系列的技術的組合體,接下來認識一下其中的幾位主角github
XMLHttpRequest
XMLHttpRequest
對象是用來在瀏覽器和服務器之間傳輸數據的。
古代的操做的是:
- 瀏覽器構造
XMLHttpRequest
實例化對象 - 用這個對象發起請求
- 服務器響應一個
XML
格式的字符串,是字符串,是字符串,是字符串,也就是說響應的第四部分是字符串。 - JS解析符合XML格式的字符串,更新局部頁面。
什麼是XML,可擴展標記語言。
以上是最初的用法,用的是XML
,前端代碼片斷以下
let request = new XMLHttpRequest() //實例化XMLHttpRequest對象
request.onreadystatechange = () => {
if (request.readyState === 4) {
console.log('請求和響應都完畢了')
if (request.status >= 200 && request.status <= 300) {
console.log('說明請求成功了')
console.log(request.responseText)
let parser = new DOMParser()
let xmlDoc = parser.parseFromString(request.responseText, "text/xml")
//用parser解析request.responseText
let c = xmlDoc.getElementsByTagName('body')[0].textContent
console.log(c)
} else if (request.status >= 400) {
console.log('說明請求失敗了')
}
}
}
request.open('GET', '/xxx') //配置request
request.send()
服務器端的對應代碼片斷以下
...
response.statusCode = 200
response.setHeader('Content-Type', 'text/xml;charset=utf-8')
response.write(` <note> <to>木木</to> <from>少少</from> <heading>你好哇</heading> <body>很久不見啊</body> </note> `)
response.end()
...
本地模擬的話,必定要記得開倆不一樣的端口
例如:
node server.js 8001
node server.js 8002
XMLHttpRequest實例的詳解
正如上面的前端代碼片斷寫的同樣,主要用到了open()
send()
方法, onreadystatechange
readyState
屬性。
-
request.open(method, URL, async)方法。
- 通常用三個參數,第一個參數是請求的方法,能夠用
GET POST DELETE PUT
等等,URL是用訪問的路徑,async是是否使用同步,默認true,開啓異步,不須要作修改便可,因此實際中只寫前兩個參數 - 若是非要寫false,開啓同步,會對瀏覽器有阻塞效應,並且若是值爲false,則send()方法不會返回任何東西,直到接受到了服務器的返回數據
- 通常用三個參數,第一個參數是請求的方法,能夠用
-
request.send()方法。
- 發送請求. 若是該請求是異步模式(默認),該方法會馬上返回. 相反,若是請求是同步模式,則直到請求的響應徹底接受之後,該方法纔會返回
-
readyState
屬性。-
描述請求的五個狀態。
- 0 === 常量
UNSENT
(未打開) open()方法未調用 - 1 ===
OPENED
(未發送) 只是open()方法調用了 - 2 ===
HEADERS_RECEIVED (已獲取響應頭)
send()方法調用了,響應頭和響應狀態已經返回了 - 3 ===
LOADING (正在下載響應體)
響應體下載中,responseText
已經獲取了部分數據 - 4 ===
DONE (請求完成)
整個響應過程完畢了。 這個值是實際中用到的。 - 只要不等於4,就表示請求還在進行中。
- 0 === 常量
-
responseText
屬性是這次響應的文本內容。-
onreadystatechange
屬性。readyState
屬性的值發生改變,就會觸發readyStateChange
事件。- 咱們能夠經過
onReadyStateChange
屬性,指定這個事件的回調函數,對不一樣狀態進行不一樣處理。尤爲是當狀態變爲4的時候,表示通訊成功,這時回調函數就能夠處理服務器傳送回來的數據。即前面的代碼片斷的處理方式。
- 其餘的方法、屬性、事件詳見阮一峯博客、MDN文檔
習慣用javaScript
的前端是不想和XML
打交道的,應該用一種符合js
風格的數據格式語言。
JSON
後來一個美國程序員道格拉斯·克羅克福特發明了JSON
,解決了上面的問題,這貨還寫了一本蝴蝶書JavaScript語言精粹,還發明瞭一個JS校驗器 ----JSLint。
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。 易於人閱讀和編寫。同時也易於機器解析和生成。 它基於 JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。 JSON採用徹底獨立於語言的文本格式,可是也使用了相似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 這些特性使JSON成爲理想的數據交換語言。
以上是JSON官網的簡介,能夠看出它是一門全新的語言,不是JavaScript的子集。
JSON
很簡單,數據類型和JS有點不一樣的地方。
JavaScript | JSON |
---|---|
string | "string" 必須寫雙引號 |
number | number |
object | {"object": "name"} 必須雙引號 |
undefined | 沒有 |
null | null |
boolean | 直接寫true false |
array | array |
function | 沒有 |
variable |
- 瀏覽器的全局對象
window
上有JSON
對象,直接使用window.JSON.parse(string)
let string = request.responseText
let json = window.JSON.parse(string) //string 要符合JSON的格式
以上是JSON解析部分的代碼。
此時服務器端代碼是
response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8')
response.write(` { "note" : { "to" : "木木", "from" : "少少", "heading" : "你好哇", "content" : "很久不見啊" } } `)
- 咱們瀏覽器有同源政策,不是同協議 同域名 同端口 的網頁沒法相互訪問。
4.AJAX
剛好是同源政策的擁躉
CORS
-
若是
AJAX
向非同源的地址發起請求,會報錯。- 這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200,也就是說即便你看到了200的正確碼,也沒有用
- 可是form表單無視同源政策,能夠發起跨域請求。
<button id="myButton">點我</button>
<form action="https://www.baidu.com" method="get">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
上述請求響應都沒有問題
然而對於AJAX
就不行
...
request.open('GET', 'http://www.baidu.com')
...
- 這是爲何呢,由於
原頁面用 form 提交到另外一個域名以後,原頁面的腳本沒法獲取新頁面中的內容,因此瀏覽器認爲這是安全的。
而 AJAX 是能夠讀取響應內容的,所以瀏覽器不能容許你這樣作。若是你細心的話你會發現,其實請求已經發送出去了,你只是拿不到響應而已。
因此瀏覽器這個策略的本質是,一個域名的 JS ,在未經容許的狀況下,不得讀取另外一個域名的內容。但瀏覽器並不阻止你向另外一個域名發送請求。做者:方應杭
連接:https://www.zhihu.com/questio...
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
那麼如何讓AJAX
跨域發起請求呢。
答案是CORS
-
CORS
目前是W3C的標準,它容許瀏覽器跨域發起XMLHttpRequest
請求,並且能夠發起多種請求,不像JSONP
只能發起GET
請求,全稱是"跨域/源資源共享"(Cross-origin resource sharing)。- 若是想要發起跨域請求 例如: http://wushao.com:8001 要想訪問 http://shaolin.com:8002,能夠作以下處理
request.open('GET', 'http://wushao.com:8001/xxx') //配置request
- 服務器端的代碼須要作以下處理
response.setHeader('Access-Control-Allow-Origin', 'http://shaolin.com:8002')
必定要注意是誰去訪問誰,8001去訪問8002,那麼8001的前端代碼要告訴8002的後端代碼,我們是一家人,你和瀏覽器說說別讓它禁我了。
AJAX一些其餘知識
既然能夠發請求,那麼請求頭的四部分如何得到的,響應的四部分又是如何得到呢
得到請求和響應頭
- 得到請求頭的方法
request.open('GET', 'http://shaolin.com:8002/xxx')// 請求的第一部分
request.setRequestHeader('Content-Type', 'x-www-form-urlencoded')//請求的第二部分
request.setRequestHeader('wushao', '18') //請求的第二部分
request.send('我要設置請求的第四部分') //請求的第四部分
request.send('name=wushao&password=wushao') //請求的第四部分
對應的典型的http請求四部分
GET /xxx HTTP/1.1
HOST: http://shaolin.com:8002
Content-Type: x-www-form-urlencoded
wushao: 18
name=wushao&password=wushao
- 得到響應的方法
request.status //響應的第一部分 200
request.statusText //響應的第一部分 OK
request.getAllResponseHeaders //響應的第二部分,這個方法好啊,所有的響應頭
request.getResponseHeader('Content-Type') //響應的第二部分具體的
request.responseText //響應的第四部分
對應的典型的http響應的四部分
HTTP/1.1 200 OK
Content-Type: text/json;charset=utf-8
{
"note" : {
"to" : "木木",
"from" : "少少",
"heading" : "你好哇",
"content" : "很久不見啊"
}
}
回顧一下各個status對應的意思
100
200 === OK,請求成功
301 === 被請求的資源已永久移動到新位置
302 === 請求臨時重定向,要求客戶端執行臨時重定向
304 === 和上次請求同樣,未改變
403 === 服務器已經理解請求,可是拒絕訪問
404 === 請求失敗,服務器上沒有這個資源
502 === 做爲網關或者代理工做的服務器嘗試執行請求時,從上游服務器接收到無效的響應。
503 === Service Unavailable 因爲臨時的服務器維護或者過載,服務器當前沒法處理請求。
練習一下JQuery封裝AJAX
- 初級的jq封裝
這是一個很簡陋的效果,首先我仍是把jq假設的很簡單,就是一個window的屬性,請輕噴……
window.jQuery = function (nodeOrSelector) {
let nodes = {}
nodes.addClass = function () {}
nodes.html = function () {}
return nodes
}
window.jQuery.ajax = function (options) {
let url = options.url
let method = options.method
let headers = options.headers
let body = options.body
let successFn = options.successFn
let failFn = options.failFn
let request = new XMLHttpRequest() //實例化XMLHttpRequest對象
request.open(method, url)
for (let key in headers) {
let value = headers[key]
request.setRequestHeader(key, value)
}
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status <= 300) {
successFn.call(undefined, request.responseText)
} else if (request.status >= 400) {
failFn.call(undefined, request)
}
}
}
request.send(body)
}
以上就是jq對ajax的簡陋的封裝,ajax()方法接受一個對象做爲參數,這個對象有不少鍵。這些鍵就是http請求的頭的各個部分,以及一個成功函數和一個失敗函數。
myButton.addEventListener('click', (e) => {
window.jQuery.ajax ({
url: '/xxx',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'wushao': '18'
},
body: 'a=1&b=6',
successFn: (x) => {
...
},
failFn: (x) => {
...
}
})
})
以上就是簡化後的使用方法,給button綁定事件的時候,函數體直接就是ajax()
- 目前你會發現options這個對象傻傻的,由於總有一些用戶不但願只傳一個參數。因此咱們稍微改造一下。
let url
if (arguments.length === 1) {
url = options.url
} else if (arguments.length === 2) {
url = arguments[0]
options = arguments[1]
}
let method = options.method
let headers = options.headers
let body = options.body
let successFn = options.successFn
let failFn = options.failFn
加了一點,判斷ajax()的參數個數。
- 一千我的有一千零一個成功或失敗函數的寫法,因此爲了維護世界和平,你們約定俗成了一套理論 Promise then( )
//Promise這個對象呢,大概長這個樣子,真實面目我是沒見過
//簡單的寫一下promise
window.Promise = function (fn) {
//...一些其餘代碼
return {
then: function () {}
}
}
Promise這個構造函數呢,又會返回一個函數,這個返回的函數一個then屬性,value又是一個函數。到處都體現着函數是第一公民的地位!!!
那咱們能夠利用這個強大的Promise對象搞一些事情了。
//第一步的代碼改形成這樣,第一步用到了ES6的解構賦值法
window.jQuery.ajax = function ({url, method, body, headers}) {
return new Promise(function (resolve, reject) {
let request = new XMLHttpRequest()
request.open(method, url)
for(let key in headers) {
let value = headers[key]
request.setRequestHeader(key, value)
}
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status <= 300) {
resolve.call(undefined, request.responseText)
} else if (request.status >= 400) {
reject.call(undefined, request)
}
}
}
request.send(body)
})
}
關於解構賦值:ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)
詳見ES6解構賦值
//通過上面這麼一折騰,能夠很簡單的使用了
myButton.addEventListener('click', (e) => {
let promise = window.jQuery.ajax({
url: '/xxx',
method: 'get',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'wushao': '18'
}
})
promise.then(
(responseText) => {
console.log(responseText)
},
(request) => {
console.log(request)
}
)
})
注意then能夠傳入兩個函數,第一個函數表示成功了執行這個,第二個函數表示失敗了執行這個,並且能夠進行鏈式調用,一直點下去。
- 因此實際上jq的寫法大可能是這麼寫的
myButton.addEventListener('click', (e) => {
$.ajax({
url: '/xxx',
type: 'GET',
}).then(
(responseText) => {
console.log(responseText)
return responseText
},
(request) => {
console.log('error')
return '已經處理'
}
).then(
(responseText) => {
console.log(responseText)
},
(request) => {
console.log(error2)
}
)
})
鏈式調用的意思就是:成功函數成功了,就執行第二個then的第一個函數;成功函數失敗了,就執行第二個then的第二個函數。
完整代碼詳見個人gitHub