--- 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); // 結果為? ``` ![](https://i.imgur.com/DYHyuHc.png) - 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中),等一般程式碼執行結束後,最後才執行 同步語言下的非同步事件與事件佇列: ![](https://i.imgur.com/MaSXnzL.png) <!-- - setTimeout 會被移到事件佇列,最後才執行 --> ### ex4 ```javascript= let data = ''; setTimeout(() => { data = '小明'; }, 0); console.log(data); ``` - A:"",空字串 - setTimeout()放在事件佇列(Event Queue中)時,與設定秒數無關,仍然會最後才被執行 ### Promise #### 巢狀 callback hell(波動拳) ![](https://i.imgur.com/w7hwCkU.png) - 在以前做非同步事件或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); }) } ``` ![](https://i.imgur.com/1OcJQEJ.png) <!-- - 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/) ### 一般用戶(前台)、管理者(後台) ![管理者](https://i.imgur.com/Btb4JXp.png) * 管理者(後台)API:網址中,有admin ![一般用戶](https://i.imgur.com/ZhAnj2B.png) * 一般用戶(前台)API:網址中,無admin ### API單、複數 ![課程API](https://i.imgur.com/QzMakqP.png) * 加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 )![](https://i.imgur.com/9DVMUFX.png) - 使用this後,render時,會各自找自已的 el ![](https://i.imgur.com/o1hupJp.png) - 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(); ``` #### 執行的過程示意圖,呈現一層一層地往上跳 ![](https://i.imgur.com/AbaAuha.png) ### 模擬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 ![](https://i.imgur.com/ou0JO2j.png) ##### 方法2,setTimeout使用箭頭函式 * this 指向 newObj1 ![](https://i.imgur.com/5j3AkFC.png) 看到箭頭函式時,這時候 this 會對應外層 callMethos 這個函式,**俗稱:箭頭函式沒有自已的 this**, 外層的作用域指向哪,它就指向哪 ![](https://i.imgur.com/7lCtP1s.png) - 初學:使用 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 ![](https://i.imgur.com/YLiAwAp.png) 把 forEach 裡的 function 改成 arrow function ![](https://i.imgur.com/Fnq8uC6.png) ⚡⚡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) ![](https://i.imgur.com/nibobYW.png) #### this 指向關係(跟著影片箭頭走一次) 問題8: obj.myfn()執行後,myfn()的this指向obj,觸發myfn()後,this.fn()的this同樣指向obj,答案為「小明」 問題9: setTimeout屬於simple call,通常this指向全域,全域有var宣告的myName變數,所以答案為「全域」 ![](https://i.imgur.com/oq5Qzfj.png) ## 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()生命週期 ![](https://i.imgur.com/zHumvUF.png) - mounted:元件建立時,會立刻執行它 - createApp:建立實體 - mount:指定一個位置,渲染上去 - 所有的實體都可以看成元件,元件分內層、外層 - methods 可以使用 this , call 到 data 裡的資料 #### mounted ```javascript= mounted() { console.log(this); } ``` #### this 指向 Proxy 物件 > Vue3 雙向綁定都是透過 Proxy 物件做運行 ![](https://i.imgur.com/mqU8uIl.png) - 打開Proxy 物件後,在Target中能看到: function(), data 都是 ...展開 的型式存放在內,**表示此處 this 可以互指** ![](https://i.imgur.com/Eig10Yo.png) ![](https://i.imgur.com/NvzGf4f.png) - 原先資料放在 data(),方法放在 methods(),此時資料、方法都在各自的物件內,Vue 實體化的過程中,會把這些物件展開到Target中,變成 Target 中的型式(如下圖) > 展開的情況下,資料可以互相指向 > ❌this.methods.fn(), ✔this.fn() ![](https://i.imgur.com/PyaQ8pX.png) - EX:用 this.text 取得data()中 text 的內容 ![](https://i.imgur.com/UYE8WGK.png) #### 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 ### 示意圖 ![](https://i.imgur.com/9Pzxwaj.png) - 必需上傳自已的圖片等內容 - 必需先把資料儲存到資料庫 - 前端 VUe - 前端沒辦法直接跟資料庫溝通 - 需要伺服器和資料庫溝通 - 溝通的行為包括增、刪、修、查 - 溝通的過程稱為 **api** - 請求,request - 伺服器接收到請求後,才會向資料庫要資料,資料庫才會把資料以回應的方式,回傳給前端 - 回應,response - 依據請求,給予不同的回應 - 請求成功或失敗有不同的回應 - get,查 - post,增 - put,修 - delete,刪 ### 申請 Api ![](https://i.imgur.com/AL4v2qy.png) - [課程 Api 申請](https://vue3-course-api.hexschool.io/) - 申請 Api 路徑 - 路徑不符合,無法新增資料 - 測試 Api - 新增 - 取得 - 每個人的 Api 都是獨立的 ### 用戶分為2大類 ![](https://i.imgur.com/wuUCFMQ.png) - 管理控制台 - 帳密先傳給伺服器 - 伺服器檢查帳密是否正確 - 正確的話,產生一組 token(像鑰匙) - 下一次前端不用再給密碼,給 token 就好 - 伺服器確定是本人後,放行管理控制台的操作 - 失敗結果可用 console.dir()查看![](https://i.imgur.com/SHncW9a.png) - 客戶購物 ### 使用 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 ![](https://i.imgur.com/7Ww0WtT.png) - 圖片檔案不會直接上傳 #### 圖片上傳 api ![](https://i.imgur.com/4bH1ega.png) - 上傳圖片,大都用表單的形式做 :::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 ![](https://i.imgur.com/lCWx83h.png) ::: - 上傳範例 :::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 看![](https://i.imgur.com/4SfbByI.png) - 把檔案取出來 ![](https://i.imgur.com/Yf8eRE8.png) ![](https://i.imgur.com/Ch6E2MB.png) - 觀察 api 對應欄位 ![](https://i.imgur.com/u81KVug.png) ![](https://i.imgur.com/YtIjCn1.png) - 未登入進行上傳 ![](https://i.imgur.com/cX7Jk3X.png) - 登入後成功上傳![](https://i.imgur.com/cAygFiN.png) ## 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)