[FE102] Part 1


Posted by yymarlerr on 2021-05-27

在瀏覽器上執行 JavaScript 的方式

  • 在 HTML 檔案裡面,用 <script></script> 的標籤,裡面包 JavaScript 的程式碼,如果 <script><body> 的前面,可以用 document.addEventListener("DOMContentLoaded", function(){}) 先去解析 body 裡面的標籤(DOM)
  • 或用 <script src= './demo.js'></script>,並將程式碼也在 demo.js 這個檔案中
    • ./ 是相對路徑的意思,表示皆為在同個資料夾下的檔案

DOM (Document Object Model)

簡介

  • 把 document 轉換成物件,物件會變成樹狀結構,樹狀結構由節點(node)組成,像是文字或標籤。瀏覽器提供 DOM 作為橋樑,讓 JavaScript 改變畫面上的東西。JavaScript 可以透過 DOM 拿到某個物件(元素),並針對該物件作改變。

如何選擇想要的元素

程式介紹

getElementsByTagName

  • <script> 如果在 hello 的上面,會跑不出來,因為瀏覽器是由上至下跑程式碼,用 dev tool 會顯示空陣列:
  • <script> 移到下面後:
<body>
  <div>
    hello
  </div>
  <script>
    const elements = document.getElementsByTagName('div')
    console.log(elements)
  </script>
</body>

  • 也可以印出特定元素
<body>
  <div>
    hello
  </div>
  <div>
    哩後
  </div>
  <script>
    const elements = document.getElementsByTagName('div')
    console.log(elements[1])
  </script>
</body>

getElementsByClassName

  • 範例:
<body>
  <div class='block'>
    hello
  </div>
  <div>
    哩後
  </div>
  <script>
    const elements = document.getElementsByClassName('block')
    console.log(elements)
  </script>
</body>

getElementById

  • 後面接 ID

querySelector

  • 後面接 CSS 的 selector
  • 只會選到一個元素
<body>
  <div class='block'>
    hello
  </div>
  <div>
    哩後
  </div>
  <script>
    const elements = document.querySelector('.block')
    console.log(elements)
  </script>
</body>

querySelectorAll

  • 會選到多個符合的元素,用類似陣列的形式表示

改變 CSS

  • 後面不能接 -,所以要改成駝峰式寫法:
 <script>
    const element = document.querySelector('.block2')
    element.style.paddingTop = '30px'
  </script>
  • 也可以用中括號:
 <script>
    const element = document.querySelector('.block2')
    element.style['padding-top'] = '30px'
  </script>
  • 不過一般來說,不會將 style 直接寫在 script 裡面,因為這樣程式碼會很長,通常會寫在另一個 class 裡

改變元素的 class

.classList.add('')

  • 新增 class,若要新增多個 class,可直接放在後面接 class 名稱,前面不需要加 . ("class1", "class2"...)
<head>
 <style>
    .decor {
      background: blue;
    }
  </style>
</head>

<body>
  <div class='block2'>
    hello
  </div>
  <script>
    const element = document.querySelector('.block2')
    element.classList.add('decor')
  </script>
</body>

.classList.remove('')

  • 去掉元素 class

.classList.toggle('')

  • 若元素原本有該 class 則 remove;原本無則 add

.classList.contains('')

  • 判斷是否有這個 class,有的話 console.log 會印出 true

改變內容

  • element.innerText = ''

innerText

  • 標籤中的文字內容

innerHTML

  • 標籤裡面的所有內容

outerHTML

  • 標籤和標籤裡所有的內容

插入與刪除元素

removeChild()

  • 要刪除元素要知道 parent 是誰,所以要先找到母元素,再從母元素中 remove 子元素,也可以用 子元素.parentNode 來找到母元素
<body>
  <div id="block">
    Good morning
    <a>My name is Apple</a>
  <div id="block2">
    hello
  </div>
  <div>
    哩後
  </div>
  <script>
    const element = document.querySelector("#block")
    element.removeChild(document.querySelector('a'))
  </script>
</body>

appendChild()

  • 利用 document.createElement 新增標籤或 document.createTextNode 新增文字
 <script>
    const element = document.querySelector("#block")
    const item = document.createElement('div')
    item.innerText = 'Stay calm'
    element.appendChild(item)
  </script>
 <script>
    const element = document.querySelector("#block3")
    const item = document.createTextNode('Heyyyy')
    element.appendChild(item)
  </script>

參考資料

JavaScript 網頁事件處理

addEventListener('<事件>', function)

  • 事件監聽,針對事件做出反應
 <script>
    const element = document.querySelector("#block3")
    element.addEventListener('click', onclick)

    function onclick() {
      alert('click')
    }

  </script>
  • 或用匿名函式(和 callback function 無關係)
  <script>
    const element = document.querySelector("#block3")
    element.addEventListener('click', function() {
      alert('click!')
    })
  </script>

callback function

  • 跟瀏覽器說,當⋯⋯時,幫我呼叫這個 function,幫我呼叫就是 callback 的意思。根據 MDN:回呼函式(callback function)是指能藉由參數(argument)通往另一個函式的函式。它會在外部函式內調用、以完成某些事情。

