第二章:变量声明
本章概述
变量声明是编程的基础,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 - 解构赋值