# [FE102] 前端必備:JavaScript(筆記) ###### tags: `Lidemy學院` ` [FE102] 前端必備:JavaScript` # 課程簡介 ## 課程簡介 基本上在網頁上頁JavaScript時,所關注的面向,有3個 : 1. 介面 : 如何改變 如何使用 JavaScript 去改變,用 html 跟 css ,所寫出來的 UI,例如改變元素,新增元素。 2. 事件 : 如何監聽事件並做出反應 比如,按一個按紐去新增元素,或是按按鈕改變背景顏色,如果會介面跟事件,能做出7-8成的東西。 3. 資料 : 如何跟伺服器交換資料 或是如何讓資料再瀏覽器保存下來。 > 基本上如果會以上3點,能寫出任何的程式,網頁的部分,不外乎就這3點。 ___ # JavaScript 與瀏覽器的溝通 ## 執行 JavaScript 的一百種方式 在執行 JavaScript 前,要思考 JavaScript 能在哪裡執行,重點是==在哪裡執行 ?== 第一種方式 : `<script>`放在任何地方都可以,但通常放在`</body>`前, ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <script> alert('hello') </script> </head> <body></body> </html> ``` 第二種方式 : 將 JS 檔,寫成一個檔案,在引入html檔中 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <script src="./index.js"></script> </head> <body></body> </html> ``` ``` //index.js alert('hello') ``` 探討 : 在 Node.js 與 瀏覽器跑 JS 的差別 ? 1. 在跑 JS 時,都是一樣的,在瀏覽器中, Node.js 有些語法並不支援,Node.js 與 瀏覽器跑,是兩個不同的執行環境,所以可能這個支援,那個不支援,所以有些用法在 Node.js 中是可以的,在瀏覽器中不行的,反過來也是一樣 ___ ## DOM 是什麼? [文件物件模型 (DOM)](https://developer.mozilla.org/zh-TW/docs/Web/API/Document_Object_Model),(Document Object Model, DOM),簡單的解釋是將 Document 轉換成 Object,DOM 是指在 html 的那些內容,可以想像把 html 文件的這結構,變成很多節點,並有階層關係,可以把標籤轉換成下圖,轉換成下圖其實感覺就跟物件差不多,所以 JS 怎麼改變介面,就是透過 DOM ,會透過 JS 去拿到 DOM ,意思就是可以拿到這個節點(元素),並去做改變,儘管你寫的是 JS ,瀏覽器會幫助你去改變瀏覽器上的東西。 ==DOM 是什麼? 瀏覽器提供這個橋梁(DOM) ,讓 JS 能改變畫面的東西。== ![](https://i.imgur.com/pTT13LC.png) ___ ## 如何選到想要的元素:getElement `document` 是瀏覽器提供的物件,有許多function,可以使用。 HTMLCollection : 用起來像是陣列,但並不是陣列,算是特殊形態的陣列,使用起來並沒有甚麼差。 NodeList : 跟 HTMLCollection 意思差不多,像是陣列,但並不是陣列。 在使用上,`querySelector`跟`querySelectorAll`,較常使用。 1. `getElementsByTagName` : 取標籤 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div> hello~~ </div> <div> yoyoyo~~ </div> <script> const elements = document.getElementsByTagName('div') console.log(elements) console.log(elements[0]) </script> </body> </html> ``` 2. `getElementsByClassName` : 取Class ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div class="block"> hello~~ </div> <div class="block"> yoyoyo~~ </div> <script> const elements = document.getElementsByClassName('block') console.log(elements) console.log(elements[0]) </script> </body> </html> ``` 3. `getElementById` : 取Id,在使用上,會直接找到元素,Id在使用上是唯一的。 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> hello~~ </div> <div> yoyoyo~~ </div> <script> const elements = document.getElementById('block') console.log(elements) </script> </body> </html> ``` 4. `querySelector` : 取css的選擇器,只會回傳第一個取到的元素 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> hello~~ </div> <div> yoyoyo~~ </div> <script> const elements = document.querySelector('div') console.log(elements) </script> </body> </html> ``` 5. `querySelectorAll` : 取css的選擇器,會回傳所有符合的元素 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> hello~~ </div> <div> yoyoyo~~ </div> <script> const elements = document.querySelectorAll('div') console.log(elements) </script> </body> </html> ``` ___ ## 改變元素的 CSS 1. 所選元素`.style`.屬性 = '值' : 可以去改變元素,通常不太會如此使用,會先寫好class,在去改變。 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> hello~~ </div> <script> const element = document.querySelector('#block') element.style.background = 'red' element.style.padding = '10px' element.style['padding-top'] = '30px' element.style.paddingBottom = '40px' </script> </body> </html> ``` ___ ## 改變元素的 Class 在改變元素中通常是,改變元素的 Class,在用 Class 去負責不同的 style 。 1. 新增class : 所選元素.classList.add('className') 2. 移除class : 所選元素.classList.remove('className') 3. 開關class,有此class,會移除,無此class,會新增 : 所選元素.classList.toggle('className'),適合使用在許多地方 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .action { background-color: red; } </style> </head> <body> <div id="block" class="main box1"> hello~~ </div> <script> const element = document.querySelector('#block') element.classList.add('action') element.classList.remove('main') element.classList.toggle('box') element.classList.toggle('box1') </script> </body> </html>ement.classList.remove('main') </script> </body> </html> ``` ___ ## 改變內容:inner、outer 的 HTML 與 text 1. `innerText` : 改變內容文字,只會抓到文字 2. `innerHTML` : 將標籤中的內容都抓出來 3. `outerHTML` : 將標籤自己與其內容都抓出來 常用 : `innerText`,改變內容文字。 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <a>hello~~</a> </div> <script> const element = document.querySelector('#block > a') console.log(element.innerText) element.innerText = '!!!' console.log(element.innerHTML) console.log(element.outerHTML) element.outerHTML = '<h1>h1</h1>' </script> </body> </html> ``` ___ ## 插入與刪除元素:appendChild 與 removeChild 在操作插入與刪除元素時,需要知道父層是誰。 1. 刪除元素 : `removeChild` 2. 插入元素 : `appendChild` 3. 新增元素 : `createElement` 4. 新增文字 : `createTextNode` ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <a>hello~~</a> </div> <script> const element = document.querySelector('#block') element.removeChild(document.querySelector('a')) const item = document.createElement('div') item.innerText = '123' element.appendChild(item) const item2 = document.createTextNode('Text') element.appendChild(item2) </script> </body> </html> ``` ___ # JavaScript 網頁事件處理 ## eventListener 與 callback function ### event Listener (事件監聽) 在這 element 上,新增(add)一個 event Listener (事件監聽),叫做 click ,重點是按完後要做甚麼事情,就是 onClick 這 function,所以當 click 事件發生時,瀏覽器會去觸發 onClick 這 function ,這 function 叫做 callback function (回呼函式) ,所以在這 function 中,可以做任何想做的事,通常都是以比較偷懶的方式去寫,addEventListener( ),第 2 參數,要放 function ,所以可以直接寫 function 在裡面,這時這 function 是沒有名稱的,所以叫做匿名函式,它跟 callback function 一點關係都沒有,叫匿名函式只是沒有名字而已,任何沒有名字 function ,都叫匿名函式,有許多事件可以做監聽,需要可以去 google。 ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <a>hello~~</a> </div> <script> const element = document.querySelector('#block') element.addEventListener('click', onClick) function onClick() { alert('click!') } </script> </body> </html> ``` ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <a>hello~~</a> </div> <script> const element = document.querySelector('#block') element.addEventListener('click', function () { alert('click!') }) </script> </body> </html> ``` ___ ## 詳細講解 callback function ### callback function : 當有觸發事情,才會去做事的 function 大約可以叫做 callback function,傳入的參數是函式,就可以叫做 callback function。 callback function 的意思其實就是:「當某事發生的時候,請利用這個 function 通知我」 在使用 callback function 時,有一個初學者很常犯的錯誤一定要特別注意。都說了傳進去的參數是 callback function,是一個「function」,不是 function 執行後的結果(除非你的 function 執行完會回傳 function,這就另當別論)。 [JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.techbridge.cc/2019/10/05/javascript-async-sync-and-callback/) ___ ## event(e) 是什麼碗糕? 在上述範例中,其實在 eventListener 中,可以拿到許多資訊,瀏覽器在呼叫你的 function 時,會傳遞進來一個參數,通常會叫 event ,或最多的簡寫是 e ,名稱是可以隨便取的,只是一個參數而已。 開啟瀏覽器去點擊, 可以開 console.log ,出來看到點了哪裡,這 e 是一個 MouseEvent,可以知道看到 x 跟 y ,比較重要 的是 tarage 是指點到的元素, ``` //index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <a>hello~~</a> </div> <script> const element = document.querySelector('#block') element.addEventListener('click', function (e) { console.log(e) }) </script> </body> </html> ``` ![](https://i.imgur.com/HS47iGA.png) 可以用 e.tarage,知道點到的元素 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <a>hello~~</a> </div> <script> const element = document.querySelector('#block') element.addEventListener('click', function (e) { console.log(e.target) }) </script> </body> </html> ``` ![](https://i.imgur.com/a0S3VQX.png) 所以在 e 中,可以取到許多資訊,e 是瀏覽器給的資訊,可以利用此資訊,取的跟事件相關的東西。 以下為 input 做示範 ,keydown: ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> </head> <body> <div id="block"> yoyo~~ <input type="text" name="" id="" /> <a>hello~~</a> </div> <script> const element = document.querySelector('input') element.addEventListener('keydown', function (e) { console.log(e.key) }) </script> </body> </html> ``` ![](https://i.imgur.com/sD2tdo8.png) 示範 : 按按鈕切換背景顏色的功能 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .active { background: red; } </style> </head> <body> <div id="block"> yoyo~~ <input type="text" name="" id="" /> <a>hello~~</a> <button class="change-btn">change</button> </div> <script> const element = document.querySelector('.change-btn') element.addEventListener('click', function (e) { document.querySelector('body').classList.toggle('active') }) </script> </body> </html> ``` ___ ## 表單事件處理 onSubmit 範例 : ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .active { background: red; } </style> </head> <body> <form class="login-form"> <div> username: <input name="username" /> </div> <div> password: <input name="password" type="password" /> </div> <div> password again: <input name="password2" type="password" /> </div> <input type="submit" /> </form> <script> const element = document.querySelector('.login-form') element.addEventListener('submit', function (e) { alert('submit!!') }) </script> </body> </html> ``` preventDefault() : 瀏覽器提供的 function ,可以取消事件預設行為 ,Submit 預設行為就是送出表單。 可以使用在哪裡,判斷 password 跟 password again ,是否相同 : ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .active { background: red; } </style> </head> <body> <form class="login-form"> <div> username: <input name="username" /> </div> <div> password: <input name="password" type="password" /> </div> <div> password again: <input name="password2" type="password" /> </div> <input type="submit" /> </form> <script> const element = document.querySelector('.login-form') element.addEventListener('submit', function (e) { const input1 = document.querySelector('input[name = password]') const input2 = document.querySelector('input[name = password2]') if (input1.value !== input2.value) { alert('密碼不同') e.preventDefault() } }) </script> </body> </html> ``` ___ ## 阻止預設行為:preventDefault e.preventDefault : 阻止瀏覽器預設行為,其實除了表單送出外,還可以用在其他地方,以下舉例 : [[筆記][JavaScript]所謂的「停止事件」到底是怎麼一回事?](https://ithelp.ithome.com.tw/articles/10198999) `<a>` 超連結 : 取消連到某個地方 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .active { background: red; } </style> </head> <body> <form class="login-form"> <div> username: <input name="username" /> </div> <div> password: <input name="password" type="password" /> </div> <div> password again: <input name="password2" type="password" /> </div> <input type="submit" /> <a href="/test">link</a> </form> <script> const element = document.querySelector('.login-form') element.addEventListener('click', function (e) { e.preventDefault() }) </script> </body> </html> ``` `<input>` 輸入框 : 無法送出 e ,以下為範例 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .active { background: red; } </style> </head> <body> <form class="login-form"> <div> username: <input name="username" /> </div> <div> password: <input name="password" type="password" /> </div> <div> password again: <input name="password2" type="password" /> </div> <input type="submit" /> <a href="/test">link</a> </form> <script> const element = document.querySelector('input[name = username]') element.addEventListener('keypress', function (e) { if (e.key === 'e') { e.preventDefault() } else { console.log(e.key) } }) </script> </body> </html> ``` ![](https://i.imgur.com/c3OMPAT.png) ___ ## 比複雜更複雜的事件傳遞機制 瀏覽器 事件傳遞機制 : 點擊區塊的影響,是有順序的,會先從近的開始。以下 console.log(),是在 冒泡階段 [DOM 的事件傳遞機制:捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/) ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .outer { width: 500px; height: 200px; background: red; } .inner { width: 300px; height: 100px; background: green; } </style> </head> <body> <div class="outer"> <div class="inner"> <button class="btn">click me</button> </div> </div> <script> addEvent('.outer') addEvent('.inner') addEvent('.btn') function addEvent(className) { document .querySelector(className) .addEventListener('click', function () { console.log(className) }) } </script> </body> </html> ``` ![](https://i.imgur.com/TSu5TOS.png) ___ ## 事件傳遞機制詳解:捕獲與冒泡 以下圖去理解,事件傳遞機制。 問題1 : 如何將 event Listener (事件監聽) ,掛在不同階度。 `addEventListener( )`,有第3參數,可以放 bool * false : 放在冒泡階段上。 * true : 放在捕獲階段上。 在 target Phase 上比較特別,會先看 哪個 event Listener 在前,就執行哪個。 ![](https://i.imgur.com/ihpy9hm.png) 以下為範例 : 點擊 click me ,開啟 console ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .outer { width: 500px; height: 200px; background: red; } .inner { width: 300px; height: 100px; background: green; } </style> </head> <body> <div class="outer"> <div class="inner"> <button class="btn">click me</button> </div> </div> <script> addEvent('.outer') addEvent('.inner') addEvent('.btn') function addEvent(className) { document.querySelector(className).addEventListener( 'click', function () { console.log(className, '捕獲') }, true, ) document.querySelector(className).addEventListener( 'click', function () { console.log(className, '冒泡') }, false, ) } </script> </body> </html> ``` ![](https://i.imgur.com/ETX98OE.png) ___ ## 別向上級回報:stopPropagation 舉例 : 冒泡階段 就像是 向上級回報一樣,那如果不想向上回報時,只想自己可以接收到事件就好,不想到冒泡階段,讓事件傳遞下去。 * stopPropagation() * stopImmediatePropagation() 它們的共同點: 都是阻止後續的偵聽行為,即能阻擋掉事件流中事件的冒泡,簡而言之就是讓後面的偵聽都不執行; 不同點: 是擁有事件監聽函數的當前的節點是否執行該函數,stopPropagation()方法阻止事件對象移到到另一個節點上,但是允許當前節點的其他事件監聽函數執行,而stopImmediatePropagation()方法不僅阻止事件從當前節點移動到另一個節點上,它還不允許當前節點的其他事件監聽函數執行。 舉例 : 如下,可以在 botton ,下不只一個監聽,如使用 e.stopPropagation(),多個監聽都會有效過,如下 e.stopImmediatePropagation(),只會監聽一個事件,其他都沒效果。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> .outer { width: 500px; height: 200px; background: red; } .inner { width: 300px; height: 100px; background: green; } </style> </head> <body> <div class="outer"> <div class="inner"> <button class="btn">click me</button> </div> </div> <script> addEvent('.outer') addEvent('.inner') function addEvent(className) { document .querySelector(className) .addEventListener('click', function () { console.log(className, '冒泡') }) } document.querySelector('.btn').addEventListener('click', function (e) { e.stopPropagation() console.log('btn 冒泡') }) </script> </body> </html> ``` ![](https://i.imgur.com/wsMIPYk.png) ___ ## 新手 100% 會搞錯的事件機制問題 程式是一行一行執行,在用 querySelectorAll 時,這個 elements 是只有那兩個按鈕 ,動態新增的按鈕,並沒有 事件機制。 data-value : 存放資訊 。 動態新增的元素,不會加入先寫好的事件監聽。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style></style> </head> <body> <div class="outer"> <button class="add-btn">add</button> <button class="btn" data-value="1">1</button> <button class="btn" data-value="2">2</button> </div> <script> let num = 3 const elements = document.querySelectorAll('.btn') for (var i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function (e) { alert(e.target.getAttribute('data-value')) }) } document.querySelector('.add-btn').addEventListener('click', function () { const btn = document.createElement('button') btn.setAttribute('data-value', num) btn.innerText = num num++ document.querySelector('.outer').appendChild(btn) } ) </script> </body> </html> ``` ___ ## 欸等等幫我拿餐點:event delegation event delegation (事件代理),請回憶起 事件傳遞機制詳解:捕獲與冒泡 * 常用,比較有效率,節省許多資源,不用加那麼多 function ,浪費那麼多 function,去監聽每一個事件,因為每一個是事件做的事情都差不多,所以可以只使用一個 EventListener 去做管理就好 * 可以處理,動態新增的情形,動態還是可以抓到,特過冒泡機制,傳遞上去,所以即使底下新增的元素,還是可以接到他的事件。 在範例中,將在 `btn` 的事件監聽,加在 `.outer` 上,稱事件代理。 * `contains('n')` : 判斷,`classList`,是否有 n 這 class。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style></style> </head> <body> <div class="outer"> <button class="add-btn">add</button> <button class="btn" data-value="1">1</button> <button class="btn" data-value="2">2</button> </div> <script> let num = 3 const elements = document.querySelectorAll('.btn') for (var i = 0; i < elements.length; i++) { elements[i].addEventListener('click', function (e) { alert(e.target.getAttribute('data-value')) }) } document.querySelector('.add-btn').addEventListener('click', function () { const btn = document.createElement('button') btn.setAttribute('data-value', num) btn.classList.add('btn') btn.innerText = num num++ document.querySelector('.outer').appendChild(btn) }) document.querySelector('.outer').addEventListener('click', function (e) { if (e.target.classList.contains('btn')) { alert(e.target.getAttribute('data-value')) } }) </script> </body> </html> ``` ___ ## 綜合示範:簡易密碼產生器 備註: 影片裡的程式碼其實有一個 bug,那就是儘管你沒有勾選英文字母,還是會產生出來,那是因為 getChar 這個 function 最後忘記加上 return '' 所以預設會 return undefined,被轉成文字之後產生出的結果就會有 undefined 或是部分字母,造成程式的 bug getChar 的最後加上 return '' 就行了 範例 : ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> body { font-size: 40px; } .result { background: rgba(0, 0, 0, 0.5); } </style> </head> <body> <div class="app"> <div> <label for=""> <input type="checkbox" name="en" /> 英文 </label> </div> <div> <label for=""> <input type="checkbox" name="num" /> 數字 </label> </div> <div> <label for=""> <input type="checkbox" name="sp" /> 特殊符號 </label> </div> <div> <button class="btn-generate">產生</button> <div class="result"></div> </div> </div> <script> document .querySelector('.btn-generate') .addEventListener('click', function () { let availableChar = '' if (document.querySelector('input[name = en]').checked) { availableChar += 'abcdefghijklmnopqrstuvwxyz' } if (document.querySelector('input[name = num]').checked) { availableChar += '0123456789' } if (document.querySelector('input[name = sp]').checked) { availableChar += '!@#$%^&*()' } let result = '' for (let i = 0; i < 10; i++) { //0 ~ availableChar.length - 1 const number = Math.floor(Math.random() * availableChar.length) result += availableChar[number] } document.querySelector('.result').innerText = result }) </script> </body> </html> ``` 優化 : ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> body { font-size: 40px; } .result { background: rgba(0, 0, 0, 0.5); } </style> </head> <body> <div class="app"> <div> <label for=""> <input type="checkbox" name="en" /> 英文 </label> </div> <div> <label for=""> <input type="checkbox" name="num" /> 數字 </label> </div> <div> <label for=""> <input type="checkbox" name="sp" /> 特殊符號 </label> </div> <div> <button class="btn-generate">產生</button> <div class="result"></div> </div> </div> <script> function getChar(name, char) { if (document.querySelector('input[name = ' + name + ']').checked) { return char } return } document .querySelector('.btn-generate') .addEventListener('click', function () { let availableChar = '' availableChar += getChar('en', 'abcdefghijklmnopqrstuvwxyz') availableChar += getChar('num', '0123456789') availableChar += getChar('sp', '!@#$%^&*()') let result = '' for (let i = 0; i < 10; i++) { //0 ~ availableChar.length - 1 const number = Math.floor(Math.random() * availableChar.length) result += availableChar[number] } document.querySelector('.result').innerText = result }) </script> </body> </html> ``` 再優化 : ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> body { font-size: 40px; } .result { background: rgba(0, 0, 0, 0.5); } </style> </head> <body> <div class="app"> <div> <label> <input type="checkbox" name="en" data-char="abcdefghijklmnopqrstuvwxyz" /> 英文 </label> </div> <div> <label> <input type="checkbox" name="num" data-char="0123456789" /> 數字 </label> </div> <div> <label> <input type="checkbox" name="sp" data-char="!@#$%^*()" /> 特殊符號 </label> </div> <div> <button class="btn-generate">產生</button> <div class="result"></div> </div> </div> <script> document .querySelector('.btn-generate') .addEventListener('click', function () { let availableChar = '' const elements = document.querySelectorAll('input[type=checkbox]') for (let i = 0; i < elements.length; i++) { if (elements[i].checked) { availableChar += elements[i].getAttribute('data-char') } } let result = '' for (let i = 0; i < 10; i++) { //0 ~ availableChar.length - 1 const number = Math.floor(Math.random() * availableChar.length) result += availableChar[number] } document.querySelector('.result').innerText = result }) </script> </body> </html> ``` ___ ## 綜合示範:動態表單通訊錄 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style> body { font-size: 40px; } .result { background: rgba(0, 0, 0, 0.5); } </style> </head> <body> <div class="app"> <div> <button class="add-btn">新增聯絡人</button> </div> <div class="contacts"> <div class="row"> 姓名: <input name="name" /> 電話: <input name="phone" /> <button class="delete">刪除</button> </div> </div> </div> <script> document.querySelector('.add-btn').addEventListener('click', function () { const div = document.createElement('div') div.classList.add('row') div.innerHTML = `姓名: <input name="name" /> 電話: <input name="phone" /> <button class="delete">刪除</button>` document.querySelector('.contacts').appendChild(div) }) document .querySelector('.contacts') .addEventListener('click', function (e) { if (e.target.classList.contains('delete')) { document .querySelector('.contacts') .removeChild(e.target.closest('.row')) } }) </script> </body> </html> ``` ___ # 如何在瀏覽器上儲存資料? ## 網頁的資料都存在哪裡?為什麼換台電腦購物車就清空了? 瀏覽器的資料儲存,我們可以利用js把資料做儲存,把想存的資料存在瀏覽器中,所以我們可以存資料的地方不只一個,瀏覽器會有一個地方拿來存你的資料,因為一個最基本的問題,http的狀態是如何,也就是說它不知道,上一個人跟現在是不是同一個人,所以我們需要一些方法,將資料自動帶到server去,讓他可以認識我們,所以這也是,為甚麼有些購物網站,我把一些商品加入購物車,換了個b電腦,購物車商品就沒有了,代表商品是存在a電腦上,不是存在後端,如果是存在後端的資料庫,不管事用哪一台電腦,都是抓地到的,如果換了一台電腦購物車就清空,代表是將資訊純在瀏覽器裡的 ___ ## 最古老的方式:Cookie [Cookie - wiki](https://zh.wikipedia.org/wiki/Cookie) : 其實是個小型文字檔,會自動帶到 server。 我們可以用js將資料寫在 Cookie , server 也可以透過,http 的 response ,把資料寫進 Cookie, server 有一個 response ,叫做 Set-Cookie : ...,裡面會放一些資訊,只要瀏覽器看到 Set-Cookie,就會將 Cookie 給寫入,所以的 request 都會把 瀏覽器的 Cookie 給帶上去,為了讓 瀏覽器 辨識身分 ,辨識身分指的意思,有時候會被用在 廣告追蹤 ,可以打開隨便一個網站,開啟 檢查,的 Application,Storage 裡 就有 Cookie ,裡面毀有一堆 domain ,裡面有些 id 就是拿來追蹤你用的,當你拜訪網站時,這網站插入了些 google 程式碼,那 google 就知道,你來哪一些網站,google 就能用這去辨認 剛剛跟現在是否是同一個人。 所以 Cookie ,是會用在 身分驗證上。 舉例 : 登入,如何判斷是同一個人,第一次拜訪某網站時,server 發通行證會放在 Cookie,第2次拜訪時,瀏覽器 都會資自動帶上 Cookie,那 server 就知道是同一個人。 Cookie 會放很多 domain,瀏覽器會帶跟你在拜訪的網站相關 Cookie,給 你在拜訪的 server。 ___ ## 最推薦的儲存方式:local storage Cookie 像是伺服器跟瀏覽器的一個溝通。實際上伺服器,也可以設定 Cookie,所以一般來說,我們想要 儲存資訊,跟 server沒關時,其實會用另一個叫 local storage。 範例 : 按按鈕,將資訊存入 local storage,開啟 檢查,的 Application,local storage 裡可以看到資訊,只能存字串。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style></style> </head> <body> <div class="app"> <input type="text" class="text" /> <button class="add-btn">储存</button> </div> <div class="app1"></div> <script> const oldValue = window.localStorage.getItem('text') document.querySelector('.text').value = oldValue document.querySelector('button').addEventListener('click', function () { const value = document.querySelector('.text').value window.localStorage.setItem('text', value) }) </script> </body> </html> ``` ___ ## 一閃即逝:session storage session storage 跟 Cookie 用法是一樣的,session 是一種有時間概念的意思在。 特點是拿來存些短期的資訊,如換分頁, session storage 的資訊會消失。 範例 : 將上方程式,改成 session storage,其他都不用變。開新分頁,資訊就不見了,只能短期方資料。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>FE102</title> <style></style> </head> <body> <div class="app"> <input type="text" class="text" /> <button class="add-btn">储存</button> </div> <div class="app1"></div> <script> const oldValue = window.sessionStorage.getItem('text') document.querySelector('.text').value = oldValue document.querySelector('button').addEventListener('click', function () { const value = document.querySelector('.text').value window.sessionStorage.setItem('text', value) }) </script> </body> </html> ``` ___ # 中場總結 ## 學了這些之後,可以幹嘛? 發揮想像力,學會以上技術基本上只要不跟網路相關,都能做出來,現在可以監聽是監,可以改變UI,所以這兩件事,就是網頁的元素,像上學會這兩項,可以寫出些遊戲,只是欠缺些網路資料交換的能力,其他都寫得出來,貪吃蛇,表單驗證等等... ___ ## 遺漏的最後一塊拼圖:資料的交換 已學會,如何用JS改變UI,如何用JS去監聽事件,然後做出反應,還沒學到的事,怎麼跟 server 做資料交換,例如送資料給 server , server 在返回資料給你,JS關注的要點都會了,只要會這3點,所有看的網頁跟JS有關的都做得出來。 ___ # 網頁與伺服器的溝通 ## 幫你喚回記憶:API 與網頁伺服器 Client 發送 request 到 Server,Server 回傳 response ,response 有可能是 .html 檔案或是 JSON 格式檔案。 瀏覽器上輸入網址(Client 發送 request),顯示的畫面(Server 回傳 response(.html 檔案))。 ___ ## 用 node.js 呼叫 API 與在網頁上呼叫的根本差異是什麼? 差別 : 使用 node.js 發送 request ,到 Server ,這中間過程是沒有人限制你的。 使用瀏覽器上的JS 發送 request,到 Server ,中間會被瀏覽器限制,第一個是瀏覽器可能會阻止你做一些事情,第二個是瀏覽器會幫我們加一些東西,例如,你的瀏覽器的版本是甚麼,會加些而外的資訊,Server 收到的 request ,就會有而外的資訊,那再用 node.js 時,你不加的資訊,就不會發送到 Server。 ![](https://i.imgur.com/k8o9RDP.png) ![](https://i.imgur.com/i9Ysj4Y.png) ___ ## 傳送資料的第一種方式:表單 form 範例 : 使用 form 傳送資料 跟 JS 沒關係 ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>form</title> </head> <body> <form method="GET" action="https://google.com"> username: <input name="username" /> <input type="submit" /> </form> </body> </html> ``` 再用 GET,發送時,資訊會帶到網址後面,POST,發送時,資訊會帶到body裡。 發送出去後,瀏覽器收到後,會直接換頁(回傳的 response 是 .html 檔案 )。 ==下圖,輸入123,提交,301轉址。== ![](https://i.imgur.com/xTGq81l.png) ==下圖,出現google頁面,因為回傳的response,是google的html檔== ![](https://i.imgur.com/kK4ZkBz.png) ==下圖,response回傳回來的,google的html檔== ![](https://i.imgur.com/Hk61DoA.png) ___ ## 傳送資料的第二種方式:ajax 第一種方式的缺點,會換頁。 第二種方式:可以不換頁,在頁面上,去改變部分內容。 ajax(Asynchronous JavaScript and XML),只要是任何伺服器交換資料,是用 JavaScript,都可以叫 ajax。 原理: 瀏覽器的 JS 透過瀏覽器發送request給Server回應response給瀏覽器,瀏覽器將結果給瀏覽器的 JS。 瀏覽器的 JS -> 瀏覽器(request) -> Server -> 瀏覽器(response) -> 瀏覽器的 JS ![](https://i.imgur.com/Sa1FBtd.png) ``` 解釋 : // XMLHttpRequest(),瀏覽器提供的物件,new可以去實作。 const request = new XMLHttpRequest() //request.onload,放一個function,request現在是一個物件,實作後,會執行onload,這function。 //if,判斷是狀態是否成功,responseText,取得的內容。err,錯誤訊息。 request.onload = function () { if (request.status >= 200 && request.status <= 400) { console.log(request.responseText) } else { console.log(err) } } //如果有錯誤,會觸發的 function request.onerror = function () { console.log('error') } //open,要發request的地方,可以填3個參數,method,url,bool。 //bool,true,非同步。false,同步。ajax一定是非同步。 request.open('GET', '', true) //真的將request,發出。 request.send() ``` 範例 : 可以開啟,檢查,Network,Response,查看資料。 ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ajax - text</title> </head> <body> <div class="app"></div> </body> <script> const request = new XMLHttpRequest() request.onload = function () { if (request.status >= 200 && request.status <= 400) { console.log(request.responseText) } else { console.log(err) } } request.onerror = function () { console.log('error') } request.open('GET', 'https://reqres.in/api/users', true) request.send() </script> </html> ``` ___ ## 詳細解析 XMLHttpRequest 重點 : 回傳回來的是字串,需要用[JSON.parse()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse),去轉成物件使用。 在解釋一次,ajax,的實作範例。可以將上章範例,的onload,用EventListene去做。 ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ajax - text</title> </head> <body> <div class="app"></div> </body> <script> const request = new XMLHttpRequest() request.addEventListener('load', function () { if (request.status >= 200 && request.status <= 400) { console.log(request.responseText) } else { console.log(request.status) } }) request.onerror = function () { console.log('error') } request.open('GET', 'https://reqres.in/api/users', true) request.send() </script> </html> ``` ___ ## Same origin policy 與跨網域問題 好文推薦:https://blog.techbridge.cc/2017/05/20/api-ajax-cors-and-jsonp/ 1.瀏覽器的政策 : Same origin policy(同源政策),甚麼是同源,可以看成相同網域,就是同源,HTTP跟HTTPS,是分開的。 2.瀏覽器的政策 : 跨來源資源公用 ( CORS ),這東西就是為了解決,同源政策的問題,有時可能會去別人的Server,拿資料,所以需要有此功能,這時就有一個規範。 如果想要跨來源存取在header,加 Access-Control-Allow-Origin,哪些來源可以存取API的response,這來源是甚麼,在發 request,瀏覽器會在 request 的 header 裡,加上 Origin,就是你現在網域的 Domain,瀏覽器會自動加上來源,在server端,就可以決定是否給他存取的權限。 所以要解決這問題,除非是同來源,在同樣的 Domain,或是 server 加上 Access-Control-Allow-Origin。 ___ ## 瀏覽器住海邊嗎,為什麼管這麼寬? 為甚麼要有同源政策,跨來源資源公用,這兩個政策,所以到底要怎麼存取,跨網域的資源。 因為安全性,瀏覽器許多政策,都是因為安全性,為了不讓他人可以隨意存取他人電腦的資源。 以上瀏覽器政策,都是瀏覽器加的枷鎖,如果沒有瀏覽器,像是用 node,去發 request,並沒有那些枷鎖。 ___ ## 你絕對用過,但絕對沒想過的第三種方式 補充講解,現在已經很少再用了。[JSONP (JSON with Padding)](https://www.fooish.com/json/jsonp.html) 利用有些標籤不會受到同源政策的影響,例如`<img>`、`<script>`,使用`<script>`去拿到資料。 ___ ## 單向傳送資料的延伸應用(email 與追蹤Domain) 單向傳送資料,範例,如何知道發送的 email,被打開的次數追蹤,廣告信的次數追蹤。 例如,在 email中,放一個透明的`<img width="1" heigth="1" src="https://example.com/users_open/123">`圖,當使用者開信,就會發送一個 request,去計算次數。 ___ ## 綜合示範:抓取資料並顯示 1.先將版型刻好。 ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ajax - text</title> <style> body { font-size: 38px; } .profile { border: 1px solid #333; margin-top: 20px; display: inline-flex; align-items: center; } .profile__name { margin: 0 10px; } </style> </head> <body> <div class="app"> <div class="profile"> <div class="profile__name">George Bluth</div> <img class="profile__img" src="https://reqres.in/img/faces/1-image.jpg" /> </div> </div> </body> <script> const request = new XMLHttpRequest() request.addEventListener('load', function () { if (request.status >= 200 && request.status <= 400) { console.log(request.responseText) } else { console.log(request.status) } }) request.onerror = function () { console.log('error') } request.open('GET', 'https://reqres.in/api/users', true) request.send() </script> </html> ``` 2.拿到資料 3.將固定的內容,換成是動態的內容。 ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ajax - text</title> <style> body { font-size: 38px; } .profile { border: 1px solid #333; margin-top: 20px; display: inline-flex; align-items: center; } .profile__name { margin: 0 10px; } </style> </head> <body> <div class="app"></div> </body> <script> const request = new XMLHttpRequest() const container = document.querySelector('.app') request.addEventListener('load', function () { if (request.status >= 200 && request.status <= 400) { const response = request.responseText const json = JSON.parse(response) const users = json.data for (let i = 0; i < users.length; i++) { const div = document.createElement('div') div.classList.add('profile') div.innerHTML = `<div class="profile__name">${users[i].first_name} ${users[i].last_name}</div> <img class="profile__img" src="${users[i].avatar}"/> ` container.appendChild(div) } } else { console.log(request.status) } }) request.onerror = function () { console.log('error') } request.open('GET', 'https://reqres.in/api/users', true) request.send() </script> </html> ``` ___ ## 小地方大學問:內容產生的地方在 client 還是 server client side rendering : 用 js 動態去產生內容。 以上範例,去檢視網頁原始碼,看起來是沒有內容的。 缺點是,搜尋引擎,看到此網頁會認為,是沒有內容的,因為你的內容都是 js 動態產生的。 ___ # 總結 ## 課程總結 基本上在網頁上頁JavaScript時,所關注的面向,有3個 : 1. 介面 : 如何改變 如何使用 JavaScript 去改變,用 html 跟 css ,所寫出來的 UI,例如改變元素,新增元素。 2. 事件 : 如何監聽事件並做出反應 比如,按一個按紐去新增元素,或是按按鈕改變背景顏色,如果會介面跟事件,能做出7-8成的東西。 3. 資料 : 如何跟伺服器交換資料 或是如何讓資料再瀏覽器保存下來。 > 基本上如果會以上3點,能寫出任何的程式,網頁的部分,不外乎就這3點。 ___