手寫 AJAX

目錄

  1. 沒有 AJAX 的年代,怎么發(fā)請求
  2. AJAX 是什么
  3. XMLHttpRequest 的實例屬性
  4. XMLHttpRequest 的實例方法
  5. 手寫 AJAX
    • (1)原生 ajax
    • (2)封裝 jQuery.ajax
  6. 總結(jié)

沒有 AJAX 的年代,怎么發(fā)請求

  1. <form> 可以發(fā)請求,但是會刷新頁面或新開頁面
  1. <a> 可以發(fā) get 請求,但是也會刷新頁面或新開頁面
  1. <img> 可以發(fā) get 請求,但是只能以圖片的形式展示
  1. <link> 可以發(fā) get 請求,但是只能以 CSS、favicon 的形式展示
  1. <script> 可以發(fā) get 請求,但是只能以腳本的形式運行

AJAX 是什么

AJAX(Asynchronous JavaScript and XML),指的是通過 JavaScript 的異步通信,從服務(wù)器獲取 XML 文檔從中提取數(shù)據(jù),再更新當前網(wǎng)頁的對應(yīng)部分,而不用刷新整個網(wǎng)頁。

后來,AJAX 這個詞就成為 JavaScript 腳本發(fā)起 HTTP 通信的代名詞,也就是說,只要用腳本發(fā)起通信,就可以叫做 AJAX 通信。

AJAX 的步驟

  1. 創(chuàng)建 XMLHttpRequest 實例
  2. 發(fā)出 HTTP 請求
  3. 服務(wù)器返回 XML 格式的字符串
  4. JS 解析 XML,并更新局部頁面

不過隨著歷史進程的推進,XML 已經(jīng)被淘汰,取而代之的是 JSON。

JSON(JavaScript Object Notation,JavaScript對象表示法)是一種由 Douglas Crockford 構(gòu)想和設(shè)計、輕量級的數(shù)據(jù)交換語言。它是 JavaScript 的一個子集,因此 JSON 在語法上保留了很多 JavaScript 的特征。

區(qū)別:

  1. JSON 沒有 function、undefined,也沒有 Number 中的 NaN 和 Infinity
  2. JSON 字符串的首尾必須是雙引號,這意味著對象的鍵也必須加上雙引號
  3. JSON 只是一種數(shù)據(jù)格式,數(shù)據(jù)格式其實就是一種規(guī)范,格式、形式、規(guī)范是不能用來存諸數(shù)據(jù)的。因此諸如 var obj={"width":100,"height":200,"name":"rose"} 這樣的不能稱之為 JSON 對象,而是一種 JSON 格式的 JS 對象。

如下是 JavaScript 與 JSON 的對比:

XMLHttpRequest 對象是 AJAX 的主要接口,用于瀏覽器與服務(wù)器之間的通信。盡管名字里面有 XML 和 HTTP,它實際上可以使用多種協(xié)議(比如 file 或 ftp),發(fā)送任何格式的數(shù)據(jù)(包括字符串和二進制)。

注意:AJAX 只能向同源網(wǎng)址(協(xié)議、域名、端口都相同)發(fā)出 HTTP 請求,如果發(fā)出跨域請求,就會報錯。

XMLHttpRequest 的實例屬性

XMLHttpRequest.readyState

XMLHttpRequest.readyState 屬性返回一個 XMLHttpRequest 代理當前所處的狀態(tài)。

狀態(tài) 描述
0 UNSENT 代理被創(chuàng)建,但尚未調(diào)用 open() 方法
1 OPENED open() 方法已經(jīng)被調(diào)用
2 HEADERS_RECEIVED send() 方法已經(jīng)被調(diào)用,并且頭部和狀態(tài)已經(jīng)可獲得
3 LOADING 下載中; responseText 屬性已經(jīng)包含部分數(shù)據(jù)
4 DONE 下載操作已完成

XMLHttpRequest.status

XMLHttpRequest.status 屬性返回一個整數(shù),表示服務(wù)器回應(yīng)的 HTTP 狀態(tài)碼。

XMLHttpRequest.onreadystatechange

XMLHttpRequest.onreadystatechange 屬性指向一個監(jiān)聽函數(shù)。readystatechange 事件發(fā)生時(實例的 readyState 屬性變化),就會執(zhí)行這個屬性。

XMLHttpRequest.responseText

