---
title: 'JS 入門 - AJAX'
tags: JS, Javascript, AJAX
description: 2021/01/27
---
JS 入門 -- AJAX
===
## 什麼是 AJAX ?
1. AJAX 是「Asynchronous JavaScript and XML」(非同步的 JavaScript 與 XML 技術)的縮寫,簡單說就是網頁不用重新整理,就能即時地透過瀏覽器去跟伺服器溝通,撈出資料。這也是為什麼當我們瀏覽 Facebook、Twitter 內容時,不會看見整個網頁一直重新整理。
2. 伺服器對 AJAX 資料請求回應通常是以三種資料格式其中之一(HTML、XML、JSON),最常與 Javascript 做搭配就是 JSON。
## XMLHttpRequest 物件 — 跨瀏覽器撈資料
### 先設變數 xhr 來撈對方伺服器的資料
儲存一個 XMLHttpRequest 物件以抓取別人的資料,該物件裡面有很多事件跟屬性可以做應用,例如 onload / readyState 等。
```
var xhr = new XMLHttpRequest(); // 透過瀏覽器和對方伺服器要資料
// readyState:0
```
* onload : 當從遠端**撈完**資料後會觸發此 event 事件
* readyState : 屬性的數值用以判斷目前讀取資料的狀態
* 0 - 已經產生一個 XMLHttpRequest,但是還沒連結要撈的資料
* 1 - 用了open,但是還沒傳資料
* 2 - 偵測到你有用 send
* 3 - 資料 loading 中
* 4 - 你撈到資料了,數據已經接收到

