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}`; |
特点:无独立 this 、 arguments ,不能用作构造函数,语法更简洁。
三者中,函数声明最适用于模块顶层逻辑;表达式和箭头函数更适合内联使用。
# 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 限制在块内 |
推荐使用 let 和 const 替代 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] |
数组原生方法如 map 、 filter 、 reduce 均基于此模式。
# 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 代码。