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

JavaScript函数实参与形参的匹配规则

2024-07-073.6k 阅读

函数的形参与实参基础概念

在JavaScript中,函数是一种重要的代码组织和复用工具。函数定义时括号内声明的变量被称为形参(Formal Parameters),它们就像是函数内部的占位符,在函数调用时会被实际传入的值所替代。而在函数调用时括号内传入的值则被称为实参(Actual Arguments)。

例如:

function addNumbers(a, b) { // a 和 b 是形参
    return a + b;
}

let result = addNumbers(3, 5); // 3 和 5 是实参
console.log(result); 

在上述代码中,addNumbers函数定义了两个形参ab,当调用该函数并传入实参3和5时,函数内部的a被赋值为3,b被赋值为5,进而计算并返回两数之和。

实参与形参的匹配规则基础

按顺序匹配

JavaScript函数的实参与形参默认按照它们在函数定义和调用时出现的顺序进行匹配。

function greet(name, message) {
    console.log(`${message}, ${name}!`);
}

greet('Alice', 'Hello'); 

在这个greet函数中,第一个实参'Alice'匹配第一个形参name,第二个实参'Hello'匹配第二个形参message。这种按顺序的匹配是非常直观和基础的规则,大部分情况下开发者都会遵循这样的方式来调用函数。

形参数量与实参数量不一致的情况

实参数量多于形参数量

当函数调用时传入的实参数量多于函数定义时的形参数量,额外的实参不会被赋值给任何形参,并且在函数内部也无法通过形参名直接访问它们。

function multiply(a, b) {
    return a * b;
}

let product = multiply(2, 3, 4); 
console.log(product); 

在上述代码中,multiply函数只定义了两个形参ab,但调用时传入了三个实参。第三个实参4并没有被函数内部使用,函数依然按照前两个实参计算乘法结果。

实参数量少于形参数量

如果传入的实参数量少于形参数量,缺少值的形参会被赋值为undefined

function divide(a, b) {
    if (b === undefined) {
        return '缺少除数';
    }
    return a / b;
}

let quotient1 = divide(10); 
console.log(quotient1); 

let quotient2 = divide(10, 2); 
console.log(quotient2); 

divide函数中,当只传入一个实参时,形参b的值为undefined,函数可以根据这个情况进行相应的处理。而传入两个实参时,函数正常进行除法运算。

剩余参数(Rest Parameters)

剩余参数的定义与语法

从ES6开始,JavaScript引入了剩余参数的概念,它允许我们将函数调用时的多个实参收集到一个数组中。剩余参数必须是函数参数列表中的最后一个参数,并且使用...语法。

function sumAll(...numbers) {
    let total = 0;
    for (let num of numbers) {
        total += num;
    }
    return total;
}

let sum1 = sumAll(1, 2, 3); 
console.log(sum1); 

let sum2 = sumAll(10, 20, 30, 40); 
console.log(sum2); 

sumAll函数中,...numbers就是剩余参数,它将所有传入的实参收集到numbers数组中,函数内部可以通过遍历这个数组来计算总和。

剩余参数与普通形参的结合使用

剩余参数可以和普通形参一起使用,不过剩余参数依旧要放在参数列表的最后。

function greetAll(greeting, ...names) {
    for (let name of names) {
        console.log(`${greeting}, ${name}!`);
    }
}

greetAll('Hello', 'Alice', 'Bob', 'Charlie'); 

greetAll函数中,第一个形参greeting接收第一个实参'Hello',而剩余的实参'Alice''Bob''Charlie'被收集到names数组中,函数通过遍历names数组对每个名字进行问候。

默认参数

默认参数的定义与语法

JavaScript函数允许为形参指定默认值。如果在函数调用时没有为具有默认值的形参传入实参,那么该形参就会使用其默认值。

function greet(name = 'Guest') {
    console.log(`Hello, ${name}!`);
}

greet(); 
greet('Alice'); 

greet函数中,形参name有一个默认值'Guest'。当不传入实参调用greet函数时,name就会使用默认值'Guest';而传入实参'Alice'时,name则被赋值为'Alice'

默认参数的求值时机

默认参数的值在函数调用时进行求值,而不是在函数定义时。这意味着默认参数的值可以依赖于函数调用时的其他变量或表达式。

