Ajax知識體系大梳理,web前端開發做項目

mb6130455f7bea2 2021-09-19 13:27:15 阅读数:574

ajax 梳理 web 前端

function getXHR(){

var xhr = null;

if(window.XMLHttpRequest) {

xhr = new XMLHttpRequest();

} else if (window.ActiveXObject) {

try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
alert("您的瀏覽器暫不支持Ajax!");
}
}

}

return xhr;

}


### ajax有沒有破壞js單線程機制
對於這個問題, 我們先看下瀏覽器線程機制. 一般情况下, 瀏覽器有如下四種線程:
* GUI渲染線程
* javascript引擎線程
* 瀏覽器事件觸發線程
* HTTP請求線程
那麼這麼多線程, 它們究竟是怎麼同js引擎線程交互的呢?
通常, 它們的線程間交互以事件的方式發生, 通過事件回調的方式予以通知. 而事件回調, 又是以先進先出的方式添加到`任務隊列`?的末尾 , 等到js引擎空閑時,?`任務隊列`?中排隊的任務將會依次被執行. 這些事件回調包括 setTimeout, setInterval, click, ajax异步請求等回調.
瀏覽器中, js引擎線程會循環從?`任務隊列`?中讀取事件並且執行, 這種運行機制稱作?`Event Loop`(事件循環).
對於一個ajax請求, js引擎首先生成?`XMLHttpRequest`?實例對象, open過後再調用send方法. 至此, 所有的語句都是同步執行. 但從send方法內部開始, 瀏覽器為將要發生的網絡請求創建了新的http請求線程, 這個線程獨立於js引擎線程, 於是網絡請求异步被發送出去了. 另一方面, js引擎並不會等待 ajax 發起的http請求收到結果, 而是直接順序往下執行.
當ajax請求被服務器響應並且收到response後, 瀏覽器事件觸發線程捕獲到了ajax的回調事件?`onreadystatechange`?(當然也可能觸發onload, 或者 onerror等等) . 該回調事件並沒有被立即執行, 而是被添加到?`任務隊列`?的末尾. 直到js引擎空閑了,?`任務隊列`?的任務才被撈出來, 按照添加順序, 挨個執行, 當然也包括剛剛append到隊列末尾的?`onreadystatechange`?事件.
在?`onreadystatechange`?事件內部, 有可能對dom進行操作. 此時瀏覽器便會掛起js引擎線程, 轉而執行GUI渲染線程, 進行UI重繪(repaint)或者回流(reflow). 當js引擎重新執行時, GUI渲染線程又會被掛起, GUI更新將被保存起來, 等到js引擎空閑時立即被執行.
以上整個ajax請求過程中, 有涉及到瀏覽器的4種線程. 其中除了?`GUI渲染線程`?和?`js引擎線程`?是互斥的. 其他線程相互之間, 都是可以並行執行的. 通過這樣的一種方式, ajax並沒有破壞js的單線程機制.
### ajax與setTimeout排隊問題
通常, ajax 和 setTimeout 的事件回調都被同等的對待, 按照順序自動的被添加到?`任務隊列`?的末尾, 等待js引擎空閑時執行. 但請注意, 並非xhr的所有回調執行都滯後於setTImeout的回調. 請看如下代碼:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

function ajax(url, method){

var xhr = getXHR();

xhr.onreadystatechange = function(){

 console.log('xhr.readyState:' + this.readyState);

}

xhr.onloadstart = function(){

 console.log('onloadStart');

}

xhr.onload = function(){

 console.log('onload');

}

xhr.open(method, url, true);

xhr.setRequestHeader(‘Cache-Control’,3600);

xhr.send();

}

var timer = setTimeout(function(){

console.log(‘setTimeout’);

},0);

