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

TypeScript中的可选参数与默认参数

2023-07-295.2k 阅读

可选参数

在TypeScript中,函数参数可以被标记为可选的。这意味着在调用函数时,这些参数可以不被传递。可选参数在函数定义时通过在参数名后添加问号 ? 来表示。

基本语法

function greet(name: string, message?: string) {
    if (message) {
        console.log(`Hello, ${name}! ${message}`);
    } else {
        console.log(`Hello, ${name}!`);
    }
}

greet('John');
greet('Jane', 'How are you?');

在上述代码中,message 参数是可选的。当我们调用 greet('John') 时,message 参数未被传递,函数仍然能够正常执行并输出 Hello, John!。而当我们调用 greet('Jane', 'How are you?') 时,message 参数被传递,函数输出 Hello, Jane! How are you?

可选参数的位置

可选参数必须放在必选参数之后。如果将可选参数放在必选参数之前,TypeScript会报错。

// 错误示例
function wrongOrder(message?: string, name: string) {
    console.log(`${message} ${name}`);
}

上述代码会报错,提示 A required parameter cannot follow an optional parameter。正确的顺序应该是将 name 放在前面,message 放在后面。

可选参数类型推断

TypeScript会根据函数调用时是否传递可选参数来推断其类型。如果在函数内部使用了可选参数,我们需要进行类型检查,以确保在参数未传递时不会出现运行时错误。

function printValue(value: number, exponent?: number) {
    let result;
    if (exponent) {
        result = Math.pow(value, exponent);
    } else {
        result = value;
    }
    console.log(result);
}

printValue(5);
printValue(5, 2);

在这个例子中,当 exponent 存在时,我们执行幂运算;否则,直接输出 value。这样就避免了在 exponent 未传递时出现 exponentundefined 导致的运行时错误。

默认参数

默认参数为函数参数提供了一个默认值。如果在函数调用时没有传递该参数,函数将使用默认值。在TypeScript中,默认参数通过在参数名后添加 = 和默认值来定义。

基本语法

function greet(name: string, message = 'Welcome!') {
    console.log(`Hello, ${name}! ${message}`);
}

greet('Tom');
greet('Jerry', 'Nice to see you!');

在上述代码中,message 参数有一个默认值 Welcome!。当调用 greet('Tom') 时,由于没有传递 message 参数,函数使用默认值,输出 Hello, Tom! Welcome!。而当调用 greet('Jerry', 'Nice to see you!') 时,传递了 message 参数,函数使用传递的值,输出 Hello, Jerry! Nice to see you!

默认参数的类型推断

TypeScript会根据默认值的类型来推断参数的类型。如果没有显式指定参数类型,TypeScript会从默认值推断类型。

function addNumbers(a = 1, b = 2) {
    return a + b;
}

let sum1 = addNumbers();
let sum2 = addNumbers(5);
let sum3 = addNumbers(5, 10);

在这个例子中,ab 都有默认值,TypeScript会推断 ab 的类型为 number

默认参数与必选参数的混合使用

默认参数可以与必选参数混合使用,但默认参数必须放在必选参数之后。

function calculateTotal(price: number, taxRate = 0.1) {
    return price * (1 + taxRate);
}

let total1 = calculateTotal(100);
let total2 = calculateTotal(100, 0.05);

在上述代码中,price 是必选参数,taxRate 是默认参数。我们可以只传递 price 参数,函数将使用默认的 taxRate;也可以传递 pricetaxRate 参数来自定义税率。

可选参数与默认参数的区别

虽然可选参数和默认参数在功能上有一些相似之处,但它们之间也存在一些重要的区别。

调用方式的区别

  • 可选参数:调用函数时可以选择传递或不传递该参数。如果不传递,函数内部需要进行额外的逻辑判断来处理参数缺失的情况。
function printFullName(firstName: string, lastName?: string) {
    if (lastName) {
        console.log(`${firstName} ${lastName}`);
    } else {
        console.log(firstName);
    }
}

printFullName('Alice');
printFullName('Bob', 'Smith');
  • 默认参数:调用函数时如果不传递该参数,函数会自动使用默认值,函数内部不需要额外的逻辑判断来处理参数缺失的情况。
function printFullNameWithDefault(firstName: string, lastName = 'Doe') {
    console.log(`${firstName} ${lastName}`);
}

printFullNameWithDefault('Charlie');
printFullNameWithDefault('David', 'Johnson');

