自己實(shí)現(xiàn)AJAX

如何發(fā)請(qǐng)求?

用form可以發(fā)請(qǐng)求,但是會(huì)刷新頁(yè)面或新開(kāi)頁(yè)面;
用a可以發(fā)get請(qǐng)求,但是也會(huì)刷新頁(yè)面或新開(kāi)頁(yè)面;
用image可以發(fā)get請(qǐng)求,但是只能以圖片形式展示;
用link可以發(fā)get請(qǐng)求,但是只能以css、favicon的形式展示;
用script可以發(fā)get請(qǐng)求,但是只能以腳本的形式運(yùn)行。

前端需要一種方式實(shí)現(xiàn):

  1. get、post、put、delete等請(qǐng)求都行
  2. 想以什么方式展示就以什么方式展示
AJAX

AJAX(async javascript and XML) 異步的javascript和XML
滿足如下技術(shù)叫AJAX:

  1. 使用XMLHttpRequest發(fā)請(qǐng)求
  2. 服務(wù)器返回XML格式的字符串(現(xiàn)已升級(jí)為返回JSON格式字符串)
  3. JS解析XML,并更新局部頁(yè)面
原生JS發(fā)送AJAX請(qǐng)求
let request = new XMLHttpRequest()  //產(chǎn)生request
request.open('get','http://jack.com/xxx')  //配置request
request.send()  //發(fā)送request
request.onreadystatechange = () => {  //監(jiān)聽(tīng)request狀態(tài)的變化
    if(request.readyState === 4){  //請(qǐng)求響應(yīng)都完畢了
        if(request.status >= 200 && request.status < 300){  //請(qǐng)求成功 3xx也有可能為成功,這里不考慮
            let string = request.responseText
            let object = window.JSON.parse(string)  //把符合JSON語(yǔ)法的字符串轉(zhuǎn)換成JS對(duì)應(yīng)的值
        }else if(request.status >= 400){ //請(qǐng)求失敗
        }
    }
}
readyState

請(qǐng)求的5種狀態(tài)

狀態(tài) 描述
0 UNSENT(未打開(kāi)) open()方法還沒(méi)有調(diào)用
1 OPENED(未發(fā)送) open()方法已經(jīng)被調(diào)用
2 HEADERS.RECEIVED(已獲取響應(yīng)頭) send()方法已經(jīng)被調(diào)用
3 LOADING(正在下載響應(yīng)體) 響應(yīng)體下載中;responseText中已經(jīng)獲取了部分?jǐn)?shù)據(jù)
4 DONE(請(qǐng)求完成) 整個(gè)請(qǐng)求過(guò)程已經(jīng)完畢
setInterval( () => {
    console.log(request.readyState)
},1)  //試試每毫秒打印一次readyState

上面代碼每毫秒打印一次readyState,會(huì)發(fā)現(xiàn)一般無(wú)法打印出所有的狀態(tài),因?yàn)檎?qǐng)求的過(guò)程太快了。

onreadystatechange

使用onreadystatechange可以監(jiān)聽(tīng)readyState的每次變化

request.onreadystatechange = () => {
    console.log(request.readyState)
}
status

該請(qǐng)求的響應(yīng)狀態(tài)碼,只讀。

if(request.status >= 200 && request.status < 300){ //3xx也可能成功,此處不考慮
    console.log('請(qǐng)求成功')
}else if(request.status >= 400){
    console.log('請(qǐng)求失敗')
}
responseText

此次請(qǐng)求的響應(yīng)文本。
當(dāng)請(qǐng)求未成功或還未發(fā)送時(shí)為null。只讀。

JS vs JSON

JSON和JS是兩門(mén)不同的語(yǔ)言,道格拉斯(JSON作者)抄襲了JS,所以JSON很像JS。JSON官網(wǎng)

  • JSON沒(méi)有function和undefined
  • JSON的字符串首尾必須是雙引號(hào)
  • JSON不能表示復(fù)雜對(duì)象,只能表示哈希
  • JSON沒(méi)有變量
  • JSON沒(méi)有原型鏈

響應(yīng)第四部分是字符串,可以是符合JSON語(yǔ)法的字符串。

//把符合JSON語(yǔ)法的字符串轉(zhuǎn)換成JS對(duì)應(yīng)的值
let string = request.responseText
let object = window.JSON.parse(string)

至此AJAX得到升級(jí),不再返回XML格式的字符串,而是返回更親近JS的JSON格式的字符串。

同源策略

只有 協(xié)議+端口+域名 一模一樣才允許發(fā)AJAX請(qǐng)求
因?yàn)锳JAX可以讀取響應(yīng)內(nèi)容,所以瀏覽器不允許一個(gè)域名的JS,在未經(jīng)允許的情況下讀取另一個(gè)域名的內(nèi)容,但是瀏覽器并不阻止你向另一個(gè)域名發(fā)請(qǐng)求。
CORS可以告訴瀏覽器,我倆是一家的,別阻止他。
在jack.com的服務(wù)器寫(xiě)上

response.setHeader('Access-Control-Allow-Origin','http://frank.com')

這樣frank.com就可以訪問(wèn)jack.com了。
CORS (Cross-Origin Resource Sharing) 跨來(lái)源資源共享。
突破同源策略 === 跨域

AJAX的所有功能

