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

TypeScript函数参数默认值的应用场景

2022-03-105.9k 阅读

函数参数默认值在基础函数中的应用

在前端开发中,许多基础函数会经常使用到参数默认值。例如,一个用于格式化日期的函数,它可能接收一个日期对象作为参数,如果用户没有传入,我们希望它使用当前日期。

function formatDate(date: Date = new Date()) {
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    return `${year}-${month}-${day}`;
}

// 不传入参数,使用默认值(当前日期)
console.log(formatDate()); 
// 传入自定义日期
const customDate = new Date('2023-10-01');
console.log(formatDate(customDate)); 

在这个例子中,date 参数有一个默认值 new Date()。当调用 formatDate 函数时,如果没有传入 date 参数,它就会使用默认的当前日期进行格式化。这使得函数的使用更加灵活,调用者可以根据实际需求选择是否传入自定义日期。

处理用户配置的函数

在开发应用程序时,我们常常需要处理用户的配置。以一个图片加载函数为例,它可能需要一些加载选项,如图片的质量、是否启用懒加载等。

function loadImage(url: string, options: { quality: number; lazyLoad: boolean } = { quality: 80, lazyLoad: false }) {
    console.log(`Loading image from ${url} with quality ${options.quality} and lazyLoad ${options.lazyLoad ? 'enabled' : 'disabled'}`);
}

// 使用默认选项加载图片
loadImage('https://example.com/image.jpg'); 
// 使用自定义选项加载图片
loadImage('https://example.com/image.jpg', { quality: 90, lazyLoad: true }); 

这里的 options 参数有一个默认值,它包含了图片加载的默认质量和懒加载状态。如果调用者没有传入 options,函数会使用默认的配置。这种方式让函数既可以满足大多数常见的使用场景(使用默认配置),又能在特殊需求下支持自定义配置。

简化函数调用

当一个函数有多个参数,而其中一些参数在大多数情况下具有相同的值时,使用参数默认值可以大大简化函数调用。

function sendHttpRequest(url: string, method: 'GET' | 'POST' = 'GET', headers: { [key: string]: string } = { 'Content-Type': 'application/json' }, data: any = null) {
    console.log(`Sending ${method} request to ${url} with headers:`, headers);
    if (data) {
        console.log('Data:', data);
    }
}

// 简单的 GET 请求,使用所有默认值
sendHttpRequest('https://example.com/api'); 
// POST 请求,自定义 data
sendHttpRequest('https://example.com/api', 'POST', null, { key: 'value' }); 

sendHttpRequest 函数中,methodheadersdata 都有默认值。这意味着在大多数情况下,如果是简单的 GET 请求,调用者只需要传入 url 即可,无需重复设置其他参数。而在需要自定义请求方法、头信息或发送数据时,也可以方便地传入相应的值。

模块化和复用性场景

在模块化开发中,函数的复用性非常重要。一个通用的工具函数可能被多个模块使用,而不同模块对某些参数的需求可能不同,但又存在一些默认合理的值。

