AJAX的出現與跨域處理

XMLHttpRequest JSON AJAX CORS 四個名詞來開會

如何發請求

在前端的世界裏也逛蕩了很多日子了,目前已經get到大約5種發起請求的方式,主流的、非主流的。javascript

何種方式 請求方法  
最多見的form表單 默認GET,多用POST,只此兩種 會刷新頁面或者新開頁面
a 標籤 GET請求 也會刷新頁面或者新開頁面
imgsrc屬性 GET 只能以圖片的形式展示
link標籤 GET 只能以CSSfavicon的形式展示
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對象是用來在瀏覽器和服務器之間傳輸數據的。

古代的操做的是:

  1. 瀏覽器構造XMLHttpRequest實例化對象
  2. 用這個對象發起請求
  3. 服務器響應一個XML格式的字符串,是字符串,是字符串,是字符串,也就是說響應的第四部分是字符串。
  4. 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 屬性。

  1. request.open(method, URL, async)方法。

    • 通常用三個參數,第一個參數是請求的方法,能夠用GET POST DELETE PUT等等,URL是用訪問的路徑,async是是否使用同步,默認true,開啓異步,不須要作修改便可,因此實際中只寫前兩個參數
    • 若是非要寫false,開啓同步,會對瀏覽器有阻塞效應,並且若是值爲false,則send()方法不會返回任何東西,直到接受到了服務器的返回數據
  2. request.send()方法。

    • 發送請求. 若是該請求是異步模式(默認),該方法會馬上返回. 相反,若是請求是同步模式,則直到請求的響應徹底接受之後,該方法纔會返回
  3. readyState屬性。

    • 描述請求的五個狀態。

      • 0 === 常量 UNSENT (未打開) open()方法未調用
      • 1 === OPENED (未發送) 只是open()方法調用了
      • 2 === HEADERS_RECEIVED (已獲取響應頭) send()方法調用了,響應頭和響應狀態已經返回了
      • 3 === LOADING (正在下載響應體) 響應體下載中,responseText已經獲取了部分數據
      • 4 === DONE (請求完成) 整個響應過程完畢了。 這個值是實際中用到的。
      • 只要不等於4,就表示請求還在進行中。
  4. responseText屬性是這次響應的文本內容。
  5. onreadystatechange屬性。

    • readyState屬性的值發生改變,就會觸發readyStateChange事件。
    • 咱們能夠經過onReadyStateChange屬性,指定這個事件的回調函數,對不一樣狀態進行不一樣處理。尤爲是當狀態變爲4的時候,表示通訊成功,這時回調函數就能夠處理服務器傳送回來的數據。即前面的代碼片斷的處理方式。
  6. 其餘的方法、屬性、事件詳見阮一峯博客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的子集

  1. JSON很簡單,數據類型和JS有點不一樣的地方。
JavaScript JSON
string "string" 必須寫雙引號
number number
object {"object": "name"} 必須雙引號
undefined 沒有
null null
boolean 直接寫true false
array array
function 沒有
variable  
  1. 瀏覽器的全局對象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" : "很久不見啊" } } `)
  1. 咱們瀏覽器有同源政策,不是同協議 同域名 同端口 的網頁沒法相互訪問。

4.AJAX剛好是同源政策的擁躉

CORS

  1. 若是AJAX向非同源的地址發起請求,會報錯。

    • 這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200,也就是說即便你看到了200的正確碼,也沒有用
  2. 可是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

  1. CORS目前是W3C的標準,它容許瀏覽器跨域發起XMLHttpRequest請求,並且能夠發起多種請求,不像JSONP只能發起GET請求,全稱是"跨域/源資源共享"(Cross-origin resource sharing)。

request.open('GET', 'http://wushao.com:8001/xxx') //配置request
  • 服務器端的代碼須要作以下處理
response.setHeader('Access-Control-Allow-Origin', 'http://shaolin.com:8002')

必定要注意是誰去訪問誰,8001去訪問8002,那麼8001的前端代碼要告訴8002的後端代碼,我們是一家人,你和瀏覽器說說別讓它禁我了。

AJAX一些其餘知識

既然能夠發請求,那麼請求頭的四部分如何得到的,響應的四部分又是如何得到呢

得到請求和響應頭
  1. 得到請求頭的方法
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
  1. 得到響應的方法
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
  1. 初級的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()

  1. 目前你會發現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()的參數個數。

  1. 一千我的有一千零一個成功或失敗函數的寫法,因此爲了維護世界和平,你們約定俗成了一套理論 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能夠傳入兩個函數,第一個函數表示成功了執行這個,第二個函數表示失敗了執行這個,並且能夠進行鏈式調用,一直點下去。

  1. 因此實際上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

相關文章
相關標籤/搜索