第 4 課:變數宣告與作用域
學習目標
- 掌握 let、const 和 var 的區別及最佳使用場景
- 理解 TypeScript 中的作用域概念和變數提升
- 學會在解構賦值中正確使用類型註解
- 了解塊級作用域、函數作用域和全域作用域
- 能夠避免常見的作用域相關錯誤
1. 變數宣告方式
1.1 var vs let vs const
在 TypeScript 中,我們有三種變數宣告方式,每種都有其特定的行為和使用場景。
var(不推薦使用)
typescript
1var oldStyle = "這是舊式宣告";let(可變變數)
typescript
1let mutableValue = "可以改變的值";const(常數)
typescript
1const immutableValue = "不可改變的值";1.2 var 的問題
var 有幾個重要的問題,這就是為什麼我們推薦使用 let 和 const:
1.2.1 函數作用域 vs 塊級作用域
typescript
1function demonstrateScope() {
2 if (true) {
3 var varVariable = "我是 var";
4 let letVariable = "我是 let";
5 const constVariable = "我是 const";
6 }
7
8 console.log(varVariable); // ✅ 可以訪問
9 // console.log(letVariable); // ❌ 錯誤:塊級作用域外無法訪問
10 // console.log(constVariable); // ❌ 錯誤:塊級作用域外無法訪問
11}1.2.2 變數提升(Hoisting)
typescript
1function hoistingExample() {
2 console.log(varVariable); // undefined(而不是錯誤)
3 // console.log(letVariable); // ❌ 錯誤:在初始化前無法訪問
4
5 var varVariable = "var 被提升了";
6 let letVariable = "let 沒有被提升";
7}1.2.3 重複宣告
typescript
1function redeclarationExample() {
2 var name = "第一次宣告";
3 var name = "第二次宣告"; // ✅ var 允許重複宣告
4
5 let age = 25;
6 // let age = 30; // ❌ 錯誤:let 不允許重複宣告
7}1.3 let 的特性
let 提供了更安全的變數宣告方式:
typescript
1// 塊級作用域
2{
3 let blockScoped = "只在這個塊中可見";
4 console.log(blockScoped); // ✅ 可以訪問
5}
6// console.log(blockScoped); // ❌ 錯誤:超出作用域
7
8// 暫時性死區(Temporal Dead Zone)
9function temporalDeadZone() {
10 // console.log(value); // ❌ 錯誤:在初始化前訪問
11 let value = "安全的變數";
12 console.log(value); // ✅ 可以訪問
13}
14
15// 可以重新賦值
16let counter = 0;
17counter = 1; // ✅ 可以重新賦值
18counter = "字串"; // ❌ 錯誤:類型不匹配1.4 const 的特性
const 用於宣告常數:
typescript
1// 基本類型的常數
2const PI = 3.14159;
3// PI = 3.14; // ❌ 錯誤:不能重新賦值
4
5// 物件常數(引用不可變,內容可變)
6const user = {
7 name: "Alice",
8 age: 25
9};
10
11// user = {}; // ❌ 錯誤:不能重新賦值引用
12user.age = 26; // ✅ 可以修改物件屬性
13
14// 陣列常數
15const colors = ["red", "green", "blue"];
16// colors = []; // ❌ 錯誤:不能重新賦值引用
17colors.push("yellow"); // ✅ 可以修改陣列內容2. 作用域深入理解
2.1 全域作用域
typescript
1// 全域變數
2let globalVariable = "我是全域變數";
3const GLOBAL_CONSTANT = "我是全域常數";
4
5function accessGlobal() {
6 console.log(globalVariable); // ✅ 可以訪問全域變數
7 console.log(GLOBAL_CONSTANT); // ✅ 可以訪問全域常數
8}2.2 函數作用域
typescript
1function functionScope() {
2 let functionVariable = "我在函數內";
3 const FUNCTION_CONSTANT = "我也在函數內";
4
5 function innerFunction() {
6 console.log(functionVariable); // ✅ 可以訪問外層函數變數
7 console.log(FUNCTION_CONSTANT); // ✅ 可以訪問外層函數常數
8
9 let innerVariable = "我在內層函數";
10 }
11
12 innerFunction();
13 // console.log(innerVariable); // ❌ 錯誤:無法訪問內層函數變數
14}2.3 塊級作用域
typescript
1function blockScope() {
2 let outerVariable = "外層變數";
3
4 if (true) {
5 let blockVariable = "塊級變數";
6 console.log(outerVariable); // ✅ 可以訪問外層變數
7 console.log(blockVariable); // ✅ 可以訪問當前塊變數
8
9 {
10 let nestedBlockVariable = "嵌套塊變數";
11 console.log(blockVariable); // ✅ 可以訪問外層塊變數
12 }
13
14 // console.log(nestedBlockVariable); // ❌ 錯誤:無法訪問嵌套塊變數
15 }
16
17 // console.log(blockVariable); // ❌ 錯誤:無法訪問塊級變數
18}2.4 模組作用域
typescript
1// 在模組頂層宣告的變數
2let moduleVariable = "模組級變數";
3
4export function getModuleVariable() {
5 return moduleVariable; // ✅ 可以訪問模組變數
6}
7
8// 其他模組無法直接訪問 moduleVariable,除非明確 export3. 解構賦值與類型註解
3.1 物件解構
typescript
1interface User {
2 id: number;
3 name: string;
4 email?: string;
5 age: number;
6}
7
8const userData: User = {
9 id: 1,
10 name: "Alice",
11 email: "[email protected]",
12 age: 25
13};
14
15// 基本解構(類型推斷)
16const { id, name, email } = userData;
17// id: number, name: string, email: string | undefined
18
19// 解構時重命名
20const { id: userId, name: userName } = userData;
21
22// 解構時提供預設值
23const { email: userEmail = "[email protected]" } = userData;
24
25// 明確類型註解
26const { age }: { age: number } = userData;
27
28// 複雜解構與類型註解
29const {
30 id: userIdentifier,
31 name: fullName,
32 email: contactEmail = "unknown"
33}: {
34 id: number;
35 name: string;
36 email?: string
37} = userData;3.2 陣列解構
typescript
1// 基本陣列解構
2const numbers: number[] = [1, 2, 3, 4, 5];
3const [first, second, third] = numbers;
4// first: number, second: number, third: number
5
6// 跳過元素
7const [firstNum, , thirdNum] = numbers;
8
9// 剩餘元素
10const [head, ...tail] = numbers;
11// head: number, tail: number[]
12
13// 元組解構
14const coordinates: [number, number, string] = [10, 20, "origin"];
15const [x, y, label] = coordinates;
16// x: number, y: number, label: string
17
18// 明確類型註解
19const [xCoord, yCoord]: [number, number] = coordinates;3.3 嵌套解構
typescript
1interface Address {
2 street: string;
3 city: string;
4 country: string;
5}
6
7interface Person {
8 name: string;
9 address: Address;
10 hobbies: string[];
11}
12
13const person: Person = {
14 name: "Bob",
15 address: {
16 street: "123 Main St",
17 city: "New York",
18 country: "USA"
19 },
20 hobbies: ["reading", "swimming", "coding"]
21};
22
23// 嵌套物件解構
24const {
25 name: personName,
26 address: { city, country },
27 hobbies: [firstHobby, ...otherHobbies]
28} = person;
29
30// 帶類型註解的嵌套解構
31const {
32 address: { street }
33}: {
34 address: { street: string }
35} = person;4. 類型推斷與明確註解
4.1 何時依賴類型推斷
typescript
1// 簡單情況下可以依賴推斷
2let message = "Hello"; // 推斷為 string
3let count = 0; // 推斷為 number
4let isActive = true; // 推斷為 boolean
5
6// 陣列推斷
7let fruits = ["apple", "banana"]; // 推斷為 string[]
8let mixed = [1, "hello", true]; // 推斷為 (string | number | boolean)[]4.2 何時需要明確註解
typescript
1// 1. 變數未初始化
2let futureValue: string;
3// futureValue = "稍後賦值";
4
5// 2. 需要更寬泛的類型
6let id: string | number = 123;
7id = "user-456"; // ✅ 可以是字串或數字
8
9// 3. 複雜物件結構
10interface Product {
11 id: number;
12 name: string;
13 price: number;
14}
15
16let product: Product = {
17 id: 1,
18 name: "Laptop",
19 price: 999
20};
21
22// 4. 函數參數和返回值
23function calculateTotal(price: number, tax: number): number {
24 return price * (1 + tax);
25}5. 常見陷阱與最佳實踐
5.1 循環中的作用域問題
typescript
1// 問題:使用 var 在循環中
2function problemWithVar() {
3 var functions: (() => void)[] = [];
4
5 for (var i = 0; i < 3; i++) {
6 functions.push(function() {
7 console.log(i); // 總是印出 3
8 });
9 }
10
11 functions.forEach(fn => fn()); // 3, 3, 3
12}
13
14// 解決方案:使用 let
15function solutionWithLet() {
16 const functions: (() => void)[] = [];
17
18 for (let i = 0; i < 3; i++) {
19 functions.push(function() {
20 console.log(i); // 正確印出 0, 1, 2
21 });
22 }
23
24 functions.forEach(fn => fn()); // 0, 1, 2
25}5.2 const 的正確理解
typescript
1// const 保護的是綁定,不是值
2const obj = { value: 1 };
3obj.value = 2; // ✅ 可以修改屬性
4
5const arr = [1, 2, 3];
6arr.push(4); // ✅ 可以修改陣列
7
8// 如果需要深度不可變,使用 readonly
9interface ReadonlyUser {
10 readonly id: number;
11 readonly name: string;
12}
13
14const readonlyUser: ReadonlyUser = { id: 1, name: "Alice" };
15// readonlyUser.name = "Bob"; // ❌ 錯誤:readonly 屬性5.3 最佳實踐
- 優先使用 const,只有在需要重新賦值時才使用 let
- 避免使用 var,除非有特殊需求
- 在最小的作用域內宣告變數
- 使用有意義的變數名稱
- 適當使用類型註解提高可讀性
互動練習
練習 1:作用域理解
理解不同宣告方式的作用域行為。
typescript
1type: simple_run
2instruction: 觀察不同變數宣告方式在作用域中的行為差異。
3---
4function scopeDemo() {
5 console.log("=== 作用域演示 ===");
6
7 // 全域作用域
8 var globalVar = "全域 var";
9 let globalLet = "全域 let";
10 const globalConst = "全域 const";
11
12 if (true) {
13 // 塊級作用域
14 var blockVar = "塊級 var";
15 let blockLet = "塊級 let";
16 const blockConst = "塊級 const";
17
18 console.log("在塊內:");
19 console.log("可以訪問 globalVar:", globalVar);
20 console.log("可以訪問 globalLet:", globalLet);
21 console.log("可以訪問 globalConst:", globalConst);
22 console.log("可以訪問 blockVar:", blockVar);
23 console.log("可以訪問 blockLet:", blockLet);
24 console.log("可以訪問 blockConst:", blockConst);
25 }
26
27 console.log("\n在塊外:");
28 console.log("可以訪問 globalVar:", globalVar);
29 console.log("可以訪問 globalLet:", globalLet);
30 console.log("可以訪問 globalConst:", globalConst);
31 console.log("可以訪問 blockVar:", blockVar); // var 可以訪問
32
33 // 以下會產生錯誤(在實際 TypeScript 中)
34 // console.log("無法訪問 blockLet:", blockLet);
35 // console.log("無法訪問 blockConst:", blockConst);
36
37 console.log("✅ 作用域演示完成!");
38}
39
40scopeDemo();typescript
1// 作用域規則總結:
2
3// 1. var 的特性:
4// - 函數作用域或全域作用域
5// - 可以在宣告前訪問(值為 undefined)
6// - 允許重複宣告
7
8// 2. let 的特性:
9// - 塊級作用域
10// - 暫時性死區(不能在宣告前訪問)
11// - 不允許重複宣告
12
13// 3. const 的特性:
14// - 塊級作用域
15// - 暫時性死區
16// - 不允許重複宣告
17// - 不允許重新賦值(但物件內容可變)
18
19// 最佳實踐:
20// - 優先使用 const
21// - 需要重新賦值時使用 let
22// - 避免使用 var練習 2:變數重新賦值與類型安全
學習 TypeScript 如何保護變數的類型安全。
typescript
1type: output_check
2instruction: 完成變數管理系統,展示 let 和 const 的正確使用。
3expectedOutput: 用戶: Alice (25歲), 積分: 1500, 設定: {"theme":"dark","notifications":true}
4---
5// 用戶資料管理系統
6class UserManager {
7 // 使用 const 宣告不會改變的配置
8 private readonly CONFIG = {
9 maxScore: 2000,
10 defaultTheme: "light"
11 };
12
13 // 使用 let 宣告會改變的狀態
14 private currentUser: string | null = null;
15 private userAge: number = 0;
16 private userScore: number = 0;
17
18 // 使用 const 宣告物件(引用不變,內容可變)
19 private readonly userSettings = {
20 theme: "light",
21 notifications: false
22 };
23
24 setUser(name: string, age: number): void {
25 this.currentUser = name; // let 變數可以重新賦值
26 this.userAge = age;
27 this.userScore = 0; // 重置積分
28 }
29
30 updateScore(points: number): void {
31 // 檢查是否超過最大值
32 const newScore = this.userScore + points;
33 if (newScore <= this.CONFIG.maxScore) {
34 this.userScore = newScore; // let 變數可以重新賦值
35 }
36 }
37
38 updateSettings(theme: string, notifications: boolean): void {
39 // const 物件的屬性可以修改
40 this.userSettings.theme = theme;
41 this.userSettings.notifications = notifications;
42 }
43
44 getUserInfo(): string {
45 if (!this.currentUser) {
46 return "沒有用戶登入";
47 }
48
49 // 使用解構賦值獲取設定
50 const { theme, notifications } = this.userSettings;
51 const settings = { theme, notifications };
52
53 return `用戶: ${this.currentUser} (${this.userAge}歲), 積分: ${this.userScore}, 設定: ${JSON.stringify(settings)}`;
54 }
55}
56
57// 測試用戶管理系統
58const manager = new UserManager();
59manager.setUser("Alice", 25);
60manager.updateScore(1000);
61manager.updateScore(500);
62manager.updateSettings("dark", true);
63
64console.log(manager.getUserInfo());typescript
1// 變數宣告的最佳實踐:
2
3// 1. const 的使用場景:
4// - 配置物件(CONFIG)
5// - 不會重新賦值的引用(userSettings)
6// - 類別實例(manager)
7
8// 2. let 的使用場景:
9// - 需要重新賦值的變數(currentUser, userAge, userScore)
10// - 循環計數器
11// - 條件性賦值的變數
12
13// 3. readonly 修飾符:
14// - 防止類別屬性被重新賦值
15// - 提供額外的類型安全保護
16
17// 4. 解構賦值的好處:
18// - 簡潔的語法
19// - 自動類型推斷
20// - 提高程式碼可讀性
21
22// 記住:const 保護的是綁定,不是值!練習 3:解構賦值實戰
掌握複雜的解構賦值模式和類型註解。
typescript
1type: output_check
2instruction: 使用解構賦值處理複雜的數據結構,包括嵌套物件和陣列。
3expectedOutput: 公司: TechCorp, CEO: John Doe, 部門: [Engineering,Marketing], 第一個員工: Alice (Engineer)
4---
5// 複雜數據結構
6interface Employee {
7 name: string;
8 position: string;
9 salary: number;
10}
11
12interface Department {
13 name: string;
14 employees: Employee[];
15}
16
17interface Company {
18 name: string;
19 ceo: {
20 firstName: string;
21 lastName: string;
22 };
23 departments: Department[];
24 founded: number;
25}
26
27const companyData: Company = {
28 name: "TechCorp",
29 ceo: {
30 firstName: "John",
31 lastName: "Doe"
32 },
33 departments: [
34 {
35 name: "Engineering",
36 employees: [
37 { name: "Alice", position: "Engineer", salary: 80000 },
38 { name: "Bob", position: "Senior Engineer", salary: 95000 }
39 ]
40 },
41 {
42 name: "Marketing",
43 employees: [
44 { name: "Carol", position: "Marketing Manager", salary: 75000 }
45 ]
46 }
47 ],
48 founded: 2010
49};
50
51// 使用解構賦值提取數據
52function analyzeCompany(company: Company): string {
53 // 解構公司基本資訊
54 const {
55 name: companyName,
56 ceo: { firstName, lastName },
57 departments
58 } = company;
59
60 // 解構部門名稱
61 const departmentNames = departments.map(({ name }) => name);
62
63 // 解構第一個部門的第一個員工
64 const [firstDepartment] = departments;
65 const { employees: [firstEmployee] } = firstDepartment;
66 const { name: employeeName, position } = firstEmployee;
67
68 // 組合 CEO 全名
69 const ceoFullName = `${firstName} ${lastName}`;
70
71 return `公司: ${companyName}, CEO: ${ceoFullName}, 部門: [${departmentNames.join(",")}], 第一個員工: ${employeeName} (${position})`;
72}
73
74// 測試解構賦值
75const result = analyzeCompany(companyData);
76console.log(result);typescript
1// 解構賦值的高級技巧:
2
3// 1. 嵌套物件解構:
4// const { ceo: { firstName, lastName } } = company;
5// 直接從嵌套物件中提取屬性
6
7// 2. 陣列解構:
8// const [firstDepartment] = departments;
9// 提取陣列的第一個元素
10
11// 3. 組合解構:
12// const { employees: [firstEmployee] } = firstDepartment;
13// 先解構物件屬性,再解構陣列元素
14
15// 4. 重命名:
16// const { name: companyName } = company;
17// 解構時重命名變數
18
19// 5. 在函數參數中使用解構:
20// departments.map(({ name }) => name)
21// 直接在 map 回調中解構
22
23// 最佳實踐:
24// - 保持解構模式簡潔易讀
25// - 適當使用類型註解
26// - 避免過度嵌套的解構練習 4:作用域陷阱避免
學習如何避免常見的作用域相關問題。
typescript
1type: output_check
2instruction: 修復循環中的作用域問題,確保每個函數都能正確捕獲其對應的值。
3expectedOutput: 按鈕 0 被點擊,按鈕 1 被點擊,按鈕 2 被點擊
4---
5// 模擬按鈕點擊處理器
6type ClickHandler = () => void;
7
8class ButtonManager {
9 private handlers: ClickHandler[] = [];
10
11 // 錯誤的實現(使用 var)
12 createHandlersWrong(count: number): void {
13 this.handlers = [];
14
15 for (var i = 0; i < count; i++) {
16 this.handlers.push(function() {
17 return `按鈕 ${i} 被點擊`; // 問題:i 總是最終值
18 } as any);
19 }
20 }
21
22 // 正確的實現(使用 let)
23 createHandlersCorrect(count: number): void {
24 this.handlers = [];
25
26 for (let i = 0; i < count; i++) { // 使用 let 創建塊級作用域
27 this.handlers.push(() => {
28 return `按鈕 ${i} 被點擊`; // 正確:每個 i 都有自己的作用域
29 } as any);
30 }
31 }
32
33 // 另一種解決方案:使用立即執行函數
34 createHandlersIIFE(count: number): void {
35 this.handlers = [];
36
37 for (var i = 0; i < count; i++) {
38 this.handlers.push((function(index) {
39 return function() {
40 return `按鈕 ${index} 被點擊`;
41 };
42 })(i) as any);
43 }
44 }
45
46 // 現代解決方案:使用 forEach
47 createHandlersModern(count: number): void {
48 this.handlers = [];
49
50 Array.from({ length: count }, (_, i) => i).forEach(i => {
51 this.handlers.push(() => {
52 return `按鈕 ${i} 被點擊`;
53 } as any);
54 });
55 }
56
57 simulateClicks(): string {
58 return this.handlers.map(handler => (handler as any)()).join(",");
59 }
60}
61
62// 測試不同的實現
63const manager = new ButtonManager();
64
65// 使用正確的實現
66manager.createHandlersCorrect(3);
67const result = manager.simulateClicks();
68console.log(result);
69
70// 驗證其他實現也能工作
71manager.createHandlersModern(3);
72const modernResult = manager.simulateClicks();
73console.log("現代方法:", modernResult);typescript
1// 作用域問題的解決方案:
2
3// 1. 問題根源:
4// var 是函數作用域,在循環中所有迭代共享同一個變數
5// 當回調函數執行時,循環已經結束,i 是最終值
6
7// 2. 解決方案一:使用 let
8// let 是塊級作用域,每次迭代都創建新的綁定
9// 每個回調函數捕獲自己的 i 值
10
11// 3. 解決方案二:立即執行函數(IIFE)
12// 創建新的函數作用域,將當前的 i 值作為參數傳入
13// 在 ES6 之前的常用解決方案
14
15// 4. 解決方案三:現代方法
16// 使用 Array.from 和 forEach
17// forEach 的回調函數參數自然形成閉包
18
19// 最佳實踐:
20// - 優先使用 let/const 而不是 var
21// - 理解閉包和作用域的關係
22// - 在循環中創建函數時要特別注意作用域練習 5:類型註解最佳實踐
學習何時使用明確的類型註解,何時依賴類型推斷。
typescript
1type: output_check
2instruction: 重構程式碼,在適當的地方添加類型註解,在其他地方依賴類型推斷。
3expectedOutput: 處理結果: 成功處理了 3 個項目,跳過了 1 個無效項目
4---
5// 數據處理系統
6interface DataItem {
7 id: number;
8 value: string;
9 isValid: boolean;
10}
11
12interface ProcessResult {
13 processed: number;
14 skipped: number;
15 errors: string[];
16}
17
18class DataProcessor {
19 // 明確註解:公共 API 的參數和返回值
20 processData(items: DataItem[]): ProcessResult {
21 // 依賴推斷:簡單的初始化
22 let processed = 0;
23 let skipped = 0;
24 const errors: string[] = []; // 明確註解:空陣列需要類型資訊
25
26 // 依賴推斷:forEach 回調參數類型可以推斷
27 items.forEach(item => {
28 if (this.validateItem(item)) {
29 processed++;
30 } else {
31 skipped++;
32 }
33 });
34
35 // 依賴推斷:返回物件類型可以推斷
36 return {
37 processed,
38 skipped,
39 errors
40 };
41 }
42
43 // 明確註解:私有方法也建議註解參數和返回值
44 private validateItem(item: DataItem): boolean {
45 // 依賴推斷:簡單的布林運算
46 return item.isValid && item.value.length > 0;
47 }
48
49 // 明確註解:處理未知類型的數據
50 processUnknownData(data: unknown): ProcessResult | null {
51 // 明確註解:類型守衛需要明確類型
52 if (!this.isDataItemArray(data)) {
53 return null;
54 }
55
56 // 依賴推斷:類型守衛後類型已確定
57 return this.processData(data);
58 }
59
60 // 明確註解:類型守衛函數
61 private isDataItemArray(data: unknown): data is DataItem[] {
62 return Array.isArray(data) &&
63 data.every(item =>
64 typeof item === 'object' &&
65 item !== null &&
66 typeof item.id === 'number' &&
67 typeof item.value === 'string' &&
68 typeof item.isValid === 'boolean'
69 );
70 }
71}
72
73// 測試數據處理
74const processor = new DataProcessor();
75
76// 明確註解:複雜的數據結構
77const testData: DataItem[] = [
78 { id: 1, value: "valid data", isValid: true },
79 { id: 2, value: "also valid", isValid: true },
80 { id: 3, value: "valid too", isValid: true },
81 { id: 4, value: "", isValid: false } // 無效項目
82];
83
84// 依賴推斷:函數返回值類型可以推斷
85const result = processor.processData(testData);
86
87// 依賴推斷:字串模板中的表達式
88const message = `處理結果: 成功處理了 ${result.processed} 個項目,跳過了 ${result.skipped} 個無效項目`;
89
90console.log(message);typescript
1// 類型註解的最佳實踐:
2
3// 何時使用明確註解:
4// 1. 函數參數和返回值(特別是公共 API)
5// 2. 空陣列或物件的初始化
6// 3. 複雜的數據結構
7// 4. 類型守衛函數
8// 5. 處理 unknown 或 any 類型時
9
10// 何時依賴推斷:
11// 1. 簡單的變數初始化
12// 2. 函數內部的局部變數
13// 3. 回調函數參數(當上下文明確時)
14// 4. 返回物件字面量
15// 5. 數學運算和字串操作
16
17// 平衡原則:
18// - 在介面邊界使用明確註解
19// - 在實現細節中依賴推斷
20// - 當推斷不夠精確時使用註解
21// - 為了可讀性而添加註解
22
23// 記住:類型註解是文檔,也是契約!練習 6:進階解構模式
掌握複雜的解構模式和預設值處理。
typescript
1type: output_check
2instruction: 實現一個配置管理器,使用進階解構模式處理複雜的配置物件。
3expectedOutput: 應用配置: 主題=dark, 語言=zh-TW, 通知=true, API端點=https://api.example.com, 超時=5000ms
4---
5// 配置管理系統
6interface DatabaseConfig {
7 host: string;
8 port: number;
9 username: string;
10 password: string;
11}
12
13interface ApiConfig {
14 endpoint: string;
15 timeout: number;
16 retries?: number;
17}
18
19interface AppConfig {
20 theme: "light" | "dark";
21 language: string;
22 notifications: boolean;
23 database: DatabaseConfig;
24 api: ApiConfig;
25 features?: {
26 beta?: boolean;
27 analytics?: boolean;
28 };
29}
30
31class ConfigManager {
32 private defaultConfig: Partial<AppConfig> = {
33 theme: "light",
34 language: "en-US",
35 notifications: false,
36 api: {
37 endpoint: "https://api.example.com",
38 timeout: 5000
39 },
40 features: {
41 beta: false,
42 analytics: true
43 }
44 };
45
46 // 使用解構和預設值處理配置
47 processConfig(userConfig: Partial<AppConfig>): string {
48 // 複雜的解構模式,包含預設值
49 const {
50 theme = this.defaultConfig.theme!,
51 language = this.defaultConfig.language!,
52 notifications = this.defaultConfig.notifications!,
53 api: {
54 endpoint = this.defaultConfig.api!.endpoint,
55 timeout = this.defaultConfig.api!.timeout,
56 retries = 3
57 } = {},
58 features: {
59 beta: betaEnabled = this.defaultConfig.features!.beta!,
60 analytics: analyticsEnabled = this.defaultConfig.features!.analytics!
61 } = {}
62 } = userConfig;
63
64 // 使用解構賦值組織輸出
65 const configSummary = this.formatConfig({
66 theme,
67 language,
68 notifications,
69 endpoint,
70 timeout,
71 retries,
72 betaEnabled,
73 analyticsEnabled
74 });
75
76 return configSummary;
77 }
78
79 // 使用解構參數簡化函數簽名
80 private formatConfig({
81 theme,
82 language,
83 notifications,
84 endpoint,
85 timeout
86 }: {
87 theme: string;
88 language: string;
89 notifications: boolean;
90 endpoint: string;
91 timeout: number;
92 retries: number;
93 betaEnabled: boolean;
94 analyticsEnabled: boolean;
95 }): string {
96 return `應用配置: 主題=${theme}, 語言=${language}, 通知=${notifications}, API端點=${endpoint}, 超時=${timeout}ms`;
97 }
98
99 // 陣列解構處理多個配置源
100 mergeConfigs(...configs: Partial<AppConfig>[]): Partial<AppConfig> {
101 // 使用解構展開運算符合併配置
102 const [baseConfig, ...overrides] = configs;
103
104 return overrides.reduce((merged, override) => ({
105 ...merged,
106 ...override,
107 // 深度合併嵌套物件
108 api: { ...merged.api, ...override.api },
109 features: { ...merged.features, ...override.features }
110 }), baseConfig || {});
111 }
112}
113
114// 測試配置管理器
115const configManager = new ConfigManager();
116
117// 用戶提供的部分配置
118const userConfig: Partial<AppConfig> = {
119 theme: "dark",
120 language: "zh-TW",
121 notifications: true,
122 api: {
123 endpoint: "https://api.example.com",
124 timeout: 5000
125 }
126};
127
128// 處理配置
129const result = configManager.processConfig(userConfig);
130console.log(result);typescript
1// 進階解構模式的技巧:
2
3// 1. 嵌套解構與預設值:
4// const { api: { endpoint = "default" } = {} } = config;
5// 處理可能不存在的嵌套物件
6
7// 2. 重命名與預設值結合:
8// const { features: { beta: betaEnabled = false } = {} } = config;
9// 解構時重命名並提供預設值
10
11// 3. 函數參數解構:
12// function formatConfig({ theme, language }: ConfigType)
13// 直接在參數中解構,提高可讀性
14
15// 4. 剩餘參數與解構:
16// const [first, ...rest] = array;
17// 分離第一個元素和其餘元素
18
19// 5. 展開運算符合併:
20// { ...base, ...override }
21// 合併物件,後者覆蓋前者
22
23// 最佳實踐:
24// - 為嵌套解構提供預設值
25// - 使用有意義的重命名
26// - 保持解構模式的可讀性
27// - 適當使用類型註解確保安全性總結
在這一課中,我們深入學習了 TypeScript 的變數宣告和作用域:
關鍵要點
- 變數宣告:優先使用
const,需要重新賦值時使用let,避免使用var - 作用域理解:掌握塊級作用域、函數作用域和全域作用域的區別
- 解構賦值:靈活使用物件和陣列解構,配合適當的類型註解
- 類型註解策略:在介面邊界使用明確註解,在實現細節中依賴推斷
- 常見陷阱:避免循環中的作用域問題,正確理解
const的行為
最佳實踐
- 使用
const作為預設選擇 - 在最小的作用域內宣告變數
- 為公共 API 提供明確的類型註解
- 善用解構賦值提高程式碼可讀性
- 理解並避免作用域相關的常見錯誤
下一步
- 學習函數的定義和類型註解
- 掌握函數重載和泛型函數
- 了解箭頭函數和回調函數的類型處理
- 探索更複雜的類型操作
準備好了嗎?讓我們在下一課深入學習 TypeScript 中的函數!