程式導師實驗計畫 FE102


Posted by YongChenSu on 2020-12-08

什麼是 DOM?

DOM 全名為 Document Object Model,中文譯名為文件物件模型,把 HTML 文件內的各個標籤,包括文字、圖片等等都定義成物件,而這些物件最終會形成一個樹狀結構,圖示如下:

許多公司有設計瀏覽器,故需要一個讓各大瀏覽器都能編譯的規範,而 W3S 定義了許多規範,DOM 是其中一個。

事件傳遞機制的順序是什麼;什麼是冒泡,什麼又是捕獲?

  • 捕獲階段CAPTURING_PHASE
    由 DOM 樹的最外層依序向內,過程中觸發個別元素的捕獲階段事件監聽。
  • 目標階段AT_TARGET
    到達事件目標,依照註冊順序觸發事件監聽。
  • 冒泡階段BUBBLING_PHASE
    由事件目標依序向外,過程中觸發個別元素的冒泡階段事件監聽。



什麼是 event delegation,為什麼我們需要它?

利用事件的冒泡機制 (Event Bubbling),把子節點們的事件統一處理,可避免掛載過多的監聽器。

event.preventDefault() 跟 event.stopPropagation() 差在哪裡,可以舉個範例嗎?

  • event.preventDefault():阻止網頁預設事件。
  • event.stopPropagation():阻止當前事件繼續捕捉及冒泡階段的傳遞,。

範例如下:
preventDefault & stopPropagation

什麼是 callback funciton?

const element = document.querySelector('#block')
element.addEventListener('click', function(e) {
   console.log(e.target)  
})


e.target 可拿到上點了哪個元素

const element = document.querySelector('#block')
element.addEventListener('click', function(e) {
   console.log(e.target)  
})


toggle

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>toggle practice</title>
</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', (e) => {
    document.querySelector('body').classList.toggle('active')
  })
  </script>
</body>
</html>
<style>
  .active {
    background-color: red;
  }
</style>


stopPropagation

  • stopPropagation
  • stopImediatePropagation

delegation 事件代理機制

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>eventMechanism</title>
</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>
    // 事件代理機制 delegation
    let num = 3

    document.querySelector('.add-btn').addEventListener('click', (e) => {
      const btn = document.createElement('button')
      btn.setAttribute('data-value', num)
      btn.classList.add('btn')
      btn.innerText = num
      num += 1
      document.querySelector('.outer').appendChild(btn)
    })

    document.querySelector('.outer').addEventListener('click', (e) => {
      if (e.target.classList.contains('btn')) {
        alert(e.target.getAttribute('data-value'))
      }
    })
  </script>
</body>
</html>


簡易密碼產生器

建立 function

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <style>
    body {
      font-family: '微軟正黑體';
      font-size: 40px;
    }

    .result {
      background-color: lightcyan;
    }
  </style>
  <div class="app">
    <div>
      <label for="english">
        <input type="checkbox" id="english" name="english" />英文
      </label>
    </div>
    <div>
      <label for="number">
        <input type="checkbox" id="number" name="number" />數字
      </label>
    </div>
    <div>
      <label for="special"">
        <input type="checkbox" id="special" name="special" />特殊符號
      </label>
    </div>
    <button class="btn-generate">產生</button>
    <div class="result"></div>
  </div>
  <script>
    function getChar(name, char) {
      if (document.querySelector('input[name=' + name + ']').checked) {
        return char
      }
    }
    const BTN = document.querySelector('.btn-generate')
    BTN.addEventListener('click', () => {
      let availableChar = ''
      availableChar += getChar('english', 'abcdefghijklmnopqrstuvwxyz')
      availableChar += getChar('number', '0123456789')
      availableChar += getChar('special', '!@#$%^&*(')

      let result = ''

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

      document.querySelector('.result').innerHTML = result
    })


  </script>
</body>
</html>

將密碼組成字元放在 html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <style>
    body {
      font-family: '微軟正黑體';
      font-size: 40px;
    }

    .result {
      background-color: lightcyan;
    }
  </style>
  <div class="app">
    <div>
      <label for="english">
        <input type="checkbox" id="english" name="english" data-char="abcdefghijklmnopqrstuvwxyz" />英文
      </label>
    </div>
    <div>
      <label for="number">
        <input type="checkbox" id="number" name="number" data-char="0123456789" />數字
      </label>
    </div>
    <div>
      <label for="special"">
        <input type="checkbox" id="special" name="special" data-char="!@#$%^&*(" />特殊符號
      </label>
    </div>
    <div>
      <label for="chi"">
        <input type="checkbox" id="chi" name="chi" data-char="你是八七" />你是八七
      </label>
    </div>
    <button class="btn-generate">產生</button>
    <div class="result"></div>
  </div>
  <script>
    const BTN = document.querySelector('.btn-generate')
    BTN.addEventListener('click', () => {
      let result = ''
      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')
        }
      }

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

      document.querySelector('.result').innerHTML = result
    })


  </script>
</body>
</html>


簡易動態 表單通訊錄

closest() 找最近的一層

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      font-size: 32px;
    }
  </style>
</head>
<body>
  <div class="app">
    <div>
      <button class="add-btn">新增聯絡人</button>
    </div>
    <div class="contacts">
      <div class="row">
        姓名: <input type="text" name="name">
        電話:<input type="text" name="phone">
        <button class="delete">刪除</button>
      </div>
    </div>
  </div>
  <script>
    const addBTN = document.querySelector('.add-btn')

    addBTN.addEventListener('click', (e) => {
      const contactRow = document.createElement('div')
      contactRow.classList.add('row')
      contactRow.innerHTML =  `
        姓名: <input type="text" name="name">
        電話:<input type="text" name="phone">
        <button class="delete">刪除</button>
      `
      document.querySelector('.contacts').appendChild(contactRow)
    })

    document.querySelector('.contacts').addEventListener('click', (e) => {
      if (e.target.classList.contains('delete')) {
        document.querySelector('.contacts').removeChild(e.target.closest('.row'))
      }
    })
  </script>