XMLHttpRequest.responseText 屬性返回從服務(wù)器接收到的字符串,該屬性為只讀。只有 HTTP 請求完成接收以后,該屬性才會包含完整的數(shù)據(jù)。

XMLHttpRequest 的實例方法

XMLHttpRequest.open()

XMLHttpRequest.open 方法用于指定 HTTP 請求的參數(shù),或者說初始化 XMLHttpRequest 實例對象。

它一共可以接受五個參數(shù):

  • method:表示 HTTP 動詞方法,比如GET、POST、PUT、DELETE、HEAD等
  • url: 表示請求發(fā)送目標 URL
  • async(可選): 布爾值,表示請求是否為異步,默認為 true。如果設(shè)為 false,則 send() 方法只有等到收到服務(wù)器返回了結(jié)果,才會進行下一步操作。由于同步 AJAX 請求會造成瀏覽器失去響應(yīng),許多瀏覽器已經(jīng)禁止在主線程使用,只允許 Worker 里面使用。所以,這個參數(shù)輕易不應(yīng)該設(shè)為false。
  • user(可選):表示用于認證的用戶名,默認為空字符串
  • password(可選):表示用于認證的密碼,默認為空字符串

注意:再次使用 open(),等同于調(diào)用 abort()。

XMLHttpRequest.send()

XMLHttpRequest.send 方法用于實際發(fā)出 HTTP 請求。

它的參數(shù)是可選的:

  • 如果不帶參數(shù),就表示 HTTP 請求只有一個 URL,沒有數(shù)據(jù)體,典型例子就是 GET 請求
  • 如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是 POST 請求

GET 請求:

var xhr = new XMLHttpRequest();
xhr.open('GET',
  'http://www.example.com/?id=' + encodeURIComponent(id),
  true
);
xhr.send(null);

POST 請求:

var xhr = new XMLHttpRequest();
var data = 'email='
  + encodeURIComponent(email)
  + '&password='
  + encodeURIComponent(password);

xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);

XMLHttpRequest.setRequestHeader()

XMLHttpRequest.setRequestHeader 方法用于設(shè)置瀏覽器發(fā)送的 HTTP 請求的頭信息。

該方法必須在open()之后、send()之前調(diào)用。
該方法接受兩個參數(shù)。第一個參數(shù)是字符串,表示頭信息的字段名,第二個參數(shù)是字段值。

xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));

XMLHttpRequest.getResponseHeader()

XMLHttpRequest.getResponseHeader 方法返回 HTTP 頭信息指定字段的值。

XMLHttpRequest.getAllResponseHeaders()

XMLHttpRequest.getAllResponseHeaders 方法返回一個字符串,表示服務(wù)器發(fā)來的所有 HTTP 頭信息。

格式為字符串,每個頭信息之間使用 CRLF 分隔(回車+換行),如果沒有收到服務(wù)器回應(yīng),該屬性為 null。如果發(fā)生網(wǎng)絡(luò)錯誤,該屬性為空字符串。

手寫 AJAX

簡而言之,AJAX 就是在瀏覽器通過 XMLHttpRequest 對象, 構(gòu)造(set)HTTP 請求和獲?。╣et)HTTP 響應(yīng)的技術(shù)。

那么 AJAX 具體如何實現(xiàn)?

  1. JS 設(shè)置(set)任意請求 header
    • 請求內(nèi)容第一部分 request.open('get', '/xxx')
    • 請求內(nèi)容第二部分 request.setRequestHeader('content-type','x-www-form-urlencoded')
    • 請求內(nèi)容第四部分 request.send('a=1&b=2')
  2. JS 獲?。╣et)任意響應(yīng) header
    • 響應(yīng)內(nèi)容第一部分 request.status / request.statusText
    • 響應(yīng)內(nèi)容第二部分 request.getResponseHeader() / request.getAllResponseHeaders()
    • 響應(yīng)內(nèi)容第四部分 request.responseText

(1)原生 ajax

了解了屬性和方法之后,根據(jù) AJAX 的步驟,手寫最簡單的 GET 請求。

version 1.0:

myButton.addEventListener('click', function(){
    ajax()
})

function ajax() {
    let request = new XMLHttpRequest()
    request.open('get', 'https://www.google.com')
    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            if (request.status >= 200 && request.status <300) {
                let string = request.responseText
                let object = JSON.parse(string)
            }
        }
    }
    request.send()
}

