MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript 变量声明:let 与 const 的最佳实践

2021-11-116.4k 阅读

变量声明基础:let 与 const 简介

在 TypeScript 中,letconst 是用于声明变量的关键字,它们与 JavaScript 中的对应关键字功能基本一致,但在 TypeScript 强类型的环境下有更明确的使用场景和优势。

let 用于声明可变的变量。这意味着在变量声明后,其值可以在后续代码中被修改。例如:

let num: number = 10;
num = 20; // 合法,因为 num 是用 let 声明的可变变量

const 则用于声明常量,即一旦声明,其值就不能再被改变。例如:

const PI: number = 3.14159;
// PI = 3.14; // 错误,PI 是常量,不能重新赋值

let 的特性与最佳实践

块级作用域

let 声明的变量具有块级作用域。块级作用域是由一对花括号 {} 界定的区域,这与 var 声明的变量不同,var 具有函数作用域。例如:

function testLetScope() {
    if (true) {
        let localVar: number = 10;
        console.log(localVar); // 输出: 10
    }
    // console.log(localVar); // 报错,localVar 在此处不可访问,因为其作用域仅限于 if 块
}

在循环中使用 let 时,这种块级作用域特性尤为重要。考虑以下示例:

for (let i: number = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}

在上述代码中,每次循环迭代时,let 都会创建一个新的块级作用域,i 的值在每个块级作用域中都是独立的。所以,一秒后,控制台会依次输出 01234

如果使用 var 来代替 let

for (var i: number = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}

这里 var 的函数作用域特性使得 i 在整个函数内只有一个实例。一秒后,控制台会输出五次 5,因为 setTimeout 的回调函数在循环结束后才执行,此时 i 的值已经变为 5

最佳实践场景

  1. 值会变化的局部变量:当你需要一个在块级作用域内值会发生变化的变量时,使用 let。比如在一个函数内部,你需要根据不同的条件改变某个变量的值:
function calculateSum() {
    let sum: number = 0;
    for (let i: number = 1; i <= 10; i++) {
        sum += i;
    }
    return sum;
}
  1. 循环计数器:由于 let 的块级作用域特性,在循环中作为计数器变量是非常合适的选择。如上述循环示例,let 保证了每次迭代中计数器变量的独立性。

const 的特性与最佳实践

常量声明与不可变性

const 声明的常量在声明时必须初始化,因为一旦声明后其值就不能再被修改。例如:

const maxCount: number = 100;
// maxCount = 200; // 报错,不能重新赋值给常量

对于对象和数组类型,const 保证的是引用的不可变性,而不是对象或数组内部值的不可变性。例如:

const user: { name: string; age: number } = { name: 'John', age: 30 };
// user = { name: 'Jane', age: 25 }; // 报错,不能重新赋值给常量 user
user.age = 31; // 合法,对象内部属性可以修改

同样,对于数组:

const numbers: number[] = [1, 2, 3];
// numbers = [4, 5, 6]; // 报错,不能重新赋值给常量 numbers
numbers.push(4); // 合法,数组内部元素可以修改

如果要确保对象或数组内部值也不可变,可以使用 Readonly 类型。对于对象:

const readonlyUser: Readonly<{ name: string; age: number }> = { name: 'John', age: 30 };
// readonlyUser.age = 31; // 报错,Readonly 对象的属性不能被修改

对于数组:

const readonlyNumbers: ReadonlyArray<number> = [1, 2, 3];
// readonlyNumbers.push(4); // 报错,ReadonlyArray 不能修改

最佳实践场景

  1. 全局配置常量:在应用程序中,有一些全局的配置值不会发生变化,比如 API 的基础 URL、应用程序的版本号等。使用 const 声明这些常量可以提高代码的可读性和可维护性。
const API_BASE_URL: string = 'https://api.example.com';
const APP_VERSION: string = '1.0.0';
  1. 数学常量和物理常量:类似于数学中的 PI、物理中的光速等常量,使用 const 声明是非常合适的。
const LIGHT_SPEED: number = 299792458;
  1. 不可变数据结构:当你希望确保某个数据结构不被意外修改时,使用 const 并结合 Readonly 类型。例如,在函数接收一个不应该被修改的数据结构作为参数时:
function processData(data: Readonly<{ key: string; value: number }>) {
    // 这里不能修改 data 的属性
    console.log(data.key, data.value);
}

const myData: Readonly<{ key: string; value: number }> = { key: 'test', value: 10 };
processData(myData);

何时选择 let 何时选择 const

基于变量是否会改变

最直接的判断依据就是变量的值是否会在其作用域内发生变化。如果变量的值在声明后不会再改变,那么使用 const。例如:

// 计算圆的面积,半径不变
const radius: number = 5;
const area: number = Math.PI * radius * radius;

如果变量的值会在后续代码中被修改,那么使用 let。比如在一个动画效果中,需要不断更新元素的位置:

let elementPosition: number = 0;
function animate() {
    elementPosition += 10;
    // 更新元素位置的逻辑
}

代码的可维护性和可读性

从代码的可维护性和可读性角度考虑,const 更有助于明确变量的意图。当其他开发者看到 const 声明的变量时,他们知道这个变量的值不会改变,这有助于理解代码的逻辑。

例如,在一个复杂的业务逻辑函数中,如果有一些中间变量是作为临时计算结果且不会改变的,使用 const 声明可以让代码更清晰:

function calculateTotalPrice(prices: number[], taxRate: number) {
    const subtotal: number = prices.reduce((acc, price) => acc + price, 0);
    const taxAmount: number = subtotal * taxRate;
    const totalPrice: number = subtotal + taxAmount;
    return totalPrice;
}

这里 subtotaltaxAmounttotalPrice 在计算过程中值不会改变,使用 const 声明使得代码意图更加明确。

性能考虑

在现代 JavaScript 引擎中,letconst 在性能上的差异微乎其微。引擎在优化代码时,通常会对 const 声明的常量进行更积极的优化,因为它们的值是固定的。但这种优化在大多数实际应用场景中对整体性能的提升并不明显。

不过,从代码的简洁性和语义正确性角度出发,合理使用 letconst 可以让代码在长期维护中更易于理解和优化。例如,对于一些在整个应用程序生命周期中都不会改变的全局配置常量,使用 const 不仅语义清晰,而且可能在某些情况下有助于引擎的优化。

与函数和类的结合使用

在函数内部

在函数内部,letconst 的使用遵循一般的规则。const 适用于那些在函数执行过程中不会改变的值,而 let 适用于需要变化的中间变量。

例如,在一个函数中计算两个数的最大公约数(GCD):

function gcd(a: number, b: number) {
    while (b!== 0) {
        let temp: number = b;
        b = a % b;
        a = temp;
    }
    return a;
}

这里 temp 作为临时变量,其值在每次循环中会改变,所以使用 let 声明。

再比如,在一个函数中根据固定的税率计算税额:

function calculateTax(amount: number) {
    const TAX_RATE: number = 0.1;
    const taxAmount: number = amount * TAX_RATE;
    return taxAmount;
}

这里 TAX_RATE 是固定的税率,使用 const 声明;taxAmount 是根据 amountTAX_RATE 计算得出的,在函数执行过程中不会改变,也使用 const 声明。

在类中

在类中,letconst 主要用于声明类的属性。对于类的实例属性,如果属性的值在实例的生命周期内不会改变,可以使用 const 声明。

例如:

class Circle {
    constructor(public readonly radius: number) {}
    const PI: number = 3.14159;
    calculateArea() {
        return this.PI * this.radius * this.radius;
    }
}

这里 PI 是一个类的常量属性,对于所有 Circle 实例都是相同的且不会改变,使用 const 声明。radius 使用 readonly 修饰符,确保在实例创建后不能重新赋值,类似于 const 的不可变性,但用于实例属性。

如果实例属性的值会在实例的方法中改变,则使用 let 声明。例如,在一个表示计数器的类中:

class Counter {
    private let count: number = 0;
    increment() {
        this.count++;
    }
    getCount() {
        return this.count;
    }
}

这里 count 作为计数器的值,会在 increment 方法中改变,所以使用 let 声明。

常见错误与避免方法

const 变量重新赋值错误

最常见的错误之一就是试图对 const 声明的常量重新赋值。例如:

const message: string = 'Hello';
message = 'World'; // 报错,不能重新赋值给常量 message

要避免这种错误,在声明变量时仔细考虑其值是否真的不会改变。如果不确定,先使用 let 声明,等确定其值稳定后再改为 const

块级作用域理解错误

在使用 let 时,对块级作用域的误解可能导致错误。例如:

function wrongLetScope() {
    let localVar: number = 10;
    if (true) {
        let localVar: number = 20; // 这里创建了一个新的块级作用域内的 localVar
        console.log(localVar); // 输出: 20
    }
    console.log(localVar); // 输出: 10,因为这里访问的是外层作用域的 localVar
}

为了避免这种混淆,在块级作用域内声明变量时,要确保变量名的唯一性,或者明确理解不同块级作用域内同名变量的独立性。

对对象和数组 const 不可变性的误解

如前文所述,const 声明的对象和数组只是保证引用的不可变性,而不是内部值的不可变性。如果错误地认为对象或数组内部值也不能改变,可能会导致意外的结果。

例如:

const myArray: number[] = [1, 2, 3];
myArray.push(4); // 合法,但可能与预期不符,如果认为数组不能改变的话

要确保对象和数组内部值也不可变,使用 Readonly 类型。

总结与建议

在 TypeScript 开发中,letconst 的正确使用对于编写清晰、健壮的代码至关重要。遵循以下几点建议可以帮助你更好地使用它们:

  1. 优先使用 const:除非你明确知道变量的值会在其作用域内改变,否则优先使用 const。这有助于提高代码的可读性和可维护性,同时也符合函数式编程的理念,使代码更易于推理。
  2. 理解块级作用域:对于 let 声明的变量,要清楚其块级作用域特性,避免在不同块级作用域中意外创建同名变量导致混淆。
  3. 注意对象和数组的不可变性:对于 const 声明的对象和数组,要明白其引用不可变但内部值可变的特性。如果需要确保内部值也不可变,使用 Readonly 类型。
  4. 结合函数和类的特性:在函数和类中,根据变量的用途和可变性,合理选择 letconst 进行声明。

通过深入理解 letconst 的特性,并在实际项目中遵循最佳实践,你可以编写出更高效、更易于维护的 TypeScript 代码。无论是小型项目还是大型企业级应用,正确使用变量声明关键字都是构建高质量前端应用的基础。