函数是 JavaScript 的核心。它不仅是代码复用的基本单位,更是一等公民——可以像普通值一样传递和操作。
🎯 定义函数#
函数声明#
// 函数声明(Function Declaration)function greet(name) { return `你好,${name}!`}
console.log(greet('张三')) // "你好,张三!"
// 函数声明会被提升(hoisting)sayHello() // "Hello!"(可以在声明前调用)
function sayHello() { console.log('Hello!')}函数表达式#
// 函数表达式(Function Expression)const greet = function (name) { return `你好,${name}!`}
// 不会被提升// sayHi() // ReferenceErrorconst sayHi = function () { console.log('Hi!')}
// 命名函数表达式(用于递归和调试)const factorial = function fact(n) { if (n <= 1) return 1 return n * fact(n - 1) // 内部可以用 fact}// fact(5) // ReferenceError(外部不能用 fact)箭头函数#
// 箭头函数(ES6)const add = (a, b) => a + b
// 单参数可省略括号const double = (n) => n * 2
// 无参数需要括号const random = () => Math.random()
// 多行需要花括号和 returnconst greet = (name) => { const greeting = `你好,${name}!` return greeting}
// 返回对象字面量需要包在括号里const createUser = (name) => ({ name, createdAt: Date.now() })构造函数#
// 使用 Function 构造函数(不推荐)const add = new Function('a', 'b', 'return a + b')console.log(add(1, 2)) // 3
// 🔶 有安全和性能问题,极少使用函数参数#
基本参数#
function add(a, b) { return a + b}
add(1, 2) // 3add(1) // NaN(b 是 undefined)add(1, 2, 3) // 3(多余参数被忽略)默认参数#
// ES6 默认参数function greet(name = '游客', greeting = '你好') { return `${greeting},${name}!`}
greet() // "你好,游客!"greet('张三') // "你好,张三!"greet('张三', '早上好') // "早上好,张三!"
// 只传第二个参数greet(undefined, '晚上好') // "晚上好,游客!"
// 默认值可以是表达式function getTimestamp(date = new Date()) { return date.getTime()}
// 默认值可以引用前面的参数function createRect(width, height = width) { return { width, height }}createRect(10) // { width: 10, height: 10 }剩余参数#
// 收集剩余参数到数组function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0)}
sum(1, 2, 3) // 6sum(1, 2, 3, 4, 5) // 15
// 与普通参数结合function log(level, ...messages) { console.log(`[${level}]`, ...messages)}
log('INFO', '用户登录', '用户ID: 123')// [INFO] 用户登录 用户ID: 123
// 🔶 剩余参数必须是最后一个// function bad(...rest, last) {} // SyntaxErrorarguments 对象#
// 传统方式(非箭头函数)function sum() { let total = 0 for (let i = 0; i < arguments.length; i++) { total += arguments[i] } return total}
sum(1, 2, 3) // 6
// arguments 是类数组,不是真正的数组function showArgs() { console.log(arguments.length) // 3 console.log(arguments[0]) // 'a' // arguments.map(...) // TypeError
// 转为数组 const args = Array.from(arguments) // 或 const args2 = [...arguments]}
showArgs('a', 'b', 'c')
// 🔶 箭头函数没有 argumentsconst arrowFn = () => { // console.log(arguments) // ReferenceError}
// 在箭头函数中使用剩余参数代替const arrowSum = (...args) => args.reduce((a, b) => a + b, 0)解构参数#
// 对象解构function createUser({ name, age, city = '未知' }) { return { name, age, city }}
createUser({ name: '张三', age: 25 })// { name: "张三", age: 25, city: "未知" }
// 数组解构function swap([a, b]) { return [b, a]}swap([1, 2]) // [2, 1]
// 配合默认值function config({ host = 'localhost', port = 3000 } = {}) { return { host, port }}
config() // { host: "localhost", port: 3000 }config({ port: 8080 }) // { host: "localhost", port: 8080 }返回值#
return 语句#
// 返回值function add(a, b) { return a + b // 返回结果并结束函数}
// 提前返回function divide(a, b) { if (b === 0) { return null // 提前返回,下面的代码不执行 } return a / b}
// 没有 return 或 return 后没有值,返回 undefinedfunction noReturn() { console.log('执行')}console.log(noReturn()) // undefined
function emptyReturn() { return}console.log(emptyReturn()) // undefined返回多个值#
// 返回数组function minMax(arr) { return [Math.min(...arr), Math.max(...arr)]}const [min, max] = minMax([3, 1, 4, 1, 5])
// 返回对象function getUser() { return { name: '张三', age: 25, city: '北京', }}const { name, age } = getUser()
// 返回函数function createMultiplier(factor) { return function (number) { return number * factor }}const double = createMultiplier(2)double(5) // 10换行陷阱#
// 🔶 return 后换行会自动插入分号function bad() { return { value: 1 }}bad() // undefined(不是对象!)
// ✅ 正确写法function good() { return { value: 1, }}// 或者function good2() { return { value: 1, }}函数作用域#
词法作用域#
// 函数作用域由定义位置决定,不是调用位置const x = 10
function outer() { const x = 20
function inner() { console.log(x) // 查找最近的 x }
return inner}
const fn = outer()fn() // 20(不是 10)变量遮蔽#
const name = '全局'
function showName() { const name = '局部' // 遮蔽全局变量 console.log(name)}
showName() // "局部"console.log(name) // "全局"闭包预览#
// 函数可以记住创建时的作用域function createCounter() { let count = 0 // 私有变量 return function () { count++ return count }}
const counter = createCounter()console.log(counter()) // 1console.log(counter()) // 2console.log(counter()) // 3
// count 对外部不可见,但函数可以访问函数是一等公民#
赋值给变量#
const greet = function (name) { return `你好,${name}`}
// 可以重新赋值let fn = greetconsole.log(fn('张三')) // "你好,张三"作为参数传递#
// 回调函数function processArray(arr, callback) { const result = [] for (const item of arr) { result.push(callback(item)) } return result}
const numbers = [1, 2, 3]const doubled = processArray(numbers, (n) => n * 2)console.log(doubled) // [2, 4, 6]
// 数组方法numbers.map((n) => n * 2)numbers.filter((n) => n > 1)numbers.forEach((n) => console.log(n))作为返回值#
// 高阶函数function withLogging(fn) { return function (...args) { console.log('调用参数:', args) const result = fn(...args) console.log('返回结果:', result) return result }}
const add = (a, b) => a + bconst loggedAdd = withLogging(add)
loggedAdd(1, 2)// 调用参数: [1, 2]// 返回结果: 3存储在数据结构中#
// 存储在数组中const operations = [(x) => x + 1, (x) => x * 2, (x) => x ** 2]
let value = 2for (const op of operations) { value = op(value)}console.log(value) // ((2 + 1) * 2) ** 2 = 36
// 存储在对象中const calculator = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b, divide: (a, b) => a / b,}
calculator.add(5, 3) // 8立即执行函数(IIFE)#
// 定义后立即执行;(function () { const private = '私有变量' console.log(private)})()
// 箭头函数版本;(() => { console.log('立即执行')})()
// 传递参数;(function (name) { console.log(`你好,${name}`)})('张三')
// 返回值const result = (function () { return 42})()
// 用于创建私有作用域(ES6 前的模块化)const counter = (function () { let count = 0 return { increment() { count++ }, getCount() { return count }, }})()递归函数#
// 阶乘function factorial(n) { if (n <= 1) return 1 return n * factorial(n - 1)}factorial(5) // 120
// 斐波那契数列function fibonacci(n) { if (n <= 1) return n return fibonacci(n - 1) + fibonacci(n - 2)}fibonacci(10) // 55
// 🔶 简单递归效率低,可以用记忆化优化function memoizedFib() { const cache = {} return function fib(n) { if (n in cache) return cache[n] if (n <= 1) return n cache[n] = fib(n - 1) + fib(n - 2) return cache[n] }}const fib = memoizedFib()fib(50) // 快速计算
// 深度遍历对象function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj } const clone = Array.isArray(obj) ? [] : {} for (const key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]) } } return clone}函数的属性和方法#
name 属性#
function foo() {}console.log(foo.name) // "foo"
const bar = function () {}console.log(bar.name) // "bar"
const baz = function qux() {}console.log(baz.name) // "qux"
console.log((() => {}).name) // ""length 属性#
// 形参数量(不含默认值和剩余参数)function fn1(a) {}function fn2(a, b) {}function fn3(a, b = 1) {}function fn4(a, ...rest) {}
console.log(fn1.length) // 1console.log(fn2.length) // 2console.log(fn3.length) // 1(默认参数不计入)console.log(fn4.length) // 1(剩余参数不计入)call 和 apply#
function greet(greeting, punctuation) { return `${greeting},${this.name}${punctuation}`}
const person = { name: '张三' }
// call:参数逐个传递greet.call(person, '你好', '!') // "你好,张三!"
// apply:参数作为数组传递greet.apply(person, ['你好', '!']) // "你好,张三!"
// 实际应用:借用数组方法const arrayLike = { 0: 'a', 1: 'b', length: 2 }Array.prototype.slice.call(arrayLike) // ["a", "b"]bind#
const person = { name: '张三' }
function greet() { return `你好,${this.name}`}
// 创建绑定 this 的新函数const boundGreet = greet.bind(person)boundGreet() // "你好,张三"
// 预设参数(柯里化)function add(a, b, c) { return a + b + c}
const add5 = add.bind(null, 5) // 预设 a = 5add5(3, 2) // 10
const add5and3 = add.bind(null, 5, 3) // 预设 a = 5, b = 3add5and3(2) // 10最佳实践#
函数命名#
// 动词开头,描述行为function getUserById(id) {}function validateEmail(email) {}function calculateTotal(items) {}
// 布尔返回值用 is/has/canfunction isValid(value) {}function hasPermission(user) {}function canEdit(document) {}
// 事件处理用 handle/onfunction handleClick(event) {}function onSubmit(data) {}单一职责#
// 🔶 做太多事function processUser(user) { // 验证 if (!user.name) throw new Error('名字必填') // 格式化 user.name = user.name.trim() // 保存 database.save(user) // 发送邮件 email.send(user.email, '欢迎')}
// ✅ 拆分职责function validateUser(user) { if (!user.name) throw new Error('名字必填')}
function formatUser(user) { return { ...user, name: user.name.trim() }}
function saveUser(user) { return database.save(user)}
function sendWelcomeEmail(email) { return email.send(email, '欢迎')}避免副作用#
// 🔶 有副作用:修改外部状态let total = 0function addToTotal(value) { total += value}
// ✅ 纯函数:相同输入总是相同输出function add(a, b) { return a + b}
// 🔶 修改参数function addItem(arr, item) { arr.push(item) // 修改了原数组 return arr}
// ✅ 不修改参数function addItem(arr, item) { return [...arr, item] // 返回新数组}总结#
| 定义方式 | 提升 | this 绑定 | arguments | 适用场景 |
|---|---|---|---|---|
| 函数声明 | 是 | 动态 | 有 | 通用 |
| 函数表达式 | 否 | 动态 | 有 | 回调、条件定义 |
| 箭头函数 | 否 | 继承 | 无 | 回调、简短函数 |
核心要点:
- 函数是一等公民,可以像值一样传递
- 使用默认参数和剩余参数处理可变参数
- 箭头函数没有自己的
this和arguments - 保持函数简短、单一职责、无副作用