### 下 open 指令 (有三個參數)
第一個參數: 格式 - get (讀取資料) / post (傳送資料到伺服器)
第三個參數: 同步與非同步
```
xhr.open('格式', '讀取的網址', true); // readyState: 1
```
### 傳送資料 send
1. <span class="red">如果只是要讀取資料,則 ( ) 內要打 null (空值)</span>。此時 readyState = 2,代表偵測到有用 send。
2. readyState = 2 (send中) 到 readyState = 3 (loading中) 過程太快,所以直接會看到結果為 readyState = 4 (接收完成)。
3. 如果成功讀取到資料,則 XML 物件內的 responseText 屬性會把撈到的資料紀錄進去,此時 readyState = 4,代表已經成功撈到資料。
```
xhr.send( ); // readyState: 4
```
### 實作 : 成功接收資料
[練習用的AJAX](https://hexschool.github.io/ajaxHomework/data.json)
```typescript=
// readyState ﹕0 還沒open任何網址,也沒做傳送動作,responseText也沒回傳資料
var xhr = new XMLHttpRequest();
// readyState ﹕1 先在瀏覽器設定好資料,但還沒做傳送
xhr.open('get', 'https://hexschool.github.io/ajaxHomework/data.json', true);
// readyState : 4 接收資料成功
xhr.send(null);
```
如果成功讀取到資料,則 XML 物件內的 responseText 屬性會把撈到的資料紀錄進去。

## AJAX 非同步請求觀念
1. 通過 XMLHttpRequest 生成的請求可以有兩種方式來獲取資料,非同步模式或同步模式。
2. XMLHttpRequest 物件的 open( ) 方法的第三個參數,true 代表非同步、false 代表同步。
### 非同步 : true
==可以同時進行多個任務==,不需要等待上一個動作完成,就能讓程式碼繼續往下跑,等到請求資料完成才會執行 callback 函式。
:bulb: 範例 : 不會等資料回傳回來 (JS執行很快),就直接往下跑,所以 (第4行) 根本撈不到資料。
```typescript=
var xhr = new XMLHttpRequest();
xhr.open('get', 'https://hexschool.github.io/ajaxHomework/data.json', true);
xhr.send(null);
console.log(xhr.responseText); // 撈不到資料
```
### 同步 : false
必須等到請求完成 response 後才會繼續往下執行程式碼,等待的期間無法處理其他事情。
### 使用非同步請求 true 搭配 onload 事件應用
#### 有時候要撈的資料很大,後面也有東西要執行,不能卡住,所以大部分都用true做設計。然而非同步請求在下了 send() 指令後,還需要一些時間 load 資料,在還沒撈到資料的情況下,程式碼繼續往下跑,那要怎樣才能順利將資料印出來呢?
<span class="red">解決方式:使用XMLHttpRequest 中的 onload 事件,該事件的意思是「當確定資料全部跑完以後,才會觸發此事件」( 等 readyState : 4 全部載入完,才執行 )。</span>
```typescript=
var xhr = new XMLHttpRequest();
xhr.open('get', 'https://hexschool.github.io/ajaxHomework/data.json', true);
xhr.send(null);
xhr.onload = function() {
console.log(xhr.responseText); // [{"name":"王小名"}]
//把抓到的資料物件化或陣列化,加以運用
var str = JSON.parse(xhr.responseText);
//選取 DOM,並渲染至網頁
document.querySelector('.message').textContent = str[0].name;
}
```
## HTTP 狀態碼
* 打開 chrome 開發工具選擇 Network 頁籤," 重整一下 " 可以看到該網頁讀取資料的狀況。
* 其中有一個 status 欄,裡面的數字就是 HTTP 狀態碼,代表網頁連線的狀態。
* Status : 200 代表有正確回傳 (有撈到) (順利的取得)
* Status : 404 資料讀取錯誤 (沒有撈到)

* 也可從 "console" 裡觀察 status

<span class="red">有時候雖然readyState: 4有回傳成功,但資料並沒有撈到。
承上的 onload 事件所觸發的函式,可以加入 if 條件把 HTTP 連線狀態加入判斷。</span>
```typescript=
xhr.onload = function() {
console.log(xhr.responseText); // [{"name":"王小名"}]
if(xhr.status === 200) {
var str = JSON.parse(xhr.responseText);
document.querySelector('.message').textContent = str[0].name;
}else {
console.log('資料錯誤');
}
}
```
### 步驟
1. 建立了一個 XMLHttpRequest 物件 : <span class="green">var xhr = newXMLHttpRequest();</span>
2. 傳送到伺服器要資料 : <span class="green">xhr.open('格式', '讀取的網址', true)</span>
3. 回傳資料到自己的瀏覽器 : <span class="green">xhr.send()</span>
4. 拿到資料後,增加 onload 事件 : <span class="green">xhr.onload = function( ){ }</span>
5. 在 onload 事件上綁定函式,判斷 HTTP 連線是否正常,是的話將回傳的資料渲染至網頁,否則畫面顯示資料錯誤
## Cross-Origin Resource Sharing (CORS)
* 開了這個權限,資源才能共享、其他開發者才能**跨網域**撈取資料。
* 查詢某筆資料(JSON 或 API)有沒有開 CORS,可以上 [test-cros.org](http://www.test-cors.org/) 查詢。
* 如果接本機或跨網域想撈別人的資料,會被阻擋 : 怕資安有問題。
* 練習參考網址 : [行政院環境保護署 “open data” JSON資料](https://data.epa.gov.tw/)
## post — 傳統表單輸入介紹
* 這個功能常用在註冊帳號時,將使用者輸入的資料跟資料庫做比對,檢查是否有重複的帳號。
* form 表單的 action 屬性要填入後端伺服器的網址,使用者輸入的資料將會被傳送到那個位址。
* <span class="green">form 標籤必須帶有 action、method 屬性</span>
* <span class="green">input 必須有 name 屬性</span>
* <span class="red">不可與 AJAX 傳送方式混用</span>
* <span class="green">若 type="submit",要用 e.preventDefault( ) 設定阻止表單送出的預設行為。</span>
* 按了 "傳送的按鈕" 後,該網頁的網址將會有所不同。
「原本網址」 **/** 「傳送至後端的網址」 **?** 「表單欄位的 name」**=**「使用者輸入的值(參數)」
* 數個 name 加參數的組合之間,會用 **&** 來做連接 ( 欄位間資訊用 & 隔開 )。

```typescript=
<form action="index.html">
帳號: <input type="text" name="account"><br>
密碼: <input type="password" name="password"><br>
<input type="submit" value="送出">
</form>
```
## get 與 post 的 AJAX 差異
* get:從瀏覽器發出請求,伺服器會回傳資料(在 responseText 裡面回傳一個物件)。
* post:從瀏覽器發出請求,傳送資料時註明格式,若選擇用表單格式,則傳送資料的內容要仿照表單傳送後的網址。
* <span class="red">post 使用 AJAX 內容格式,一般有兩種:</span>
* 表單輸入格式:content-type application/x-www-form-urlencoded
* JSON 格式:content-type application/json
* 傳送資料格式應依後端工程師的要求而定,有些情況可以直接丟 JSON 格式到後端,有些情況則是要用表單輸入格式傳送。
### AJAX POST - 表單輸入格式寫法
:bulb: **範例** [API post 練習](https://github.com/hexschool/nodejs_ajax_tutorial)
1. action=" " 是傳統表單的傳送方式,不可與 AJAX 傳送方式混用。
2. 若 type=”submit”,要用 e.preventDefault ( ) 設定阻止表單送出的預設行為。
3. 使用AJAX執行,有xhr.open、xhr.send以及xhr.setRequestHeader設定form固定的表頭資訊,就不需要傳統方式的action、method屬性、甚至form標籤,且即使input沒有name屬性,也可藉由AJAX成功傳送。
```
<form>
帳號:<input type="text" class="email"><br>
密碼:<input type="password" class="password"><br>
<input type="submit" class="submit" value="註冊">
</form>
<p class="message"></p>
```
1. 在 click 監聽事件外取值的話,其代表的意思則是當頁面載入完成後就去取值並丟到對應的變數當中,而此時的帳號密碼並未被輸入,因此值也將會是空值,所以這邊才必須要寫在監聽事件當中,以確保每次輸入完帳號密碼後並按下按鈕才去抓取值,這樣抓值才會是正確的
2. 收到伺服器回傳資料後要再作利用的時候,<span class="red">要把 responseText 裡的字串物件化(JSON.parse( ))再取出值。</span>
```typescript=
var sendPost = document.querySelector('.submit');
sendPost.addEventListener('click',function(e){
e.preventDefault();
// 一定要在監聽內取值才不會報錯
var account = document.querySelector('.email').value;
var password = document.querySelector('.password').value;
// 會用 post 方法把資料傳到 https://hexschool.. 這個網址去做比對
var xhr = new XMLHttpRequest();
xhr.open('post', 'https://hexschool-tutorial.herokuapp.com/api/signup', true);
// 比對資料傳過去的值是什麼格式(格式:模擬一般表單輸入的狀況)
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
// 傳送的值,帶相對應的內容
xhr.send(`email=${account}&password=${password}`);
xhr.onload = function() {
console.log(JSON.parse(xhr.responseText));
var str = JSON.parse(xhr.responseText).message;
alert(str); // 此帳號已被使用
document.querySelector('.message').textContent = str;
}
});
```
因為是把 xhr 寫在 function( ) 裡,所以無法在 "console" 裡查詢 xhr 狀態。 函式執行完 xhr 此區域變數就刪除了。
至「Network」多了一支資料為xhr , 點開「signup」觀察得知資料是否有傳遞成功,查看相關回傳訊息。

### AJAX JSON 格式傳遞寫法
:bulb: **範例** [API post 練習](https://github.com/hexschool/nodejs_ajax_tutorial)
* <span class="red">傳送資料只能接收**字串**內容,需把物件object轉成字串</span>
* 收到伺服器回傳資料後,<span class="red">要把 responseText 裡的字串物件化(JSON.parse( ))再取出值。</span>
```typescript=
var submit = document.querySelector('.submit');
submit.addEventListener('click',function(e){
e.preventDefault();
// 一定要在監聽內取值才正確
var emailStr = document.querySelector('.email').value;
var passwordStr = document.querySelector('.password').value;
if(emailStr.trim() === '' || passwordStr.trim() ===''){
alert('不可空白!!');
return;
};
var account = {}; //用來儲存使用者輸入的資訊的物件
account.email = emailStr;
account.password = passwordStr;
console.log(account); // 檢查傳送的格式是否正確
var xhr = new XMLHttpRequest();
xhr.open('post', 'https://hexschool-tutorial.herokuapp.com/api/signup', true);
xhr.setRequestHeader("content-type","application/json"); // 傳送資料的格式選擇為 JSON
var data = JSON.stringify(account); // 轉成字串格式才能傳送
xhr.send(data); // 傳送
xhr.onload = function() {
console.log(xhr); // 檢查伺服器回傳的資料
var str = JSON.parse(xhr.responseText);
document.querySelector('.message').textContent = str.message;
var veriStr = str.success;
if( veriStr == true ){ // 撈出 veriStr 的值去做比對
alert(str.message); // 帳號註冊成功
}else{
alert(str.message); // 帳號註冊失敗
}
}
});
```
### 試著用 jQuery 撈取遠方資料
當使用 jQuery 框架,若傳入的是 JSON 格式,會自動轉為 JavaScript 物件,讓開發者少做轉檔步驟。
> 使用 jQuery 框架撈取遠方資料將直接是 object
載入 jQuery CDN : <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
(要放在 all.js 前面)
```
$.ajax({
url: "https://randomuser.me/api/",
}).done(function (data) {
console.log(data);
console.log(typeof data); // object
});
```
透過「NetWork」可看到「api/」這個檔案 → 點開右邊有「Response」為原本字串的型式
> JSON 並不是 JavaScript 的物件,是一個字串的型式,便於網路上做傳遞。

## :memo: 參考文章 :bulb:
:::info
[搞懂非同步請求 (Asynchronous request)概念](https://tw.alphacamp.co/blog/ajax-asynchronous-request)
[HTTP 狀態碼](https://blog.miniasp.com/post/2009/01/16/Web-developer-should-know-about-HTTP-Status-Code)
[特別整理 API 網站列表](https://github.com/hsiangfeng/APIList)
[我每天都接一個 API 系列文](https://ithelp.ithome.com.tw/articles/10197459)
[json-server - 讓你不會後端也能開 API](https://www.youtube.com/watch?v=9TAanXxNvEI)
:::
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>