JS 紀錄10 - XMLHttpRequest、Ajax、Fetch、JSONP

JavaScript 傳送資料

XMLHttpRequest

步驟

  • 建立 XMLHTTP 物件
  • 建立 callback function 回傳資料更新
    • 用 readyState & status 判斷回傳狀態
    • 當 return response 時要做的事,通常內容就是 update 畫面上資料
  • 向 server 發送資料
    • open(‘格式’, ‘網址’, ‘同步與非同步’)
      • 非同步 - true
        • 等所有資料跑完之後才回傳我們需要的資料
        • 為了不讓檔案撈取太慢通常使用這個
      • 同步 - false
        • 會等資料傳回來才會讓程式往下跑,所以沒跑完值不會顯示
  • 送出資料
    • Send the request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Create XMLHttpRequest
var xhr = new XMLHttpRequest();

// Create Callback function
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
document.getElementById('ajax').innerHTML = xhr.responseText;
}
};

// open('格式','網址','同步與非同步')
xhr.open('GET','sidebar.html’)

// send request
// 傳送資料沒有要傳的所以送 null
// 就算沒有要送 data 過去也要送 xhr.send() 空值過去跟他要資料
xhr.send();

readyState

  • 0: request not initialized
    • 已經產生一個 XMLHttpRequest 但是還沒有連接你要的資料
  • 1: server connection established
    • 用了 open 但資料還沒傳送過去
  • 2: request received
    • 偵測到用了 send
  • 3: processing request
    • loding 抓取中
  • 4: request finished and response is ready
    • 撈到資料了

readyState status from server

  • 200,OK(正常)
  • 301,Moved Permanently(永久性移動)
  • 304,Not Modified(未作修改)
  • 307,Temporary Redirect(暫時重新導向)
  • 401,Unauthorized(未授權)
  • 403,Forbidden(禁止存取)
  • 404,Not Found(找不到)
  • 500,Internal Server Error(內部伺服器錯誤

onload

  • 接資料
  • 是當有資料回傳時就自動觸發該函式,而 callback 是有辦法自訂的,兩者不太一樣 onload
  • 建立一個 XMLHttpRequest
  • 向對方 server 要資料
  • 回傳資料內容到自己的瀏覽器
  • 處理資料

表單驗證

  • 不論是傳送什麼樣的格式或形式(陣列、物件),在用 send 屬性傳送前都必須要先轉 string
  • 顯示在 web 上要轉成 object
  • 一般表單驗證在 HTML 要加 <form></form>
  • form 資料傳送到後端時就會需要使用
    • xhr.setRequestHeader(‘Content-type’,’application/x-www-form-urlencoded’);
  • JSON 格式
    • 透過 AJAX 傳送,不需要加 <form></form> 可以把按鈕格式換成 input
    • xhr.setRequestHeader(‘Content-type’,’application/json’)
  • get

title

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
// 要更後端討論傳到哪個頁面給他做驗證
<form action="xhr.html" method="get">
帳號:
<input type="text" name="person" id="">

密碼:
<input type="password" name="password" id="">
<input type="submit" value="送出">
</form>
/*----------------------------------
// 建立 xhr 物件
var xhr = new XMLHttpRequest()

// 向對方 server 拿資料
// 已經產生一個 XMLHttpRequest 但是還沒有連接你要的資料
xhr.open('get', 'https://hexschool.github.io/ajaxHomework/data.json', true)

// 回傳送資料內容 因為沒有要傳的所以送 null
// 撈到資料了
xhr.send()

// 處理資料
xhr.onload = function(){
// 判定狀態碼是正確的在撈取
if (xhr.readyState === 4 && xhr.status === 200){
// 轉為 js 值做操作
var str = JSON.parse(xhr.responseText)
document.querySelector('.message').textContent = str[0].name
}else{
console.log('錯誤')
}
}
console.log(xhr.responseText) // 王小明
  • post
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 建立 xhr 物件
var xhr = new XMLHttpRequest()

// 去對方 server 拿資料
// 已經產生一個 XMLHttpRequest 但是還沒有連接你要的資料
xhr.open(‘post', 'https://hexschool.github.io/ajaxHomework/data.json', true)

// post 一般表單傳送格式
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")

// 傳送資料內容
xhr.send("email=lsss@hexschool.com&password=12345678")

// 處理資料
xhr.onload = function(){
// 判定狀態碼是正確的在撈取資料
if (xhr.readyState === 4 && xhr.status === 200){
// 轉為 js 值做操作
var str = JSON.parse(xhr.responseText)
document.querySelector('.message').textContent = str
}else{
console.log('錯誤')
}
}
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
帳號:
<input type="email" name="person" class="mail" placeholder="在此輸入帳號">

密碼:
<input type="password" name="password" class="pwd" placeholder="在此輸入密碼">

<input type="button" value="註冊" class="send">
<input type="button" value="登入" class="sendIn">
<div class="message"></div>

//-------------------------------------------
// user 填寫確認可否註冊
// 點擊按鈕
var send = document.querySelector('.send')
send.addEventListener('click',singup,false)

// click 把user註冊物件傳送給 server
function singup(){
var emailStr = document.querySelector('.mail').value
var pwdStr = document.querySelector('.pwd').value
var accont = {}
accont.email = emailStr
accont.password = pwdStr

// 傳給 server 看帳號有沒有被註冊過
var xhr = new XMLHttpRequest()
xhr.open('post','https://hexschool-tutorial.herokuapp.com/api/signup', true)
xhr.setRequestHeader('Content-type', 'application/json’)

// 轉 string,server 只接受 string
var data = JSON.stringify(accont)

// Ajax 不能傳 function
xhr.send(data)

// 處理資料顯示(撈取資料) 轉為 object
xhr.onload = function(){
if (xhr.readyState === 4 && xhr.status === 200){
var call = JSON.parse(xhr.responseText)
var callStr = call.message
document.querySelector('.message').innerHTML = callStr
}
}
}

JSON.stringify(value [, replacer] [, space])

  • 將物件(或陣列,甚至是原始型別)序列化為一個 JSON 字串,然後將這個 JSON 字串傳送給 Server 端或 storage
  • value
    • 要轉換的 JavaScript 值,這個值通常是物件或陣列
  • replacer
    • 用來轉換結果的函式或陣列
    • 如果 replacer 是函式
      • 則 JSON.stringify 會呼叫函式,並傳入每個成員的索引鍵和值,並使用傳回值而不是原始值
      • 如果函式傳回 undefined,就表示已排除這個成員,根物件的索引鍵是空字串:””
    • 如果 replacer 是陣列
      • 則只會轉換陣列中有索引鍵值的成員
      • 成員的轉換順序與陣列中索引鍵的順序相同。 如果 value 引數也是陣列,則轉換時便會忽略 replacer 陣列
  • space
    • 加入縮排,空白字元和分行符號字元,讓傳回值 JSON 文字更容易閱讀
  • 存到 localStorage
    • localStorage.setItem(‘listdata’,JSON.stringify(data))
1
2
3
4
5
6
7
8
9
10
11
const someObj = {
a: 2,
b: function() {},
}

var d = JSON.stringify(someObj, function(key, value) {
if (key !== 'b') {
return value
}
});
console.log(d) // "{\"a\":2}"

JSON.parse(text [, reviver])

  • 將 JSON 字串剖析為 JavaScript 物件
  • text
    • 有效的 JSON 字串
  • reviver
    • 用來轉換結果的函式,呼叫這個函式時,會針對這個物件的每個成員進行呼叫
    • 如果成員包含巢狀物件,則會先轉換巢狀物件,然後再轉換父物件
    • 就個別成員而言,發生的情況如下:
      • 如果 reviver 傳回有效值,轉換後的值會取代成員值
      • 如果 reviver 的傳回值與它接收的值相同,則不會修改成員值
      • 如果 reviver 傳回 null 或 undefined,表示成員已刪除
  • 轉到 Web 顯示
    • JSON.parse(localStorage.getItem(‘listdata’))
1
2
3
var jsonStr = '{"name": "Summer"}';
var parsedObj = JSON.parse(jsonStr);
console.log(parsedObj.name); // Summer

jQuery

注意

語法

  • 解決的不只是簡化語法或解決不同瀏覽器之間問題而已
  • 擴充了原有 XHR 並加入類似 Promise 的設計
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var postData = { "param1" : "param1", "areaId" : 2, "deleteId" : 3 };

$.ajax({
url: API 網址,

dataType:"json", // 返回格式為 json

async:true, // 請求是否異步,默認為異步,這也是ajax重要特性

type:get, // 使⽤的⽅法(預設是get)

// { ⼀些要傳的資料... } 前台傳送data給後台
data:JSON.stringify(postData) or { "id": "value” },

beforeSend: function(request) { // 請求前的處理
request.setRequestHeader("Content-type","application/json");
request.setRequestHeader("Source","101");
request.setRequestHeader("Token","aaw--wssw-ss..."); },

error: function() { 載入錯誤...},

success: function(res){…..執行動作(e.g. 更新網⾴)}
從服務器返回的數據,根據 dataType 參數或 dataFilter 回調函數格式化(如果指定)
})

語法簡寫

  • 預設方法是 GET
  • then 預防 callback 地獄 (promise 概念)
1
$.ajax( API 網址).then(function(res){…..執行動作(e.g. 更新網⾴)})

Method

  • 常見方法對應場景
    • get 取得
    • post 新增
    • put 更新
    • delete 刪除
  • [GET] /post 取得所有⽂章
  • [GET] /post/:id 取得單篇⽂章
  • [POST] /post 儲存新⽂章
  • [PUT] /post/:id 更新單篇⽂章
  • [DELETE] /post/:id 更新單篇⽂章

非同步設定

  • 預設 async: false
1
2
3
4
5
6
7
$.ajax({
url: '',
async: false,
success: function() {
console.log(2);
}
});

.done()表示

  • 有 Deferred.done() 方法接受一個或多個參數,所有這些都參數可以是一個單一的函數或一個函數數組,當 deferred(延遲)解決時,依照添加的順序執行doneCallbacks
  • $.ajax()如果執行成功,則執行 .done(),funtion(e) 中的 e 為返回結果
  • $.ajax()執行發生錯誤,則 .done()不執行
1
2
3
4
5
6
function getArticleList(callback){
$.ajax("https://beta.json-generator.com/api/json/get/Ey8JqwIh")
.done(function(result){
callback(result);
});
}

Fetch

  • https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
  • fetch(input[, init])
    • 使用 fecth 的時候它會先回傳 Promise 給我們,再回傳第一個參數 readableStream,需要先把它解析成 json 來讀取,在 then 中 return 的內容會到下一個 then 的 callback 中可以使用
    • 第二個參數是選用的,可以傳送一個 init Object 來設定 request
  • 與 $.ajax 不同點
    • 默認情況下,fetch 不會從 server 發送或接收任何 cookie ,如果依賴於用戶 session,則會導致未經認證的請求(要發送 cookies,必須設置 credentials 選項)
    • then 作為下一步
    • catch 作為錯誤回應 (404, 500…)
    • 回傳的為 ReadableStream 物件,需要使用不同資料類型使用對應方法,才能正確取得資料物件
1
2
3
4
5
6
7
8
9
10
11
12
fetch('https://randomuser.me/api/', {}) 
.then((response) => {
// 這裡會得到一個 ReadableStream 的物件
console.log(response);

// 可以透過 blob(), json(), text() 轉成可用的資訊
return response.json();
}).then((jsonData) => {
console.log(jsonData);
}).catch((err) => {
console.log('錯誤:', err);
});
  • ReadableStream
    • Response 物件中的 body 屬性提供了一個 ReadableStream 的實體
    • ReadableStream 物件中可用以下對應的方法來取得資料(轉換資料)
      • arrayBuffer()
      • blob() — 物件
      • formData() — 上傳文件
      • json()
      • text() — 字串
  • URL 是新的 API,可以將 blob 物件轉為網址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// unsplash 上的圖片
let url = 'https://images.unsplash.com/photo-1513313778780-9ae4807465f0?auto=format&fit=crop&w=634&q=80'

fetch(url)
.then((response) => {
return response.blob();
})
.then((imageBlob) => {
let img = document.createElement('IMG')
document.querySelector('.newImg').appendChild(img);

// 將 blog 物件轉為 url
img.src = URL.createObjectURL(imageBlob);
})
  • 傳送 JSON 格式到 server
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
var url = 'https://example.com/profile';
var data = {username: 'example'};

fetch(url, {
method: 'POST',
body: JSON.stringify(data), // data can be `string` or {object}!
headers: new Headers({
'Content-Type': 'application/json’ // 傳送 json 格式
})
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

//-------------------------------
let url = 'https://hexschool-tutorial.herokuapp.com/api/signup';
fetch(url, {
method: 'POST',
// headers 加入 json 格式
headers: {
'Content-Type': 'application/json'
},
// body 將 json 轉字串送出
body: JSON.stringify({
email: 'lovef1232e@hexschool.com',
password: '12345678'
})
}).then((response) => {
return response.json();
}).then((jsonData) => {
console.log(jsonData);
}).catch((err) => {
console.log('錯誤:', err);
})

JSONP

使用時機

  • <script type="text/javascript">進行跨域請求(因為他沒有限制)

使用方式

  • 可以給整體數據添加一個變量名,在 <script> 中用參數傳遞(但這會有整體設計問題)
1
2
3
4
5
6
7
8
// hello.txt
str('Hi')

// html
<script src="hello.txt"></script>
<script type="text/javascript">
alert(str)
</script>
  • 動態創建 <script>
    • 通過函數調用,給函數傳遞參數的方式將整體數據傳遞過去
    • A 在資源加載之前定義一個後台定義的函數,在函數中接收一個參數,在函數中即是獲取數據後的操作
      • A 的 function 要跟 B 調用的名子一樣
    • B,需要時通過後台定義的函數加載對應的數據,當數據加載進來的後,就會調用前面定義好的函數,並且將數據當做上函數的參數傳入
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
// html
// 1.定義函數(得到數據後的操作)
// A
<script type="text/javascript">

function showNews(data){
let oUl = document.createElement('ul')
document.body.appendChild(oUl)
for(let i = 0; i < data.length; i++){
let oLi = document.createElement('li')
oLi.innerHTML = data[i]
oUl.appendChild(oLi)
}
}
</script>


// 2.撈取數據(將參數透過 function 傳遞過來)
// 通過 <script>, 撈取資料, 獲取數據
// B
<script type="text/javascript">

window.onload = function(){
document.onclick = function(){

// 獲取數據,添加 <script>
let oScript = document.createElement('script')
oScript.src = 'http://bluezyz.com/study/01-data.php'
document.head.appendChild(oScript)
}
}
</script>

JSONP 使用方式以環保署 api 為例

title

jQuery 封装 JSONP

  • jsonp: ‘callback’ 為要和伺服器端進行溝通,這個請求傳送到伺服器端,實際上這樣的
  • 伺服器端需要通過 callback 來取值也就是取後面的 jqueryxxxx 等自動生成的值,這個值實際上就是對應的我們傳送請求的 ajax 方法中的 success ,假設伺服器端如果返回
    • jqueryxxxx({“ret”:”ok”})
  • 頁面中會自動執行 success 方法,且將 {“ret”:”ok”} 傳給 success方法的引數 result
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
// 1.定義函數(得到數據後的操作)
<script type="text/javascript”>
function jsonPfunction() {
let oUl = document.createElement('ul')
document.body.appendChild(oUl)
for(let i = 0 ; i < jsonPdata.length; i++){
let animalia = jsonPdata[i]
let oLi = document.createElement('li')
oLi.innerHTML = `${animalia.ClassC}, ${animalia.CoaCType}, ${animalia.CommonNameC}, ${animalia.OrderC}`
oUl.appendChild(oLi)
}
}
</script>

// 2.撈取數據(將參數透過 function 傳遞過來)
<script type="text/javascript">
let jsonPdata = [ ] // 建立陣列傳輸資料
$(function(){
$('#btn').click(function(){
$.ajax({
async:true,
url: "http://opendata.epa.gov.tw/webapi/Data/Wildlife/?$skip=0&$top=1000&format=json",
type: "GET",
dataType: "jsonp", // 返回的數據類型,設置為 JSONP 方式
jsonp: 'callback', // 指定一個查詢參數名稱來覆蓋默認的 JSONP 回調參數名 callback
success: function (res) {
console.log("載入所有資料");
jsonPdata = res // 傳輸資料
jsonPfunction() // 自行設定傳出去的要使用的 function
},
error: function(){
console.log("載入失敗")
}
})
})
})

/*
* 哺乳綱, 珍貴稀有, 柯氏喙鯨, 鯨目
* 哺乳綱, 珍貴稀有, 麝香貓;小靈貓, 食肉目
* 哺乳綱, 瀕臨滅絕, 臺灣黑熊;黑熊, 食肉目
* 鳥綱, 其它應予保育, 臺灣藍鵲;紅嘴山鵲;長尾山娘, 雀形目
* 鳥綱, 瀕臨滅絕, 草鴞;東方草鴞, 鴞形目
*/

$.getJSON()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script type="text/javascript">

$.getJSON("http://opendata.epa.gov.tw/webapi/Data/Wildlife/?$skip=0&$top=1000&format=json&callback=?",
function (data) {
let oUl = document.createElement('ul')
document.body.appendChild(oUl)
for(let i = 0; i < data.length; i++){
let animalia = data[i]
let oLi = document.createElement('li')
oLi.innerHTML = `${animalia.ClassC}, ${animalia.CoaCType}, ${animalia.CommonNameC}, ${animalia.OrderC}`
oUl.appendChild(oLi)
}
console.log(data);
});
</script>
/*
* 哺乳綱, 珍貴稀有, 柯氏喙鯨, 鯨目
* 哺乳綱, 珍貴稀有, 麝香貓;小靈貓, 食肉目
* 哺乳綱, 瀕臨滅絕, 臺灣黑熊;黑熊, 食肉目
* 鳥綱, 其它應予保育, 臺灣藍鵲;紅嘴山鵲;長尾山娘, 雀形目
* 鳥綱, 瀕臨滅絕, 草鴞;東方草鴞, 鴞形目
*/
0%