// 工具模块
export function debounce(func: (...args: any[]) => void, delay: number = 300) {
    let timer: NodeJS.Timeout;
    return function (this: any, ...args: any[]) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// 业务模块
import { debounce } from './utils';

function handleSearchInput() {
    console.log('Searching...');
}

const debouncedSearch = debounce(handleSearchInput, 500);

document.getElementById('search-input')?.addEventListener('input', debouncedSearch);

在这个例子中,debounce 函数是一个通用的防抖工具函数,它接收一个函数 func 和延迟时间 delaydelay 参数有一个默认值 300 毫秒。在业务模块中,我们可以根据实际需求传入不同的 delay 值,也可以使用默认值。这种方式使得工具函数在不同的业务场景下都能方便地复用,同时保持代码的简洁性。

类方法中的参数默认值

在 TypeScript 的类中,类方法也可以使用参数默认值。这在处理对象的初始化或特定操作时非常有用。

class User {
    constructor(private name: string, private age: number = 18) { }

    greet(message: string = 'Hello') {
        console.log(`${message}, I'm ${this.name} and I'm ${this.age} years old.`);
    }
}

const user1 = new User('Alice');
user1.greet(); 
const user2 = new User('Bob', 25);
user2.greet('Hi'); 

User 类的构造函数中,age 参数有一个默认值 18。这意味着如果创建 User 对象时只传入 nameage 会使用默认值。greet 方法中的 message 参数也有默认值 Hello。这种方式使得类的使用更加灵活,用户可以根据需要选择是否传入自定义值。

与函数重载结合使用

函数重载是指在同一个作用域内,可以有多个同名函数,但它们的参数列表不同。参数默认值可以与函数重载结合使用,进一步增强函数的灵活性。

function printValue(value: string): void;
function printValue(value: number, prefix: string = 'Number: '): void;
function printValue(value: boolean, suffix: string ='is the truth'): void;
function printValue(value: any, option: any = null) {
    if (typeof value ==='string') {
        console.log(value);
    } else if (typeof value === 'number') {
        console.log(option + value);
    } else if (typeof value === 'boolean') {
        console.log(value + option);
    }
}

printValue('Hello'); 
printValue(42); 
printValue(42, 'The answer is '); 
printValue(true); 
printValue(true, '?'); 

在这个例子中,printValue 函数有多个重载定义。不同的重载可以根据传入的参数类型进行不同的处理,并且某些重载中的参数有默认值。这种结合方式使得函数在处理不同类型数据时既保持了类型安全,又能提供灵活的调用方式。

处理异步操作中的参数默认值

在处理异步操作时,参数默认值同样发挥着重要作用。以一个使用 fetch 进行网络请求的封装函数为例。

async function apiFetch(url: string, options: RequestInit = { method: 'GET', headers: { 'Content-Type': 'application/json' } }) {
    try {
        const response = await fetch(url, options);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

// 简单的 GET 请求
apiFetch('https://example.com/api/data'); 
// POST 请求,自定义数据
apiFetch('https://example.com/api/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ key: 'value' })
}); 

apiFetch 函数封装了 fetch 操作,options 参数有默认值,适用于大多数简单的 GET 请求。当需要进行其他类型的请求(如 POST、PUT 等)并携带自定义数据时,调用者可以传入自定义的 options。这种方式在异步操作中提供了方便的默认配置,同时支持灵活的自定义。

与泛型结合的应用场景

泛型是 TypeScript 中强大的特性,它可以使函数或类在不指定具体类型的情况下保持类型安全。参数默认值可以与泛型很好地结合。

function createArray<T>(length: number, value: T = null as unknown as T): T[] {
    const arr: T[] = [];
    for (let i = 0; i < length; i++) {
        arr.push(value);
    }
    return arr;
}

const numberArray = createArray<number>(5, 10); 
const stringArray = createArray<string>(3, 'hello'); 
const defaultArray = createArray(4); 

createArray 函数中,T 是泛型类型参数,value 参数有一个默认值 null as unknown as T。这个默认值在类型系统中是允许的,因为 null 可以被类型断言为任何类型。调用函数时,如果没有传入 value,数组元素将使用默认值。这种结合方式在处理通用的数据结构创建时非常有用,既保持了类型的灵活性,又提供了默认值的便利性。

函数柯里化中的参数默认值

函数柯里化是将一个多参数函数转换为一系列单参数函数的技术。参数默认值在函数柯里化中也有独特的应用。

function multiply(a: number, b: number, c: number = 1) {
    return a * b * c;
}

const curriedMultiply = (a: number) => (b: number) => (c: number = 1) => multiply(a, b, c);

const result1 = curriedMultiply(2)(3); 
const result2 = curriedMultiply(2)(3)(4); 

在这个例子中,multiply 函数有三个参数,c 参数有默认值 1。通过柯里化,我们得到了 curriedMultiply 函数。调用 curriedMultiply 时,可以逐步传入参数,并且可以利用 c 的默认值。如果需要自定义 c 的值,也可以在最后一步传入。这种方式使得函数的调用更加灵活,适用于一些需要动态构建计算逻辑的场景。

处理复杂业务逻辑中的参数默认值

在处理复杂业务逻辑时,函数可能会有多个参数,并且不同的业务场景对这些参数的需求不同。参数默认值可以帮助我们更好地组织和管理这些复杂情况。

function calculateTotalPrice(products: { price: number; quantity: number }[], discount: number = 0, tax: number = 0.1) {
    let subtotal = 0;
    for (const product of products) {
        subtotal += product.price * product.quantity;
    }
    const discountedPrice = subtotal * (1 - discount);
    return discountedPrice * (1 + tax);
}

const products = [
    { price: 10, quantity: 2 },
    { price: 15, quantity: 1 }
];

const total1 = calculateTotalPrice(products); 
const total2 = calculateTotalPrice(products, 0.1); 
const total3 = calculateTotalPrice(products, 0.1, 0.05); 

calculateTotalPrice 函数中,discounttax 参数都有默认值。这在大多数情况下,我们只需要传入产品列表即可计算总价。而在一些特殊的促销活动或不同地区的税收政策下,可以传入自定义的 discounttax 值。这种方式使得函数在复杂业务逻辑中既能够满足常见场景,又能应对特殊情况。

事件处理函数中的参数默认值

在前端开发中,事件处理函数是非常常见的。参数默认值可以在事件处理函数中提供额外的灵活性。

document.addEventListener('click', function (event: MouseEvent, customData: string = 'default data') {
    console.log(`Clicked at ${event.clientX}, ${event.clientY} with custom data: ${customData}`);
});

在这个 click 事件处理函数中,customData 参数有一个默认值。这意味着当事件触发时,如果没有传入自定义的 customData,函数会使用默认值。这种方式可以在事件处理时携带一些额外的默认信息,同时也允许在必要时传入自定义数据。

函数参数默认值在代码可维护性方面的作用

使用函数参数默认值不仅可以让函数的调用更加灵活,还对代码的可维护性有很大帮助。

  1. 减少重复代码:当多个地方调用同一个函数且大部分参数值相同时,使用默认值可以避免在每个调用处都重复设置相同的参数值。例如,在一个电商应用中,有多个地方需要调用计算商品总价的函数,大部分情况下税率和折扣率是固定的,通过设置默认值,就不需要在每次调用时都传入这些固定值。

  2. 易于修改和扩展:如果需要修改某个参数的默认值,只需要在函数定义处修改即可,而不需要在所有调用该函数的地方进行修改。例如,在图片加载函数中,如果默认的图片质量需要提高,只需要在 loadImage 函数中修改 options 默认值中的 quality 字段即可。

  3. 增强代码可读性:通过合理设置参数默认值,函数的调用者可以更清晰地理解函数的常用使用方式。例如,sendHttpRequest 函数,调用者看到函数定义就知道默认是 GET 请求,并且默认的头信息是 application/json 类型。

注意事项

  1. 默认值的类型兼容性:默认值的类型必须与参数声明的类型兼容。例如,如果参数声明为 number 类型,默认值就不能是 string 类型。

  2. 默认值与函数重载:在使用函数重载时,要确保默认值的设置与各个重载定义相匹配,避免出现类型错误或不符合预期的行为。

  3. 性能影响:虽然参数默认值在大多数情况下不会对性能产生明显影响,但在一些性能敏感的场景下,如高频调用的函数,要注意默认值的计算开销。例如,如果默认值是一个复杂的对象创建或函数调用,可能会对性能产生一定影响。

  4. 与可选参数的区别:可选参数和有默认值的参数虽然都可以在调用时省略,但它们的语义和类型检查略有不同。可选参数使用 ? 标记,并且在函数体中需要额外检查是否传入;而有默认值的参数在调用时如果省略,会直接使用默认值,并且在函数体中可以直接使用,无需额外检查。

通过合理应用函数参数默认值,我们可以使前端代码更加简洁、灵活和可维护。无论是基础函数、复杂业务逻辑函数还是类方法等,参数默认值都能在不同场景下发挥重要作用,提高代码的质量和开发效率。