类型推断的区别

  • 可选参数:由于可选参数可能不会被传递,其类型实际上是指定类型与 undefined 的联合类型。例如,message?: string 的实际类型是 string | undefined。在函数内部使用可选参数时,需要进行类型检查以避免 undefined 导致的错误。
  • 默认参数:TypeScript会根据默认值的类型来推断参数的类型。例如,message = 'Welcome!' 会推断 message 的类型为 string,因为默认值是 string 类型。在函数内部使用默认参数时,不需要额外的类型检查,因为参数已经有了确定的类型。

位置限制的区别

  • 可选参数:必须放在必选参数之后。如果将可选参数放在必选参数之前,TypeScript会报错。
// 错误示例
function wrongOptionalOrder(message?: string, name: string) {
    console.log(`${message} ${name}`);
}
  • 默认参数:同样必须放在必选参数之后。如果将默认参数放在必选参数之前,TypeScript也会报错。
// 错误示例
function wrongDefaultOrder(taxRate = 0.1, price: number) {
    return price * (1 + taxRate);
}

实际应用场景

可选参数的应用场景

  1. 灵活的函数调用:在一些工具函数中,可能有一些参数不是每次调用都需要的。例如,一个用于获取元素的函数,可能可以通过传递一个可选的父元素来限定查找范围。
function getElementById(id: string, parent?: HTMLElement): HTMLElement | null {
    return parent ? parent.querySelector(`#${id}`) : document.getElementById(id);
}

let element1 = getElementById('myElement');
let parentElement = document.getElementById('parent');
let element2 = getElementById('childElement', parentElement);
  1. 向后兼容:当需要对现有函数进行扩展,但又不想破坏已有的调用代码时,可以使用可选参数。例如,一个旧的函数只接受一个参数,现在需要增加一个新的功能参数,但又不能影响原有的调用方式。
// 旧函数
function processData(data: string) {
    console.log('Processing data:', data);
}

// 扩展后的函数
function processDataWithOption(data: string, option?: boolean) {
    if (option) {
        console.log('Processing data with option:', data);
    } else {
        console.log('Processing data:', data);
    }
}

// 旧的调用方式仍然可用
processData('old data');
processDataWithOption('new data');
processDataWithOption('new data', true);

默认参数的应用场景

  1. 简化函数调用:在一些经常使用默认配置的函数中,使用默认参数可以减少调用时传递的参数数量。例如,一个用于发送HTTP请求的函数,可能有一些常用的默认配置。
function sendHttpRequest(url: string, method = 'GET', headers = {'Content-Type': 'application/json'}) {
    console.log(`Sending ${method} request to ${url} with headers:`, headers);
}

sendHttpRequest('https://example.com/api');
sendHttpRequest('https://example.com/api', 'POST', {'Content-Type': 'application/x-www-form-urlencoded'});
  1. 提供合理的默认值:对于一些可能被多个地方调用的函数,提供默认值可以确保函数在大多数情况下能够正常工作。例如,一个用于格式化日期的函数,可以提供默认的日期格式。
function formatDate(date: Date, format = 'yyyy - MM - dd') {
    let year = date.getFullYear();
    let month = (date.getMonth() + 1).toString().padStart(2, '0');
    let day = date.getDate().toString().padStart(2, '0');
    return format.replace('yyyy', year.toString()).replace('MM', month).replace('dd', day);
}

let today = new Date();
console.log(formatDate(today));
console.log(formatDate(today, 'dd/MM/yyyy'));

与剩余参数的结合使用

可选参数与剩余参数

剩余参数允许我们将多个参数收集到一个数组中。可选参数可以与剩余参数结合使用,以实现更灵活的函数定义。

function processArgs(arg1: string, arg2?: string, ...restArgs: string[]) {
    console.log('arg1:', arg1);
    if (arg2) {
        console.log('arg2:', arg2);
    }
    console.log('restArgs:', restArgs);
}

processArgs('first');
processArgs('first','second');
processArgs('first','second', 'third', 'fourth');

在上述代码中,arg1 是必选参数,arg2 是可选参数,restArgs 是剩余参数。当调用 processArgs('first') 时,arg2 未传递,restArgs 为空数组。当调用 processArgs('first','second') 时,arg2 被传递,restArgs 为空数组。当调用 processArgs('first','second', 'third', 'fourth') 时,arg2 被传递,restArgs 包含 'third''fourth'

