---
title: 第二堂:非同步與 This
tags: 2022 Vue 作品實戰班, 六角學院
date: 20220114
image:
---
# 第二堂:非同步與 This
## 課前閒聊
### 作品
#### 素材選擇(注意授權)
* [六角作品牆](https://https://works.hexschool.io/#/)
* [unsplash](https://unsplash.com/)
#### 主題&課程API說明
* 本周開始想主題,有方向後查找[unsplash](https://unsplash.com/),登入[unsplash](https://unsplash.com/)網站後可分將圖片分類
* 同一作者的作品風格較接近
* 電商網站產品圖至少20+,一個產品可能會有多張圖片
* 課程提供API能被突破限制(ex:node.js轉接,類似多加欄位,並無儲存資料)
* 課程API為NoSQL,除必要欄位外,其餘可客製化
#### 常見主題
* 咖啡、旅遊、服飾、餐飲
#### 特殊主題(可挑戰,但請注意素材來源授權)
* 音樂、求職網、遊戲、電影
## Axios / 非同步 與 API 說明
### 同步
特性 1:依序執行
特性 2:一個完成,才執行下一個,所以當某一行程式碼噴錯,後方程式碼就完全不能動了
### 非同步
特性:一次可以做很多事情
### ex1 同步語言
JS 是同步語言。
```javascript=
let data = '';
function updateData() {
data = '小明';
}
updateData()
console.log(data); // 結果為?
```

- A:小明
- 先執行13行
在14行宣告函式(還沒執行)
在17行呼叫函式並行它,進入執行堆疊,進入執行堆疊後,把 "小明" 附在 data 上面
在18行看到的 data 是 "小明"
- 同步語言特點:中間有出錯,後面就不會動
- **Javascript 是同步語言**
### ex2 立即函式
```javascript=
let data = '';
(function() {
data = '小明';
})();
console.log(data);
```
- 立即函式:立即函式會即刻被執行
- A:小明,ex2 本質上跟 ex1 的函式一樣
### ex3 setTimeout()
```javascript=
let data = '';
setTimeout(() => {
data = '小明';
}, 10);
console.log(data);
```
<!-- - setTimeout 延遲了10秒 -->
- A:"",空字串
- setTimeout()為非同步事件,屬於callback function
- 同步語言會依序執行,非同步事件無法依序執行,當非同步事件執行時,會先放在事件佇列(Event Queue中),等一般程式碼執行結束後,最後才執行
同步語言下的非同步事件與事件佇列:

<!-- - setTimeout 會被移到事件佇列,最後才執行 -->
### ex4
```javascript=
let data = '';
setTimeout(() => {
data = '小明';
}, 0);
console.log(data);
```
- A:"",空字串
- setTimeout()放在事件佇列(Event Queue中)時,與設定秒數無關,仍然會最後才被執行
### Promise
#### 巢狀 callback hell(波動拳)

- 在以前做非同步事件或call api,會用巢狀式 callback function 結構,一直往裡面加 callback function
- Promise 結構,解決巢狀結構不好閱讀的問題
#### 先瞭解如何呼叫 Promise
```javascript=
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (status) {
resolve('promiseSetTimeout 成功')
} else {
reject('promiseSetTimeout 失敗')
}
}, 10);
})
}
```

<!-- - new Promise
- 第1個參數回傳成功的結果
- 第2個參數回傳失敗的結果 -->
- Promise結構:
定義 promiseSetTimeout ,並return 回傳 new Promise的方法。
new Promise的方法中,成功回傳resolve的結果,失敗回傳reject的結果,**resolve、 reject ,同時只會得到一個結果**
- Promise,通常用於接外部非同步資料
- 每次使用Promise時,都要加上.then、.catch,確保接收到成功或失敗資訊
- [JavaScript Promise 全介紹](https://wcc723.github.io/development/2020/02/16/all-new-promise/)
#### 成功案例,用1表示true
- 只要看到函式庫是 Promise Base 開發,都可以這樣接
ex:課程使用axios
本處省略.catch
``` javascript
promiseSetTimeout(1)
.then(function(res){
console.log(res);
})
```
#### 失敗,用0表示false
- 本處省略.then
``` javascript
promiseSetTimeout(0)
.catch(function(err){
console.log(err);
})
```
#### 正常案例.then、.catch
``` javascript
promiseSetTimeout(0)
.then(function(res){
console.log(res);
})
.catch(function(err){
console.log(err);
})
```
#### 依序鏈結(多個promise串接)
- 多個promise串接時,每組.then會與.catch互相對應,第一個.then會對應第一個.catch,以此類推
因.then與.catch,同時只會得到一個結果,如果回傳為.catch,則對應的.then會被**跳過**
- 原生Web API語法fetch,也會利用.then跳至下一段
``` javascript
promiseSetTimeout(0)
.then(function(res){
console.log(res);
return promiseSetTimeout(1)
})
.then(function(res){
console.log(res);
return promiseSetTimeout(1)
})
.catch(function(err){
console.log(err);
})
.catch(function(err){
console.log(err);
})
```
## AXIOS
> 是一種promise-based
> promise是JS語法,axios基於promise而開發的套件
* [randomuser](https://randomuser.me/)
* [codpen練習](https://codepen.io/ldddl/pen/ExwrBJb)
### AXIOS 鏈接
``` javascript
console.clear();
axios.get('https://randomuser.me/api/')
.then((res) => {
// console.log(res);
const { seed } = res.data.info; // 解構
console.log(seed);
return axios.get(`https://randomuser.me/api/?seed=${seed}`)
})
.then((res) => {
console.log(res);
})
```
### AXIOS 示範錯誤連接
#### console.dir
> 無法在一般 console.log 查伺服器回傳的物件格式時,可用 console.dir
> console.dir 會讓從 server 回傳的錯誤訊息變成可以展開的物件
``` javascript
console.clear();
axios.get('https://randomuser.me/api/')
.then((res) => {
// console.log(res);
const { seed } = res.data.info; // 解構
console.log(seed);
return axios.get(`https://randomuser.me/api/?seed=${seed}`)
})
.then((res) => {
console.log(res);
})
```
## 課程API
[課程API](https://hexschool.github.io/vue3-courses-swaggerDoc/)
### 一般用戶(前台)、管理者(後台)

* 管理者(後台)API:網址中,有admin

* 一般用戶(前台)API:網址中,無admin
### API單、複數

* 加s表示複數 → (orders)
* 無加s表示單數,使用時會多帶入特定id → (order/{id})
## This
參考 : [鐵人賽:JavaScript 的 this 到底是誰?](https://wcc723.github.io/javascript/2017/12/12/javascript-this/)
### 關於一個函數中可以運用到的 “參數”
```javascript=
// 關於一個函數中可以運用到的 “參數”
var a = '全域'
function fn(params) {
console.log(params, this, window, arguments);
debugger;
}
fn('參數');
```
- debugger:除錯模式
- params:外部傳入參數
- this:目前是 window
- window:在 Global 裡面
- arguments:特殊參數,取出 params 或是 params 以外的參數,類陣列
> arguments,過去常用,現在少用,不是正式的結構是類陣列,類陣列提供較少的方法,使用上較不方便
### 在不同調用下的this變化
* 此處this指向obj
```javascript=
// 在不同的調用下的變化
var obj = {
name: '小明',
fn: function(params) {
console.log(params, this, window, arguments);
debugger;
}
}
obj.fn('參數');
```
- this 改變成當前的 object(obj)
### 為什麼會用到 this
#### 渲染2張卡片範例
```htmlmixed=
<div class="container">
<div id="app"></div>
<div id="app2"></div>
</div>
```
```javascript=
const Card = {
el: '#app',
template: `<div class="card">
<div class="card-body">
This is some text within a card body.
</div>
</div>`,
render() {
const dom = document.querySelector(Card.el);
dom.innerHTML = Card.template;
}
};
const Card2={
...Card,
}
console.log(Card.el, Card2.el);
Card2.el = '#app2';
Card.render();
Card2.render();
```
- **渲染位置**:Card.el
**說明**:使用展開運算子,造成function render 裡的 ```Card``` 無法替換,Card 和 Card2 的 el 都會指向 #app(Card.el),最後只渲染一個card
**解決作法**:Card.el 換成 this.el,改為指向各自的 el,就可渲染出兩個card
- Vue 中會以元件化開發,類似此卡片範例
- **使用this,可提高程式碼的重複可用性**
#### 卡片範例示意圖
- render 時,Card2 中的 render會指向 Card1 中的 el ( 指向同一 el )
- 使用this後,render時,會各自找自已的 el 
- el:渲染的位置
- 代名詞的概念,this 就是我
#### 中樂透的特徵 - 不是Ray是財富自由Ray
- 吃都吃最好的,點最貴的
- 拿兩支手機
- 吃布丁都不舔封膜
- 𦾧條加大後不吃完,不拿糖醋醬
#### this 如何指向
```javascript=
// This 的要點
var someone = '全域變數'; // 請注意,這裡用的是 var
function callSomeone() {
console.log(this.someone);
}
callSomeone();
```
- 範例用 var 宣告全域變數
- 目前的 this 指向全域
#### this 的指向關鍵在「如何呼叫它」
> 呼叫函式的前一個“物件”,this會指向該物件
> (老師:也可以這樣理解)誰呼叫包含this的函式,this就是誰
```javascript=
function callSomeone() {
console.log(this.someone);
}
callSomeone();
// This 指向關鍵在如何呼叫它,請注意呼叫函式的前一個 “物件”
var obj = {
someone: '物件',
callSomeone() {
console.log(this.someone); // A:物件
}
}
obj.callSomeone();
var obj2 = {
someone: '物件2',
callSomeone
}
obj2.callSomeone(); // A:物件2
```
- 調用 this 的程式碼,往前看,物件指向哪裡,this 就指向哪裡
- 不要看 this this 怎麼定義的,看它是怎麼被呼叫的
#### 無論幾層,都是看調用的前一個 “物件”
```javascript=
// 無論幾層,都是看調用的前一個 “物件”
function callSomeone() {
console.log(this.someone);
}
callSomeone();
var wrapObj = {
someone: '外層物件',
callSomeone,
innerObj: {
someone: '內層物件',
callSomeone,
}
}
wrapObj.callSomeone(); // A:外層物件
wrapObj.innerObj.callSomeone(); // A:內層物件
```
### this 的目的
<!-- javascript=
var newObj1 = {
someone: '物件',
callSomeone() {
console.log('callSomeone:', this.someone);
},
callMethods() {
// 請問如何在此觸發 callSomeone
this.callSomeone(); //答案
}
}
newObj1.callMethods();
- 上面這段 code 和 Vue 運作的情況一模一樣 -->
#### this 的目的是為了可以調用物件內的其它方法
> 此段 code 和 Vue 運作的情況一模一樣
```javascript=
var newObj1 = {
someone: '物件',
callSomeone() {
console.log('callSomeone:', this.someone);
},
callMethods() {
// 請問如何在此觸發 callSomeone
this.callSomeone(); //答案
}
}
newObj1.callMethods();
```
#### 執行的過程示意圖,呈現一層一層地往上跳 
### 模擬Vue中的this運作
#### 如何取得 data 的值
```javascript=
const newObj1 = {
data: '物件',
render() {
// #2 如何取得 data 的值
console.log(this.data); // ans
},
init() {
// #1 請問如何在此觸發 render()
this.render(); // ans
}
}
newObj1.init(); //⚡⚡ this 指向 newObj1
```
#### 如何取得物件中元素的值
```html=
<div class="container">
<div id="app"></div>
</div>
```
```javascript=
const Card = {
el: '#app',
template: `<div class="alert alert-primary" role="alert">
A simple primary alert—check it out!
</div>`,
render() {
// 如何取得本物件 el
const dom = document.querySelector('this.el'); // ans
// 如何取得本物件 template
dom.innerHTML = 'this.template'; // ans
}
};
Card.render(); // ⚡⚡this 指向 Card
```
#### 在 Vue 裡面,this 會像上面這2個例子使用
- 因箭頭函式和傳統函式的this指向不一樣,定義Vue的函式時,會使用傳統函式
### 箭頭函式
```html=
<div class="container">
<div id="app"></div>
</div>
```
```javascript=
var newObj1 = {
someone: '物件',
callSomeone() {
console.log('callSomeone:', this.someone);
},
callMethods() {
// 說明此段問題及修正
// 修正方法 1. vm
// 修正方法 2. arrow function
// setTimeout(function () {
// this.callSomeone();
// }, 100);
}
}
newObj1.callMethods();
```
#### simple call
- **simple call 的 this 大部分指向 window**
- callback function 屬於 simple call
##### setTimeout
- 屬於callback function
- 在 setTimeout 事件中被執行時,看不出實際上this在哪裡被調用,這時候的 this 會特別危險
##### 補充this
- this都是在函式中生成,相同函式中的this都會指向同一個
#### 修正this指向
##### 方法1,先把 this 指定到變數 vm

##### 方法2,setTimeout使用箭頭函式
* this 指向 newObj1

看到箭頭函式時,這時候 this 會對應外層 callMethos 這個函式,**俗稱:箭頭函式沒有自已的 this**, 外層的作用域指向哪,它就指向哪

- 初學:使用 vm
- 想精進,多觀察,助教推薦,用箭頭函式
- 可以試著這樣聯想: this 在 arrow function 中,是指向父層的 funciton,看外層被哪個作用域調用
- 再加一層 setTimeout ,結果一樣
### 如何操作物件中的陣列(⚡⚡實戰常見)
```htmlmixed=
<div class="container">
<div id="app"></div>
</div>
```
```javascript=
const newObj1 = {
data: [],
array: [1, 2, 3, 4, 5],
callMethod() {
// 請將所有數值 * 2,並儲存在 data 內
this.array.forEach(function(item) {
this.data.push(item);
});
console.log(this.data);
}
}
newObj1.callMethod();
```
報錯,找不到 data 做 push

把 forEach 裡的 function 改成 arrow function

⚡⚡simple call 在實戰中不會用到
### 陷阱題
#### var & const
> const定義的變數,不會在window下
- [jsfiddle](https://jsfiddle.net/ycst/Lafh2nbm/28/)
- [const 的作用域](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/const)

#### this 指向關係(跟著影片箭頭走一次)
問題8:
obj.myfn()執行後,myfn()的this指向obj,觸發myfn()後,this.fn()的this同樣指向obj,答案為「小明」
問題9:
setTimeout屬於simple call,通常this指向全域,全域有var宣告的myName變數,所以答案為「全域」

## This 與 Vue 的關係
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in people">
{{ item.email }}
</li>
</ul>
<button type="button" v-on:click="getRandomUserData">取得遠端資料</button>
</div>
<script>
// #1 說明 this 與 Vue 的關係
// #2 v-on 如何觸發方法
// #3 介紹生命週期如何觸發方法
Vue.createApp({
// 資料集合
data() {
return {
people: [],
}
},
// 方法集
methods: {
getRandomUserData() {
axios.get('https://randomuser.me/api/')
.then((res) => {
console.log(res);
this.people = res.data.results
}).catch((err) => {
console.log(err.response);
});
}
},
// 生命週期
created() {
this.getRandomUserData();
}
}).mount('#app');
</script>
</body>
</html>
```
### this 與 Vue 的關係
- Vue 裡的所有集合,都是物件型式,除data()資料集合外
- data()資料集合,使用 function return 目的:
- 為了 this 綁定正確
- 可複用性
- 直接被執行的,用函式的型式,例如methods()生命週期

- mounted:元件建立時,會立刻執行它
- createApp:建立實體
- mount:指定一個位置,渲染上去
- 所有的實體都可以看成元件,元件分內層、外層
- methods 可以使用 this , call 到 data 裡的資料
#### mounted
```javascript=
mounted() {
console.log(this);
}
```
#### this 指向 Proxy 物件
> Vue3 雙向綁定都是透過 Proxy 物件做運行

- 打開Proxy 物件後,在Target中能看到:
function(), data 都是 ...展開 的型式存放在內,**表示此處 this 可以互指**


- 原先資料放在 data(),方法放在 methods(),此時資料、方法都在各自的物件內,Vue 實體化的過程中,會把這些物件展開到Target中,變成 Target 中的型式(如下圖)
> 展開的情況下,資料可以互相指向
> ❌this.methods.fn(), ✔this.fn()

- EX:用 this.text 取得data()中 text 的內容

#### methods方法集的觸發方式
1. 從mounted()生命週期中觸發
2. 從畫面上方法來觸發,ex:`v-on:click`
- 使用 button,記得加 type="button"
- v-on:click="getData"
##### 1.從mounted()生命週期中觸發,並取得遠端資料,存到data資料集
> [codepen參考](https://codepen.io/ldddl/pen/poWBJrK)
- Data() 裡,新增一個屬性名稱people,值為空陣列[]
- methods(),新增getData()方法,建立 axios.get(),取得遠端資料,並將遠端資料,利用 this.people,賦予到people中
- mounted(),呼叫 this.getData()
##### 2.從畫面上來觸發方法(v-on)
> [codepen參考](https://codepen.io/ldddl/pen/wvrZGEP)
* HTML上加上button,並用Vue元件v-on綁定methods方法名稱
> @ 為 v-on 縮寫
> getData 為 methods方法名稱
```
<button type="button" @click="getData">按我</button>
```
## 隨堂小考
- [第一系列](https://bejewled-air-4cb.notion.site/639c3dd5d18b4b54828f0792cae887d1)
- [第二系列](https://bejewled-air-4cb.notion.site/fe69c43e07a94b24bf5e26a6733561b2)
- [第三系列](https://bejewled-air-4cb.notion.site/0aa9e0be66d649b0a6443a2ccfe55be6)
## API 概念
###### tags: hex api
### 示意圖

- 必需上傳自已的圖片等內容
- 必需先把資料儲存到資料庫
- 前端 VUe
- 前端沒辦法直接跟資料庫溝通
- 需要伺服器和資料庫溝通
- 溝通的行為包括增、刪、修、查
- 溝通的過程稱為 **api**
- 請求,request
- 伺服器接收到請求後,才會向資料庫要資料,資料庫才會把資料以回應的方式,回傳給前端
- 回應,response
- 依據請求,給予不同的回應
- 請求成功或失敗有不同的回應
- get,查
- post,增
- put,修
- delete,刪
### 申請 Api

- [課程 Api 申請](https://vue3-course-api.hexschool.io/)
- 申請 Api 路徑
- 路徑不符合,無法新增資料
- 測試 Api
- 新增
- 取得
- 每個人的 Api 都是獨立的
### 用戶分為2大類

- 管理控制台
- 帳密先傳給伺服器
- 伺服器檢查帳密是否正確
- 正確的話,產生一組 token(像鑰匙)
- 下一次前端不用再給密碼,給 token 就好
- 伺服器確定是本人後,放行管理控制台的操作
- 失敗結果可用 console.dir()查看
- 客戶購物
### 使用 cookie
#### 儲存 token
- [MDN cookie](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie)
```javascript=
if (document.cookie.replace(/(?:(?:^|.*;\s*)someCookieName\s*\=\s*([^;]*).*$)|^.*$/, "$1") !== "true") {
alert("Do something here!");
document.cookie = "someCookieName=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/";
}
}
```
```javascript=
document.cookie = `hexToken=${ token }; expires=${ new Date(expired) };`;
```
#### 使用 token
把 test2 換成儲存的 token 名稱
```javascript=
const token = document.cookie.replace(/(?:(?:^|.*;\s*)test2\s*\=\s*([^;]*).*$)|^.*$/, "$1");
```
```javascript=
const token = document.cookie.replace(/(?:(?:^|.*;\s*)hexToken\s*\=\s*([^;]*).*$)|^.*$/, "$1");
```
#### axios 加入 hearder
- [axios](https://github.com/axios/axios)
- `axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;`
## 檔案上傳
#### 產品上傳 api

- 圖片檔案不會直接上傳
#### 圖片上傳 api

- 上傳圖片,大都用表單的形式做
:::spoiler form 格式
```javascript=
<form action="/api/thisismycourse2/admin/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file-to-upload">
<input type="submit" value="Upload">
</form>
```
:::
- 因為表單有 type="file" 的格式,`input type="file"`
- input 內容
:::spoiler input

:::
- 上傳範例
:::spoiler upload
```javascript=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
</head>
<body>
<div>
</div>
<div>
<input
type="file"
class="form-control"
id="file"
placeholder="請輸入圖片連結"
/>
</div>
<script>
const url = ''; // 請加入站點
const path = ''; // 請加入個人 API Path
const fileInput = document.querySelector('#file');
fileInput.addEventListener('change', upload);
function upload() {
// #1 撰寫上傳的 API 事件
}
</script>
</body>
</html>
```
:::
- 上傳後從 console 看
- 把檔案取出來


- 觀察 api 對應欄位


- 未登入進行上傳 
- 登入後成功上傳
## ref
- [串接 api code 參考](https://github.com/stevecyj/vue-2022-api)
- [Document.cookie](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie)
## 其他
* 六角API使用的服務:[lambda serverless](https://aws.amazon.com/tw/lambda/)
* 用途:伺服器流量過大時,Server的處理機制會自動加開伺服器來分散流量
(老師應該是這意思!?)
* [Day1 - Serverless 介紹](https://ithelp.ithome.com.tw/articles/10213431)