學習目標
- • 了解 JavaScript 的用途和特色
- • 掌握變數宣告和基本資料型別
- • 學會使用運算子和表達式
- • 掌握條件判斷和迴圈控制
- • 學會操作陣列和物件
- • 了解函數的定義和使用
- • 學會 DOM 操作和事件處理
- • 能夠編寫互動式網頁功能
第1課:JavaScript 簡介與第一個程式
基礎了解什麼是 JavaScript,它的用途和特色,並寫出你的第一個 JavaScript 程式。
什麼是 JavaScript?
JavaScript 是一種程式語言,最初是為了讓網頁變得更有互動性而創造的。現在它已經成為網頁開發不可或缺的一部分。
JavaScript 的特色:
- 直譯式語言:不需要編譯,直接在瀏覽器中執行
- 動態型別:變數的型別可以在執行時改變
- 事件驅動:可以回應使用者的操作
- 跨平台:可以在瀏覽器、伺服器等環境執行
你的第一個 JavaScript 程式:
// 這是註解,瀏覽器會忽略這行
// 你的第一個 JavaScript 程式
// 在控制台顯示訊息
console.log("Hello, World!");
console.log("歡迎來到 JavaScript 的世界!");
console.log("我正在學習程式設計");
// 在網頁中彈出對話框
alert("歡迎學習 JavaScript!");
// 在網頁中顯示確認對話框
let userResponse = confirm("你準備好學習 JavaScript 了嗎?");
console.log("使用者回應:", userResponse);
// 詢問使用者輸入
let userName = prompt("請輸入你的名字:");
console.log("你好," + userName + "!");
程式碼解釋:
- // 註解:以 // 開頭的是註解,用來解釋程式碼
- console.log():在瀏覽器的開發者工具控制台顯示訊息
- alert():彈出警告對話框
- confirm():彈出確認對話框,返回 true 或 false
- prompt():彈出輸入對話框,返回使用者輸入的文字
- 分號:每行程式碼結尾建議加分號
如何執行 JavaScript?
方法1:在 HTML 中嵌入
<script>
console.log("Hello, World!");
</script>
方法2:外部檔案
<script src="script.js"></script>
方法3:瀏覽器控制台
按 F12 開啟開發者工具,在 Console 標籤中直接輸入程式碼
💡 實作練習
開啟瀏覽器的開發者工具(按 F12),在控制台中輸入 console.log("我的第一個 JavaScript 程式") 並按 Enter!
JavaScript 的應用領域:
🌐 前端開發
網頁互動、動畫效果
🖥️ 後端開發
Node.js 伺服器開發
📱 行動應用
React Native、Ionic
🖥️ 桌面應用
Electron(如 VS Code)
第2課:變數與資料型別
基礎學習如何宣告變數來儲存資料,以及 JavaScript 中的基本資料型別。
變數宣告的三種方式:
// 1. let - 現代的變數宣告(推薦)
let name = "小明";
let age = 25;
console.log(name, age);
// 可以重新賦值
name = "小華";
age = 30;
console.log(name, age);
// 2. const - 常數宣告
const PI = 3.14159;
const COMPANY_NAME = "我的公司";
console.log(PI, COMPANY_NAME);
// const 不能重新賦值(會出錯)
// PI = 3.14; // 這會產生錯誤
// 3. var - 舊式宣告(不建議使用)
var oldStyle = "這是舊的寫法";
console.log(oldStyle);
// 變數命名規則
let userName = "camelCase 命名法"; // 推薦
let user_name = "snake_case 命名法"; // 也可以
let $element = "可以用 $ 開頭";
let _private = "可以用 _ 開頭";
// 不能用數字開頭
// let 1name = "錯誤"; // 這會產生錯誤
基本資料型別:
// 字串 (String)
let message = "Hello World";
let singleQuote = '也可以用單引號';
let templateString = `模板字串可以嵌入變數:${message}`;
// 數字 (Number)
let integer = 42; // 整數
let decimal = 3.14; // 小數
let negative = -10; // 負數
let scientific = 2.5e6; // 科學記號 (2500000)
// 布林值 (Boolean)
let isTrue = true;
let isFalse = false;
let isActive = Boolean(1); // 轉換為布林值
// null - 故意設為空值
let emptyValue = null;
// undefined - 未定義
let notDefined;
let alsoUndefined = undefined;
// 檢查資料型別
console.log(typeof message); // "string"
console.log(typeof integer); // "number"
console.log(typeof isTrue); // "boolean"
console.log(typeof emptyValue); // "object" (這是 JavaScript 的特殊情況)
console.log(typeof notDefined); // "undefined"
// 顯示所有變數
console.log("字串:", message);
console.log("數字:", integer);
console.log("布林值:", isTrue);
console.log("空值:", emptyValue);
console.log("未定義:", notDefined);
字串操作:
let firstName = "小";
let lastName = "明";
// 字串連接
let fullName1 = firstName + lastName; // "小明"
let fullName2 = firstName.concat(lastName); // "小明"
// 模板字串(推薦)
let greeting = `你好,${firstName}${lastName}!`;
console.log(greeting);
// 字串長度
console.log(fullName1.length); // 2
// 字串方法
let text = "JavaScript 很有趣";
console.log(text.toUpperCase()); // 轉大寫
console.log(text.toLowerCase()); // 轉小寫
console.log(text.indexOf("很")); // 找到字元的位置
console.log(text.includes("Java")); // 檢查是否包含
console.log(text.slice(0, 4)); // 擷取部分字串 "Java"
console.log(text.replace("很有趣", "超棒")); // 替換文字
// 多行字串
let multiLine = `這是第一行
這是第二行
這是第三行`;
console.log(multiLine);
型別轉換:
// 轉換為字串
let num = 123;
let str1 = String(num); // "123"
let str2 = num.toString(); // "123"
let str3 = num + ""; // "123"
// 轉換為數字
let text = "456";
let num1 = Number(text); // 456
let num2 = parseInt(text); // 456 (整數)
let num3 = parseFloat("3.14"); // 3.14 (浮點數)
let num4 = +text; // 456 (簡短寫法)
// 轉換為布林值
let bool1 = Boolean(1); // true
let bool2 = Boolean(0); // false
let bool3 = Boolean(""); // false (空字串)
let bool4 = Boolean("hello"); // true (非空字串)
let bool5 = !!text; // true (雙重否定)
// 自動型別轉換(要小心)
console.log("5" + 3); // "53" (字串連接)
console.log("5" - 3); // 2 (數字運算)
console.log("5" * 3); // 15 (數字運算)
console.log(true + 1); // 2 (true 變成 1)
console.log(false + 1); // 1 (false 變成 0)
// 檢查轉換結果
console.log("原始:", text, typeof text);
console.log("轉數字:", num1, typeof num1);
console.log("轉布林:", bool4, typeof bool4);
💡 實作練習
建立變數儲存你的姓名、年齡和興趣,然後用模板字串組合成一句自我介紹!
第3課:運算子與表達式
中級學習如何使用各種運算子進行計算、比較和邏輯運算。
算術運算子:
// 基本算術運算
let a = 10;
let b = 3;
console.log(a + b); // 加法:13
console.log(a - b); // 減法:7
console.log(a * b); // 乘法:30
console.log(a / b); // 除法:3.333...
console.log(a % b); // 餘數:1
console.log(a ** b); // 次方:1000
// 遞增和遞減
let count = 5;
console.log(count++); // 後遞增:先回傳5,再變成6
console.log(++count); // 前遞增:先變成7,再回傳7
console.log(count--); // 後遞減:先回傳7,再變成6
console.log(--count); // 前遞減:先變成5,再回傳5
// 複合賦值運算子
let x = 10;
x += 5; // x = x + 5,結果是15
x -= 3; // x = x - 3,結果是12
x *= 2; // x = x * 2,結果是24
x /= 4; // x = x / 4,結果是6
x %= 4; // x = x % 4,結果是2
console.log("最終結果:", x);
比較運算子:
let x = 5;
let y = "5";
// 相等比較
console.log(x == y); // true (值相等,會自動轉型)
console.log(x === y); // false (嚴格相等,型別也要相同)
console.log(x != y); // false (值不相等)
console.log(x !== y); // true (嚴格不相等)
// 大小比較
console.log(x > 3); // true
console.log(x < 10); // true
console.log(x >= 5); // true
console.log(x <= 4); // false
// 字串比較
let name1 = "Alice";
let name2 = "Bob";
console.log(name1 < name2); // true (按字母順序)
// 特殊情況
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(0 == false); // true
console.log(0 === false); // false
console.log("" == false); // true
console.log("" === false); // false
// 建議:總是使用 === 和 !== 進行比較
邏輯運算子:
let age = 20;
let hasLicense = true;
let hasExperience = false;
// AND 運算子 (&&)
let canDrive = age >= 18 && hasLicense;
console.log("可以開車:", canDrive); // true
// OR 運算子 (||)
let canApply = hasLicense || hasExperience;
console.log("可以申請:", canApply); // true
// NOT 運算子 (!)
let isMinor = !(age >= 18);
console.log("是未成年:", isMinor); // false
// 短路求值
let result1 = true || console.log("這不會執行"); // true
let result2 = false && console.log("這也不會執行"); // false
// 實用的預設值設定
let username = null;
let displayName = username || "訪客";
console.log("顯示名稱:", displayName); // "訪客"
// 空值合併運算子 (??) - ES2020
let config = null;
let defaultConfig = config ?? "預設設定";
console.log("設定:", defaultConfig); // "預設設定"
// 三元運算子 (條件運算子)
let status = age >= 18 ? "成年" : "未成年";
console.log("狀態:", status); // "成年"
let message = hasLicense ? "有駕照" : "沒有駕照";
console.log(message); // "有駕照"
💡 實作練習
建立一個簡單的計算機,讓使用者輸入兩個數字和運算符號,然後計算結果!
第4課:條件判斷 (if/else/switch)
中級學習如何讓程式根據不同條件執行不同的動作。
基本 if 語句:
// 基本 if 語句
let age = 18;
if (age >= 18) {
console.log("你已經成年了!");
console.log("可以投票了");
}
// if-else 語句
let weather = "sunny";
if (weather === "sunny") {
console.log("今天天氣很好,適合出門");
} else {
console.log("今天天氣不太好");
}
// if-else if-else 語句
let score = 85;
let grade;
if (score >= 90) {
grade = "A";
console.log("優秀!");
} else if (score >= 80) {
grade = "B";
console.log("良好!");
} else if (score >= 70) {
grade = "C";
console.log("及格");
} else if (score >= 60) {
grade = "D";
console.log("勉強及格");
} else {
grade = "F";
console.log("不及格,需要加油");
}
console.log(`你的等級是:${grade}`);
switch 語句:
// switch 語句
let day = 3;
let dayName;
switch (day) {
case 1:
dayName = "星期一";
break;
case 2:
dayName = "星期二";
break;
case 3:
dayName = "星期三";
break;
case 4:
dayName = "星期四";
break;
case 5:
dayName = "星期五";
break;
case 6:
dayName = "星期六";
break;
case 7:
dayName = "星期日";
break;
default:
dayName = "無效的日期";
}
console.log(dayName); // "星期三"
// 多個 case 共用同一個動作
let month = 12;
let season;
switch (month) {
case 12:
case 1:
case 2:
season = "冬天";
break;
case 3:
case 4:
case 5:
season = "春天";
break;
case 6:
case 7:
case 8:
season = "夏天";
break;
case 9:
case 10:
case 11:
season = "秋天";
break;
default:
season = "無效的月份";
}
console.log(`${month}月是${season}`);
實用範例:
// 登入驗證
let username = "admin";
let password = "123456";
if (username === "admin" && password === "123456") {
console.log("登入成功!");
} else if (username !== "admin") {
console.log("使用者名稱錯誤");
} else {
console.log("密碼錯誤");
}
// 年齡分組
let userAge = 25;
let category;
if (userAge < 13) {
category = "兒童";
} else if (userAge < 20) {
category = "青少年";
} else if (userAge < 60) {
category = "成年人";
} else {
category = "長者";
}
console.log(`年齡分組:${category}`);
// 成績等級判定
function getGrade(score) {
if (score < 0 || score > 100) {
return "無效分數";
} else if (score >= 90) {
return "A";
} else if (score >= 80) {
return "B";
} else if (score >= 70) {
return "C";
} else if (score >= 60) {
return "D";
} else {
return "F";
}
}
console.log(getGrade(85)); // "B"
console.log(getGrade(105)); // "無效分數"
// 簡單的計算機
let num1 = 10;
let operator = "+";
let num2 = 5;
let result;
switch (operator) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num2 !== 0 ? num1 / num2 : "不能除以零";
break;
default:
result = "無效的運算符";
}
console.log(`${num1} ${operator} ${num2} = ${result}`);
💡 實作練習
寫一個程式判斷一個年份是否為閏年(能被4整除但不能被100整除,或者能被400整除)!
第5課:陣列與物件
中級學習 JavaScript 中最重要的資料結構:陣列和物件的操作方法。
陣列 (Array) 基礎:
// 建立陣列
let fruits = ["蘋果", "香蕉", "橘子"];
let numbers = [1, 2, 3, 4, 5];
let mixed = ["文字", 123, true, null];
// 存取陣列元素
console.log(fruits[0]); // "蘋果"
console.log(fruits.length); // 3
// 新增元素
fruits.push("葡萄"); // 在末尾新增
fruits.unshift("草莓"); // 在開頭新增
console.log(fruits); // ["草莓", "蘋果", "香蕉", "橘子", "葡萄"]
// 移除元素
let lastFruit = fruits.pop(); // 移除最後一個
let firstFruit = fruits.shift(); // 移除第一個
console.log(lastFruit); // "葡萄"
console.log(firstFruit); // "草莓"
// 陣列方法
let scores = [85, 92, 78, 96, 88];
// 尋找元素
console.log(scores.indexOf(92)); // 1
console.log(scores.includes(100)); // false
// 排序
let sortedScores = scores.sort((a, b) => a - b);
console.log(sortedScores); // [78, 85, 88, 92, 96]
// 過濾
let highScores = scores.filter(score => score >= 90);
console.log(highScores); // [92, 96]
// 映射
let doubledScores = scores.map(score => score * 2);
console.log(doubledScores); // [170, 184, 156, 192, 176]
物件 (Object) 基礎:
// 建立物件
let person = {
name: "小明",
age: 25,
city: "台北",
isStudent: true,
hobbies: ["閱讀", "游泳", "程式設計"]
};
// 存取物件屬性
console.log(person.name); // "小明"
console.log(person["age"]); // 25
// 新增/修改屬性
person.email = "ming@example.com";
person.age = 26;
console.log(person);
// 刪除屬性
delete person.isStudent;
// 檢查屬性是否存在
console.log("name" in person); // true
console.log(person.hasOwnProperty("age")); // true
// 取得所有鍵值
let keys = Object.keys(person);
let values = Object.values(person);
let entries = Object.entries(person);
console.log("鍵:", keys);
console.log("值:", values);
console.log("鍵值對:", entries);
// 物件方法
let calculator = {
result: 0,
add: function(num) {
this.result += num;
return this;
},
subtract: function(num) {
this.result -= num;
return this;
},
multiply: function(num) {
this.result *= num;
return this;
},
getResult: function() {
return this.result;
}
};
// 鏈式呼叫
let finalResult = calculator.add(10).multiply(2).subtract(5).getResult();
console.log(finalResult); // 15
進階陣列操作:
let students = [
{ name: "小明", score: 85, subject: "數學" },
{ name: "小華", score: 92, subject: "英文" },
{ name: "小美", score: 78, subject: "數學" },
{ name: "小強", score: 96, subject: "英文" }
];
// reduce - 計算總分
let totalScore = students.reduce((sum, student) => sum + student.score, 0);
console.log("總分:", totalScore);
// find - 尋找第一個符合條件的元素
let topStudent = students.find(student => student.score >= 95);
console.log("最高分學生:", topStudent);
// some - 檢查是否有元素符合條件
let hasHighScore = students.some(student => student.score >= 90);
console.log("有高分學生:", hasHighScore);
// every - 檢查所有元素是否都符合條件
let allPassed = students.every(student => student.score >= 60);
console.log("全部及格:", allPassed);
// 多維陣列
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log(matrix[1][2]); // 6
// 陣列解構
let [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// 物件解構
let { name, age, city = "未知" } = person;
console.log(name, age, city);
💡 實作練習
建立一個學生管理系統,使用陣列儲存學生物件,實作新增、刪除、搜尋和排序功能!
第6課:函數與作用域
進階深入學習函數的定義、參數、回傳值,以及 JavaScript 的作用域概念。
函數定義方式:
// 1. 函數宣告 (Function Declaration)
function greet(name) {
return `你好,${name}!`;
}
// 2. 函數表達式 (Function Expression)
const add = function(a, b) {
return a + b;
};
// 3. 箭頭函數 (Arrow Function)
const multiply = (a, b) => a * b;
// 4. 立即執行函數 (IIFE)
(function() {
console.log("立即執行!");
})();
// 呼叫函數
console.log(greet("小明")); // "你好,小明!"
console.log(add(5, 3)); // 8
console.log(multiply(4, 6)); // 24
// 預設參數
function introduce(name, age = 18, city = "台北") {
return `我是${name},今年${age}歲,住在${city}`;
}
console.log(introduce("小華")); // 使用預設值
console.log(introduce("小美", 25, "高雄")); // 覆蓋預設值
// 不定參數 (Rest Parameters)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 解構參數
function createUser({name, email, age = 18}) {
return {
id: Date.now(),
name,
email,
age,
createdAt: new Date()
};
}
let user = createUser({name: "小明", email: "ming@example.com"});
console.log(user);
作用域與閉包:
// 全域作用域
let globalVar = "我是全域變數";
function outerFunction() {
// 函數作用域
let outerVar = "我是外層變數";
function innerFunction() {
// 內層作用域
let innerVar = "我是內層變數";
// 可以存取所有上層變數
console.log(globalVar); // 可以存取
console.log(outerVar); // 可以存取
console.log(innerVar); // 可以存取
}
innerFunction();
// console.log(innerVar); // 錯誤!無法存取內層變數
}
outerFunction();
// 閉包 (Closure) 範例
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
let counter1 = createCounter();
let counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (獨立的計數器)
// 實用的閉包應用
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(4)); // 12
// 模組模式
const Calculator = (function() {
let result = 0;
return {
add: function(num) {
result += num;
return this;
},
subtract: function(num) {
result -= num;
return this;
},
getResult: function() {
return result;
},
reset: function() {
result = 0;
return this;
}
};
})();
Calculator.add(10).subtract(3);
console.log(Calculator.getResult()); // 7
高階函數:
// 高階函數:接受函數作為參數或回傳函數
// 1. 接受函數作為參數
function processArray(arr, callback) {
let result = [];
for (let item of arr) {
result.push(callback(item));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
let squared = processArray(numbers, x => x * x);
let doubled = processArray(numbers, x => x * 2);
console.log(squared); // [1, 4, 9, 16, 25]
console.log(doubled); // [2, 4, 6, 8, 10]
// 2. 回傳函數
function createValidator(minLength) {
return function(input) {
return input.length >= minLength;
};
}
let validatePassword = createValidator(8);
let validateUsername = createValidator(3);
console.log(validatePassword("123456")); // false
console.log(validatePassword("12345678")); // true
console.log(validateUsername("abc")); // true
// 3. 函數組合
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
let addOne = x => x + 1;
let multiplyByTwo = x => x * 2;
let addOneThenDouble = compose(multiplyByTwo, addOne);
console.log(addOneThenDouble(3)); // (3 + 1) * 2 = 8
// 4. 柯里化 (Currying)
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function add3Numbers(a, b, c) {
return a + b + c;
}
let curriedAdd = curry(add3Numbers);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
💡 實作練習
建立一個函數庫,包含常用的工具函數:防抖動、節流、深拷貝等功能!
第7課:DOM 操作與事件處理
進階學習如何操作網頁元素(DOM)和處理用戶互動事件,實現動態網頁效果。
DOM 元素選取:
// HTML 範例
/*
<div id="container">
<h1 class="title">標題</h1>
<p class="content">段落1</p>
<p class="content">段落2</p>
<button id="myButton">點我</button>
</div>
*/
// 1. 根據 ID 選取
let container = document.getElementById('container');
let button = document.getElementById('myButton');
// 2. 根據 class 選取
let title = document.getElementsByClassName('title')[0];
let contents = document.getElementsByClassName('content');
// 3. 根據標籤選取
let allParagraphs = document.getElementsByTagName('p');
// 4. CSS 選擇器(推薦)
let containerByQuery = document.querySelector('#container');
let titleByQuery = document.querySelector('.title');
let allContentsByQuery = document.querySelectorAll('.content');
// 5. 進階選擇器
let firstParagraph = document.querySelector('p:first-child');
let lastParagraph = document.querySelector('p:last-child');
let buttonInContainer = document.querySelector('#container button');
console.log('容器元素:', container);
console.log('所有段落:', allContentsByQuery);
DOM 元素操作:
// 修改內容
let title = document.querySelector('.title');
title.textContent = '新的標題'; // 純文字
title.innerHTML = '<em>強調的標題</em>'; // HTML 內容
// 修改屬性
let button = document.querySelector('#myButton');
button.setAttribute('disabled', 'true');
button.setAttribute('data-count', '0');
// 取得屬性
let buttonId = button.getAttribute('id');
let dataCount = button.getAttribute('data-count');
// 修改樣式
title.style.color = 'blue';
title.style.fontSize = '24px';
title.style.backgroundColor = '#f0f0f0';
// 使用 CSS 類別(推薦)
title.classList.add('highlight');
title.classList.remove('old-style');
title.classList.toggle('active');
title.classList.contains('highlight'); // 檢查是否有此類別
// 建立新元素
let newParagraph = document.createElement('p');
newParagraph.textContent = '這是新的段落';
newParagraph.classList.add('content', 'new');
// 插入元素
let container = document.querySelector('#container');
container.appendChild(newParagraph); // 加到最後
container.insertBefore(newParagraph, button); // 插入到按鈕前
// 移除元素
let oldElement = document.querySelector('.old');
if (oldElement) {
oldElement.remove(); // 現代方法
// 或 oldElement.parentNode.removeChild(oldElement); // 舊方法
}
// 複製元素
let originalButton = document.querySelector('#myButton');
let clonedButton = originalButton.cloneNode(true); // true = 深拷貝
clonedButton.id = 'clonedButton';
container.appendChild(clonedButton);
事件處理:
// 1. 基本事件監聽
let button = document.querySelector('#myButton');
button.addEventListener('click', function() {
alert('按鈕被點擊了!');
});
// 2. 箭頭函數寫法
button.addEventListener('click', () => {
console.log('使用箭頭函數處理點擊');
});
// 3. 事件物件
button.addEventListener('click', function(event) {
console.log('事件類型:', event.type);
console.log('目標元素:', event.target);
console.log('滑鼠位置:', event.clientX, event.clientY);
// 阻止預設行為
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
});
// 4. 常用事件類型
let input = document.querySelector('#textInput');
input.addEventListener('focus', () => {
console.log('輸入框獲得焦點');
});
input.addEventListener('blur', () => {
console.log('輸入框失去焦點');
});
input.addEventListener('input', (e) => {
console.log('輸入內容:', e.target.value);
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
console.log('按下 Enter 鍵');
}
});
// 5. 滑鼠事件
let box = document.querySelector('.box');
box.addEventListener('mouseenter', () => {
box.style.backgroundColor = 'lightblue';
});
box.addEventListener('mouseleave', () => {
box.style.backgroundColor = 'white';
});
box.addEventListener('mousemove', (e) => {
let rect = box.getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
console.log(`滑鼠在元素內的位置: (${x}, ${y})`);
});
// 6. 事件委派(Event Delegation)
let list = document.querySelector('#todoList');
list.addEventListener('click', function(e) {
if (e.target.classList.contains('delete-btn')) {
e.target.parentElement.remove();
}
if (e.target.classList.contains('todo-item')) {
e.target.classList.toggle('completed');
}
});
// 7. 移除事件監聽器
function handleClick() {
console.log('處理點擊事件');
}
button.addEventListener('click', handleClick);
// 稍後移除
button.removeEventListener('click', handleClick);
實用範例 - 動態待辦清單:
// HTML 結構
/*
<div id="todoApp">
<input type="text" id="todoInput" placeholder="輸入待辦事項">
<button id="addBtn">新增</button>
<ul id="todoList"></ul>
</div>
*/
class TodoApp {
constructor() {
this.input = document.querySelector('#todoInput');
this.addBtn = document.querySelector('#addBtn');
this.todoList = document.querySelector('#todoList');
this.todos = [];
this.init();
}
init() {
// 綁定事件
this.addBtn.addEventListener('click', () => this.addTodo());
this.input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.addTodo();
}
});
// 事件委派處理清單項目
this.todoList.addEventListener('click', (e) => {
const li = e.target.closest('li');
const id = parseInt(li.dataset.id);
if (e.target.classList.contains('delete-btn')) {
this.deleteTodo(id);
} else if (e.target.classList.contains('toggle-btn')) {
this.toggleTodo(id);
}
});
}
addTodo() {
const text = this.input.value.trim();
if (!text) return;
const todo = {
id: Date.now(),
text: text,
completed: false
};
this.todos.push(todo);
this.input.value = '';
this.render();
}
deleteTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
this.render();
}
toggleTodo(id) {
const todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.completed = !todo.completed;
this.render();
}
}
render() {
this.todoList.innerHTML = '';
this.todos.forEach(todo => {
const li = document.createElement('li');
li.dataset.id = todo.id;
li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
li.innerHTML = `
<span class="todo-text">${todo.text}</span>
<button class="toggle-btn">${todo.completed ? '取消' : '完成'}</button>
<button class="delete-btn">刪除</button>
`;
this.todoList.appendChild(li);
});
}
}
// 初始化應用程式
document.addEventListener('DOMContentLoaded', () => {
new TodoApp();
});
💡 實作練習
建立一個互動式圖片輪播器,包含自動播放、手動切換、暫停功能!
第8課:非同步程式設計
進階學習 JavaScript 的非同步程式設計:回調函數、Promise、async/await 和 API 呼叫。
回調函數 (Callback):
// 基本回調函數
function greetUser(name, callback) {
console.log(`正在準備問候 ${name}...`);
// 模擬非同步操作
setTimeout(() => {
const greeting = `你好,${name}!`;
callback(greeting);
}, 1000);
}
greetUser('小明', function(message) {
console.log(message); // 1秒後輸出:你好,小明!
});
// 常見的瀏覽器 API 回調
setTimeout(() => {
console.log('3秒後執行');
}, 3000);
setInterval(() => {
console.log('每2秒執行一次');
}, 2000);
// 事件監聽器也是回調
document.addEventListener('click', function(event) {
console.log('頁面被點擊了');
});
// 回調地獄 (Callback Hell) 問題
function step1(callback) {
setTimeout(() => {
console.log('步驟1完成');
callback();
}, 1000);
}
function step2(callback) {
setTimeout(() => {
console.log('步驟2完成');
callback();
}, 1000);
}
function step3(callback) {
setTimeout(() => {
console.log('步驟3完成');
callback();
}, 1000);
}
// 巢狀回調(不推薦)
step1(() => {
step2(() => {
step3(() => {
console.log('所有步驟完成');
});
});
});
Promise 基礎:
// 建立 Promise
function createPromise() {
return new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
if (success) {
resolve('操作成功!');
} else {
reject('操作失敗!');
}
}, 1000);
});
}
// 使用 Promise
createPromise()
.then(result => {
console.log('成功:', result);
})
.catch(error => {
console.log('錯誤:', error);
})
.finally(() => {
console.log('無論成功或失敗都會執行');
});
// Promise 鏈式呼叫
function fetchUserData(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: '小明' });
}, 1000);
});
}
function fetchUserPosts(user) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ title: '文章1', author: user.name },
{ title: '文章2', author: user.name }
]);
}, 1000);
});
}
// 鏈式呼叫解決回調地獄
fetchUserData(123)
.then(user => {
console.log('用戶資料:', user);
return fetchUserPosts(user);
})
.then(posts => {
console.log('用戶文章:', posts);
})
.catch(error => {
console.log('發生錯誤:', error);
});
// Promise 工具方法
const promise1 = Promise.resolve('立即解決');
const promise2 = Promise.reject('立即拒絕');
// Promise.all - 等待所有 Promise 完成
Promise.all([
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
]).then(users => {
console.log('所有用戶:', users);
}).catch(error => {
console.log('有用戶載入失敗:', error);
});
// Promise.race - 第一個完成的 Promise
Promise.race([
new Promise(resolve => setTimeout(() => resolve('快速'), 1000)),
new Promise(resolve => setTimeout(() => resolve('慢速'), 2000))
]).then(result => {
console.log('最快的結果:', result); // "快速"
});
// Promise.allSettled - 等待所有 Promise 完成(不管成功或失敗)
Promise.allSettled([
Promise.resolve('成功1'),
Promise.reject('失敗1'),
Promise.resolve('成功2')
]).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} 成功:`, result.value);
} else {
console.log(`Promise ${index} 失敗:`, result.reason);
}
});
});
async/await:
// 基本 async/await 語法
async function fetchData() {
try {
const result = await createPromise();
console.log('結果:', result);
return result;
} catch (error) {
console.log('錯誤:', error);
throw error;
}
}
// 呼叫 async 函數
fetchData()
.then(result => console.log('最終結果:', result))
.catch(error => console.log('最終錯誤:', error));
// 順序執行多個非同步操作
async function sequentialExecution() {
try {
console.log('開始執行...');
const user = await fetchUserData(123);
console.log('1. 用戶資料載入完成:', user);
const posts = await fetchUserPosts(user);
console.log('2. 文章載入完成:', posts);
console.log('所有操作完成');
return { user, posts };
} catch (error) {
console.log('執行過程中發生錯誤:', error);
}
}
// 並行執行多個非同步操作
async function parallelExecution() {
try {
console.log('開始並行執行...');
// 同時發起多個請求
const [user1, user2, user3] = await Promise.all([
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
]);
console.log('所有用戶載入完成:', { user1, user2, user3 });
return [user1, user2, user3];
} catch (error) {
console.log('並行執行失敗:', error);
}
}
// 處理多個可能失敗的操作
async function handleMultipleOperations() {
const results = await Promise.allSettled([
fetchUserData(1),
fetchUserData(2),
fetchUserData(999) // 可能失敗
]);
const successfulUsers = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failedRequests = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
console.log('成功載入的用戶:', successfulUsers);
console.log('失敗的請求:', failedRequests);
}
// 迴圈中的非同步操作
async function processUsersSequentially(userIds) {
const users = [];
for (const id of userIds) {
try {
const user = await fetchUserData(id);
users.push(user);
console.log(`用戶 ${id} 處理完成`);
} catch (error) {
console.log(`用戶 ${id} 處理失敗:`, error);
}
}
return users;
}
async function processUsersConcurrently(userIds) {
const promises = userIds.map(id => fetchUserData(id));
try {
const users = await Promise.all(promises);
return users;
} catch (error) {
console.log('批次處理失敗:', error);
return [];
}
}
// 使用範例
async function main() {
await sequentialExecution();
await parallelExecution();
await handleMultipleOperations();
const userIds = [1, 2, 3, 4, 5];
await processUsersSequentially(userIds);
await processUsersConcurrently(userIds);
}
main();
Fetch API 與實際應用:
// 基本 GET 請求
async function fetchUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
console.log('用戶列表:', users);
return users;
} catch (error) {
console.log('載入用戶失敗:', error);
return [];
}
}
// POST 請求
async function createUser(userData) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`創建失敗: ${response.status}`);
}
const newUser = await response.json();
console.log('新用戶創建成功:', newUser);
return newUser;
} catch (error) {
console.log('創建用戶失敗:', error);
throw error;
}
}
// PUT 請求(更新)
async function updateUser(userId, userData) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
const updatedUser = await response.json();
console.log('用戶更新成功:', updatedUser);
return updatedUser;
} catch (error) {
console.log('更新用戶失敗:', error);
throw error;
}
}
// DELETE 請求
async function deleteUser(userId) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, {
method: 'DELETE'
});
if (response.ok) {
console.log(`用戶 ${userId} 刪除成功`);
return true;
} else {
throw new Error('刪除失敗');
}
} catch (error) {
console.log('刪除用戶失敗:', error);
return false;
}
}
// 實用的 API 工具類
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`API Error: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('API 請求失敗:', error);
throw error;
}
}
async get(endpoint) {
return this.request(endpoint);
}
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(endpoint) {
return this.request(endpoint, {
method: 'DELETE'
});
}
}
// 使用 API 工具類
const api = new ApiClient('https://jsonplaceholder.typicode.com');
async function demonstrateAPI() {
try {
// 獲取所有用戶
const users = await api.get('/users');
console.log('所有用戶:', users);
// 創建新用戶
const newUser = await api.post('/users', {
name: '新用戶',
email: 'newuser@example.com'
});
console.log('新用戶:', newUser);
// 更新用戶
const updatedUser = await api.put('/users/1', {
name: '更新的用戶名',
email: 'updated@example.com'
});
console.log('更新後的用戶:', updatedUser);
// 刪除用戶
await api.delete('/users/1');
console.log('用戶已刪除');
} catch (error) {
console.error('API 操作失敗:', error);
}
}
demonstrateAPI();
💡 實作練習
建立一個天氣查詢應用,使用 async/await 呼叫天氣 API,並實現載入狀態和錯誤處理!