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

JavaScript函数实参与形参的类型检查

2023-07-034.0k 阅读

JavaScript 函数实参与形参的类型检查基础概念

什么是形参和实参

在 JavaScript 函数中,形参(formal parameter)是定义函数时在函数声明中列出的变量名。它们就像是函数内部的占位符,用来接收调用函数时传入的值。例如:

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

这里的 ab 就是形参。

而实参(actual parameter)则是在调用函数时实际传递给函数的值。比如:

let result = addNumbers(3, 5);

这里的 35 就是实参,它们被传递给 addNumbers 函数,分别赋值给形参 ab

JavaScript 的弱类型特性

JavaScript 是一种弱类型语言,这意味着它在变量声明时不需要显式指定类型,并且在运行时对变量类型的检查相对宽松。例如:

let num = 10;
num = 'ten';

这里变量 num 先被赋值为数字类型,之后又被赋值为字符串类型,JavaScript 并不会报错。

这种弱类型特性在函数的形参和实参方面表现为:函数调用时,实参的类型不一定与形参预期的类型严格匹配。例如:

function printLength(str) {
    console.log(str.length);
}

printLength(123);

在这个例子中,printLength 函数预期接收一个字符串类型的形参 str,但在调用时传递了一个数字类型的实参 123。由于数字类型没有 length 属性,这会导致运行时错误(Uncaught TypeError: Cannot read property 'length' of number)。

为什么需要类型检查

避免运行时错误

如上述 printLength 函数的例子,由于 JavaScript 不会在函数调用时自动进行严格的类型检查,传递错误类型的实参会导致运行时错误。这些错误往往难以调试,尤其是在大型项目中,代码逻辑复杂,错误可能在远离函数调用的地方才表现出来。通过在函数内部进行类型检查,可以在错误发生的源头捕获问题,提高代码的稳定性和可靠性。

增强代码的可读性和可维护性

当函数对形参的类型有明确要求时,进行类型检查可以让代码的意图更加清晰。其他开发人员在阅读代码时,能够更容易理解函数的预期输入,从而降低理解和维护代码的成本。例如:

function calculateCircleArea(radius) {
    if (typeof radius!== 'number') {
        throw new Error('Radius must be a number');
    }
    return Math.PI * radius * radius;
}

在这个 calculateCircleArea 函数中,通过类型检查明确了 radius 必须是数字类型。后续开发人员在调用这个函数时,就会知道需要传递正确类型的参数。

基本类型检查方法

typeof 操作符

typeof 操作符是 JavaScript 中用于检查变量类型的基本方法。它返回一个表示变量数据类型的字符串。例如:

let num = 10;
console.log(typeof num); // 输出 'number'

let str = 'hello';
console.log(typeof str); // 输出'string'

let bool = true;
console.log(typeof bool); // 输出 'boolean'

let arr = [1, 2, 3];
console.log(typeof arr); // 输出 'object'

let obj = {name: 'John'};
console.log(typeof obj); // 输出 'object'

let func = function() {};
console.log(typeof func); // 输出 'function'

在函数中,可以利用 typeof 进行形参类型检查。例如:

function add(a, b) {
    if (typeof a!== 'number' || typeof b!== 'number') {
        throw new Error('Both arguments must be numbers');
    }
    return a + b;
}

这里通过 typeof 检查 ab 是否为数字类型,如果不是则抛出错误。

instanceof 操作符

instanceof 操作符用于检查一个对象是否是某个构造函数的实例。它主要用于检查对象类型,特别是自定义对象类型。例如:

function Person(name) {
    this.name = name;
}

let john = new Person('John');
console.log(john instanceof Person); // 输出 true

let arr = [1, 2, 3];
console.log(arr instanceof Array); // 输出 true

在函数中,如果形参预期是某个自定义对象类型,可以使用 instanceof 进行检查。例如:

function greet(person) {
    if (!(person instanceof Person)) {
        throw new Error('Argument must be an instance of Person');
    }
    console.log(`Hello, ${person.name}`);
}

greet(john);

这里通过 instanceof 检查 person 是否是 Person 类型的实例,如果不是则抛出错误。

复杂类型检查

数组类型检查

虽然 typeof 操作符对于数组返回 'object',但我们可以使用 Array.isArray() 方法来准确判断一个值是否为数组。例如:

function sumArray(arr) {
    if (!Array.isArray(arr)) {
        throw new Error('Argument must be an array');
    }
    return arr.reduce((acc, num) => acc + num, 0);
}

let numbers = [1, 2, 3];
console.log(sumArray(numbers));

try {
    sumArray(123);
} catch (error) {
    console.error(error.message);
}

sumArray 函数中,通过 Array.isArray() 检查 arr 是否为数组,不是则抛出错误。

对象类型检查

对于普通对象,除了使用 typeof 检查为 'object' 外,还可以进一步检查对象的属性和方法。例如,假设函数预期接收一个具有特定属性的对象:

function printUser(user) {
    if (typeof user!== 'object' ||!('name' in user) ||!('age' in user)) {
        throw new Error('Argument must be an object with name and age properties');
    }
    console.log(`Name: ${user.name}, Age: ${user.age}`);
}

let user = {name: 'Jane', age: 25};
printUser(user);

try {
    printUser({name: 'Bob'});
} catch (error) {
    console.error(error.message);
}

这里通过 typeof 先检查 user 是否为对象,再使用 in 操作符检查 user 是否具有 nameage 属性。

函数类型检查

有时候函数的形参可能预期是另一个函数。可以使用 typeof 检查其类型是否为 'function'。例如:

function callFunction(func) {
    if (typeof func!== 'function') {
        throw new Error('Argument must be a function');
    }
    func();
}

function sayHello() {
    console.log('Hello');
}

callFunction(sayHello);

try {
    callFunction(123);
} catch (error) {
    console.error(error.message);
}

callFunction 函数中,通过 typeof 检查 func 是否为函数,不是则抛出错误。

利用工具库进行类型检查

PropTypes

在 React 项目中,PropTypes 是一个常用的库,用于对组件的属性(类似于函数的形参)进行类型检查。虽然它主要用于 React 组件,但其中的类型检查思想可以借鉴。首先安装 prop-types

npm install prop-types

然后在代码中使用:

import PropTypes from 'prop-types';

function MyComponent(props) {
    // 假设 props 中有一个名为 'name' 的属性
    const {name} = props;
    return <div>Hello, {name}</div>;
}

MyComponent.propTypes = {
    name: PropTypes.string.isRequired
};

这里使用 PropTypes.string.isRequired 表示 name 属性必须是字符串类型且是必填的。如果传递的 name 不是字符串类型,React 开发环境会在控制台给出警告。

TypeScript

TypeScript 是 JavaScript 的超集,它为 JavaScript 添加了静态类型检查功能。通过在项目中引入 TypeScript,可以在编译阶段就发现函数形参和实参类型不匹配的问题。例如,定义一个函数:

function addNumbers(a: number, b: number): number {
    return a + b;
}

let result = addNumbers(3, 5);
// 以下代码会在编译时报错,因为传递的不是数字类型
let wrongResult = addNumbers('3', '5');

在 TypeScript 中,通过在函数声明中明确指定形参的类型,编译器会在编译时进行严格的类型检查,大大提高了代码的健壮性。

类型检查的性能考量

检查的时机

在函数内部进行类型检查会增加函数的执行时间。因此,对于性能敏感的代码,需要谨慎考虑类型检查的时机。如果函数在性能关键路径上被频繁调用,过多的类型检查可能会影响性能。一种策略是在开发和测试阶段进行全面的类型检查,而在生产环境中可以适当减少不必要的检查,通过其他手段(如单元测试)来保证类型的正确性。

检查的复杂度

复杂的类型检查,例如对深层嵌套对象的属性类型检查,会消耗更多的性能。尽量保持类型检查的简洁和高效。如果必须进行复杂的类型检查,可以考虑将其封装成独立的函数,以便复用和优化。例如:

function isValidNestedObject(obj) {
    if (typeof obj!== 'object' || obj === null) {
        return false;
    }
    if (!('nested' in obj) || typeof obj.nested!== 'object' || obj.nested === null) {
        return false;
    }
    if (!('value' in obj.nested) || typeof obj.nested.value!== 'number') {
        return false;
    }
    return true;
}

function processNestedObject(obj) {
    if (!isValidNestedObject(obj)) {
        throw new Error('Invalid nested object');
    }
    // 处理对象的逻辑
}

这里将复杂的嵌套对象类型检查封装在 isValidNestedObject 函数中,processNestedObject 函数在调用时只需要调用这个封装函数,提高了代码的可读性和可维护性,同时也便于对 isValidNestedObject 函数进行性能优化。

总结类型检查的最佳实践

明确函数的输入要求

在编写函数时,要清晰地定义形参的类型要求,并通过类型检查来确保实参符合要求。这可以通过文档注释、使用工具库(如 TypeScript 或 PropTypes)或在函数内部进行手动类型检查来实现。

遵循 DRY(Don't Repeat Yourself)原则

如果多个函数对相同类型的形参有类似的类型检查需求,将这些类型检查逻辑封装成独立的函数或工具函数,以减少重复代码,提高代码的可维护性。

平衡性能和健壮性

在性能关键的代码区域,要权衡类型检查带来的性能开销和代码健壮性的提升。在开发和测试阶段可以进行严格的类型检查,在生产环境中根据实际情况适当调整,确保代码在性能和可靠性之间达到平衡。

通过以上对 JavaScript 函数实参与形参类型检查的深入探讨,我们了解了各种类型检查的方法、工具以及性能考量和最佳实践。在实际的开发中,合理运用这些知识,可以编写出更加健壮、可读和高效的 JavaScript 代码。