ajax(‘ https://s2.51cto.com/images/20210919/1632028724804700.jpg’,'GET’);

console.warn(‘這裏的log並不是最先打印出來的.’);


上述代碼執行結果如下圖:
![Ajax知識體系大梳理,web前端開發做項目_前端](https://s2.51cto.com/images/20210919/1632028723884898.jpg)setTimeout & ajax & 同步
由於ajax异步, setTimeout回調本應該最先被執行, 然而實際上, 一次ajax請求, 並非所有的部分都是异步的, 至少"readyState==1"的?`onreadystatechange`?回調以及?`onloadstart`?回調就是同步執行的. 因此它們的輸出排在最前面.
### XMLHttpRequest 屬性解讀
首先在Chrome console下創建一個 XMLHttpRequest 實例對象xhr. 如下所示:
![Ajax知識體系大梳理,web前端開發做項目_Web_02](https://s5.51cto.com/images/20210919/1632028724804700.jpg)XMLHttpRequest
#### inherit
試運行以下代碼.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

var xhr = new XMLHttpRequest(),

i=0;

for(var key in xhr){

if(xhr.hasOwnProperty(key)){
i++;

}

}

console.log(i);//0

console.log(XMLHttpRequest.prototype.hasOwnProperty(‘timeout’));//true


可見, XMLHttpRequest 實例對象沒有自有屬性. 實際上, 它的所有屬性均來自於?`XMLHttpRequest.prototype`?.
追根溯源, XMLHttpRequest 實例對象具有如下的繼承關系. (下面以a<<b錶示a繼承b)
`xhr`?<<?`XMLHttpRequest.prototype`?<<?`XMLHttpRequestEventTarget.prototype`?<<?`EventTarget.prototype`?<<?`Object.prototype`
由上, xhr也具有Object等原型中的所有方法. 如toString方法.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

xhr.toString();//“[object XMLHttpRequest]”


通常, 一個xhr實例對象擁有10個普通屬性+9個方法.
#### readyState
只讀屬性, readyState屬性記錄了ajax調用過程中所有可能的狀態. 它的取值簡單明了, 如下:
| readyState | 對應常量 | 描述 |
| --- | --- | --- |
| 0 (未初始化) | xhr.UNSENT | 請求已建立, 但未初始化(此時未調用open方法) |
| 1 (初始化) | xhr.OPENED | 請求已建立, 但未發送 (已調用open方法, 但未調用send方法) |
| 2 (發送數據) | xhr.HEADERS\_RECEIVED | 請求已發送 (send方法已調用, 已收到響應頭) |
| 3 (數據傳送中) | xhr.LOADING | 請求處理中, 因響應內容不全, 這時通過responseBody和responseText獲取可能會出現錯誤 |
| 4 (完成) | xhr.DONE | 數據接收完畢, 此時可以通過通過responseBody和responseText獲取完整的響應數據 |
注意, readyState 是一個只讀屬性, 想要改變它的值是不可行的.
#### onreadystatechange
onreadystatechange事件回調方法在readystate狀態改變時觸發, 在一個收到響應的ajax請求周期中, onreadystatechange 方法會被觸發4次. 因此可以在 onreadystatechange 方法中綁定一些事件回調, 比如:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

xhr.onreadystatechange = function(e){

if(xhr.readystate==4){

var s = xhr.status;
if((s >= 200 && s < 300) || s == 304){
var resp = xhr.responseText;
//TODO ...
}

}

}


注意: onreadystatechange回調中默認會傳入Event實例, 如下:
![Ajax知識體系大梳理,web前端開發做項目_前端_03](https://s9.51cto.com/images/20210919/1632028724661910.jpg)Event
#### status
只讀屬性, status錶示http請求的狀態, 初始值為0. 如果服務器沒有顯式地指定狀態碼, 那麼status將被設置為默認值, 即200.
#### statusText
只讀屬性, statusText錶示服務器的響應狀態信息, 它是一個 UTF-16 的字符串, 請求成功且status==20X時, 返回大寫的?`OK`?. 請求失敗時返回空字符串. 其他情况下返回相應的狀態描述. 比如: 301的?`Moved Permanently`?, 302的?`Found`?, 303的?`See Other`?, 307 的?`Temporary Redirect`?, 400的?`Bad Request`?, 401的?`Unauthorized`?等等.
#### onloadstart
onloadstart事件回調方法在ajax請求發送之前觸發, 觸發時機在?`readyState==1`?狀態之後,?`readyState==2`?狀態之前.
onloadstart方法中默認將傳入一個ProgressEvent事件進度對象. 如下:
![Ajax知識體系大梳理,web前端開發做項目_Web_04](https://s7.51cto.com/images/20210919/1632028725632198.jpg)ProgressEvent
ProgressEvent對象具有三個重要的Read only屬性.
* lengthComputable 錶示長度是否可計算, 它是一個布爾值, 初始值為false.
* loaded 錶示已加載資源的大小, 如果使用http下載資源, 它僅僅錶示已下載內容的大小, 而不包括http headers等. 它是一個無符號長整型, 初始值為0.
* total 錶示資源總大小, 如果使用http下載資源, 它僅僅錶示內容的總大小, 而不包括http headers等, 它同樣是一個無符號長整型, 初始值為0.
#### onprogress
onprogress事件回調方法在?`readyState==3`?狀態時開始觸發, 默認傳入 ProgressEvent 對象, 可通過?`e.loaded/e.total`?來計算加載資源的進度, 該方法用於獲取資源的下載進度.
注意: 該方法適用於 IE10+ 及其他現代瀏覽器.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.

xhr.onprogress = function(e){

console.log(‘progress:’, e.loaded/e.total);

}


#### onload
onload事件回調方法在ajax請求成功後觸發, 觸發時機在?`readyState==4`?狀態之後.
想要捕捉到一個ajax异步請求的成功狀態, 並且執行回調, 一般下面的語句就足够了:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

xhr.onload = function(){

var s = xhr.status;

if((s >= 200 && s < 300) || s == 304){

var resp = xhr.responseText;
//TODO ...

}

}


#### onloadend
onloadend事件回調方法在ajax請求完成後觸發, 觸發時機在?`readyState==4`?狀態之後(收到響應時) 或者?`readyState==2`?狀態之後(未收到響應時).
onloadend方法中默認將傳入一個ProgressEvent事件進度對象.
#### timeout
timeout屬性用於指定ajax的超時時長. 通過它可以靈活地控制ajax請求時間的上限. timeout的值滿足如下規則:
* 通常設置為0時不生效.
* 設置為字符串時, 如果字符串中全部為數字, 它會自動將字符串轉化為數字, 反之該設置不生效.
* 設置為對象時, 如果該對象能够轉化為數字, 那麼將設置為轉化後的數字.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

xhr.timeout = 0; //不生效

xhr.timeout = ‘123’; //生效, 值為123

xhr.timeout = ‘123s’; //不生效

xhr.timeout = [‘123’]; //生效, 值為123

xhr.timeout = {a:123}; //不生效


#### ontimeout
ontimeout方法在ajax請求超時時觸發, 通過它可以在ajax請求超時時做一些後續處理.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

xhr.ontimeout = function(e) {

console.error(“請求超時!!!”)

}


#### response responseText
均為只讀屬性, response錶示服務器的響應內容, 相應的, responseText錶示服務器響應內容的文本形式.
#### responseXML
只讀屬性, responseXML錶示xml形式的響應數據, 缺省為null, 若數據不是有效的xml, 則會報錯.
#### responseType
responseType錶示響應的類型, 缺省為空字符串, 可取?`"arraybuffer"`?,?`"blob"`?,?`"document"`?,?`"json"`?, and?`"text"`?共五種類型.
#### responseURL
responseURL返回ajax請求最終的URL, 如果請求中存在重定向, 那麼responseURL錶示重定向之後的URL.
#### withCredentials
withCredentials是一個布爾值, 默認為false, 錶示跨域請求中不發送cookies等信息. 當它設置為true時,?`cookies`?,?`authorization headers`?或者`TLS客戶端證書`?都可以正常發送和接收. 顯然它的值對同域請求沒有影響.
注意: 該屬性適用於 IE10+, opera12+及其他現代瀏覽器.
#### abort
abort方法用於取消ajax請求, 取消後, readyState 狀態將被設置為?`0`?(`UNSENT`). 如下, 調用abort 方法後, 請求將被取消.
![Ajax知識體系大梳理,web前端開發做項目_程序員_05](https://s4.51cto.com/images/20210919/1632028726659589.jpg)Event
#### getResponseHeader
getResponseHeader方法用於獲取ajax響應頭中指定name的值. 如果response headers中存在相同的name, 那麼它們的值將自動以字符串的形式連接在一起.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.

console.log(xhr.getResponseHeader(‘Content-Type’));//“text/html”


#### getAllResponseHeaders
getAllResponseHeaders方法用於獲取所有安全的ajax響應頭, 響應頭以字符串形式返回. 每個HTTP報頭名稱和值用冒號分隔, 如key:value, 並以\\r\\n結束.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

xhr.onreadystatechange = function() {

if(this.readyState == this.HEADERS_RECEIVED) {

console.log(this.getAllResponseHeaders());

}

}

//Content-Type: text/html"


以上,?`readyState === 2`?狀態時, 就意味著響應頭已接受完整. 此時便可以打印出完整的 response headers.
#### setRequestHeader
既然可以獲取響應頭, 那麼自然也可以設置請求頭, setRequestHeader就是幹這個的. 如下:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

//指定請求的type為json格式

xhr.setRequestHeader(“Content-type”, “application/json”);

//除此之外, 還可以設置其他的請求頭

xhr.setRequestHeader(‘x-requested-with’, ‘123456’);


#### onerror
onerror方法用於在ajax請求出錯後執行. 通常只在網絡出現問題時或者ERR\_CONNECTION\_RESET時觸發(如果請求返回的是407狀態碼, chrome下也會觸發onerror).
#### upload
upload屬性默認返回一個?`XMLHttpRequestUpload`?對象, 用於上傳資源. 該對象具有如下方法:
* onloadstart
* onprogress
* onabort
* onerror
* onload
* ontimeout
* onloadend
上述方法功能同 xhr 對象中同名方法一致. 其中, onprogress 事件回調方法可用於跟踪資源上傳的進度.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

xhr.upload.onprogress = function(e){

var percent = 100 * e.loaded / e.total |0;

console.log('upload: ’ + precent + ‘%’);

}


#### overrideMimeType
overrideMimeType方法用於强制指定response 的 MIME 類型, 即强制修改response的?`Content-Type`?. 如下, 服務器返回的response的 MIME 類型為?`text/plain`?.
![Ajax知識體系大梳理,web前端開發做項目_程序員_06](https://s4.51cto.com/images/20210919/1632028726743117.jpg)response headers

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

xhr.getResponseHeader(‘Content-Type’);//“text/plain”

xhr.responseXML;//null


通過overrideMimeType方法將response的MIME類型設置為?`text/xml;charset=utf-8`?, 如下所示:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

xhr.overrideMimeType(“text/xml; charset = utf-8”);

xhr.send();


此時雖然 response headers 如上圖, 沒有變化, 但?`Content-Type`?已替換為新值.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

xhr.getResponseHeader(‘Content-Type’);//“text/xml; charset = utf-8”


此時,?`xhr.responseXML`?也將返回DOM對象, 如下圖.
![Ajax知識體系大梳理,web前端開發做項目_Web_07](https://s8.51cto.com/images/20210919/1632028727436544.jpg)response headers
### XHR一級
XHR1 即 XMLHttpRequest Level 1. XHR1時, xhr對象具有如下缺點:
* 僅支持文本數據傳輸, 無法傳輸二進制數據.
* 傳輸數據時, 沒有進度信息提示, 只能提示是否完成.
* 受瀏覽器?`同源策略`?限制, 只能請求同域資源.
* 沒有超時機制, 不方便掌控ajax請求節奏.
### XHR二級
XHR2 即 XMLHttpRequest Level 2. XHR2針對XHR1的上述缺點做了如下改進:
* 支持二進制數據, 可以上傳文件, 可以使用FormData對象管理錶單.
* 提供進度提示, 可通過?`xhr.upload.onprogress`?事件回調方法獲取傳輸進度.
* 依然受?`同源策略`?限制, 這個安全機制不會變. XHR2新提供?`Access-Control-Allow-Origin`?等headers, 設置為?`*`?時錶示允許任何域名請求, 從而實現跨域CORS訪問(有關CORS詳細介紹請耐心往下讀).
* 可以設置timeout 及 ontimeout, 方便設置超時時長和超時後續處理.
這裏就H5新增的FormData對象舉個例.

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.

//可直接創建FormData實例

var data = new FormData();

data.append(“name”, “louis”);

xhr.send(data);

//還可以通過傳入錶單DOM對象來創建FormData實例

var form = document.getElementById(‘form’);

var data = new FormData(form);

data.append(“password”, “123456”);

xhr.send(data);


目前, 主流瀏覽器基本上都支持XHR2, 除了IE系列需要IE10及更高版本. 因此IE10以下是不支持XHR2的.
那麼問題來了, IE7, 8,9的用戶怎麼辦? 很遺憾, 這些用戶是比較尷尬的. 對於IE8,9而言, 只有一個閹割版的?`XDomainRequest`?可用,IE7則沒有. 估計IE7用戶只能哭暈在廁所了.
### XDomainRequest
### 最後
你要問前端開發難不難,我就得說計算機領域裏常說的一句話,這句話就是『**難的不會,會的不難**』,對於不熟悉某領域技術的人來說,因為不了解所以產生神秘感,神秘感就會讓人感覺很難,也就是『難的不會』;當學會這項技術之後,知道什麼什麼技術能做到什麼做不到,只是做起來花多少時間的問題而已,沒啥難的,所以就是『會的不難』。
**[CodeChina開源項目:【大廠前端面試題解析+核心總結學習筆記+真實項目實戰+最新講解視頻】](https://ali1024.coding.net/public/P7/Web/git)**
**我特地針對初學者整理一套前端學習資料**
![Ajax知識體系大梳理,web前端開發做項目_程序員_08](https://s6.51cto.com/images/20210919/1632028728741847.jpg)
![Ajax知識體系大梳理,web前端開發做項目_程序員_09](https://s5.51cto.com/images/20210919/1632028728753036.jpg)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
版权声明:本文为[mb6130455f7bea2]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210919132714771X.html