JavaScript 中的函数不仅是代码块的封装单元,更是具备复杂行为的一等公民。理解其底层机制,是掌握语言本质的关键。

# 1. 函数定义方式对比

JavaScript 提供多种函数创建语法,语义相近但行为略有差异。

# 函数声明(Function Declaration)

function greet(name) {
  return `Hello, ${name}`;
}

特点:存在提升(hoisting),函数在整个作用域内可提前调用。

# 函数表达式(Function Expression)

const greet = function(name) {
  return `Hello, ${name}`;
};

特点:赋值前不可用,无提升,常用于回调或条件定义。

# 箭头函数(Arrow Function)

const greet = (name) => `Hello, ${name}`;

特点:无独立 thisarguments ,不能用作构造函数,语法更简洁。

三者中,函数声明最适用于模块顶层逻辑;表达式和箭头函数更适合内联使用。


# 2. 参数处理机制

JavaScript 函数对参数数量不敏感,调用时传入的实参与形参无需严格匹配。

function describe(name, age) {
  console.log(`${name}${age}`);
}
describe("Alice");           // "Alice, undefined"
describe("Bob", 30, "extra"); // "Bob, 30"
  • 实参不足:未传参数值为 undefined
  • 实参过多:超出部分被忽略

# 动态参数收集

arguments 对象(旧式)

仅在非箭头函数中可用,类数组对象:

function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

Rest 参数(推荐)

ES6 引入,语法清晰,返回真数组:

function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

Rest 参数必须位于参数列表末尾。


# 3. 作用域规则

变量的作用域决定了其可访问范围。

# 全局与局部作用域

let globalVar = "global";
function scopeTest() {
  let localVar = "local";
  console.log(globalVar); // 可访问
  console.log(localVar);  // 可访问
}
scopeTest();
//console.log (localVar); // 错误:作用域外不可访问

函数内部可访问外部变量,反之则不行。

# 块级作用域(Block Scope)

var 声明不具备块级作用域:

if (true) {
  var a = 1;
  let b = 2;
}
console.log(a); // 1,var 提升至函数作用域
//console.log (b); // 错误,let 限制在块内

推荐使用 letconst 替代 var ,避免变量提升带来的逻辑混乱。


# 4. 闭包(Closure)

闭包是指函数能够访问并保留其词法作用域中变量的能力,即使该函数在其原始作用域之外执行。

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

count 变量被内部函数引用,因此不会被垃圾回收,形成私有状态。

# 实际应用:数据封装

function createAccount(initial) {
  let balance = initial;
  return {
    deposit(amount) {
      balance += amount;
    },
    withdraw(amount) {
      if (amount <= balance) balance -= amount;
    },
    get() {
      return balance;
    }
  };
}
const account = createAccount(1000);
account.deposit(500);
console.log(account.get()); // 1500

外部无法直接访问 balance ,实现数据隐藏。


# 5. this 绑定与箭头函数

this 的值由函数调用方式决定,而非定义位置。

const obj = {
  name: "App",
  regular() {
    console.log(this.name);
  },
  arrow: () => {
    console.log(this.name);
  }
};
obj.regular(); // "App"
obj.arrow();   //undefined(箭头函数继承外层 this)
  • 普通函数: this 指向调用者
  • 箭头函数:无自身 this ,继承外层作用域

因此,对象方法应使用普通函数,避免使用箭头函数。


# 6. 立即执行函数(IIFE)

用于创建独立作用域,防止变量污染全局。

(function() {
  const secret = "private";
  console.log("Executed");
})();
(() => {
  console.log("Arrow IIFE");
})();

在模块化普及前,IIFE 是组织代码的主要手段。


# 7. 高阶函数与函数式编程

函数可作为参数或返回值传递,体现 “一等公民” 特性。

function transform(arr, fn) {
  const result = [];
  for (const item of arr) {
    result.push(fn(item));
  }
  return result;
}
const nums = [1, 2, 3];
const doubled = transform(nums, x => x * 2);
console.log(doubled); // [2, 4, 6]

数组原生方法如 mapfilterreduce 均基于此模式。


# 8. 实战:事件系统(发布订阅者模式)

结合闭包与高阶函数实现事件系统:

function createEmitter() {
  const listeners = {};
  return {
    on(event, fn) {
      if (!listeners[event]) listeners[event] = [];
      listeners[event].push(fn);
    },
    emit(event, data) {
      if (listeners[event]) {
        listeners[event].forEach(fn => fn(data));
      }
    },
    off(event, fn) {
      if (listeners[event]) {
        listeners[event] = listeners[event].filter(f => f !== fn);
      }
    }
  };
}
const emitter = createEmitter();
emitter.on("data", d => console.log("Received:", d));
emitter.emit("data", "Hello");

# 9. 常见陷阱与最佳实践

# 循环中的闭包问题

// 错误示例
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}
// 正确方式 1:使用 let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// 正确方式 2:IIFE 封装
for (var i = 0; i < 3; i++) {
  ((j) => setTimeout(() => console.log(j), 100))(i);
}

# 最佳实践

  • 优先使用 const ,避免意外赋值
  • 函数逻辑简单时使用箭头函数
  • 对象方法使用普通函数以正确绑定 this
  • 合理利用闭包,但注意内存泄漏风险

# 总结

概念核心要点
函数定义声明有提升,表达式无提升,箭头函数无 this
参数数量灵活,推荐使用 rest 参数
作用域let / const 支持块级作用域
闭包函数保留对外部变量的引用
this普通函数动态绑定,箭头函数静态继承
高阶函数函数作为参数或返回值
最佳实践避免 var ,合理使用 const / let ,慎用闭包

深入理解这些机制,有助于编写更健壮、可维护的 JavaScript 代码。