[ 學習筆記系列 ] 網頁本質 (三) - JavaScript 篇


Posted by ClayGao on 2020-03-24

前言

繼之前的學習筆記中,以 JavaScript 來做程式入門之後,很快的又要學習 JavaScript 於瀏覽器中是如何運作的。

另外,同樣在前幾周的學習中,操作 JavaScript 的環境都是在 node.js,如今轉換至瀏覽器,想當然爾一些 API 會與在 node.js 操作 JavaScript 不同,比如說讀取作業系統的 os,與讀取檔案的 fs 在瀏覽器中就看不到,但還是有一些相同的 API,比如說 setTimeout 等等。

理解兩者的分別,可以讓我們更明白 JavaScript 之於執行環境的關係,也讓我們理解瀏覽器不過也就是一個程式而已。

瀏覽器中的 JavaScript

  • Javascript

    瀏覽器中的 JavaScript,意思就是 JavaScript 跑在瀏覽器的 JavaScript 引擎上 (比如說你用的是 Chrome,那麼這個 JavaScript 引擎就會是 V8),JavaScript 引擎負責解析並執行瀏覽器中的 JavaScript 語言。

    而在資料傳輸的部分,JavaScript 會藉由瀏覽器發出 request 給 Server,Server 再回傳 response 給瀏覽器,若是 AJAX,則由瀏覽器中的 JavaScript 接收 response。

    若要講得更精確一點,瀏覽器與發送 request 與瀏覽器接收 response 中間,還有作業系統的幫忙,但作業系統不在本次的討論範圍之中,回歸正題。

    因為前後端資料的交換,需要透過瀏覽器,自然會受到一些限制,如同源政策。


    我們可以先探討用 Html 與 CSS 編撰的靜態網頁,以 JavaScript 改變預設的 DOM 元素,做法是先使用 JavaScript 元素選取標籤,再使用語法修改。