AJAX:用JS發(fā)送請(qǐng)求,用JS處理響應(yīng)。

  • 客戶端JS發(fā)起請(qǐng)求(瀏覽器上的)
    1. 第一部分 request.open('get','./xxx') 動(dòng)詞,路徑
    2. 第二部分 request.setRequestHeader('Content-Type','x-www-form-urlencoded')
    3. 第四部分 request.send('a=1&b=2')
  • 服務(wù)端的JS發(fā)送響應(yīng)(Node.js上的)
    1. 第一部分 request.status/request.statusText 例如200/OK
    2. 第二部分 request.getResponseHeader()/request.getAllResponseHeaders()
    3. 第四部分 request.responseText

通過(guò)AJAX可以任意設(shè)置請(qǐng)求四部分中的所有東西(有些不安全的不讓設(shè)置),也可以獲取響應(yīng)四部分中的所有內(nèi)容。

XMLHttpRequest.setRequestHeader()是設(shè)置HTTP請(qǐng)求頭的方法,此方法必須在open()方法和send()方法之間調(diào)用。

自己封裝jQuery.ajax
window.jQuery.ajax = function(url, method, body, successFn, failFn){
    //... 
}
//給參數(shù)命名,有結(jié)構(gòu)的參數(shù)--對(duì)象(JS里只有對(duì)象有結(jié)構(gòu))

window.jQuery.ajax = function(options){
    let url = options.url
    let method = options.method
    let body = options.body
    let successFn = options.successFn
    let failFn = options.failFn
    
    let request = new XMLHttpRequest()
    request.open(method, url) 
    /*for(let key in headers) {
      let value = headers[key]
      request.setRequestHeader(key, value)
    }*/ //設(shè)置headers,下文
    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)
  }
//調(diào)用
let obj = {
    url: '/xxx',
    method: 'get',
    successFn: () => {}
    failFn: () => {}
}
window.jQuery.ajax(obj)
//調(diào)用可以直接寫(xiě)成
window.jQuery.ajax({
    url: '/xxx',
    method: 'get',
    successFn: () => {}
    failFn: () => {}
})
//如果請(qǐng)求成功時(shí)需要執(zhí)行兩個(gè)函數(shù)
successFn: (x) => {
    f1.call(undefined,x)
    f2.call(undefined,x)
}

如果需要設(shè)置headers

headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'jack': '18'
}

//在open()和send()之間遍歷headers,設(shè)置請(qǐng)求頭
let headers = options.headers

for(let key in headers) {
      let value = headers[key]
      request.setRequestHeader(key, value)
}

jQuery.ajax有兩種傳參方式,傳一個(gè)參數(shù),或傳兩個(gè)參數(shù),第一個(gè)為url

window.jQuery.ajax = function(options){
    let url
    if(arguments.length === 1){ //傳一個(gè)參數(shù)
        url = options.url
    }else if(argument.length === 2){ //傳兩個(gè)參數(shù)
        url = arguments[0]  //url是第一個(gè)參數(shù)
        options = arguments[1]
    }
    let method = options.method
    let body = options.body
    let successFn = options.successFn
    let failFn = options.failFn
    let headers = options.headers
    //...
}
ES6語(yǔ)法之解構(gòu)賦值

解構(gòu)賦值語(yǔ)法是一個(gè)JavaScript表達(dá)式,這是的可以將 值從數(shù)組屬性從對(duì)象 提取到不同的變量。

    let url = options.url
    let method = options.method
    let body = options.body
    let successFn = options.successFn
    let failFn = options.failFn
    let headers = options.headers

//上面代碼可以寫(xiě)成
let{url,mmethod,body,successFn,failFn,headers} = options

//直接從第一個(gè)參數(shù)里解構(gòu),拿到幾個(gè)變量,同時(shí)用let聲明幾個(gè)變量
window.jQuery.ajax = function({url,method,body,successFn,failFn,headers}){}
Promise

不同的庫(kù)風(fēng)格不一樣,如果不看文檔,不知道成功時(shí)傳什么參數(shù),失敗時(shí)傳什么參數(shù)。
例如我自己封裝的jQuery.ajax中,成功時(shí)傳successFn,失敗時(shí)傳failFn,與原版的jQuery是不同的。

$.ajax({
    url:'/xxx',
method: 'get',
}).then(
    (responseText) => {console.log(responseText)}, //成功時(shí)執(zhí)行第一個(gè)參數(shù)
    (request) => {console.log'error'} //失敗時(shí)執(zhí)行第二個(gè)參數(shù)
)

上面代碼中,使用.then(),它有兩個(gè)函數(shù)作為參數(shù),成功時(shí)執(zhí)行第一個(gè)參數(shù),失敗時(shí)執(zhí)行第二個(gè)參數(shù),不用再給成功、失敗時(shí)執(zhí)行的函數(shù)命名。

.then()后面可以繼續(xù)續(xù)接.then(),可以基于上一次的處理結(jié)果繼續(xù)處理。

把自己封裝的jQuery.ajax變成promise形式
window.jQuery.ajax = function({url, method, body, headers}){
    return new Promise(function(resolve, reject){
      let request = new XMLHttpRequest()
      request.open(method, url) // 配置request
      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) //成功調(diào)用resolve
          }else if(request.status >= 400){
            reject.call(undefined, request)  //失敗調(diào)用reject
          }
        }
      }
      request.send(body)
    })
  } //返回值是一個(gè)Promise實(shí)例對(duì)象,這個(gè)實(shí)例對(duì)象有then屬性;then也會(huì)返回一個(gè)Promise對(duì)象,所以可以再接.then()
  

全部代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容