let baseValue = 10;

function addToBase(value = baseValue) {
    return value + baseValue;
}

console.log(addToBase()); 
baseValue = 20;
console.log(addToBase()); 

addToBase函数中,形参value的默认值依赖于外部变量baseValue。第一次调用addToBase时,baseValue的值为10,所以value的默认值也是10,函数返回20。当baseValue的值改变为20后再次调用addToBasevalue的默认值也随之变为20,函数返回40。

默认参数与剩余参数的组合使用

默认参数和剩余参数可以一起在函数中使用,这为函数的灵活性提供了更多可能。

function printValues(prefix = 'Value:', ...values) {
    for (let value of values) {
        console.log(`${prefix} ${value}`);
    }
}

printValues('Number:', 1, 2, 3); 
printValues(undefined, 'a', 'b', 'c'); 

printValues函数中,prefix是具有默认值的形参,...values是剩余参数。当调用函数时,如果不传入prefix的值,它会使用默认值'Value:',同时剩余参数会收集其他实参并进行相应的打印操作。

函数的严格模式对形参与实参匹配的影响

严格模式的开启

在JavaScript中,通过在脚本或函数的开头添加'use strict';来开启严格模式。严格模式对函数的形参与实参匹配规则有一些特殊的影响。

function strictFunction(a, a) { // 非严格模式下不会报错,但严格模式下会报错
    return a + a;
}

function nonStrictFunction(a, a) {
    return a + a;
}

// 严格模式下报错:SyntaxError: Duplicate parameter name not allowed in this context
// strictFunction(1, 2); 

nonStrictFunction(1, 2); 

严格模式下形参与实参匹配的特殊规则

禁止重复的形参名

在严格模式下,函数不能有重复的形参名。在非严格模式下,虽然可以定义重复的形参名,但这是不推荐的做法,并且在实际使用中会导致难以调试的问题。在严格模式下,引擎会直接抛出语法错误,避免潜在的错误和混淆。

对实参数量不匹配的处理更加严格

在严格模式下,当实参数量与形参数量不匹配时,函数的行为可能会与非严格模式有所不同。例如,在非严格模式下,多余的实参通常会被忽略,而缺少的实参会被赋值为undefined。但在严格模式下,某些JavaScript引擎可能会抛出更详细的警告或错误信息,以提醒开发者注意这种不匹配情况,有助于尽早发现代码中的潜在问题。

实参与形参匹配规则在函数重载中的应用

JavaScript中函数重载的概念

与一些强类型语言不同,JavaScript本身并没有传统意义上基于函数签名(形参数量、类型和顺序)的函数重载机制。在强类型语言中,可以定义多个同名函数,但它们的形参列表不同,编译器会根据函数调用时传入的实参来决定调用哪个函数。然而,在JavaScript中,函数名是唯一的标识符,不能直接定义多个同名且形参列表不同的函数。

模拟函数重载

虽然JavaScript没有原生的函数重载,但可以通过一些技巧来模拟函数重载的效果,这其中实参与形参的匹配规则起到了关键作用。

根据实参数量模拟重载

function area() {
    if (arguments.length === 1) {
        let radius = arguments[0];
        return Math.PI * radius * radius;
    } else if (arguments.length === 2) {
        let length = arguments[0];
        let width = arguments[1];
        return length * width;
    }
}

let circleArea = area(5); 
let rectangleArea = area(4, 6); 

在上述area函数中,通过检查arguments对象(在函数内部自动可用,包含了所有传入的实参)的长度来判断传入实参的数量。根据实参数量的不同,执行不同的计算逻辑,从而模拟了不同形参列表的函数重载效果。

根据实参类型模拟重载

function printValue(value) {
    if (typeof value ==='string') {
        console.log(`String: ${value}`);
    } else if (typeof value === 'number') {
        console.log(`Number: ${value}`);
    }
}

printValue('Hello'); 
printValue(42); 

printValue函数中,通过检查实参的类型来决定执行不同的逻辑。这种方式也是利用实参的特性,模拟了根据实参类型不同而进行的函数重载。

实参与形参匹配规则在闭包中的表现

闭包的概念

闭包是指函数能够访问并记住其词法作用域,即使函数在其原始作用域之外被调用。在闭包中,实参与形参的匹配规则同样适用,并且有着一些独特的表现。