event(e)

  • e 為瀏覽器所提供的參數(名稱可自己取),用 console.log(e) 可以印出事件的資訊。
 <script>
    const element = document.querySelector("#block3")
    element.addEventListener('click', onclick)

    function onclick(e) {
      alert('click')
    }

  </script>
<body>
  <div id="block3">
    Good morning
    <button class="change-btn1">點我</button>
  </div>
  <div id="block2">
    hello
    <a>My name is Apple</a>
  </div>
  <div>
    哩後
  </div>
  <script>
    const element = document.querySelector("#block3")
    element.addEventListener('click', function(e) {
      document.querySelector("#block3").classList.toggle("decor2")
    })
  </script>
  • 用 console.log(e.key),可以印出自己點了哪個元素
<body>
  <div id="block3">
    Good morning
    <button class="change-btn1">點我</button>
    <input />
  </div>
  <script>
    const element = document.querySelector("input")
    element.addEventListener('keydown', function(e) {
      console.log(e.key)
    })
  </script>
</body>

onSubmit

  • 表單處理:在點擊 submit 之後,發生事件,通常用在表單驗證上面
  • 範例,method 預設值為 GET:

    <body>
    <form class="log-in" method="GET" Action="">
      <div>
        username<input type="name" />
      </div>
      <div>
        password<input name="password1" type="password" />
      </div>
      <div>
        confirm password<input name="password2" type="password" />
      </div>
      <div>
        <input name="submit" type="submit" value="送出" />
      </div>
    </form>
    <script>
      const element = document.querySelector(".log-in")
      element.addEventListener('submit', function(e) {
        const input1 = document.querySelector("input[name=password1]")
        const input2 = document.querySelector("input[name=password2]")
        if (input1.value !== input2.value) {
          alert("密碼輸入錯誤")
          e.preventDefault()
        }
      })
    </script>
    </body>
    
    • e.preventDefault() 為阻止瀏覽器預設的行為,在這邊的話就是指「阻止表單被送出」的這個行為
    • .value 可以拿到值

e.preventDefault()

  • 範例,事件 keypress 按下按鍵:
 <script>
    const element = document.querySelector("input[name=username]")
    element.addEventListener('keypress', function(e) {
      if (e.key === 'e') {
        e.preventDefault()
      }
    })
  </script>

瀏覽器事件傳遞機制

事件發生的順序:

  1. 從 window 開始由上往下傳,這個 phase 為 capturing phase
  2. 到點擊的元素後,開始進入 target phase
  3. 再從點擊的元素由下往上傳,這個 phase 為 bubbling phase
  • addEventListner('<事件>', function, 'boolean') 布林值是 true,代表事件會在捕獲階段執行;布林值是 false,代表事件在冒泡階段執行;預設值為 false
  • 舊版的 Chrome,在進入 target phase 之後,冒泡和捕獲的觸發會根據 EventListner 的排序;新版的則更改了此行為,可參考此篇文章
  • e.preventDefault 會一直傳下去,所以整條鏈都會執行 preventDefault

參考文章

e.stopPropagation

  • 阻止事件繼續傳遞
<body>
  <div class="outer">
    outer
    <div class="inner">
      inner
      <div class="btn">
        <input class="btn" type="button" value="btn" />
      </div>
    </div>
  </div>
  <script>
    const btn1 = document.querySelector(".outer")
    btn1.addEventListener("click", function(e) {
      e.stopPropagation()
      console.log("click 1", "捕獲")
    })

    btn1.addEventListener("click", function(e) {
      console.log("click 2", "捕獲")
    })
  </script>
</body>

  • e.stopImmediatePropagation 立即阻止事件傳遞
<body>
  <div class="outer">
    outer
    <div class="inner">
      inner
      <div class="btn">
        <input class="btn" type="button" value="btn" />
      </div>
    </div>
  </div>
  <script>
    const btn1 = document.querySelector(".outer")
    btn1.addEventListener("click", function(e) {
      e.stopImmediatePropagation()
      console.log("click 1", "捕獲")
    })

    btn1.addEventListener("click", function(e) {
      console.log("click 2", "捕獲")
    })
  </script>
</body>

Event Delegation

  • 須注意迴圈裡面變數的作用域
<body>
  <div class="outer">
    <button class="btn" data-value="1">1</button>
    <button class="btn" data-value="2">2</button>
  </div>

  <script>
    const elements = document.querySelectorAll(".btn")
    for (let i = 0; i < elements.length; i++) {
      elements[i].addEventListener("click", function() {
        alert(i+1)
      })
    }

  </script>
</body>

