HackMD
  • New!
    New!  Markdown linter lands.
    Write with consistency! Enable markdown linter that hints better markdown styling you can take. Click the lightbulb on the editor status bar 💡
    Got it
      • Create new note
      • Create a note from template
    • New!  Markdown linter lands.
      New!  Markdown linter lands.
      Write with consistency! Enable markdown linter that hints better markdown styling you can take. Click the lightbulb on the editor status bar 💡
      Got it
      • Options
      • Versions and GitHub Sync
      • Transfer ownership
      • Delete this note
      • Template
      • Save as template
      • Insert from template
      • Export
      • Dropbox
      • Google Drive
      • Gist
      • Import
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
      • Download
      • Markdown
      • HTML
      • Raw HTML
      • ODF (Beta)
      • Sharing Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • More (Comment, Invitee)
      • Publishing
        Everyone on the web can find and read all notes of this public team.
        After the note is published, everyone on the web can find and read this note.
        See all published notes on profile page.
      • Commenting Enable
        Disabled Forbidden Owners Signed-in users Everyone
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Invitee
      • No invitee
    Menu Sharing Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Versions and GitHub Sync Transfer ownership Delete this note
    Export
    Dropbox Google Drive Gist
    Import
    Dropbox Google Drive Gist Clipboard
    Download
    Markdown HTML Raw HTML ODF (Beta)
    Back
    Sharing
    Sharing Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Note Permission
    Read
    Owners
    • Owners
    • Signed-in users
    • Everyone
    Owners Signed-in users Everyone
    Write
    Owners
    • Owners
    • Signed-in users
    • Everyone
    Owners Signed-in users Everyone
    More (Comment, Invitee)
    Publishing
    Everyone on the web can find and read all notes of this public team.
    After the note is published, everyone on the web can find and read this note.
    See all published notes on profile page.
    More (Comment, Invitee)
    Commenting Enable
    Disabled Forbidden Owners Signed-in users Everyone
    Permission
    Owners
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Invitee
    No invitee
       owned this note    owned this note      
    Published Linked with GitHub
    Like BookmarkBookmarked
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Dom ###### tags: `Tag(Dom)` ### 什麼是 `DOM`? >[參考資料](https://ithelp.ithome.com.tw/articles/10191666) 重新認識 JavaScript: Day 11 前端工程師的主戰場:瀏覽器裡的 JavaScript Dom簡單來說就是 ==javascript跟html/瀏覽器之間的橋樑==,瀏覽器提供了這個橋樑(Dom),讓我們去改變畫面 <font color="#3ca59d">**DOM API 定義了讓 JavaScript 可以存取、改變 HTML 架構、樣式和內容的方法,甚至是對節點綁定的事件。**</font> <font color="#3ca59d">**JavaScript 透過 DOM 提供的 API 來 ==對 HTML 做存取與操作==。**</font> ```javascript= // 根據傳入的值,找到 DOM 中 id 為 'xxx' 的元素。 document.getElementById('xxx'); // 針對給定的 tag 名稱,回傳所有符合條件的 NodeList 物件 document.getElementsByTagName('xxx'); // 針對給定的 class 名稱,回傳所有符合條件的 NodeList 物件。 document.getElementsByClassName('xxx'); // 針對給定的 Selector 條件,回傳第一個 或 所有符合條件的 NodeList。 document.querySelector('xxx'); document.querySelectorAll('xxx'); ``` :::warning ``` c NodeList 物件是節點的集合,可藉由 Node.childNodes 屬性 或 document.querySelectorAll() 等方法取得。 NodeList 雖然有著與陣列相似的特性,但不是陣列,所以也不會有陣列相關的 method 可以使用 (如 map、filter 等)。 ``` ::: --- ### DOM 節點的選取 >[參考資料](https://ithelp.ithome.com.tw/articles/10191765) 重新認識 JavaScript: Day 12 透過 DOM API 查找節點 當我們要存取 HTML 時,都==從 document 物件開始==。 常見的 DOM 選取方法有: ```javascript= // 根據傳入的值,找到 DOM 中 id 為 'xxx' 的元素。 document.getElementById('xxx'); <scrip> //注意! 這邊Element沒有s,因為id只能用一個! const elements = document.getElementById('block') // 抓到指定的Id的東西 console.log(elements[0]) </scrip> // 針對給定的 tag 名稱,回傳所有符合條件的集合 document.getElementsByTagName('xxx'); <scrip> const elements = document.getElementsByTagName('div') //抓到所有屬於這個tag的元素 console.log(elements[1]) </scrip> // 抓到指定的class的東西 // IE9 後開始支援 document.getElementsByClassName('xxx'); <scrip> const elements = document.getElementsByClassName('block') console.log(elements[0]) </scrip> // 針對給定的 Selector 條件,回傳匹配到的第一個元素 // IE8 後開始支援 document.querySelector('xxx'); <scrip> // 只會回傳匹配到的第一個元素 const elements = document.querySelector('#block') // 抓到css得選擇器,可以選Id、class、div console.log(elements[0]) </scrip> // 針對給定的 Selector 條件,回傳所有符合條件的 NodeList document.querySelectorAll('xxx'); ``` :::warning ```css= document.querySelector 與 document.querySelectorAll 可以用 「CSS 選擇器」 (CSS Selectors) 來取得「第一個」 或「所有」符合條件的元素集合 (NodeList)。 ``` ::: --- ### DOM 節點間的查找遍歷 (Traversing) >[參考資料](https://ithelp.ithome.com.tw/articles/10191765) 重新認識 JavaScript: Day 12 透過 DOM API 查找節點 ==DOM 節點有分層的概念==,節點與節點之間的關係,大致上可以分成兩種: ```markedown 父子關係: 除了 document 之外,每一個節點都會有個上層的節點,我們通常稱之 為「父節點」 (Parent node),而相對地,從屬於自己下層的節點, 就會稱為「子節點」(Child node)。 兄弟關係: 有同一個「父節點」的節點,那麼他們彼此之間就是「兄弟節點」(Siblings node)。 ``` ![](https://i.imgur.com/8pq9UmZ.png) ### 選到前一個元素 使用 `previousElementSibling` ###### `範例` ```javascript= document .querySelector('.list').previousElementSibling.innerHTML; ``` ### `document.querySelectorAll` >[參考資料](https://ithelp.ithome.com.tw/articles/10191765) 重新認識 JavaScript: Day 12 透過 DOM API 查找節點 >[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧 `document.getElementById` 以及 `document.querySelector `因爲 ==取得的一定只會有一個元素/節點== ,所以不會有 index 與 length 屬性。 <font color="#3ca59d">**`document.querySelectorAll` 回傳 「NodeList」。**</font> 「NodeList」除了 HTML element 節點,也包含文字節點、屬性節點等。 雖然不能使用陣列型別的 method,但 ==可以用「陣列索引」的方式來存取內容== `NodeList` 在大部分情況下是即時更新的,但 ==透過 `document.querySelector / document.querySelectorAll` 取得的 NodeList 是靜態的==。 ```javascript= <div id="outer"> <div id="inner">inner</div> </div> <script> // <div id="outer"> var outerDiv = document.getElementById('outer'); // 所有的 <div> 標籤 var allDivs = document.querySelectorAll('div'); console.log(allDivs.length); // 2 // 清空 <div id="outer"> 下的節點 outerDiv.innerHTML = ''; // document.querySelector 回傳的是靜態的 NodeList,不受 outerDiv 更新影響 console.log(allDivs.length); // 2 </script> ``` #### 把 `querySelectorAll()` 回傳的 `NodeList` 轉成 Array 之後,就能用 `forEach()` 方式走訪每個元素。 ```javascript= Array.from(allElements).forEach(element => { // do something... }) // IE 還不支援 Array.from(),可以用: Array.prototype.forEach.call(allElements, element => { // do something... }) // 更短的寫法: [].forEach.call(allElements, element => { // do something... }) ``` --- ### 順利選到元素後 來試看看改變它! #### 如何改變`css` `element.style `後面==接想要改的東西== ```htmlmixed= <body> <div id='block'> hello~ </div> <script> const element = document.querySelector('#block') element.style.background = 'red' </script> </body> ``` :::success ==`.` 點後面不能接 `-`== 有兩種方式可以解決: 第一種 `['padding-top'] `用字串包起來,不會有不合格語法的問題 ```html <body> <div id='block'> hello~ </div> <script> const element = document.querySelector('#block') elemaent.style['padding-top'] = '10px' </script> </body> ``` 第二種 駝峰式寫法,小寫開頭碰到一個單字就大寫 ```html <body> <div id='block'> hello~ </div> <script> const element = document.querySelector('#block') elemaent.style.paddingTop = '10px' </script> </body> ``` ::: #### 如何改變`class`? >[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧 要修改元素的 class,可以==用方便的 `classList` 操作==。 ```javascript= oneElement.classList.add('baz') oneElement.classList.remove('baz') oneElement.classList.toggle('baz') // 檢查是否有指定的 class oneElement.classList.contains('baz') ``` 第一種 讓javascript動態把class加上去 ``` c <body> <div id='block'> hello~ </div> <script> const element = document.querySelector('#block') element.classList.add('active') </script> </body> ``` 第二種 如何移除class ``` c <body> <div id='block' class='main'> hello~ </div> <script> const element = document.querySelector('#block') element.classList.add('active') element.classList.remove('main') </script> </body> ``` 第三種 ==toggle就像是開關==(本來沒有的話,會便有; 本來有的話,會便沒有) ``` c <body> <div id='block' class='main'> hello~ </div> <script> const element = document.querySelector('#block') element.classList.add('active') element.classList.toggle('main') </script> </body> ``` #### 如何改變內容(修改 DOM)? >[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧 ```javascript= // 在 element1 裡插入一個 element2 element1.appendChild(element2) // 在 element1 裡的 element3 之前插入一個 element2 element1.insertBefore(element2, element3) // 複製 DOM const newElement = oneElement.cloneNode() element1.appendChild(newElement) // 建立新的 DOM const newElement = document.createElement('div') const newTextNode = document.createTextNode('hello world') // 移除 DOM,需要參照到親元素 parentElement.removeChild(element1) // 自己移除自己 element1.parentNode.removeChild(element1) ``` 第一種 `innerText` ``` c <body> <div id='block'> yo <a>hello~</a> </div> <script> const element = document.querySelector('#block') console.log(element.innerText) </script> </body> ``` 第二個`innerHTML` 插入HTML 可以將元素內的html==重新覆蓋寫入== 新的html。 ```javascript= var el = document.getElementById('main'); var str = '<h1 class="blue">1234</h1>' el.innerHTML = str; ``` 第三個`outerHTML` 印出整段文字包含標籤 ``` c <body> <div id='block'> yo <a>hello~</a> </div> <script> const element = document.querySelector('#block') console.log(element.outerHTML) </script> </body> ``` --- ### 插入與刪除元素 #### 刪除元素 ``` c <body> <div id='block'> yo <a>hello~</a> </div> <script> const element = document.querySelector('#block a') //刪除<a> element.removeChild(document.querySelector('a')) </script> </body> ``` #### 新增元素 >[參考資料](https://pjchender.blogspot.com/2017/06/js-node-element-appenchild-disappear.html) Node Element 在 appendChild 後消失(disappear)!? ``` c <body> <div id='block'> yo <a>hello~</a> </div> <script> const element = document.querySelector('#block a') const item = document.creatTextNode('123') element.appendChild(item) </script> </body> ``` :::info 如果 appendChild 使用時,append 的是一個已存在的 node 時,它==會做的是搬移,而非複製==。 ``` c // 把 innerElement append 到 block1 上 block1.appendChild(innerElement) // 這時候 innerElement 已經是存在的 Node 了,所會把這個 Node 進行"搬移",於是原本在 .block1 的 innerElement 被搬到 .block2 block2.appendChild(innerElement) // 同理,原本在 .block2 的 innerElement 被搬到 .block3 block3.appendChild(innerElement) ``` 使用 `appendChild()` 時,對於現存的 Node 它會採用搬移的方式,讓如果想要==複製一整個 element== 呢? 可以使用 `Node.cloneNode()` 這個方法 ``` c const buttonClone = document.querySelector('#cloneNode') buttonClone.addEventListener('click', function () { let cloneElement = innerElement.cloneNode(true) // 真的複製這個 Node if (i === 0) { block1.appendChild(cloneElement) } else if (i === 1) { block2.appendChild(cloneElement) } else { block3.appendChild(cloneElement) } i = (i + 1) % 3 }) ``` ==先使用 Node.cloneNode() 這個方法複製 Node Element,再使用 appendChild== ::: #### `createElement` - 插入dom元素 建立一個新的DOM元素,然後再使用 appendChild 新增子節點,並==不會覆蓋原有的DOM元素==。 ``` c <h1 class="title"> <em>titile</em> </h1> <script > // 建立元素 var sonElement = document.createElement("a"); sonElement.setAttribute('href','www.facebook.com'); sonElement.textContent = '前往Facebook'; // 增加子節點 var fatherElement = document.querySelector('.title'); fatherElement.appendChild(sonElement); </script> ``` --- ### `Attribute` - 增加標籤屬性 >[參考資料](https://kanboo.github.io/2017/12/30/JS-studynotes/) JS-基礎用法 #### `setAttribute` 設定 標籤屬性 ```javascript= var el = document.querySelector('.titleClass a'); el.setAttribute('href','http://www.yahoo.com.tw'); ``` #### `getAttribute` 取得 標籤屬性 ```javascript= var el3 = document.querySelector('.titleClass a').getAttribute('href'); console.log(el3); ``` --- ### `event listener `事件監聽 >[參考資料](https://jmln.tw/blog/2017-07-07-vanilla-javascript-dom-manipulation.html) 都 2017 年了,學學用原生 JS 來操作 DOM 吧 ==一但事件被觸發就會執行後面的function== ``` c <body> <div id='block'> yo <a>hello~</a> </div> <script> const element = document.querySelector('#block') element.addEventListener('click',function (){ alert('click!') } </script> </body> //常見的寫法 匿名函式 ``` :::info `event(e)` 是什麼?? 瀏覽器在呼叫function時會傳入一個參數 ==!名稱可以自己取== function (e) e => 瀏覽器會帶入一些資訊,可以 ==利用這個資訊/變數拿到跟事件相關的東西== ``` c <body> <div id='block'> yo <input/> <a>hello~</a> </div> <script> const element = document.querySelector('input') element.addEventListener('ketdown', function (e){ console.log(e.key) }) </script> </body> //按了鍵盤 瀏覽器就會印出你按了哪些按鍵 //可以用e拿到一些額外的資訊 ``` ``` c <style> .active { background: red; } </style> </head> <body> <div id='block'> yo <input/> <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> //點了按鈕 背景顏色更改 //e 就是瀏覽器幫你傳的變數 裡面會有跟事件相關的資訊 ``` ::: :::success ==`addEventListener` 有第三個參數== ``` markedown 第一個參數是事件名稱 第二個參數是callback function 第三個參數可以放一個布林變數,false的話就會被放在冒泡階段上; true的話就會被放在捕獲階段上。 ``` 原本都沒有傳第三個參數的話,==預設就是false== ::: --- ### 事件代理(Event delegation)適合用的場合 >[參考資料](http://le0zh.github.io/2016/06/04/event-delegate-in-javascript/) js中的事件代理(event delegation) * 待綁定的對象元素是==動態的==,需要頻繁的添加或者刪除等 * ==待綁定的元素很多==,比如一個100行的Table,需要對tr綁定事件; * 被綁定的父級對象離目標對象的層級越少越好,否則會有性能問題,==盡量不要直接綁定到document或者body上== * ==事件類型要注意是能冒泡的==,比如focus和blur本身是不會冒泡的 --- ### 事件監聽優化 & `e.target` >[參考資料](https://kanboo.github.io/2017/12/30/JS-studynotes/) JS-基礎用法 >[參考資料](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/event.html) 事件處理 有時子元素可能要上千個,每個都要綁上監聽的話,效能不是很優,這時可==從父節點下手==,使用 `e.target.nodeName` 來取得是哪個元素觸發的。 ###### `範例` ``` 當有一個 ul 底下有多個 li 都要監聽的話,這時我們可以利用 addEventListener-事件氣泡 的原理,只要針對 ul 監聽,讓他往上冒泡,當到達 li 時, 這時我們就可以針對 li 做事了。 ``` ###### `原本寫法` ```javascript= //取得ul底下的所有li元素 var list = document.querySelectorAll('.list li'); //forloop,將每個li元素綁上監聽事件(N次) var len = list.length; for(var i = 0;len>i;i++){ list[i].addEventListener('click',checkName,false) } function checkName(e){ console.log(e.target.textContent); } ``` ###### `優化寫法` ```javascript= //取得ul元素 var list = document.querySelector('.list'); //將ul元素綁上監事件(一次) list.addEventListener('click',checkName,false) function checkName(e){ if(e.target.nodeName !== 'LI'){return}; // 判斷是否為li元素 console.log(e.target.textContent); } ``` --- ### `e.target` 與 `e.currentTarget` 的差別 >[參考資料](https://www.itread01.com/content/1545071047.html) e.currentTarget與e.target的區別 >[參考資料](https://javascript.info/bubbling-and-capturing#this-and-event-target) Bubbling and capturing ``` marked 在W3C標準的定義中,this會相等於event.currentTarget this (=event.currentTarget) is the <form> element, because the handler runs on it. event.target is the actual element inside the form that was clicked. ``` :::warning ![](https://i.imgur.com/DI39LOE.gif) ::: --- ### 表單事件處理 `onSubmit` >[參考資料](https://morosedog.gitlab.io/web-html-20190531-web22/) HTML 表單資料驗證(一) >[參考資料](https://morosedog.gitlab.io/web-html-20190603-web23/) HTML 表單資料驗證(二) >[參考資料](https://pjchender.github.io/2017/11/01/web-%E8%A1%A8%E5%96%AE%E9%A9%97%E8%AD%89%E8%88%87%E5%8F%96%E5%80%BC-form-validation-and-value/) 表單驗證與取值 form validation and value 通常會用在==表單驗證== ``` c <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.addEvenListener('click', funtion(e){ alert('submit!') }) //提交後會有提示訊息出現在網址上 ``` --- ### 事件傳遞機制的順序是什麼? 什麼是冒泡,什麼又是捕獲? >[參考資料](https://ithelp.ithome.com.tw/articles/10191970) 重新認識 JavaScript: Day 14 事件機制的原理 >[參考資料](https://javascript.info/bubbling-and-capturing#this-and-event-target) Bubbling and capturing JavaScript 是一個==事件驅動 (Event-driven)== 的程式語言,當瀏覽器載入網頁開始讀取後,雖然馬上會讀取 JavaScript 事件相關的程式碼,但是必須等到 ==「事件」被觸發(如使用者點擊、按下鍵盤等)後== ,才會再進行對應程式的執行。 ###### `舉個例子` ``` 當使用者點擊了按鈕,才會啟動對話框的顯示,那麼「點擊按鈕」這件事, 就被稱作「事件」(Event),而負責處理事件的程式通常 被稱為「事件處理者」(Event Handler),也就是「啟動對話框的顯示」這個動作。 ``` 事件流程 (Event Flow) 指的就是 ==「網頁元素接收事件的順序」== 。 事件流程可以分成兩種機制: :::warning 事件冒泡 (Event Bubbling) 事件捕獲 (Event Capturing) ::: ``` c <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> ``` ==事件冒泡==指的是「==從啟動事件的元素節點開始,逐層往上傳遞==」,直到整個網頁的根節點,也就是 document,就像是==向上回報==。 ```htmlmixed= <!DOCTYPE html> <html> <head> <title>TITLE</title> </head> <body> <div>CLICK</div> </body> </html> ``` ``` c 「事件冒泡」的機制下,觸發事件的順序會是: 1. <div>CLICK</div> 2. <body> 3. <html> 4. document ``` ![](https://i.imgur.com/3cUoKz9.png) >[圖片來源](http://www.java2s.com/Book/JavaScript/DOM/Event_Flow_capture_target_and_bubbling.htm) Event Flow: capture, target, and bubbling --- 「事件冒泡」機制是由下往上來傳遞, ==「事件捕獲」(Event Capturing) 機制則正好相反==。 ``` c 「事件捕獲」的機制下,觸發事件的順序會是: 1. document 2. <html> 3. <body> 4. <div>CLICK</div> ``` ![](https://i.imgur.com/FAE4nda.png) >[圖片來源](http://www.java2s.com/Book/JavaScript/DOM/Event_Flow_capture_target_and_bubbling.htm) Event Flow: capture, target, and bubbling --- ### 事件是依賴哪種機制執行的? ==兩種都會執行。== ![](https://i.imgur.com/AruGovr.png) >[圖片來源](https://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-phases) Document Object Model Events --- ### 新手 100% 會搞錯的事件機制問題 假設有兩個按鈕 如果只有幫一個元素加了`EventListener` ,==只有第一個按鈕會有反應== ``` c <div> <button class='btn'>1</button> <button class='btn'>2</button> </div> <script> document.querySelector('.btn') .addEventListener('click',function(e){ alert(1) }) </script> //document.querySelector() 只會回傳一個元素 //只有幫一個元素加了EventListener 所以只有第一個按鈕會有反應 ``` #### 如何幫每一個元素都加`EventListener`? 用`querySelectorAll ` ``` c <div> <button class='btn' data-value='1'>1</button> <button class='btn' data-value='2'>2</button> <button class='btn' data-value='3'>3</button> </div> <script> const elements = document.querySelectorAll('.btn') for(var i=0;i<elements.length;i++){ elements[i].addEventListener('click',function(e){ console.log(e.target.grtAttribute('data-value')) }) } </script> //querySelectorAll回傳的是一個list,雖然不是陣列但可以當成陣列來用 //用grtAttribute() 拿到屬性 ``` #### 如何新增按鈕? ``` c <div> <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> <button class='btn' data-value='3'>3</button> </div> <script> let num = 3 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) }) //event delegation 事件代理,處理動態新增 document.querySelector('.outer').addEventListener('click',function(e){ if(e.target.classList.contains('btn')){ alert(e.target.getAttribute('data-value')) } }) </script> ``` --- #### 動態表單通訊錄 找到==點的按鈕的parent==,接著再刪掉要刪的;新增的話,新增一個row就可以了 ``` c <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> <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> ``` --- ### 什麼是 `event delegation`,為什麼我們需要它? >[參考資料](https://ithelp.ithome.com.tw/articles/10192015) 重新認識 JavaScript: Day 15 隱藏在 "事件" 之中的秘密 比較有效率 ==不用浪費很多function監聽每一個事件,可以處理動態新增==。 ```htmlmixed= <ul id="myList"> <li>Item 01</li> <li>Item 02</li> <li>Item 03</li> </ul> ``` 分別為 myList 的 li 綁定 click 事件,就要==使用 for 迴圈來一個個綁定==: ```javascript= // 取得容器 var myList = document.getElementById('myList'); // 分別為 li 綁定事件 if( myList.hasChildNodes() ) { for (var i = 0; i < myList.childNodes.length; i++) { // nodeType === 1 代表為實體 HTML 元素 if( myList.childNodes[i].nodeType === 1 ){ myList.childNodes[i].addEventListener('click', function(){ console.log(this.textContent); }, false); } } } ``` 若是再新增元素至 myList: ```javascript= // 取得容器 var myList = document.getElementById('myList'); // 分別為 li 綁定事件 if( myList.hasChildNodes() ) { for (var i = 0; i < myList.childNodes.length; i++) { // nodeType === 1 代表為實體 HTML 元素 if( myList.childNodes[i].nodeType === 1 ){ myList.childNodes[i].addEventListener('click', function(){ console.log(this.textContent); }, false); } } } // 建立新的 <li> 元素 var newList = document.createElement('li'); // 建立 textNode 文字節點 var textNode = document.createTextNode("Hello world!"); // 透過 appendChild 將 textNode 加入至 newList newList.appendChild(textNode); // 透過 appendChild 將 newList 加入至 myList myList.appendChild(newList); ``` ==後來才新增的 newList 節點並不會有 click 事件的註冊。== ``` 如果每次新增後要再重新 addEventListener 那就沒完沒了, 而且若是我們不斷地的去重覆監聽事件,又忘了移除監聽, 甚至可能會造成 memory leak 的嚴重問題。 ``` ==「事件指派」(Event Delegation)== 就會是比較好的做法: ```javascript= // 取得容器 var myList = document.getElementById('myList'); // 改讓外層 myList 來監聽 click 事件 myList.addEventListener('click', function(e){ // 判斷目標元素若是 li 則執行 console.log if( e.target.tagName.toLowerCase() === 'li' ){ console.log(e.target.textContent); } }, false); // 建立新的 <li> 元素 var newList = document.createElement('li'); // 建立 textNode 文字節點 var textNode = document.createTextNode("Hello world!"); // 透過 appendChild 將 textNode 加入至 newList newList.appendChild(textNode); // 透過 appendChild 將 newList 加入至 myList myList.appendChild(newList); ``` :::warning 把 click 事件改由外層的 myList 來監聽,利用事件傳遞的原理,判斷 e.target 是我們要的目標節點時,才去執行後續的動作。 ::: ### `event.preventDefault()&event.stopPropagation()` >[參考資料](https://ithelp.ithome.com.tw/articles/10192015) 重新認識 JavaScript: Day 15 隱藏在 "事件" 之中的秘密 `e.preventDefault`與`e.stopPropagation`的差別在 ==前者就只是取消預設行為==,跟事件傳遞沒有任何關係,==後者則是讓事件不再往下傳遞==。 #### `阻擋預設行為 event.preventDefault()` 常用在 a連結的href、Form表單的submit 上,有時可能只是想觸發呼叫Function,而==不想使用到原生附與的功能的話==,就可利用 `preventDefault `達成此需求。 ```javascript= var list = document.querySelector('a'); list.addEventListener('click',function(e){ e.preventDefault(); //取消預設觸發行為 /* 撰寫你的Code */ }) ``` ###### `範例` ```html <a id="link" href="https://www.google.com">Google</a> ``` 假設今天點擊這個 link 時,希望瀏覽器執行 `console.log('Google!')` ```javascript= var link = document.querySelector('#link'); link.addEventListener('click', function (e) { console.log('Google!'); }, false); ``` 點擊這個 link 的時候,瀏覽器依然會帶去 google 的網頁 這時候就可以利用「事件物件」 event 提供的 `event.preventDefault() `方法: ```javascript= var link = document.querySelector('#link'); // 在 evend handler 加上 e.preventDefault(); link.addEventListener('click', function (e) { e.preventDefault(); console.log('Google!'); }, false); ``` 再試著點擊 link 一次,會發現瀏覽器預設的跳轉頁面的行為不見了, `console.log('Google!')`; 也可順利執行。 ==`event.preventDefault()` 並不會阻止事件向上傳遞 (事件冒泡)== 。 :::warning 在 `event handler function` 的最後加上 `return false`; 也會有 `event.preventDefault()` 的效果,但不可以加在前面 ::: #### 阻擋事件冒泡傳遞 `event.stopPropagation()` 有時只是想==單純針對單一元素監聽==,不想因為事件冒泡的行為,而去觸發到其他元素,這時就可利用 `stopPropagation `來達成此需求。 ###### `範例` ```html <label class="lbl"> Label <input type="checkbox" name="chkbox" id="chkbox"> </label> ``` 分別為 label 與 checkbox 註冊 click 事件來實驗: ```javascript= // label var lbl = document.querySelector('.lbl'); // chkbox var chkbox = document.querySelector('#chkbox'); lbl.addEventListener('click', function (e) { console.log('lbl click'); }, false); chkbox.addEventListener('click', function (e) { console.log('chkbox click'); }, false); ``` `console.log('lbl click')`; 執行了兩次 ```javascript= "lbl click" "chkbox click" "lbl click" ``` ``` 因為在 label 標籤包覆 checkbox 的情況下,我們去點擊了 label 觸發 click 事件, 此時瀏覽器會自動把這個 click 事件帶給 checkbox。 checkbox 受到事件冒泡的影響,又會再度把 click 事件傳遞至 label 上, 導致 "lbl click" 出現了兩次。 ``` 如果要修正 label 的 click 觸發兩次的錯誤行為時,可以這樣做: ```javascript= // label var lbl = document.querySelector('.lbl'); // chkbox var chkbox = document.querySelector('#chkbox'); lbl.addEventListener('click', function (e) { console.log('lbl click'); }, false); // 阻擋 chkbox 的事件冒泡 chkbox.addEventListener('click', function (e) { e.stopPropagation(); }, false); ``` :::warning stopPropagation() 不是掛在 label ,而是要放在 checkbox 上才有效! ::: --- ### 瀏覽器的資料儲存 #### `Cookie` * Cookie就是一個==小型的文字檔==,會自動帶到server * ==可以用javascript把資料寫到cookie==,server也可以透過HTTP的response把資料庫寫到cookie * ==所有的request都會自動把瀏覽器的cookie帶上去==,為了讓瀏覽器可以辨識身份 #### `local storage` >[參考資料](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453) [JavaScript] localStorage 的使用 >[參考資料](https://developer.mozilla.org/zh-TW/docs/Web/API/Window/localStorage) Window.localStorage >[參考資料](https://kanboo.github.io/2017/12/30/JS-studynotes/) JS-基礎用法 * 伺服器跟瀏覽器的溝通 瀏覽器的全域物件提供給我們`localStorage`的用法,就像是瀏覽器提供的api, ==儲存的是key、value== ,跟物件很像! ``` c { key:'value' } // 只能存字串 ``` :::info ==localstorage 只能保存 string 資料==,當資料非字串型態時怎麼辦?? 解決在資料轉換過程中的問題,又或者想要讓儲存的資料不侷限在 ==「字串」的格式==,則可以==透過 `JSON.stringify() `方法==,將要儲存的資料轉換為 JSON 格式的字串;要==取出資料時,再透過 `JSON.parse()` 方法==,將資料轉換回原本的格式 ```javascript= var country = [ {farmer:'ruofan'} ]; //儲存 var countryString= JSON.stringify(country); // 轉字串 console.log(countryString); localStorage.setItem('countryItem',countryString); //讀取 var getData = localStorage.getItem('countryItem'); var getDataAry = JSON.parse(getData); // 轉array console.log(getDataAry[0].farmer); ``` ::: ###### `儲存` ```javascript= localStorage.setItem('countryItem',countryString); ``` ###### `讀取` ```javascript= localStorage.getItem('countryItem'); ``` 伺服器也可以設定cookie ``` c <body> <div class='app'> <input class="text"/><button>儲存</button> </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> ``` #### `session storage` * session是指一段期間,==用法跟localStorage一樣==。 * 差別在不同的分頁沒辦法共用session storage; 關了原本的分頁session storage也沒了 * ==只存在原本的分頁還開啟的狀態==,應用的範圍又更小了 * 通常拿來==存沒有想要長期保留的資訊== :::warning * `localStorage`可以存更長更久 * `session storage`用在短時間的資訊儲存 ::: --- ### `escape-html` >[參考資料](https://github.com/component/escape-html) component/escape-html >[參考資料](https://gist.github.com/hraynaud/5035148) escape-html-in-js ###### `舉例 hw3-todolist` ![](https://i.imgur.com/9wSGQkh.gif) :::warning 加上 function escapeHtml ```javascript= // 當輸入html標籤時,會印出標籤 function escapeHtml (unsafe) { return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } ``` ::: ![](https://i.imgur.com/goQoh7N.gif) --- >[參考資料](https://javascript.info/forms-submit) Forms: event and method submit >[參考資料](https://eloquentjavascript.net/2nd_edition/18_forms.html) Forms and Form Fields >[參考資料](https://www.tutorialrepublic.com/codelab.php?topic=javascript&file=form-validation) codeLad >[參考資料](https://www.tutorialrepublic.com/javascript-tutorial/javascript-form-validation.php) JavaScript Form Validation >[參考資料](https://www.javascripttutorial.net/javascript-dom/javascript-getattribute/) JavaScript getAttribute >[參考資料](https://www.the-art-of-web.com/javascript/validate/) JavaScript: Form Validation >[參考資料](https://stackoverflow.com/questions/61358620/display-message-after-form-submit-with-javascript) [stackFlow] Display message after form submit with javascript >[參考資料](https://www.youtube.com/watch?v=rsd4FNGTRBw) JavaScript Client-side Form Validation >[參考資料](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) Client-side form validation

    Import from clipboard

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lost their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template is not available.
    All
    • All
    • Team
    No template found.

    Create a template

    Delete template

    Do you really want to delete this template?

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in via Google

    New to HackMD? Sign up

    Help

    Documents

    Tutorials
    YAML Metadata
    Slide Example
    Book Example

    Contacts

    Talk to us
    Report an issue
    Send us email

    Cheatsheet

    Example Syntax
    Header # Header
    • Unordered List
    - Unordered List
    1. Ordered List
    1. Ordered List
    • Todo List
    - [ ] Todo List
    Blockquote
    > Blockquote
    Bold font **Bold font**
    Italics font *Italics font*
    Strikethrough ~~Strikethrough~~
    19th 19^th^
    H2O H~2~O
    Inserted text ++Inserted text++
    Marked text ==Marked text==
    Link [link text](https:// "title")
    Image ![image alt](https:// "title")
    Code `Code`
    var i = 0;
    ```javascript
    var i = 0;
    ```
    :smile: :smile:
    Externals {%youtube youtube_id %}
    LaTeX $L^aT_eX$

    This is a alert area.

    :::info
    This is a alert area.
    :::

    Versions

    Versions and GitHub Sync

    Sign in to link this note to GitHub Learn more
    This note is not linked with GitHub Learn more
     
    Add badge Pull Push GitHub Link Settings

    Version named by    

    More Less
    • Edit
    • Delete

    Note content is identical to the latest version.
    Compare with
      Choose a version
      No search result
      Version not found

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub

        Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

         Sign in to GitHub

        HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully