用户工具

站点工具


typescript:第二章变量声明

第二章:变量声明

本章概述

变量声明是编程的基础,TypeScript 继承了 JavaScript 的所有声明方式,并添加了类型注解。本章将深入讲解 let、const、var 的区别,解构赋值,展开运算符等重要概念。

2.1 var 声明(传统方式)

在 ES6 之前,JavaScript 只有 var 这一种声明变量的方式。了解 var 的特性有助于理解为什么需要 let 和 const。

2.1.1 函数作用域

```typescript function example() {

var x = 10;
if (true) {
  var x = 20;  // 同一个变量!
  console.log(x);  // 20
}
console.log(x);  // 20

} ```

var 声明的变量具有函数作用域,而不是块级作用域。

2.1.2 变量提升

```typescript console.log(hoisted); undefined(不会报错) var hoisted = “I am hoisted”; 实际上等价于: var hoisted2; console.log(hoisted2); hoisted2 = “I am hoisted”; ```

2.1.3 重复声明

```typescript var x = 1; var x = 2; 不会报错,覆盖了之前的值 console.log(x); 2 ```

2.1.4 为什么避免使用 var

```typescript 问题1:循环中的闭包问题 for (var i = 0; i < 3; i++) { setTimeout1); 或使用库如 lodash.cloneDeep ```

2.8 作用域与闭包

2.8.1 块级作用域详解

```typescript 块级作用域示例 { let blockScoped = “I am in block”; const alsoBlockScoped = “Me too”; } console.log(blockScoped); Error: not defined if 块级作用域 if (true) {

let ifScoped = "if block";

}

for 块级作用域 for (let i = 0; i < 1; i++) { let forScoped = “for block”; } ``` ==== 2.8.2 闭包与变量捕获 ==== ```typescript 使用 let 的正确闭包 function createCounters() {

const counters = [];
for (let i = 0; i < 3; i++) {
  counters.push(() => i);
}
return counters;

}

const counters = createCounters(); console.log(counters[0]()); 0 console.log(counters[1]()); 1 console.log(counters[2]()); 2 对比:使用 var 的问题 function createCountersVar() {

const counters = [];
for (var i = 0; i < 3; i++) {
  counters.push(() => i);
}
return counters;

}

const countersVar = createCountersVar(); console.log(countersVar[0]()); 3 console.log(countersVar[1]()); 3 console.log(countersVar[2]()); 3 ``` ===== 2.9 高级解构技巧 ===== ==== 2.9.1 解构结合类型别名 ==== ```typescript type Coordinates = [number, number, number?]; const parseLocation = (loc: string): Coordinates ⇒ { const [x, y, z] = loc.split(“,”).map(Number); return z !== undefined ? [x, y, z] : [x, y]; }; const [lat, lng, alt = 0] = parseLocation(“39.9,116.3,100”); ``` ==== 2.9.2 解构与重命名 ==== ```typescript interface APIResponse { data: { user_name: string; 蛇形命名

  user_age: number;
};

}

const response: APIResponse = {

data: {
  user_name: "Alice",
  user_age: 25
}

};

解构时转换为驼峰命名 const { data: { user_name: userName, user_age: userAge } } = response; console.log(userName, userAge); “Alice” 25 ```

2.9.3 解构默认值

```typescript interface Config {

host?: string;
port?: number;
ssl?: boolean;

}

const defaultConfig: Required<Config> = {

host: "localhost",
port: 3000,
ssl: false

};

function createServer(userConfig: Config = {}) {

const { 
  host = defaultConfig.host, 
  port = defaultConfig.port, 
  ssl = defaultConfig.ssl 
} = userConfig;

return { host, port, ssl };

} ```

2.10 本章小结

本章我们学习了:

1. var、let、const 的区别 - 理解作用域、变量提升、重复声明的差异 2. let 和 const 的最佳实践 - 默认使用 const,必要时使用 let 3. 解构赋值 - 从数组和对象中提取值 4. 展开运算符 - 复制、合并、扩展数组和对象 5. 类型注解结合 - 在解构和展开时保持类型安全

2.11 练习题

练习 1:变量声明转换

将以下使用 var 的代码改写为使用 let 和 const,并解释为什么:

```typescript var name = “Alice”; var age = 25;

for (var i = 0; i < 5; i++) {

setTimeout(function() {
  console.log(i);
}, 100);

}

var config = {

apiUrl: "https://api.example.com",
timeout: 5000

}; ```

<details> <summary>参考答案</summary>

```typescript const name = “Alice”; 不会重新赋值 let age = 25; 可能会改变

for (let i = 0; i < 5; i++) { let 创建块级作用域 setTimeout(function() { console.log(i); 正确输出 0, 1, 2, 3, 4

}, 100);

}

const config = { 对象引用不变 apiUrl: “https://api.example.com”, timeout: 5000 }; ``` 解释: - name 和 config 不会改变引用,使用 const - age 可能会改变,使用 let - for 循环使用 let 确保每次迭代有独立的 i 绑定 </details> ==== 练习 2:解构赋值练习 ==== 使用解构赋值重写以下代码: ```typescript const user = { id: 1, profile: { firstName: “Alice”, lastName: “Smith”, address: { city: “Beijing”, country: “China” } }, hobbies: [“reading”, “coding”, “gaming”] }; const firstName = user.profile.firstName; const city = user.profile.address.city; const firstHobby = user.hobbies[0]; ``` <details> <summary>参考答案</summary> ```typescript const { profile: { firstName, address: { city } }, hobbies: [firstHobby] } = user; 或者分步解构 const { profile } = user; const { firstName, address: { city } } = profile; const [firstHobby] = user.hobbies; ```

</details>

练习 3:展开运算符应用

完成以下函数,使用展开运算符实现功能:

```typescript 1. 合并配置,用户配置优先级更高 function mergeConfig(defaultConfig: object, userConfig: object): object { 实现 }

2. 将数组元素插入到另一个数组的指定位置 function insertAt<T>(arr: T[], index: number, …elements: T[]): T[] { 实现 }

3. 移除对象中的指定属性 function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> { 实现 } ```

<details> <summary>参考答案</summary>

```typescript 1. 合并配置 function mergeConfig(defaultConfig: object, userConfig: object): object { return { …defaultConfig, …userConfig }; } 2. 插入元素 function insertAt<T>(arr: T[], index: number, …elements: T[]): T[] {

return [...arr.slice(0, index), ...elements, ...arr.slice(index)];

}

3. 移除属性 function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> { const result = { …obj }; for (const key of keys) { delete result[key]; } return result; } 或使用更函数式的写法 function omit2<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {

const keySet = new Set(keys);
return Object.fromEntries(
  Object.entries(obj).filter(([key]) => !keySet.has(key as K))
) as Omit<T, K>;

} ```

</details>

练习 4:类型安全的配置解析

实现一个类型安全的配置解析器:

```typescript interface DatabaseConfig {

host: string;
port: number;
username: string;
password: string;
database: string;
ssl?: boolean;

}

实现 parseConfig 函数,从环境变量解析配置 使用解构和默认值 function parseConfig(env: NodeJS.ProcessEnv): DatabaseConfig {

// 你的实现

} ```

<details> <summary>参考答案</summary>

```typescript function parseConfig(env: NodeJS.ProcessEnv): DatabaseConfig {

const {
  DB_HOST = "localhost",
  DB_PORT = "5432",
  DB_USERNAME = "postgres",
  DB_PASSWORD = "",
  DB_DATABASE = "myapp",
  DB_SSL = "false"
} = env;
return {
  host: DB_HOST,
  port: parseInt(DB_PORT, 10),
  username: DB_USERNAME,
  password: DB_PASSWORD,
  database: DB_DATABASE,
  ssl: DB_SSL === "true"
};

}

使用示例 const config = parseConfig(process.env); ``` </details> ===== 扩展阅读 ===== - 下一章:基本类型 - MDN - let - MDN - const - MDN - 解构赋值