默认参数与剩余参数

默认参数也可以与剩余参数结合使用。这种情况下,默认参数仍然遵循放在必选参数之后的规则。

function calculateSum(base: number, multiplier = 1, ...numbers: number[]) {
    let sum = base * multiplier;
    numbers.forEach(num => sum += num);
    return sum;
}

let result1 = calculateSum(5);
let result2 = calculateSum(5, 2);
let result3 = calculateSum(5, 2, 3, 4);

在上述代码中,base 是必选参数,multiplier 是默认参数,numbers 是剩余参数。calculateSum(5) 使用默认的 multiplier1calculateSum(5, 2) 传递了自定义的 multipliercalculateSum(5, 2, 3, 4) 同时传递了 multiplier 和剩余参数。

类型兼容性与重载

可选参数与类型兼容性

在函数类型兼容性方面,可选参数在赋值和调用时的处理方式与必选参数有所不同。如果一个函数类型A的参数比另一个函数类型B的参数多,且多出的参数是可选的,那么A与B是兼容的。

let func1: (a: string, b?: number) => void;
let func2: (a: string) => void;

func1 = func2; // 可以赋值,因为func1的b参数是可选的

在上述代码中,func2 可以赋值给 func1,因为 func1func2 多一个可选参数 b

默认参数与类型兼容性

默认参数在类型兼容性方面与可选参数类似。如果一个函数类型A的参数比另一个函数类型B的参数多,且多出的参数有默认值,那么A与B是兼容的。

let func3: (a: string, b = 10) => void;
let func4: (a: string) => void;

func3 = func4; // 可以赋值,因为func3的b参数有默认值

在上述代码中,func4 可以赋值给 func3,因为 func3func4 多一个有默认值的参数 b

函数重载与可选参数、默认参数

函数重载允许我们为同一个函数提供多个不同的函数签名。可选参数和默认参数在函数重载中也有特定的表现。

function printData(data: string);
function printData(data: number, format?: string);
function printData(data: any, format?: any) {
    if (typeof data ==='string') {
        console.log('String data:', data);
    } else if (typeof data === 'number') {
        if (format) {
            console.log('Formatted number:', data.toFixed(format));
        } else {
            console.log('Number data:', data);
        }
    }
}

printData('Hello');
printData(10);
printData(10, 2);

在上述代码中,我们定义了两个函数重载签名。第一个签名只接受一个 string 类型的参数,第二个签名接受一个 number 类型的参数和一个可选的 string 类型的 format 参数。实际的函数实现根据传递的参数类型来决定如何处理数据。

注意事项

  1. 可选参数与默认参数的命名:在命名可选参数和默认参数时,应选择有意义的名称,以提高代码的可读性。避免使用过于简单或模糊的名称,否则在函数调用和维护时可能会造成困惑。
  2. 避免过多的可选参数和默认参数:虽然可选参数和默认参数提供了灵活性,但过多的这类参数可能会使函数的功能变得复杂和难以理解。尽量保持函数的职责单一,避免在一个函数中处理过多不同的情况。如果确实需要处理多种情况,可以考虑将功能拆分成多个函数。
  3. 文档注释:对于包含可选参数和默认参数的函数,应该使用文档注释来清晰地说明每个参数的用途、是否可选以及默认值的含义。这有助于其他开发人员理解和使用你的函数。
/**
 * 计算两个数的和
 * @param a 第一个数,必选
 * @param b 第二个数,可选,默认值为0
 * @returns 两数之和
 */
function add(a: number, b = 0): number {
    return a + b;
}
  1. 类型一致性:在使用可选参数和默认参数时,要确保参数类型与函数内部的处理逻辑保持一致。例如,如果一个可选参数在函数内部被当作 string 类型处理,那么在调用函数时传递的实际参数也应该是 string 类型或可转换为 string 类型的值。

通过深入理解TypeScript中的可选参数与默认参数,开发人员可以编写更加灵活、可维护的代码。合理运用这些特性,能够提高代码的复用性,减少重复代码,并使函数在不同的应用场景下都能高效地工作。无论是构建小型的工具函数,还是大型的应用程序,掌握可选参数和默认参数的使用方法都是非常重要的。在实际开发中,结合具体的业务需求,恰当地选择和使用可选参数与默认参数,将有助于提升代码的质量和开发效率。