選取 DOM 元素,語法以函式類別 document 為開頭,如 document.querySelector()

  • getElementById

    抓所屬 id 的元素,只會有一個。很好理解,因為 id 也只會有一個。

  • getElementsByTagName

    抓所屬標籤名稱的元素,但是一個集合 ( 很像陣列的集合 ),注意語法中的 Elements 是複數型態

  • getElementsByClassName

    抓所屬類別名稱的元素,與上一個抓取標籤一樣是個集合,語法中的 Elements 也是複數型態

  • querySelector

    可以選取上述三種,而選取內容和 CSS 選取相同,id 前面加「#」,class 前面加 「.」,標籤直接輸入就好

  • querySelectorAll

    即 querySelector 抓取複數的版本,由於是抓取複數,所以所抓取的也是一個似陣列的集合,需要注意

    需要注意的是,上述 ( ) 內的值皆要用 " " 包起來

  • 選取之後

    • innerText

      選取該元素內中的文本

    • innerHTML

      選取該元素內中的所有文本與 HTML

    • outerHTML

      選取包含該元素的所有文本與 HTML

  • 選取之後並修改屬性

    • setAttribite("屬性","value")
  • 選取之後並加入或移除 CSS

    • classList.add() : 加入該 CSS

    • classList.remove() : 移除該 CSS

    • classList.toggle() : 若已經有該 class 樣式則套用,否則移除,可以理解成開關

  • 選取之後插入或移除標籤元素

    通常在插入元素時,要先宣告一個變數儲存創建出來的標籤

    • createElement() : () 為標籤種類,如 div / span / img

    • createTextNode() : () 為文本,如 "安安你好"

    • appendChild() : 從父元素內部尾端插入該元素

    • prendChild() : 從父元素內部前端插入該元素

    • removeChild() : 從父元素內部移除該元素

    const item = document.createElement('div') // 先創造一個元素
    element.appendChild(item) //將此元素插入
    
  • 重要 : 選取之後加入監聽事件

    • addEventListener("事件類型", 函式, 監聽器位置限制在捕獲 / 冒泡階段)

      先談第三個參數,第三個參數為 Boolean,true 為捕獲階段,false 為冒泡階段,預設為 ture,可不輸入

      • 捕獲與冒泡

        • 注意,並不是掛了監聽器才有事件,事件一直都存在,不論我們是否監聽它

          所以按照標題寫的,並不是因為掛了監聽器才有事件,是因為你替目標元素掛了監聽器你才監聽得到這個事件,這是很多人誤會的一點,實際上你在 DOM 元素上觸發事件的時候捕獲和冒泡都一直在發生,只是如果你想要利用捕獲和冒泡觸發一些事情,你就需要監聽器去發現這個事件,進而對這個事件採取動作 ( 也就是 addEventLister() 內中的第二個參數 : 回呼函式 )

          所以請記得,當你掛上監聽器的時候,並不是你「創造了這個事件」,事件本來就存在,你只是創造了一個監聽器來發現事件,並利用監聽器所發現的事件去做你想做的事。

        • 捕獲和冒泡

          當一個父元素包住一個子元素,你對子元素做了一個事件,父元素也會被觸動,假設這個事件是 click,順序是這樣

          1. 使用者點擊了子元素

          2. 父元素發生點擊事件 -> 子元素發生點擊事件 -> 子元素發生點擊事件 -> 父元素發生點擊事件

          當然,一個子元素外面可能不會只有父級元素,還會有父父級與更甚者,但這邊所說的都不包含旁系元素

          事件一定會先捕獲後冒泡,所以事件一定會先經過父級元素 -> 子元素 -> 子元素 -> 父級元素

          那事件能不能可以被截斷 ? 可以,就利用剛剛所寫的監聽器

        • addEventListener() 中的 function

          參數約定俗成會使用 「e」或者是「event」,你可以利用這個參數調用你所要的資訊。

          比如說 e.target 會顯示觸發事件的目標為何。

          • e.preventDefault()

            這個參數是廢除該事件的作用,比如說偵測到點擊 click,就阻擋掉點擊 click 這個事件。

            但請了解,監聽器本身還是有先接收到這個事件 ( 這是當然,不然監聽器怎麼知道要呼叫 function ),然後到了呼叫 function 時才使其停止默認行為。

          • e.stopPropagation

            截斷後面的事件

            這個方法可以讓我們利用監聽器來截斷後面的事件,那從哪裏截斷?這就跟監聽器所掛勾的位置有關了,那決定監聽器的位置,我們可以利用監聽器的第三個參數 true ( 捕獲階段 ) 或 false ( 冒泡階段 ) 來做決定

            比如說不管這個頁面有多少按鈕有多少父級元素,事件都會觸發我們的大父級 DOM 元素 Window

            所以假設我在 window 掛了一個 click 監聽器,內中函式放入 e.stopPropagation,並利用參數將這個監聽器掛在捕獲階段 ( true ),那麼你會發現你怎樣點擊頁面都沒反應

            這是因為點擊頁面上的幾乎都是 Window 的子元素,一定會於最開始的捕獲階段先通過 Window,但是事件流在 window 就被截斷,後面子元素當然接收不到事件流,就自然沒反應了

          • e.stopImmediatePropagation

            當有多個相同類型監聽器綁定同一個元素,你可以替你僅僅想選擇的監聽器加入這個方法,如果你只使用 e.stopPropagation,那其他綁定同一個元素的監聽器也都會觸發到。

        • 事件代理 event delegation

          延續監聽器對於事件處理中 e.stopPropagation 說明所舉的 window 加入監聽器截斷案例,如果我們今天不是選擇截斷,而是選擇在父級元素掛一個監聽器去取代在內中各個子元素掛監聽器,那就可以達成「事件代理」

          你可以想像成一個房子裡面有十個房間,房間內都有住人,如果你想監視有沒有人從房子裡面出來,你不必每個房間門口都掛監視器,你只需要在這間房子大門掛一個監視器就好。

          事件代理也是這個意思。

    • 交換資料

      有了上述的介紹,我們大概知道如何使用 JS 來操控並利用 DOM 元素了,而我們也利用監聽器來偵測流動在 DOM 元素之間的事件流,利用事件流與回呼函式的原理使我們可以做更多的操作。

      至於操作什麼?如果我們要讓網頁從靜態網頁變成動態,光學會上述這些是不夠的,因為這樣的網頁是死的,是一層皮,並沒有與骨肉相連。

      要與骨肉相連使網頁前後端合一變成一個完整生命體,就需要做資料的交換,就像血液來回流通一樣,而使用 JS 與後端伺服器交換資料的技術,是目前最常使用的。

      在談交換資料之前,先了解一下 JS 如何發送 request 給 後端

      瀏覽器上的 JavaScript -> 瀏覽器 -> Server -> 發送 response 給瀏覽器 -> 瀏覽器上的 JavaScript

      response 有可能是 JSON,也可能是 php

      但在看 JS 如何與後端交換資料之前,我們先來看 form 表單交換資料的方式,這個方式也包含了 HTTP 中的 Method,request 也包含了 header 與 body ,如同第四週課程一樣

      • 最常見的方式:form 表單

        在之前的 HTML 標籤 有介紹到 method 和 action,method 內中為 GET / POST,action 為你的目標 URL

        但這樣交換的方式會使得頁面換頁,不是很人性化。

      • AJAX ( Asynchronous JavaScript And XML )

        AJAX 是任何非同步來與後端交換資料的技術統稱,它與本節開頭所講的不同,它的資料交換方式如下

        瀏覽器上的 JavaScript -> 透過瀏覽器發送 request -> Server -> 發送 response 給瀏覽器上的 JavaScript

        而原本的資料傳輸,是當 Server 回傳 response 時,是直接傳給瀏覽器,瀏覽器再對於回傳的頁面做渲染

        AJAX 的優勢除了提高用戶體驗外,由於 request 與 response 的往返不再需要一整個頁面,相對而言傳輸量也少掉許多

        原生的使用方法,是要 new 一個物件實例,也就是 XMLHttpResquest,這是瀏覽器提供給我們的 :

        const request = new XMLHttpRequest()
        
        request.onload = function() {
          // 當非同步函式執行時的執行內容
        }
        
        request.open("GET","www.google.com") // 使用何種 method,以及目標網址
        request.setRequestHeader("header","value") // 若要在 header 帶資料,可在此代入
        request.send() // 發送 Request
        

        你也可以使用監聽器的方式,如 request.addEventLister() 的方式。要觸發也是使用 request.send

        然後使用 request.responseText 呼叫出回傳資料,但這邊要注意的是,回傳的資料也有可能為空,這邊要看 Server 端是怎麼寫的。

      • 同源政策 ( Same origin policy )

        這邊只談同源政策的重點

        1. 同源政策是由瀏覽器所限制的,是由伺服器回傳 Response 給 JS 時通過瀏覽器,經瀏覽器辨識為不符合同源政策而擋下來的

        2. 同源的意思必須是 scheme 同源 ( 如 http 與 https 不同源 )、Domain 同源 ( 如 google.com 與 yahoo.com 不同源 )與 Port 同源,但大多數情況下你可以理解成相同網域就好。

        另外有些資料是不受同源政策限制的,如 image (<img>) 與 (<script>)

        • CORS ( Cross-Origin HTTP request ) 跨來源 HTTP 請求

          要理解該伺服器端是否受同源政策影響,可以觀察其 Response Header 中的 「Access-Control-Allow-Origi」為何,內中會編寫哪些來源的 request 符合同源政策。比如說內中若為 *,代表這份 Response 本身是允許所有網域請求資料。

      • 不受同源政策影響的瀏覽器交換資料方式 :

        我們可以知道在之前的作業中,我們使用 node.js 做資料交換時,是不受同源政策的限制的。

        那有沒有同樣使用瀏覽器交換資料也不受限制的方法呢?

        有的,那就是 JSONP ( 不是 JSON ) ,用法不贅述,這邊僅提供概念

        JSONP 是一種傳遞的方式,通常被認定是一種協議,它利用 \<script> 標籤不受同源政策限制的特性,從這個漏洞中達到資料交換的目的。

        通常是先在本地創建一個 <script> 元素,然後再創建另一個 <script>,在內中創建一個函式去調用我所要的資料,並將這個函式回傳給我的本地要發送 Request 的程式,當都扣在一起之後,我就能繞道拿取資料了。

  • Cookie、Local Storage 與 Session Storage

  • Cookie

    在後端 Server 中,Response 會自動將 Cookie 帶入 Response 回傳給前端,常用於廣告驗證或者身分驗證 ( 這邊的身分驗證不是指密碼帳號登入,而是第一次登入過後的第二次或第三次造訪不須再重新輸入帳號密碼 )

    而瀏覽器收到 Cookie 之後會放在使用者系統之中,下次再發送 Resquest 時也會自動帶上與目標 Domain 相關的 Cookie,給後端做驗證。

    • Local Storage

      但是像這種暫存資料的方式並不一定是 Cookie 的專利,有時候我們可以自己寫一些資料到 Local Storage 之中,

      window.localStorage.setItem('text','hahaha') // ("key","value")
      

      如此就能成功儲存一筆 key 到 LocalStorage

      使用的時機是如果你想做出一個使用者填資料的網站,使用者若中途輸入資料關閉,localStorage 也會存著。

    • Session Storage

      而 Session Storage 則是一段期間內儲存的資料,使用方法如以下範例

      window.sessionStorage.setItem('text','hahaha') // ("key","value")
      

      差別在於 Session Storage 是存在不同分頁並且關閉頁面就不復存在,保存性也沒有 Local Storage 來得好,所以其適用在一些短資訊儲存,比如說註冊會員表單中輸入的密碼或身分證字號之類的。


結語

基礎網頁篇的筆記大概就到這裡了,其實這篇筆記還可以延伸更多的東西來講,比如說 Ajax 的核心物件 XMLHttpResquest 詳解,又或者是同源政策下會遇到的問題等等,這邊就等以後實際開發遇到問題再另外撰文筆記即可。


#Web #javascript #新手 #初學者







Related Posts

七天打造自己的 Google Map 應用入門 - Day03

七天打造自己的 Google Map 應用入門 - Day03

[ 筆記 ] JavaScript - ES6語法

[ 筆記 ] JavaScript - ES6語法

[Day02] Pattern Matching

[Day02] Pattern Matching

[ 學習筆記系列 ] 很基礎的 Command Line 與 Git

[ 學習筆記系列 ] 很基礎的 Command Line 與 Git

[ 作業檢討 ] 第一週超級挑戰題

[ 作業檢討 ] 第一週超級挑戰題

Day01:從變數看 bytecode

Day01:從變數看 bytecode



Comments