在 1.0 版本中,并沒有設(shè)置請求報頭,因為是 GET 請求,所以 send() 中也沒有設(shè)置請求主體。并且,其內(nèi)容是寫死的,我們應(yīng)該從變量來獲取更好。

(2)封裝 jQuery.ajax

需求:實現(xiàn)一個滿足這些 API 的 jQuery.ajax(url, method, body, success, fail)。

在之前的文章中,我實現(xiàn)了一個簡易版 jQuery,這里就在它的基礎(chǔ)上再添加函數(shù) ajax。

version 2.0:

myButton.addEventListener("click", (e) => {
    $.ajax(
          '/xxx', 
          'post', 
          'a=1&b=2', 
          (responseText) => { console.log('success') }, 
          (request) => { console.log('fail') }
    )
})

window.jQuery = function(nodeOrSelector) {
    let nodes = {}
    return nodes
}

window.jQuery.ajax = function(url, method, body, success, fail) {
    let request = new XMLHttpRequest()
    request.open(method, url)
    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            if (request.status >= 200 && request.status <300) {
                success.call(undefined, request.responseText)
            } else if (request.status >= 400) {
                fail.call(undefined, request)
            }
        }
    }
    request.send(body)      
}

window.$ = window.jQuery

version 2.0 版本實現(xiàn)了 jQuery 版本的以變量傳入的方式實現(xiàn)API的 ajax,但是封裝得并不好,容易忘記每個參數(shù)是什么,并且諸如 GET 請求并不需要參數(shù) body,因此這個位置應(yīng)該填入 undefined 或者 null,這樣代碼就很丑。
因此,我們需要傳入一個有結(jié)構(gòu)的參數(shù)來包含上述功能。

version 3.0:

myButton.addEventListener("click", (e) => {
    $.ajax({
        url: '/xxx',
        method: 'post',
        body: 'a=1&b=2',
        success: (responseText) => {
            console.log('success')
            console.log(responseText)
        },
        fail: (request) => {
            console.log('fail')
            console.log(request.status)
        }
    })
})

window.jQuery = function (nodeOrSelector) {
    let nodes = {}
    return nodes
}

window.jQuery.ajax = function (options) {
    let url = options.url
    let method = options.method
    let body = options.body
    let success = options.success
    let fail = options.fail

    let request = new XMLHttpRequest()
    request.open(method, url)
    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            if (request.status >= 200 && request.status < 300) {
                success.call(undefined, request.responseText)
            } else if (request.status >= 400) {
                fail.call(undefined, request)
            }
        }
    }
    request.send(body)
}

window.$ = window.jQuery

繼續(xù)優(yōu)化上面的代碼,帶入ES6中數(shù)組的解構(gòu)賦值。

//  ES 5
    let url = options.url
    let method = options.method
    let body = options.body
    let success = options.success
    let fail = options.fail

//  ES 6
    let {url, method, body, success, fail} = options

并且,由于 options 參數(shù)只使用了一次,那么可以直接省略掉。得到:

version 4.0:

myButton.addEventListener("click", (e) => {
    $.ajax({
        url: '/xxx',
        method: 'post',
        body: 'a=1&b=2',
        success: (responseText) => {
            console.log('success')
            console.log(responseText)
        },
        fail: (request) => {
            console.log('fail')
            console.log(request.status)
        }
    })
})

window.jQuery = function (nodeOrSelector) {
    let nodes = {}
    return nodes
}

window.jQuery.ajax = function ({ url, method, body, success, fail }) {

    let request = new XMLHttpRequest()
    request.open(method, url)
    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            if (request.status >= 200 && request.status < 300) {
                success.call(undefined, request.responseText)
            } else if (request.status >= 400) {
                fail.call(undefined, request)
            }
        }
    }
    request.send(body)
}

window.$ = window.jQuery

總結(jié)

AJAX 非常重要,基本上,有了 AJAX 之后,前端才被稱之為前端,在這之前的程序員,基本可以被稱為頁面仔。因此,深入理解 AJAX 的手動實現(xiàn),如何設(shè)置和獲取 request 和 response,完成 HTTP 請求,是學(xué)習的重點,也是面試常考的內(nèi)容。

這里只提到了比較淺顯的 HTTP 相關(guān)知識,在后面補看《HTTP權(quán)威指南》后,會對 AJAX 有更深入的理解。

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

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

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