# 📚 前端網頁開發基礎(JavaScript) - 學習筆記 > [!Note] > **播放清單連結:** [點此前往](https://youtube.com/playlist?list=PL-g0fdC5RMbqW54tWQPIVbhyl_Ky6a2VI&si=R7yjmSfWzsqPtw7e) > **學習總覽:** 雖然已經學過了基礎的HTML、CSS、Javascript用來做一個網站,但是都是依靠AI來完成,希望藉由這個播放清單來複習一下。 --- ## 🚀 學習進度 - [x] **影片 21:** JavaScript Array 陣列物件 - [x] **影片 22:** JavaScript HTML DOM 核心觀念 - [x] **影片 23:** JavaScript HTML DOM 網頁畫面操作演練 - [x] **影片 24:** Javascript Event Handling 事件處理 - [x] **影片 25:** HTTP 通訊協定簡介 - [x] **影片 26:** AJAX 網路連線實務 - [x] **影片 27:** JavaScript 箭頭函式 Arrow Function - [x] **影片 28:** JavaScript 解構賦值 Destructuring Assignment --- ## 🎬 影片 21: JavaScript Array 陣列物件 ### 🎯 本集重點 (Key Takeaways) * **重點一:陣列 (Array) 是「有序的資料集合」** * 陣列是一個特殊的物件,專門用來存放一長串有順序的資料。它可以儲存任何型態的資料,包括數字、字串、布林值,甚至是其他物件或陣列。 * **重點二:使用 `[]` (陣列字面值) 來建立陣列** * 在現代 JavaScript 中,使用中括號 `[]` 是建立陣列最常用、也最推薦的方式。例如:`let scores = [100, 98, 85];`。 * **重點三:索引 (Index) 是從 `0` 開始的** * 陣列中的每個資料都有一個從 `0` 開始的編號(索引)。要取得第一個資料,我們使用 `array[0]`,第二個則是 `array[1]`,依此類推。 * **重點四:`.length` 屬性與 `.push()` 方法** * **`.length`** 屬性可以告訴我們陣列中「總共有幾個資料」。 * **`.push(資料)`** 方法可以將一筆新資料「新增到陣列的結尾」。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. 什麼是陣列 (Array)?** * **比喻**:如果說物件 (`{}`) 像是一個有「具名標籤」的檔案櫃,那麼陣列 (`[]`) 就像是一列有「編號車廂」的火車。它專門用來存放**有順序性**的資料。 * 陣列中的每個位置都有一個固定的編號,這個編號就稱為**索引 (Index)**。 --- * **2. 建立陣列** * **方法一:陣列字面值 `[]` (Array Literal) - 推薦** * 這是建立陣列最簡單、直接且最常用的方式。 * **建立空陣列**: ```javascript let arr = []; // 建立一個空的陣列 ``` * **建立時同時放入資料**: ```javascript // 每個資料用逗號隔開 let grades = [70, 50, 84]; ``` * **方法二:`new Array()` 建構式 (較少用)** * 這是另一種建立陣列的語法,在某些特殊情境下會用到,但一般情況下建議使用 `[]` 即可。 ```javascript let arr = new Array(); // 等同於 let arr = []; ``` --- * **3. 操作陣列中的資料** * **A. 新增資料到結尾 (`.push()`)** * `.push()` 是陣列物件內建的一個「方法」(功能),可以把新資料塞到陣列的最後面。 * **語法**: ```javascript let arr = []; arr.push(3); // arr 現在是 [3] arr.push(4); // arr 現在是 [3, 4] ``` ![image](https://hackmd.io/_uploads/HJsJUEBIel.png =30%x) * **B. 根據索引取得資料** * 陣列的索引是從 `0` 開始計算的,這點非常重要! * **語法**:`陣列[索引]` * **範例**: ```javascript let arr = [3, 4]; console.log(arr[0]); // 印出第一個資料:3 console.log(arr[1]); // 印出第二個資料:4 ``` * **C. 取得陣列長度 (`.length`)** * `.length` 是陣列物件的一個「屬性」,它會告訴我們這個陣列裡目前有幾筆資料。 * **範例**: ```javascript let arr = [3, 4, 10, 25]; console.log(arr.length); // 印出 4 ``` --- * **4. 陣列的最佳拍檔:`for` 迴圈** * 陣列的「有序性」和「長度 (`.length`)」,讓它和 `for` 迴圈成為處理大量資料的最佳組合。我們可以透過迴圈,逐一取出陣列中的每一筆資料來進行運算。 * **標準遍歷寫法**: ```javascript let scores = [70, 50, 84, 25, 100]; // i 從 0 開始,直到 i 小於 scores 的長度為止 for (let i = 0; i < scores.length; i++) { // scores[i] 會依序取出 scores[0], scores[1], ... console.log("第 " + (i+1) + " 個人的分數是:" + scores[i]); } ``` --- * **5. 綜合練習** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Javascript:陣列物件</title> </head> <body> <script> // === 練習 1:手動建立與操作陣列 === let grades = []; // 建立空陣列 console.log("初始空陣列:", grades); grades.push(70); grades.push(50); grades.push(84); console.log("push 後的陣列:", grades); console.log("陣列長度:", grades.length); // 印出 3 // === 練習 2:建立時即包含資料 === let grades2 = [70, 50, 84]; grades2.push(25); grades2.push(100); console.log("grades2 陣列:", grades2); // 透過索引取得資料 console.log("grades2 的第一個元素:", grades2[0]); // 70 console.log("grades2 的第五個元素:", grades2[4]); // 100 console.log("--- 使用迴圈計算總分與平均 ---"); // === 練習 3:利用 for 迴圈,計算 grades2 的總和與平均 === let sum = 0; // 用來記錄累加的結果 for(let i = 0; i < grades2.length; i++){ // sum = sum + grades2[i] 的簡寫 sum += grades2[i]; } console.log("grades2 的總分是:", sum); // 計算平均值 // let avg = total / grades.length; // 這是錯誤的邏輯!變數用錯了。 let avg = sum / grades2.length; // 正確邏輯:應使用 grades2 的總和(sum) 除以其自身的長度 console.log("grades2 的平均分數是:", avg); </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/HkDYj4rLxl.png) ::: --- ## 🎬 影片 22: JavaScript HTML DOM 核心觀念 ### 🎯 本集重點 (Key Takeaways) * **重點一:DOM 是 HTML 文件的「物件模型」** * DOM (Document Object Model) 是瀏覽器根據 HTML 原始碼,在記憶體中建立的一個樹狀結構。每一個 HTML 標籤,都會被轉換成一個 JavaScript 可以操作的「物件」。 * **重點二:JS 透過 DOM 來操控網頁** * JavaScript 本身無法直接「看到」網頁,它必須透過 DOM 這個統一的「介面」,來讀取、新增、修改或刪除網頁上的任何內容與樣式。 * **重點三:`window` 是最頂層的全域物件** * `window` 物件代表整個瀏覽器視窗,是所有客戶端 JavaScript 功能的根源。所有全域變數、函式和物件(如 `document`、`screen`)實際上都是 `window` 的屬性。 * **重點四:`document` 是網頁內容的入口** * `document` 物件代表當前載入的整個 HTML 文件,是我們用來選取和操作頁面上具體元素的起點。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. 什麼是 HTML DOM?** * **比喻**: * **HTML 原始碼**:像是一棟房子的「設計藍圖」。 * **瀏覽器**:像是「建築工人」,它會閱讀藍圖。 * **HTML DOM**:就是工人根據藍圖蓋好的「**房子實體**」。它有完整的結構(樑、柱、牆壁),而 JavaScript 就像是「室內設計師」,可以透過操作這個房子實體,來改變牆壁顏色、移動家具等。 * 每個 HTML 標籤在 JavaScript 引擎中,都會有一個對應的**標籤物件**,稱為 **HTML Element**。 * 瀏覽器將所有這些標籤物件,依照它們在 HTML 中的層級關係串接起來,就形成了樹狀的物件結構,這就是 **HTML DOM**。 ![image](https://hackmd.io/_uploads/ByVwtIB8xx.png =30%x) --- * **2. DOM 的階層結構** * 在瀏覽器環境中,所有物件都存在一個階層關係下,其中 `window` 是最頂層的物件。 * **`window` (瀏覽器視窗)** * **`screen`** (代表使用者的螢幕本身,包含解析度等資訊) * **`document`** (代表當前載入的網頁文件) * `<body>` (網頁主體物件) * `<head>` (網頁頭部物件) * ... 其他所有 HTML 標籤物件 * **`location`** (代表當前的網址列) * `alert()` (全域方法) * `prompt()` (全域方法) ![image](https://hackmd.io/_uploads/Byi5tLS8gx.png) --- * **3. 探索全域物件** * 因為 `window` 是最頂層的全域物件,所以在呼叫它的屬性或方法時,可以省略 `window.` 這段前綴。 * **`window` 物件** * **取得屬性 (視窗資訊)**: ```javascript window.innerWidth; // 取得視窗內部的寬度 window.innerHeight; // 取得視窗內部的高度 ``` * **呼叫方法 (互動視窗)**: (已修正您筆記中的 `prompt` 錯字) ```javascript // alert() 等同於 window.alert() alert("彈出警告視窗"); // prompt() 等同於 window.prompt() prompt("輸入資料", "預設值"); ``` * **`screen` 物件** * `screen` 物件是 `window` 的一個屬性,它代表使用者的「實體螢幕」資訊。 * **取得屬性 (螢幕資訊)**: ```javascript screen.width; // 取得螢幕的總寬度 screen.height; // 取得螢幕的總高度 ``` * **`document` 物件** * `document` 物件是 `window` 的一個屬性,它代表「**整個 HTML 文件**」,是操作網頁元素的入口。 * **取得屬性 (文件資訊)**: ```javascript document.title; // 取得或設定網頁標題 document.body; // 取得 <body> 標籤物件 ``` * **呼叫方法 (選取元素)**: ```javascript // 這是未來最常用的方法之一,可以透過 CSS 選擇器來選取頁面上的第一個符合條件的元素 document.querySelector("CSS 選擇器"); ``` --- * **4. 綜合練習** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript:HTML DOM</title> </head> <body> <script> // === 認識內建的 window 物件 (HTML DOM 結構的最上層) === console.log("window 物件:", window); // 取得視窗的寬度和高度 (不含工具列) console.log("視窗內部寬高:", window.innerWidth, window.innerHeight); // 透過 screen 物件,取得螢幕解析度 console.log("螢幕解析度:", window.screen.width, window.screen.height); // 取得網址列的內容 console.log("目前網址:", window.location.href); // 更改網址列的內容 (會直接跳轉網頁,練習時可保持註解) // window.location.href = "[https://www.google.com](https://www.google.com)"; console.log("--- 分隔線 ---"); // === 認識 document 物件 (代表整個 HTML 文件) === console.log("document 物件:", document); // 取得並更改網頁標題 console.log("原始標題:", document.title); document.title = "新的網頁標題"; console.log("新標題:", document.title); // 取得 Body 標籤物件 console.log("body 物件:", document.body); // 取得並更改網頁主畫面的內容 (Body 標籤的內文) console.log("原始 body 內容:", document.body.innerHTML); document.body.innerHTML = "<h1>Hello, HTML DOM</h1><p>內容已被 JavaScript 改變!</p>"; </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/BJ8W4dr8gl.png) ::: --- ## 🎬 影片 23: JavaScript HTML DOM 網頁畫面操作演練 ### 🎯 本集重點 (Key Takeaways) * **重點一:DOM 操作三部曲** * 所有網頁的互動,基本上都遵循一個核心流程:**1. 選取元素** (Select),**2. 操作元素** (Manipulate),**3. 觸發操作** (Trigger)。 * **重點二:`document.querySelector()` 是選取元素的利器** * 這是最常用、也最有彈性的元素選取方法。它使用 **CSS 選擇器** 作為參數(例如 `#my-id`, `.my-class`),來精準地找到頁面上第一個符合條件的 HTML 元素物件。 * **重點三:三大操作目標** * 選取到元素後,最常操作的三個目標是: 1. **內容**:透過 `.innerHTML` 來改變標籤內部的 HTML 內容。 2. **樣式(Class)**:透過 `.className` 來置換整個 class,套用預設好的 CSS 樣式。 3. **樣式(Style)**:透過 `.style` 來直接修改特定、單一的行內 CSS 樣式。 * **重點四:事件處理 (Event Handling)** * 透過 `onclick` 屬性或 `addEventListener` 方法,可以將「使用者的操作(如點擊)」與「特定的 JavaScript 函式」綁定在一起,實現網頁的互動性。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **DOM 操作三部曲** * 我們可以將所有對網頁畫面的操作,拆解成以下三個步驟: * **Step 1: 選取元素 (Select)** * 為了讓 JavaScript 知道要操作「哪一個」HTML 標籤,我們需要先給目標標籤一個獨一無二的識別證,最常用的就是 `id` 屬性。 * **HTML 結構**: ```html <div id="content">這是一段字</div> <span id="keyword">這是一段字</span> ``` * **JavaScript 選取**: ```javascript // 使用 document.querySelector() 並傳入 CSS 選擇器字串 let divElement = document.querySelector('#content'); let spanElement = document.querySelector('#keyword'); ``` * **Step 2: 操作元素 (Manipulate)** * 當我們用變數(如 `divElement`)存好選取到的標籤物件後,就可以像操作一般物件一樣,去改變它的屬性。 * **操作內容 (`.innerHTML`)**:改變標籤包住的內容。 ```javascript divElement.innerHTML = "這是<b>新的</b>字"; ``` * **操作 Class (`.className`)**:整個替換掉標籤的 `class` 屬性。 ```javascript divElement.className = "welcome-text"; ``` * **操作行內樣式 (`.style`)**:直接修改特定的 CSS 樣式。注意屬性名稱需改為「駝峰式命名」(Camel Case),例如 `font-size` 要寫成 `fontSize`。 ```javascript divElement.style.fontSize = "30px"; divElement.style.color = "blue"; ``` * **Step 3: 觸發操作 (Trigger)** * 這些操作通常不是在網頁載入時就執行,而是在使用者做了「某件事」之後才觸發,例如點擊按鈕。 * **方法一:HTML `onclick` 屬性** * 直接在 HTML 標籤上寫 `onclick="函式名稱()"`。這種寫法簡單直覺,適合快速的練習。 ```html <div id="content">這是一段字</div> <button onclick="change();">點我</button> <script> function change() { let divElm = document.querySelector('#content'); divElm.innerHTML = "這是新的字"; divElm.style.color = "blue"; } </script> ``` > [!TIP] > **方法二:`addEventListener` (現代推薦的作法)** > > 這是更專業、更有彈性的作法,能讓 HTML 和 JavaScript 的職責完全分離。 > ```html > <button id="my-btn">點我</button> > ``` > ```javascript > // 1. 先選取到按鈕 > let btn = document.querySelector('#my-btn'); > // 2. 為按鈕「添加事件聆聽者」 > btn.addEventListener('click', function() { > // 3. 在這裡寫下點擊後要執行的所有程式 > let divElm = document.querySelector('#content'); > divElm.innerHTML = "這是新的字"; > }); > ``` --- * **綜合練習** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript:HTML DOM 網頁畫面操作</title> <style> /* 先定義好一個 CSS class 備用 */ .text { text-decoration: underline; font-weight: bold; } </style> </head> <body> <h3>HTML DOM 畫面操作</h3> <div id="content">練習網頁的操作</div> <button onclick="change()">點我</button> <script> function change() { // 步驟一:選取要操作的標籤物件 let elem = document.querySelector('#content'); // 步驟二:操作標籤物件的各種屬性 elem.innerHTML = "對特定的標籤做操作"; // 改變內容 elem.className = "text"; // 改變 class elem.style.fontSize = "30px"; // 改變 style elem.style.color = "red"; // 改變 style } </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/HJLBTTUUex.png) ![image](https://hackmd.io/_uploads/HJZ86p8Lel.png) ::: * **練習 2:點擊文字,切換顯示不同區塊 (Tab 頁籤效果)** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript:HTML DOM 網頁畫面操作</title> <style> /* 讓 span 標籤看起來像可以點擊的連結 */ span { cursor: pointer; margin: 0 10px; color: blue; text-decoration: underline; } </style> </head> <body> <div> <span onclick="changeToAbout();">關於我</span> <span onclick="changeToExps();">學經歷</span> </div> <hr/> <div id="about">大家好,我是666</div> <div id="exps" style="display:none">666 666 666 666</div> <script> // 點擊「關於我」時要執行的函式 function changeToAbout() { // 選取要操作的兩個 div let aboutDiv = document.querySelector("#about"); let expsDiv = document.querySelector("#exps"); // 操作 style,顯示「關於我」,隱藏「學經歷」 aboutDiv.style.display = "block"; expsDiv.style.display = "none"; } // 點擊「學經歷」時要執行的函式 function changeToExps() { // 選取要操作的兩個 div let aboutDiv = document.querySelector("#about"); let expsDiv = document.querySelector("#exps"); // 操作 style,隱藏「關於我」,顯示「學經歷」 aboutDiv.style.display = "none"; expsDiv.style.display = "block"; } </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/ry05TTLLxx.png) => **點擊關於我** ![image](https://hackmd.io/_uploads/SkwspaUUgx.png) => **點擊學經歷** ::: --- ## 🎬 影片 24: JavaScript Event Handling 事件處理 ### 🎯 本集重點 (Key Takeaways) * **重點一:事件 (Event) 是「觸發」** * 事件是瀏覽器中發生的各種事情,例如使用者「點擊」了按鈕、「滑鼠移入」了圖片,或是整個頁面「載入完成」。 * **重點二:事件處理 (Event Handling) 是「回應」** * 它是指我們編寫一段特定的 JavaScript 程式碼(稱為事件處理函式),並將它與某個元素的特定事件「綁定」,當事件被觸發時,對應的程式碼就會執行。 * **重點三:`this` 的妙用** * 在 HTML 的 `on<event>` 屬性中,可以直接傳入 `this` 關鍵字。在這個情境下,`this` 就代表「觸發這個事件的 HTML 元素本身」,讓我們的函式可以更有彈性地操作不同元素。 * **重點四:`addEventListener` 是現代標準** * 雖然使用 `on<event>` 屬性很直觀,但在現代開發中,使用 `element.addEventListener()` 方法來綁定事件是更推薦、更專業的作法,因為它能更好地做到「關注點分離」。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. 什麼是事件處理?** * **比喻**:事件處理就像是設定一個「自動感應裝置」。 * **事件 (Event)**:有人走過門口(觸發)。 * **事件監聽 (Event Listener)**:安裝在門口的「紅外線感應器」。 * **事件處理函式 (Event Handler)**:感應器觸發後,執行的動作,例如「自動門打開」。 * 透過事件處理,我們的 JavaScript 程式碼就能對使用者的各種操作做出即時的回應。 * **2. 常見的事件種類** * **滑鼠事件 (Mouse Events)** * `click`:滑鼠左鍵點擊。 * `mouseover`:滑鼠指標移入元素範圍。 * `mouseout`:滑鼠指標移出元素範圍。 * `mousedown`:滑鼠按鍵被按下(還沒放開)。 * `mouseup`:滑鼠按鍵被放開。 * **鍵盤事件 (Keyboard Events)** * `keydown`:鍵盤按鍵被按下。 * `keyup`:鍵盤按鍵被放開。 * **表單事件 (Form Events)** * `submit`:表單被送出時。 * `change`:表單元素的值改變時(例如 `select` 下拉選單)。 * **視窗/文件事件 (Window/Document Events)** * `load`:整個頁面,包含所有資源(圖片、CSS)都載入完成時。 * `DOMContentLoaded`:HTML 文件本身已經被完全讀取和解析完成時(不等待圖片、CSS)。 --- * **3. 綁定事件的兩種方式** * **方法一:HTML `on<event>` 屬性 (Inline Method)** * **說明**:直接在 HTML 標籤上,使用 `on` 加上事件名稱的屬性來綁定要執行的 JavaScript 程式碼或函式。這種寫法很直觀,適合小型練習。 * **基本語法**: ```html <div onclick="console.log('被點擊了!')">點我</div> <button onmouseover="myFunction();">滑鼠移過來</button> ``` * **範例一:透過 `this` 傳遞元素** * 在 `on<event>` 屬性中,`this` 關鍵字就代表該 HTML 元素本身。我們可以把它當作參數傳入函式,讓函式知道是誰觸發了事件。 ```html <div onclick="change(this);">原本的內文</div> <script> function change(elem) { // elem 參數現在就代表被點擊的那個 div 物件 elem.innerHTML = "新的內文"; } </script> ``` * **範例二:滑鼠按住與放開** ```html <button onmousedown="down(this);" onmouseup="up(this);">按鈕</button> <script> function down(elem) { elem.style.color = "red"; } function up(elem) { elem.style.color = "blue"; } </script> ``` > [!TIP] > **方法二:`element.addEventListener()` (Modern Recommended Method)** > > **說明**:這是現代 JavaScript 的標準作法。它讓我們可以在 JavaScript 程式中,選取到元素後,再用程式化的方式為它「添加事件監聽器」,完全不需要修改 HTML。 > > **優點**: > * **關注點分離**:HTML 專注於結構,JavaScript 專注於行為,程式碼更乾淨、更易維護。 > * **可綁定多個函式**:同一個元素的同一個事件,可以綁定多個不同的處理函式。 > * **功能更強大**:提供更多進階的事件處理選項。 > > **語法**: > ```javascript > let myButton = document.querySelector("#my-btn"); > myButton.addEventListener("click", function() { > // 當按鈕被點擊時,執行這裡的程式碼 > console.log("按鈕被點擊了!"); > }); > ``` --- * **4. 練習** - **作法一:** HTML `on<event>` 屬性 (直覺,但混合了 HTML/JS) * **說明**:這種寫法將事件觸發 (`onmouseover` 等) 直接寫在 `<span>` 標籤內,並呼叫對應的函式。HTML 和 JavaScript 耦合度較高。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript:事件處理 (方法一)</title> <style> .btn { background-color: #ffcccc; padding: 8px; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; user-select: none; } </style> </head> <body> <h3>事件處理的練習</h3> <span class="btn" onmouseover="over(this);" onmouseout="out(this);" onmousedown="down(this);" onmouseup="up(this);">點我</span> <span class="btn" onmouseover="over(this);" onmouseout="out(this);" onmousedown="down(this);" onmouseup="up(this);">第二個按鈕</span> <script> function over(elem) { elem.style.backgroundColor = "#ddaaaa"; } function out(elem) { elem.style.backgroundColor = "#ffcccc"; } function down(elem) { elem.style.fontWeight = "bold"; } function up(elem) { elem.style.fontWeight = "normal"; } </script> </body> </html> ``` - **作法二:** 使用 `addEventListener` (推薦,關注點分離) ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript:事件處理 (方法二 - 推薦)</title> <style> .btn { background-color: #ffcccc; padding: 8px; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; user-select: none; } </style> </head> <body> <h3>事件處理的練習</h3> <span class="btn">點我</span> <span class="btn">第二個按鈕</span> <script> // 1. 先選取到頁面上「所有」class 為 btn 的元素 const allButtons = document.querySelectorAll(".btn"); // 2. 使用 for...of 迴圈 (比 for i=0... 更簡潔),為每一個按鈕都綁定事件 for (const button of allButtons) { // 綁定 mouseover 事件 button.addEventListener("mouseover", function() { // 在 addEventListener 的處理函式中,`this` 同樣代表觸發事件的元素本身! this.style.backgroundColor = "#ddaaaa"; }); // 綁定 mouseout 事件 button.addEventListener("mouseout", function() { this.style.backgroundColor = "#ffcccc"; }); // 綁定 mousedown 事件 button.addEventListener("mousedown", function() { this.style.fontWeight = "bold"; }); // 綁定 mouseup 事件 button.addEventListener("mouseup", function() { this.style.fontWeight = "normal"; }); } </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/HkO3mxOIex.png) ![image](https://hackmd.io/_uploads/rk_5XguUgx.png) ![image](https://hackmd.io/_uploads/Hyfimg_Uge.png) ::: --- ## 🎬 影片 25: HTTP 通訊協定簡介 ### 🎯 本集重點 (Key Takeaways) * **重點一:HTTP 是網路世界的「通用語言」** * HTTP (Hypertext Transfer Protocol) 是瀏覽器 (客戶端 Client) 和伺服器 (Server) 之間溝通的基礎通訊協定。所有的網頁資料交換,都依賴這套規則。 * **重點二:溝通模式為「請求與回應 (Request-Response)」** * 互動永遠由客戶端發起「請求」,伺服器再給予「回應」。就像你點餐(請求),餐廳為你上菜(回應)。 * **重點三:網址 (URL) 是資源的地址** * 每個網址都由**通訊協定**、**主機名稱**和**路徑**等部分組成,精準地指向網路上某個獨一無二的資源。 * **重點四:兩種主要的資料請求方式** * **傳統頁面載入**:會造成整個頁面刷新,適合用於不同頁面間的跳轉。 * **AJAX 技術**:只在背景進行資料交換,實現「不刷新頁面,只更新局部內容」的動態效果,是現代網頁應用 (SPA) 的核心。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. 網站前後端互動基礎 (Client-Server Model)** * **比喻**: * **客戶端 (Client)**:就是你手中的**瀏覽器**,像是一位顧客。 * **伺服器 (Server)**:是存放網站資料的遠端電腦,像是一家餐廳。 * **HTTP**:就是顧客(瀏覽器)和餐廳(伺服器)之間溝通的**語言和點餐流程**。 * 整個互動流程遵循「**請求 (Request) - 回應 (Response)**」模式:瀏覽器發送一個 HTTP 請求給伺服器,伺服器處理完請求後,回傳一個 HTTP 回應(通常是 HTML, CSS, JS 檔案或資料)給瀏覽器。 ![image](https://hackmd.io/_uploads/ry6wsWu8ex.png) --- * **2. 網址 (URL) 的組成** * URL (Uniform Resource Locator) 就是我們俗稱的「網址」,是網路上每個資源(網頁、圖片、影片)的唯一地址。 * `通訊協定://主機名稱/路徑` * <span style="color:green">**通訊協定 (Protocol)**</span>:`http://` 或 `https://`。它定義了資料傳輸的規則。`https` 是 `http` 的加密版本,更為安全。 * <span style="color:blue">**主機名稱 (Hostname)**</span>:`www.google.com`。它指向存放資源的伺服器電腦的地址。 * <span style="color:red">**路徑 (Path)**</span>:`/search`。它指向伺服器上特定的資源或頁面。 > [!TIP] > **補充:查詢參數 (Query Parameters)** > > 有時候網址後面還會帶有 `?` 開頭的參數,用來傳遞額外的資料給伺服器。例如:`.../search?q=javascript`,這裡的 `q=javascript` 就是一個查詢參數。 --- * **3. 兩種請求方法的區別:傳統頁面載入 vs. AJAX** * 這是您問題的核心,以下用表格為您清楚區分這兩種方法的不同: | 比較項目 | 方法一:傳統頁面載入 (Traditional Page Load) | 方法二:AJAX 技術 (Asynchronous JavaScript and XML) | | :--- | :--- | :--- | | **觸發方式** | 在網址列輸入網址後按 Enter、點擊 `<a>` 連結、送出傳統表單。 | 由 JavaScript 程式碼在背景觸發 (例如 `fetch()` 函式)。 | | **頁面反應** | **整個頁面刷新**。瀏覽器會短暫變白,然後重新載入並渲染所有內容。 | **頁面不刷新**。只有需要更新的局部區塊會改變內容。 | | **使用者體驗** | 體驗較差,有明顯的「跳轉」中斷感。 | 體驗流暢,感覺更像桌面應用程式 (App)。 | | **資料格式** | 伺服器通常回傳完整的 HTML、CSS、JS 檔案。 | 伺服器通常只回傳**純資料** (最常見的是 JSON 格式)。 | | **使用情境** | 網站不同大頁面之間的導覽。<br>例如:從首頁點擊到「關於我們」頁面。 | 在同一頁面中進行動態互動。<br>例如:社群網站的「無限滾動」、Google Map 的地圖拖動、留言送出後即時顯示。 | > [!NOTE] > AJAX 是一種「概念」,而 `XMLHttpRequest` (XHR) 或現代更推薦的 `fetch()` API 則是實現這個概念的具體 JavaScript 技術。 * **`fetch()` 簡單範例**: 這段程式碼展示了如何用 JavaScript 在背景發送請求,取得資料,並且不刷新頁面。 ```javascript // 使用 fetch 向一個公開的 API 發送請求 fetch('[https://jsonplaceholder.typicode.com/todos/1](https://jsonplaceholder.typicode.com/todos/1)') .then(response => response.json()) // 將回應轉換為 JSON 資料格式 .then(data => { // 成功取得資料後,在主控台印出 console.log(data); // 在這裡,我們就可以用 JS 把 data 的內容更新到畫面上 }); ``` --- ## 🎬 影片 26: AJAX 網路連線實務 ### 🎯 本集重點 (Key Takeaways) * **重點一:`fetch()` 是現代網路請求的標準** * `fetch()` 是 JavaScript 內建的函式,用來取代舊的 XHR 技術,是目前執行 AJAX 網路連線最主流、最推薦的方法。 * **重點二:`fetch()` 的運作是「非同步 (Asynchronous)」的** * 當 `fetch()` 發送請求後,程式不會停下來等待,而是會繼續往下執行。當伺服器回應時,才會透過 `.then()` 中的回呼函式來處理結果。 * **重點三:`.then()` 鏈式呼叫處理流程** * `fetch()` 使用 Promise Chaining (`.then().then()`) 來處理非同步流程。第一個 `.then()` 負責接收初步的 `Response` 物件並解析它;第二個 `.then()` 才能真正取得處理完畢的資料。 * **重點四:回應格式的處理是關鍵** * `Response` 物件必須透過對應的方法來解析。如果預期收到純文字,就用 **`.text()`**;如果預期收到 JSON 資料,就用 **`.json()`**,後者會自動將 JSON 字串轉換成 JavaScript 物件或陣列。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. AJAX 技術的演進** * **初期 (AJAX)**:全名為 Asynchronous JavaScript And XML,是一個概念,早期資料格式多用 XML。 * **中期 (XHR)**:透過 `XMLHttpRequest` 物件來實作 AJAX,語法較為繁瑣。 * **現代 (`fetch`)**:近年來,`fetch` 函式因其更簡潔的語法和基於 Promise 的強大功能,已成為執行網路請求的首選。 --- * **2. `fetch()` 的基本流程:非同步與 Promise** * **比喻**:`fetch()` 就像是「**線上點餐**」。 1. `fetch(網址)`:你送出訂單,立刻拿到一張「**取餐憑證 (Promise)**」。你不用在櫃檯傻等,可以先去做自己的事(程式繼續往下執行)。 2. `第一個 .then()`:餐廳(伺服器)準備好餐點了,通知你來取餐。你拿到的是一個完整的「**便當盒 (Response 物件)**」,裡面有餐點、餐具、發票等資訊,但你還沒打開它。 3. `return response.json()`:你決定要「**打開便當盒,準備吃飯**」(解析 Response)。`.json()` 就是打開 JSON 格式的便當盒,`.text()` 就是打開純文字格式的。 4. `第二個 .then()`:你終於看到了便當裡的「**主菜 (真正的資料 data)**」,現在可以開始享用了(將資料渲染到畫面上)。 * **基本語法**: ```javascript fetch(網址).then(function(回應物件) { // 在這裡解析回應物件 }).then(function(真正的資料) { // 在這裡使用真正的資料 }); ``` --- * **3. 處理不同格式的回應:方法一 vs. 方法二** * 瀏覽器送出請求後,我們無法百分之百確定伺服器會回傳什麼格式的資料。因此,`fetch` 的第一個 `.then()` 拿到 `Response` 物件後,需要我們手動指定用什麼「方法」來解析它。 * **方法一:取得純文字 (`.text()`)** * **使用時機**:當你預期後端回傳的是一段純文字、HTML 碼、或任何非 JSON 格式的字串時使用。 * **回傳結果**:第二個 `.then()` 中的 `data` 會是一個 **`String` (字串)** 型態。 ```javascript fetch(網址).then(function(response) { return response.text(); // 將回應主體解析為純文字 }).then(function(data) { console.log(data); // data 是一個字串 }); ``` * **方法二:取得 JSON 格式 (`.json()`)** * **使用時機**:當你預期後端回傳的是 JSON 格式的資料時使用,這是現代網頁開發中最常見的資料交換格式。 * **回傳結果**:`.json()` 方法會自動將 JSON 字串**解析**成 JavaScript 的 **`Object` (物件)** 或 **`Array` (陣列)**。第二個 `.then()` 中的 `data` 就會是 JS 物件/陣列,可以直接操作。 ```javascript fetch(網址).then(function(response) { return response.json(); // 將回應主體解析為 JS 物件/陣列 }).then(function(data) { console.log(data); // data 是一個 JS 物件或陣列 }); ``` > [!TIP] > **補充:錯誤處理 (`.catch()`)** > > 如果網路連線失敗(例如斷線、網址錯誤),程式會中斷。我們可以加上 `.catch()` 來捕捉錯誤,並執行對應的處理,讓程式更穩定。 > ```javascript > fetch(網址).then(...).then(...).catch(function(error) { > console.log("連線失敗:", error); > }); > ``` --- * **4. 綜合練習** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript:AJAX 網路連線實務</title> </head> <body> <h3>AJAX / XHR 網路連線實務</h3> <button onclick="getData();">連線取得資料</button> <div id="result"></div> <script> /* 測試用的連線網址(會回傳一個包含多個產品物件的 JSON 陣列): https://cwpeng.github.io/live-records-samples/data/products.json */ function getData() { // 1. 利用 fetch 發送網路請求 fetch("https://cwpeng.github.io/live-records-samples/data/products.json") .then(function(response) { // 2. 收到回應後,因為我們知道資料是 JSON 格式,所以使用 .json() 來解析它 return response.json(); }) .then(function(data) { // 3. 成功解析後,data 就是我們能直接使用的 JavaScript 陣列了 console.log("成功取得的資料:", data); // 4. 將資料呈現在畫面上 // - 先選取到要放置結果的 div // - 修正變數名稱錯誤 (querySelector 的參數是 "#result") let resultDiv = document.querySelector("#result"); // - 每次點擊都先清空之前的結果 // - 修正變數名稱錯誤 (resule -> resultDiv) resultDiv.innerHTML = ""; // - 用 for 迴圈遍歷陣列中的每一個產品物件 for (let i = 0; i < data.length; i++) { let product = data[i]; // 取得單一產品物件 // - 將產品資訊組合成 HTML 字串,並累加到 resultDiv 中 resultDiv.innerHTML += "<div>" + product.name + " - 價格:" + product.price + " - 描述:" + product.description + "</div>"; } }) .catch(function(error){ // (補充) 如果連線失敗,就在主控台印出錯誤訊息 console.log("錯誤:", error); }); } </script> </body> </html> ``` ::: spoiler 成果展示 ![image](https://hackmd.io/_uploads/rynbdwY8xl.png) ![image](https://hackmd.io/_uploads/SyQ4dDtIel.png) ::: --- ## 🎬 影片 27: JavaScript 箭頭函式 Arrow Function ### 🎯 本集重點 (Key Takeaways) * **重點一:更簡潔的函式語法** * 箭頭函式 (Arrow Function) 是 ES6 新增的一種函式寫法,它省略了 `function` 關鍵字,並使用一個 `=>` (fat arrow),讓函式的語法更簡潔、更易讀。 * **重點二:可根據情境簡化** * 在特定條件下,箭頭函式可以進一步簡化,例如:當函式只有一個參數時,可省略 `()`;當函式主體只有一行 `return` 敘述時,可省略 `{}` 和 `return`。 * **重點三:`this` 的行為不同 (最關鍵差異)** * 傳統函式的 `this` 指向是動態的,取決於「如何被呼叫」;而箭頭函式沒有自己的 `this`,它會捕捉其被建立時所在的「外部作用域」的 `this` 值。這個特性解決了許多傳統 `this` 指向混亂的問題。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. 從傳統函式到箭頭函式** * 箭頭函式是「函式表達式」的一種語法糖 (Syntactic Sugar),讓寫法更精簡。 * **傳統寫法一 (函式宣告)** ```javascript function add(n1, n2) { let result = n1 + n2; return result; } ``` * **傳統寫法二 (函式表達式)** ```javascript let add = function(n1, n2) { let result = n1 + n2; return result; }; ``` * **箭頭函式基本寫法** * 省略 `function` 關鍵字,在參數列表後方加上 `=>`。 ```javascript let add = (n1, n2) => { let result = n1 + n2; return result; }; ``` --- * **2. 箭頭函式的簡化規則** * **規則一:如果函式主體只有「一行 `return` 敘述」** * 可以同時省略大括號 `{}` 和 `return` 關鍵字。 * **範例**: ```javascript // 可簡化的版本 let add = (n1, n2) => { return n1 + n2; }; // 簡化後的版本 (Implicit Return) let addSimplified = (n1, n2) => (n1 + n2); ``` * **規則二:如果參數「只有一個」** * 可以省略參數外面的小括號 `()`。 * **範例**: ```javascript // 原本的寫法 let square = (x) => { return x * x; }; // 簡化後的版本 let squareSimplified = x => x * x; ``` * **規則三:如果「沒有參數」** * 必須保留一對空的 `()`。 * **範例**: ```javascript let sayHello = () => console.log("Hello"); ``` --- * **3. 關鍵差異:`this` 的綁定** * **傳統函式**:`this` 的值取決於**函式如何被呼叫**,指向是動態的,容易造成混亂。 * **箭頭函式**:**沒有自己的 `this`**。它會像普通變數一樣,捕捉並繼承其**外層作用域**的 `this` 值。這個 `this` 在函式定義時就已確定,不會再改變。 > **比喻**:傳統函式像個「外包人員」,`this`(老闆)是誰,取決於是哪個物件「雇用」了它。箭頭函式則像個「內部員工」,它的 `this`(老闆)永遠是它被建立時所在的那個部門(作用域),非常忠誠。 --- * **4. 綜合練習** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>箭頭函式 Arrow Function</title> </head> <body> <script> // === 傳統函式:計算 1+2+...+10 === function calculate() { let result = 0; let n = 1; while (n <= 10) { result += n; n++; } console.log("傳統函式(無參數)結果:", result); } calculate(); // === 傳統函式:計算 1+2+...+max === function calculate2(max) { let result = 0; let n = 1; while (n <= max) { result += n; n++; } return result; } let ans1 = calculate2(10); let ans2 = calculate2(20); console.log("傳統函式(有參數)結果:", ans1, ans2); console.log("--- 使用箭頭函式 ---"); // === 箭頭函式:建立 calculate3 === let calculate3 = (max) => { let result = 0; let n = 1; while (n <= max) { result += n; n++; } return result; } console.log("箭頭函式(基本)結果:", calculate3(10)); // === 箭頭函式:簡化語法 === // 這是可以簡化的版本 /* let multiply = (n1, n2) => { return n1 * n2; }; */ // 簡化後:因為只有一行 return,所以省略 {} 和 return let multiply = (n1, n2) => (n1 * n2); let ans = multiply(3, 4); console.log("箭頭函式(簡化)結果:", ans); </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/rJJqQu5Lxl.png) ::: --- ## 🎬 影片 28: JavaScript 解構賦值 Destructuring Assignment ### 🎯 本集重點 (Key Takeaways) * **重點一:更簡潔的取值語法** * 解構賦值 (Destructuring Assignment) 是一種 ES6 的語法糖,讓我們可以更快速、更直觀地從陣列或物件中「提取」資料,並直接賦值給變數。 * **重點二:語法鏡像結構** * 解構的語法與資料本身的結構相對應:用中括號 `[]` 來解構陣列,用大括號 `{}` 來解構物件。 * **重點三:陣列靠「順序」,物件靠「名稱」** * **陣列解構**是按照**順序**(索引)來賦值的。 * **物件解構**是按照**屬性名稱 (key)** 來配對賦值的,順序不重要。 * **重點四:實用技巧** * 解構賦值讓「變數交換」、設定「預設值」以及處理「函式參數」等常見情境變得極其優雅。 ### 📝 詳細筆記與心得 (Notes & Reflections) * **1. 什麼是解構賦值?** * 它的核心思想是:**將一個複雜的資料結構(陣列或物件)拆解開,並將其內部的資料,一次性地賦值給多個獨立的變數。** * **比喻**:就像你拿到一個禮物籃(陣列/物件),解構賦值讓你可以一個動作就把裡面的「蘋果、香蕉、卡片」(資料)直接放到對應的盤子(變數)裡,而不需要一個一個慢慢拿。 --- * **2. 陣列的解構賦值 (Array Destructuring)** * 陣列解構是基於**順序**進行的。 * **基本用法** * **傳統作法**: ```javascript let arr = [3, 4, 5]; let d1 = arr[0]; let d2 = arr[1]; let d3 = arr[2]; ``` * **解構賦值**: ```javascript let arr = [3, 4, 5]; let [d1, d2, d3] = arr; // 一行搞定 console.log(d1, d2, d3); // 3 4 5 ``` * **進階技巧** * **宣告與賦值分開**: ```javascript let d1, d2, d3; [d1, d2, d3] = [3, 4, 5]; ``` * **設定預設值**:當陣列中的元素數量不足時,可以提供預設值。 ```javascript let [d1, d2, d3 = 10] = [3, 4]; console.log(d1, d2, d3); // 3 4 10 (d3 使用了預設值) ``` * **(補充) 剩餘模式 (Rest Pattern)**:取得剩餘的所有元素,打包成一個新陣列。 ```javascript let [d1, ...rest] = [3, 4, 5, 6]; console.log(d1); // 3 console.log(rest); // [4, 5, 6] ``` --- * **3. 物件的解構賦值 (Object Destructuring)** * 物件解構是基於**屬性名稱 (key)** 進行的,與順序無關。 * **基本用法** * **傳統作法**: ```javascript let obj = { x: 3, y: 4 }; let x = obj.x; let y = obj.y; ``` * **解構賦值**: ```javascript let obj = { x: 3, y: 4 }; // 變數名稱必須和物件的 key 相同 let { x, y } = obj; console.log(x, y); // 3 4 ``` * **進階技巧** * **宣告與賦值分開**:需要用 `()` 包裹,避免 JavaScript 將 `{}` 誤認為是程式碼區塊。 ```javascript let x, y; let obj = { x: 3, y: 4 }; ({ x, y } = obj); // 必須用小括號包起來 ``` * **設定預設值**: ```javascript let { x, y = 5 } = { x: 3 }; console.log(x, y); // 3 5 (y 使用了預設值) ``` * **重新命名變數**:如果不想使用和 key 相同的變數名稱,可以指定一個新的名字。 ```javascript let obj = { x: 3, y: 4 }; let { x: newX, y: newY } = obj; console.log(newX, newY); // 3 4 ``` --- * **4. 實用的解構賦值技巧** * **技巧一:交換變數 (Swapping Variables)** * 傳統需要一個暫存變數,解構賦值只需一行。 ```javascript let n1 = 3; let n2 = 4; [n1, n2] = [n2, n1]; // 優雅地交換 console.log(n1, n2); // 4 3 ``` * **技巧二:函式參數解構 (Destructuring Function Parameters)** * 讓函式接收物件參數時,可以直接在參數列表進行解構,使程式碼更簡潔、易讀。 * **傳統作法**: ```javascript function add(args) { console.log(args.n1 + args.n2); } add({ n1: 3, n2: 4 }); ``` * **解構賦值**: ```javascript function add({ n1, n2 }) { // 直接在參數處解構 console.log(n1 + n2); } add({ n1: 3, n2: 4 }); ``` --- * **5. 綜合練習** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>解構賦值 Destructuring Assignment</title> </head> <body> <script> // === 陣列的解構賦值 === console.log("--- 陣列解構 ---"); let arr = [3, 4, 5]; // 使用解構賦值,並為 d4 設定預設值 let d1, d2, d3, d4; [d1, d2, d3, d4 = 10] = arr; console.log(d1, d2, d3, d4); // 3 4 5 10 // === 技巧一:變數資料交換 === console.log("--- 變數交換 ---"); let n1 = 3; let n2 = 4; console.log("交換前:", n1, n2); // 3 4 [n1, n2] = [n2, n1]; console.log("交換後:", n1, n2); // 4 3 // === 物件的解構賦值 === console.log("--- 物件解構 ---"); let obj = { x: 3, y: 4, z: 5 }; // 使用解構賦值,同時重新命名變數,並設定預設值 let newX, newY, newZ; ({ x: newX, y: newY, z: newZ = 10 } = obj); console.log(newX, newY, newZ); // 3 4 5 // === 技巧二:函式的物件參數解構 === console.log("--- 函式參數解構 ---"); // 直接在函式的參數列表中解構傳入的物件 function add({ n1, n2 }) { console.log(n1 + n2); } // 呼叫函式時,傳入一個物件 add({ n1: 3, n2: 4 }); // 7 </script> </body> </html> ``` :::spoiler 成果展示 ![image](https://hackmd.io/_uploads/B1oG3n98ge.png) :::