function outerFunction(x) {
    return function innerFunction(y) {
        return x + y;
    };
}

let addFive = outerFunction(5);
let result = addFive(3); 
console.log(result); 

在上述代码中,outerFunction返回一个内部函数innerFunctioninnerFunction形成了一个闭包,它记住了outerFunction的形参x。当addFive(即innerFunction的实例)被调用时,它的形参y与传入的实参3进行匹配,同时可以访问到outerFunctionx的值5,进而计算并返回两数之和。

闭包中形参变化对实参的影响

由于闭包记住了其词法作用域,形参在闭包内部的变化可能会对相关的实参产生影响(虽然这里的“实参”概念更多是在闭包形成时的上下文环境中)。

function counter() {
    let count = 0;
    return function increment() {
        count++;
        return count;
    };
}

let myCounter = counter();
console.log(myCounter()); 
console.log(myCounter()); 

counter函数返回的闭包increment中,count变量类似于一种特殊的“形参”(在闭包形成的上下文中)。每次调用increment函数(即闭包)时,count的值会发生变化,并且这种变化会被记住,下一次调用时会基于上一次变化后的结果继续操作。

实参与形参匹配规则在面向对象编程中的应用

构造函数中的形参与实参匹配

在JavaScript的面向对象编程中,构造函数用于创建对象实例。构造函数的形参与实参匹配规则与普通函数类似,但有着特殊的用途。

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

let alice = new Person('Alice', 30); 
console.log(alice.name); 
console.log(alice.age); 

Person构造函数中,形参nameage用于接收创建Person实例时传入的实参。这些实参被用于初始化对象的属性,从而创建出具有特定属性值的对象实例。

方法调用中的形参与实参匹配

对象的方法也是函数,它们的形参与实参匹配规则同样适用。

function Animal(name) {
    this.name = name;
    this.speak = function (sound) {
        console.log(`${this.name} makes a ${sound} sound.`);
    };
}

let dog = new Animal('Buddy');
dog.speak('woof'); 

Animal构造函数创建的对象实例dog中,speak方法接收一个形参sound。当调用dog.speak('woof')时,实参'woof'与形参sound进行匹配,从而输出相应的信息。

实参与形参匹配规则在事件处理函数中的应用

事件处理函数的形参与实参

在JavaScript的DOM编程中,事件处理函数是处理用户交互等事件的重要方式。事件处理函数通常会接收一些特定的形参,这些形参与事件相关的实参进行匹配。

document.addEventListener('click', function (event) {
    console.log(`Clicked at (${event.pageX}, ${event.pageY})`);
});

在上述代码中,addEventListener的第二个参数是一个事件处理函数,该函数接收一个event形参。当用户点击文档时,浏览器会传入一个包含点击事件相关信息的event对象作为实参,事件处理函数通过这个event对象获取点击的坐标等信息。

自定义事件处理函数的形参与实参匹配

除了浏览器原生的事件,开发者还可以自定义事件并定义相应的事件处理函数,在这种情况下同样遵循形参与实参的匹配规则。

let myEvent = new CustomEvent('myCustomEvent', { detail: 'This is custom event data' });

document.addEventListener('myCustomEvent', function (event) {
    console.log(event.detail); 
});

document.dispatchEvent(myEvent); 

在上述代码中,自定义事件myCustomEvent在触发时,会调用与之关联的事件处理函数。事件处理函数的event形参接收自定义事件的相关信息,包括通过detail属性传递的自定义数据。

总结实参与形参匹配规则的重要性

实参与形参的匹配规则是JavaScript函数机制的核心部分,它贯穿于JavaScript编程的各个方面,从基础的函数定义与调用,到复杂的闭包、面向对象编程以及事件处理。正确理解和运用这些规则,能够帮助开发者编写出更健壮、可维护且高效的代码。通过掌握按顺序匹配、处理形参与实参数量不一致的情况,以及运用剩余参数、默认参数等特性,开发者可以更加灵活地设计和使用函数,充分发挥JavaScript作为一门强大编程语言的潜力。同时,在不同的编程范式和应用场景中,合理利用实参与形参匹配规则,有助于实现代码的模块化、复用性和可扩展性,提升整个项目的质量和开发效率。