</body>
</html>


cookie 是什麼?

cookie 就是小型文字檔,會自動帶到 server。
某些網站為了辨別用戶端的身分,存在用戶端的資料。

localStorage

  • setItem()
  • getItem()
  • removeItem()
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<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', () => {
      const value = document.querySelector('.text').value
      window.localStorage.setItem('text', value)
    })
  </script>
</body>
</html>


sessionStorage

只要將上面那段程式碼中的 localStorage 改成 sessionStorage 即可。

差別在於 sessionStorage 新開分頁無法儲存資料。

在 node.js 與 瀏覽器上用 JS 的差異?


request 與 response 會被瀏覽器限制,故要了解瀏覽器上的規則。

交換資料的第一種方式:form

把資料從前端傳到後端的第一種方式,缺點是會換頁面。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      font-size: 40px;
    }
  </style>
</head>
<body>
  <div class="app">
    <form action="https://google.com/" method="GET">
      username: <input type="text" name="username" />
      <input type="submit">
    </form>
  </div>
</body>
</html>


交換資料的第二種方式:ajax

透過 JS 交換資料,AJAX (Asynchronous JavaScript and XML)

任何非同步跟伺服器交換資料的 JS 都可統稱為 AJAX。

XML 是早期常用的資料格式,現今用 JSON,ajax 還是叫 ajax,但不是 XML。

  const request = new XMLHttpRequest()
    request.onload = function () { // 當 request 拿到結果之後觸發 onload 事件
      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()

因瀏覽器的限制,我們無法發 request 到 google.com,而另一個網頁 () 可以,意思是用 JS 發一個 request

request.responseText

const request = new XMLHttpRequest()
    request.onload = function () { // 當 request 拿到結果之後觸發 onload 事件
      if (request.status >= 200 && request.status < 400) {
        console.log(request.responseText)
      } else {
        console.log(request.status, request.responseText)
      }
    }

    request.onerror = function () {
      console.log('error')
    }

    request.open('GET', 'https://reqres.in/api/users/1242435353', true) // 第三個參數決定是否要非同步
    request.send()
    // 404 "{}"


Same Origin Policy

  • 不同網域即不是同源。
  • 有無加密 (http, https)、協定不同,埠號不同、主機不同,皆是不同源
  • 除非共用同一個 domain,否則別人的網站就是不同源。



跨來源資源共用 (CORS)

cross-origin resource share

如果想要跨來源存取,在 reponse 裡面要加上 header:access-control-allow-origin,這個裡面的內容決定哪些來源的人可以存取。

這個來源是當我們發 request 的時候,瀏覽器會自動在 Request Headers 加上一個 header,origin:,server 端根據 header 的內容 (來源),決定是否給予權限。

  • access-control-allow-origin: *
    所有人皆可存取。

瀏覽器的限制

瀏覽器的許多限制幾乎是「安全性考量」。
若利用 node.js 來發 request,因沒有透過瀏覽器,故也沒有限制,可拿到資料。

交換資料的第二種方式:JSONP

JSON with padding,回傳的資料是 JS object,在 server 端做填充,在 client 端可利用 funciton 拿到資料。

利用 script 標籤不受同源政策規範的特性,在 src 裡寫 js

<script>
  function setData(users) {
    console.log(users)
  }
</script>

<script src="https://test.com/user.js"></script>

// 可動態產生 script
const element = document.createElement('script')
element.src = 'https://test.com/user.js?id=1'

// user.js

setData(
  {
    id: 1,
    name: 'hello'
  }
)
  • 以下標籤不受同源政策影響:
    • img
    • 可引入其他 JS
  • AJAX 一定受同源政策管理

單向傳送資料

應用 email 開信的追蹤、廣告的追蹤。

在 email 上放一張透明的圖片,當使用 email,圖片顯示出來後發一個 request 到該網址,server 端知道有人打開該網址,也就是知道有人打開這封信。

<img width="1" heihgt="1" src="https://example.com/users_open/123456"></img>


實作:抓取資料並顯示

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      font-size: 38px;
    }

    .profile {
      border: 1px solid #000;
      display: inline-flex;
      margin: 20px;
      align-items: center;
    }
    .profile__name {
      margin: 0 20px;
    }
  </style>
</head>

<body>
  <div class="app"></div>

  <script>
    const request = new XMLHttpRequest()
    const container = document.querySelector('.app')

    request.onload = function () { // 當 request 拿到結果之後觸發 onload 事件
      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}" alt="">
          `
          container.appendChild(div)
        }



      } else {
        console.log(request.status, request.responseText)
      }
    }

    request.onerror = function () {
      console.log('error')
    }

    request.open('GET', 'https://reqres.in/api/users', true) // 第三個參數決定是否要非同步
    request.send()
  </script>
</body>

</html>

render 的結果:

內容產生的地方在 client 還是 server?

  • clinet side rendering
    用 JS render 頁面,JS 動態產生頁面。

    缺點是:通常搜尋引擎不會幫忙執行 JS,故搜尋引擎看不到東西

總結

用 JS 改變介面、利用 JS 事件監聽、跟 server 交換資料。

參考資源


#程式導師實驗計畫第四期 #前端 #FE201







Related Posts

How does Event Loop work?

How does Event Loop work?

【Day07】透過Github將個人網頁上架至Netlify靜態網站服務

【Day07】透過Github將個人網頁上架至Netlify靜態網站服務

Python 程式設計入門共學營學習計劃

Python 程式設計入門共學營學習計劃


Comments