函数定义与调用

函数是 JavaScript 中执行代码块的引用,可传递参数并返回结果,其定义与调用方式直接影响代码的灵活性和上下文行为[3]。本节通过"计算器功能"案例对比三种函数定义方式,并结合"用户信息格式化"场景实践参数特性,深入理解函数设计的核心逻辑。

一、函数定义方式对比:以计算器功能为例

在实现基础计算器的加法功能时,三种定义方式呈现出不同的语法特性与上下文行为:

1. 函数声明
使用 function 关键字定义,具有函数提升特性,this 绑定为调用时的上下文对象:

javascript
复制代码
const calculator = {
  value: 0,
  add: function(a, b) {
    this.value = a + b; // this 指向 calculator 对象
    return this.value;
  }
};
calculator.add(2, 3); // 结果:5,calculator.value 变为 5

2. 函数表达式
通过变量赋值定义,同样绑定调用时的 this,语法更灵活:

javascript
复制代码
const calculator = {
  value: 0,
  add: function(a, b) { // 匿名函数表达式
    this.value = a + b;
    return this.value;
  }
};

3. 箭头函数
ES6 引入的简洁语法,无自己的 this 绑定,而是继承外层词法作用域的 this:

javascript
复制代码
const calculator = {
  value: 0,
  add: (a, b) => {
    this.value = a + b; // this 指向外层上下文(如全局 window 或 undefined)
    return this.value;
  }
};
calculator.add(2, 3); // 结果:NaN(因 this.value 未定义)

注意事项:箭头函数因无独立 this,不适合作为对象方法。上述案例中,箭头函数的 this 未指向 calculator 对象,导致计算失败。其更适合纯函数逻辑或需要继承上下文 this 的场景(如定时器回调)[5]。

二、参数特性实践:用户信息格式化与动态传参

通过"用户信息格式化"和"动态求和"函数,掌握默认参数、剩余参数及 call/apply 传递数组的技巧:

1. 默认参数
为函数参数设置默认值,避免 undefined 导致的逻辑错误:

javascript
复制代码
function formatUser(name = 'Guest', age) {
  return `Name: ${name}, Age: ${age || 'Unknown'}`;
}
formatUser(); // "Name: Guest, Age: Unknown"
formatUser('Alice', 25); // "Name: Alice, Age: 25"

2. 剩余参数
使用 ... 收集不定数量的参数为数组,简化多参数处理:

javascript
复制代码
function sum(...nums) { // nums 为数组,包含所有传入参数
  return nums.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3); // 6
sum(5, 10, 15, 20); // 50

3. call/apply 实现数组参数传递
传统方法中,call(逐个传参)和 apply(数组传参)可灵活控制参数传递:

javascript
复制代码
const numbers = [1, 2, 3, 4];
// apply 接收数组作为参数列表
sum.apply(null, numbers); // 10(等价于 sum(1,2,3,4))
// call 需手动展开数组(ES6 扩展运算符简化)
sum.call(null, ...numbers); // 10(与 apply 效果一致)

三、箭头函数的核心特性与局限性

箭头函数不仅语法简洁,还具有独特的上下文行为:

  • 语法精简:单参数可省略括号,单表达式函数体可省略大括号及 return:
    javascript
    复制代码
    const square = x => x * x; // 等价于 function(x) { return x * x; }
  • 继承 this:捕获外层作用域的 this,解决回调函数中 this 指向问题:
    javascript
    复制代码
    function Person() {
      this.age = 0;
      setInterval(() => {
        this.age++; // this 指向 Person 实例,而非定时器上下文
      }, 1000);
    }
  • 局限性:无 arguments 对象、不能作为构造函数(使用 new 会报错)、无 prototype 属性[5]。

通过上述案例可见,函数定义方式的选择需结合场景:对象方法优先使用函数声明/表达式,纯逻辑或回调场景适合箭头函数;参数特性则通过默认值、剩余参数及 call/apply 显著提升函数灵活性。