1)
) ⇒ console.log(i), 100); } 输出:3, 3, 3(不是 0, 1, 2) 问题2:意外的变量覆盖 var message = “Hello”; if (true) {
var message = "World";  // 意外覆盖了外部变量
} console.log(message); “World” ``` ===== 2.2 let 声明(块级作用域) ===== let 是 ES6 引入的新的变量声明方式,提供了块级作用域。 ==== 2.2.1 块级作用域 ==== ```typescript function letExample() { let x = 10; if (true) { let x = 20; 不同的变量!
  console.log(x);  // 20
}
console.log(x);  // 10
} ``` 块级作用域包括:
  1. if/for/while 语句块
  2. try/catch/finally 块
  3. 单独的 {} 块
==== 2.2.2 暂时性死区(Temporal Dead Zone) ==== ```typescript console.log(letVar); Error: Cannot access 'letVar' before initialization let letVar = “hello”; 在声明之前访问 let 变量会导致错误 function deadZone() {
// 暂时性死区开始
console.log(value);  // Error!
// 暂时性死区结束
let value = 42;
} ``` ==== 2.2.3 不允许重复声明 ==== ```typescript let x = 1; let x = 2; Error: Cannot redeclare block-scoped variable 'x' 但在不同作用域可以 let y = 1; if (true) { let y = 2; OK
console.log(y);  // 2
} console.log(y); 1 ``` ==== 2.2.4 解决循环闭包问题 ==== ```typescript let 正确工作的循环 for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} 输出:0, 1, 2 每次迭代创建新的绑定 for (let i = 0; i < 3; i++) {
let i = "inner";  // 可以重新声明,因为是新的作用域
console.log(i);
} 输出:inner, inner, inner ``` ===== 2.3 const 声明(常量) ===== const 声明的是常量,一旦被赋值就不能再改变。 ==== 2.3.1 基本用法 ==== ```typescript const PI = 3.14159; PI = 3.14; Error: Cannot assign to 'PI' because it is a constant const greeting = “Hello”; greeting = “Hi”; Error ``` ==== 2.3.2 必须初始化 ==== ```typescript const uninitialized; Error: 'const' declarations must be initialized const initialized = “I have a value”; OK ``` ==== 2.3.3 常量引用 vs 值 ==== ```typescript 基本类型 - 值不能改变 const count = 5; count = 10; Error 对象 - 引用不能改变,但属性可以修改 const person = {
name: "Alice",
age: 25
}; person.age = 26; OK person.name = “Bob”; OK person = {}; Error: Cannot assign to 'person' 数组同理 const numbers = [1, 2, 3]; numbers.push(4); OK numbers[0] = 10; OK numbers = []; Error ``` ==== 2.3.4 使用 Object.freeze 深度冻结 ==== ```typescript const frozenPerson = Object.freeze({ name: “Alice”, age: 25, address: { city: “Beijing” } }); frozenPerson.age = 26; Error in strict mode 注意:Object.freeze 是浅冻结 frozenPerson.address.city = “Shanghai”; 仍然可以修改! 深度冻结函数 function deepFreeze<T>(obj: T): T {
const propNames = Object.getOwnPropertyNames(obj);
for (const name of propNames) {
  const value = (obj as any)[name];
  if (value && typeof value === "object") {
    deepFreeze(value);
  }
}
return Object.freeze(obj);
} ``` ===== 2.4 let vs const 选择指南 ===== ==== 2.4.1 默认使用 const ==== ```typescript 好的实践:默认使用 const const apiUrl = “https://api.example.com”; const maxRetries = 3; const config = { timeout: 5000, retries: 3 }; 只有在需要重新赋值时才使用 let let currentUser = null; let retryCount = 0; let isLoading = false; ``` ==== 2.4.2 何时使用 let ==== ```typescript 1. 循环计数器 for (let i = 0; i < 10; i++) { console.log(i); } 2. 需要重新赋值的变量 let score = 0; score += 10; score *= 2; 3. 条件赋值 let result: string; if (condition) { result = “A”; } else { result = “B”; } 4. try-catch 中的变量 let data: User; try {
data = JSON.parse(jsonString);
} catch (e) {
data = defaultUser;
} ``` ===== 2.5 解构赋值(Destructuring) ===== 解构赋值允许从数组或对象中提取值并赋值给变量。 ==== 2.5.1 数组解构 ==== ```typescript 基本解构 const colors = [“red”, “green”, “blue”]; const [first, second, third] = colors; console.log(first); “red” console.log(second); “green” 跳过元素 const [primary, , tertiary] = colors; console.log(tertiary); “blue” 剩余元素 const [head, …tail] = [1, 2, 3, 4, 5]; console.log(head); 1 console.log(tail); [2, 3, 4, 5] 默认值 const [a = 10, b = 20] = [1]; console.log(a); 1 console.log(b); 20 交换变量 let x = 1, y = 2; [x, y] = [y, x]; console.log(x, y); 2, 1 ``` ==== 2.5.2 对象解构 ==== ```typescript 基本解构 const user = {
name: "Alice",
age: 25,
email: "alice@example.com"
}; const { name, age, email } = user; console.log(name); “Alice” 重命名 const { name: userName, age: userAge } = user; console.log(userName); “Alice” 默认值 const { name: n, country = “Unknown” } = user; console.log(country); “Unknown” 嵌套解构 const nested = {
user: {
  profile: {
    firstName: "Alice",
    lastName: "Smith"
  }
}
}; const { user: { profile: { firstName, lastName } } } = nested; console.log(firstName); “Alice” 剩余属性 const { name: n2, …rest } = user; console.log(rest); { age: 25, email: “alice@example.com” } ``` ==== 2.5.3 函数参数解构 ==== ```typescript 对象参数解构 function greetUser({ name, age }: { name: string; age: number }): string {
return `Hello ${name}, you are ${age} years old`;
} 带默认值 function createUser({ name, age = 18, role = “user” }: { name: string; age?: number; role?: string; }) { return { name, age, role }; } 数组参数解构 function processCoordinates([x, y]: [number, number]): string {
return `X: ${x}, Y: ${y}`;
} 嵌套解构 function processUser({ name, address: { city, country } }: { name: string; address: { city: string; country: string }; }) { return `${name} lives in ${city}, ${country}`; } ``` ==== 2.5.4 解构与类型注解 ==== ```typescript 数组解构带类型 const [firstNum, secondNum]: [number, number] = [1, 2]; 对象解构带类型 const { id, title }: { id: number; title: string } = { id: 1, title: “Hello” }; 接口配合解构 interface Point {
x: number;
y: number;
} function movePoint({ x, y }: Point, dx: number, dy: number): Point {
return { x: x + dx, y: y + dy };
} ``` ===== 2.6 展开运算符(Spread Operator) ===== 展开运算符(…)可以将可迭代对象展开为多个元素。 ==== 2.6.1 数组展开 ==== ```typescript 复制数组 const original = [1, 2, 3]; const copy = […original]; 连接数组 const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = […arr1, …arr2]; [1, 2, 3, 4] 在数组中添加元素 const withPrefix = [0, …original]; [0, 1, 2, 3] const withSuffix = […original, 4]; [1, 2, 3, 4] const inMiddle = [0, …original, 4]; [0, 1, 2, 3, 4] 数组转参数 const numbers = [1, 2, 3, 4, 5]; const max = Math.max(…numbers); 5 字符串转数组 const chars = […“hello”]; ['h', 'e', 'l', 'l', 'o'] ``` ==== 2.6.2 对象展开 ==== ```typescript 复制对象(浅拷贝) const original = { a: 1, b: 2 }; const copy = { …original }; 合并对象 const obj1 = { a: 1, b: 2 }; const obj2 = { c: 3, d: 4 }; const merged = { …obj1, …obj2 }; { a: 1, b: 2, c: 3, d: 4 } 覆盖属性 const defaults = { host: “localhost”, port: 3000 }; const config = { …defaults, port: 8080 }; { host: “localhost”, port: 8080 } 条件展开 const condition = true; const dynamic = { a: 1, …(condition && { b: 2 }), 条件为真时才包含 b }; 添加新属性 const user = { name: “Alice” }; const userWithAge = { …user, age: 25 }; ``` ==== 2.6.3 函数参数(Rest Parameters) ==== ```typescript 收集剩余参数 function sum(…numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
} sum(1, 2, 3, 4, 5); 15 与普通参数结合 function greet(greeting: string, …names: string[]): string {
return `${greeting}, ${names.join(" and ")}!`;
} greet(“Hello”, “Alice”, “Bob”, “Charlie”); “Hello, Alice and Bob and Charlie!” ``` ===== 2.7 类型声明的最佳实践 ===== ==== 2.7.1 优先使用 const ==== ```typescript ✓ 好的实践 const API_URL = “https://api.example.com”; const MAX_ITEMS = 100; const user = { name: “Alice”, age: 25 }; ✗ 避免 let API_URL = “https://api.example.com”; 不会改变的值不需要 let ``` ==== 2.7.2 解构时添加类型 ==== ```typescript ✓ 明确的类型 interface User { name: string; age: number; } function processUser(user: User): void { const { name, age }: { name: string; age: number } = user; 或使用接口
const { name: n, age: a }: User = user;
} ✓ 更简洁的方式 function processUser2({ name, age }: User): void { console.log(name, age); } ``` ==== 2.7.3 展开运算符的注意事项 ==== ```typescript ✓ 浅拷贝问题 const user = { name: “Alice”, address: { city: “Beijing” } }; const userCopy = { …user }; userCopy.address.city = “Shanghai”; console.log(user.address.city); “Shanghai” - 原始对象也被修改了! ✓ 深拷贝方案 const deepCopy = JSON.parse(JSON.stringify(user
typescript/第二章变量声明.txt · 最后更改: 127.0.0.1