會顯示

  • 如果宣告變數用 var 時,在 click 的時候會 alert 3,因為匿名函式是在點擊後才開始作用,而這時迴圈已經跑到最後一個 i 了

  • 這樣的情況下可以用:

    • data-value 存資料,value 為屬性,是自訂的,所以 dash 後面放什麼都可以,例如,也可以更名為 data-content
    • getAttribute() 可以拿到屬性
    <body>
        <div class="outer">
          <button class="btn" data-value="1">1</button>
          <button class="btn" data-value="2">2</button>
        </div>
    
        <script>
          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"))
            })
          }
        </script>
    </body>
    
  • 新增按鈕

    • setAttribute("<屬性>", <值>):加屬性名稱、值
    • .classList.add("<class 名稱>"):加 class 名稱
    • 因為 EvenListner 是加在 1、2 按鈕上,所以後來動態新增的按鈕不會 alert 數字

      <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>
      var 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("date-value", num)
        btn.classList.add("btn")
        btn.innerText = num
        num++
        document.querySelector(".outer").appendChild(btn)
      })
      </script>
      
    • 利用冒泡的特性(不管有沒有加 even listener 都會持續冒泡),點擊最內層也會觸發到最外層,所以可直接在 outer 加 EventListner,這就叫做 event delegation 「事件代理」

    <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>
          var 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)
          })
    
          document.querySelector(".outer").addEventListener("click",
          function(e) {
            if (e.target.classList.contains("btn")) {
              alert(e.target.getAttribute("data-value"))
            }
          }
          )
        </script>
    </body>
    

實戰練習

密碼產生器

<head>
  <meta charset='utf-8'>
  <title>範例</title>
  <meta name='viewport' content='width=device-width, initial-scale=1'/>
  <style>
    .app {
      background: beige;
      width: 700px;
      height: 200px;
      position: relative;
      font-size: 32px;
    }

    .result {
      background: springgreen;
      width: 700px;
      height: 100px;
      position: absolute;
    }

    button {
      width: 50px;
      height: 50px;
      background: pink;
    }



  </style>
</head>

<body>
  <div class="app">
    <div>
      <input type="checkbox" id="en" /><label for="en">英文</label>
    </div>
    <div>
      <input type="checkbox" id="number" /><label for="number">數字</label>
    </div>
    <div>
      <input type="checkbox" id="symbol" /><label for="symbol">符號</label>
    </div>

    <button class="btn-generate">產生</button> 
    <div class="result"></div>

  </div>


  <script>
    const element = document.querySelector(".btn-generate")
    element.addEventListener("click", 
      function() {
        let availableChar = ""
        if (document.querySelector("#en").checked) {
          availableChar += "abcdefghijklmnopqrstuvwxyz"
        }

        if (document.querySelector("#number").checked) {
          availableChar += "0123456789"
        }

        if (document.querySelector("#symbol").checked) {
          availableChar += "!@#$%^&()/"
        }

        let result = ""
        for (let i = 0; i < 10; i++) {
          num = Math.floor(Math.random() * availableChar.length)
          result += availableChar[num]
        }

        document.querySelector(".result").innerText = result
      }
    )
  </script>
</body>
  • 也可以簡化用 function 簡化:
<script>
    function getChar(id, char) {
      if (document.querySelector(id).checked) {
        return char
      } else {
        return " "
      }
    }
    const element = document.querySelector(".btn-generate")
    element.addEventListener("click", 
      function() {
        let availableChar = ""

        availableChar += getChar("#en", "abcdefghijklmnopqrstuvwxyz")
        availableChar += getChar("#number", "0123456789")
        availableChar += getChar("#symbol", "!@#$%^&()/")

        let result = ""
        for (let i = 0; i < 10; i++) {
          num = Math.floor(Math.random() * availableChar.length)
          result += availableChar[num]
        }

        document.querySelector(".result").innerText = result
      }
    )
  </script>

動態表單通訊錄

  • document.querySelector(".contacts").addEventListener("click", ..... 這邊利用事件代理,所以選 .contacts
  • .closest():根據 MDN,closestElement is the Element which is the closest ancestor of the selected element.
<body>
  <div class="app">
    <div>
      <button class="add">新增</button>
    </div>
    <div class="contacts">
      <div class="row">
        姓名:<input type="text" />
        電話:<input type="text" />
        <button class="delete">刪除</button>
      </div>
    </div>
  </div>
  <script>
    document.querySelector(".add").addEventListener("click", 
    function() {
      const parent = document.querySelector(".contacts")
      const newRow = document.createElement("div")
      newRow.classList.add("row")
      newRow.innerHTML = `
      姓名:<input type="text" />
      電話:<input type="text" />
      <button class="delete">刪除</button>
      `
      parent.appendChild(newRow)

    }
    )

    document.querySelector(".contacts").addEventListener("click", 
    function(e) {
      if (e.target.classList.contains("delete")) {
        document.querySelector(".contacts").removeChild(
          e.target.closest(".row")
        )
      }
    })


  </script>
</body>









Related Posts

CSS-[pseudo-classes]-偽類家族

CSS-[pseudo-classes]-偽類家族

Laravel x Vue Conf TW 2022

Laravel x Vue Conf TW 2022

[Py 百日馬 Day 1] Hello World

[Py 百日馬 Day 1] Hello World


Comments