Skip to content

函数大全

作者:青见春山
发表于:2026-07-29
字数统计:8000 字
预计阅读27分钟

涵盖 JS 函数定义方式、参数、返回值、高阶函数、闭包、IIFE、递归、异步、生成器、函数式编程、防抖节流、记忆化。原文件"函数"整章 + "变量赋值覆盖函数声明" + "传值引用" + "this 隐式丢失"中闭包代码整合到本文(闭包原理见 01)。

一、函数定义方式

1. 函数声明

JavaScript
// 函数声明(会提升)
function greet(name) {
    return `Hello, ${name}!`
}

console.log(greet('张三'))  // Hello, 张三!

// 函数声明提升
console.log(sayHi('李四'))  // Hello, 李四! (可以在声明前调用)
function sayHi(name) {
    return `Hello, ${name}!`
}

2. 函数表达式

JavaScript
// 匿名函数表达式
const greet = function(name) {
    return `Hello, ${name}!`
}

console.log(greet('王五'))  // Hello, 王五!

// 命名函数表达式
const factorial = function fact(n) {
    return n <= 1 ? 1 : n * fact(n - 1)
}

console.log(factorial(5))  // 120

// 注意:函数表达式不会提升
// console.log(add(1, 2))  // 报错!
const add = function(a, b) {
    return a + b
}

3. 箭头函数(ES6)

JavaScript
// 基本语法
const add = (a, b) => a + b
console.log(add(2, 3))  // 5

// 多行代码需要花括号和 return
const multiply = (a, b) => {
    const result = a * b
    return result
}

// 只有一个参数可省略括号
const square = x => x * x
console.log(square(4))  // 16

// 没有参数需要空括号
const sayHello = () => console.log('Hello')

// 返回对象时需要括号
const getUser = () => ({ name: '张三', age: 18 })
console.log(getUser())  // { name: '张三', age: 18 }

4. Function 构造函数(不推荐)

JavaScript
// 动态创建函数
const sum = new Function('a', 'b', 'return a + b')
console.log(sum(3, 5))  // 8

// 不推荐使用,因为性能差且不安全

二、函数的参数

1. 默认参数

JavaScript
// ES6 默认参数
function greet(name = '游客', greeting = '你好') {
    return `${greeting}, ${name}!`
}

console.log(greet())              // 你好, 游客!
console.log(greet('张三'))        // 你好, 张三!
console.log(greet('李四', '嗨'))  // 嗨, 李四!

// 默认参数表达式
function getRandom() {
    return Math.random()
}

function process(value = getRandom()) {
    return value
}

console.log(process())      // 随机数
console.log(process(100))   // 100

2. 剩余参数(Rest Parameters)

JavaScript
// 收集剩余参数为数组
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0)
}

console.log(sum(1, 2, 3, 4, 5))  // 15

// 剩余参数必须在最后
function introduce(name, age, ...hobbies) {
    console.log(`姓名: ${name}, 年龄: ${age}`)
    console.log(`爱好: ${hobbies.join(', ')}`)
}

introduce('张三', 18, '读书', '游泳', '跑步')
// 姓名: 张三, 年龄: 18
// 爱好: 读书, 游泳, 跑步

3. arguments 对象(类数组)

JavaScript
// 传统方式获取所有参数
function sum() {
    console.log(arguments)  // Arguments(5) [1, 2, 3, 4, 5]
    let total = 0
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i]
    }
    return total
}

console.log(sum(1, 2, 3, 4, 5))  // 15

// 箭头函数没有 arguments
const arrowSum = () => {
    // console.log(arguments)  // 报错!
}

// 转换为数组
function toArray() {
    const args = Array.from(arguments)
    // 或 const args = [...arguments]
    return args
}

三、函数的返回值

1. 基本返回

JavaScript
// 返回多个值(通过对象或数组)
function getUser() {
    return {
        name: '张三',
        age: 18,
        email: 'zhangsan@example.com'
    }
}

function getCoordinates() {
    return [10, 20]
}

const [x, y] = getCoordinates()
console.log(x, y)  // 10 20

// 无返回值(返回 undefined)
function logMessage(msg) {
    console.log(msg)
    // 隐式返回 undefined
}

console.log(logMessage('Hello'))  // undefined

2. 提前返回

JavaScript
function validateAge(age) {
    if (age < 0) {
        return '年龄不能为负数'
    }
    if (age > 150) {
        return '年龄不能超过150岁'
    }
    return `年龄有效: ${age}岁`
}

console.log(validateAge(-5))   // 年龄不能为负数
console.log(validateAge(200))  // 年龄不能超过150岁
console.log(validateAge(25))   // 年龄有效: 25岁

四、高阶函数

1. 函数作为参数

JavaScript
// 回调函数
function processArray(arr, callback) {
    const result = []
    for (let i = 0; i < arr.length; i++) {
        result.push(callback(arr[i], i, arr))
    }
    return result
}

const numbers = [1, 2, 3, 4, 5]
const doubled = processArray(numbers, x => x * 2)
console.log(doubled)  // [2, 4, 6, 8, 10]

// 数组方法的回调
const filtered = numbers.filter(x => x > 3)
console.log(filtered)  // [4, 5]

2. 函数作为返回值

JavaScript
// 函数工厂
function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier
    }
}

const double = createMultiplier(2)
const triple = createMultiplier(3)

console.log(double(5))   // 10
console.log(triple(5))   // 15

// 柯里化
function add(a) {
    return function(b) {
        return a + b
    }
}

const add5 = add(5)
console.log(add5(3))     // 8
console.log(add(5)(3))   // 8

// 使用箭头函数简化
const curriedAdd = a => b => a + b
console.log(curriedAdd(5)(3))  // 8

五、闭包

闭包相关的代码示例(私有变量、缓存、循环 var 等)已整合到 闭包与作用域

六、立即执行函数(IIFE)

JavaScript
// 基本 IIFE
(function() {
    console.log('立即执行')
})()

// 带参数
(function(name) {
    console.log(`Hello, ${name}`)
})('张三')

// 返回值的 IIFE
const result = (function(a, b) {
    return a + b
})(3, 5)
console.log(result)  // 8

// 箭头函数 IIFE
(() => {
    console.log('箭头函数 IIFE')
})()

// 应用:创建独立作用域
const module = (function() {
    // 私有变量和方法
    let privateVar = 0

    function privateMethod() {
        return privateVar
    }

    // 公开 API
    return {
        increment: function() {
            privateVar++
            return privateVar
        },
        getValue: function() {
            return privateMethod()
        }
    }
})()

console.log(module.getValue())  // 0
module.increment()
console.log(module.getValue())  // 1

七、递归函数

JavaScript
// 阶乘
function factorial(n) {
    if (n <= 1) return 1
    return n * factorial(n - 1)
}

console.log(factorial(5))  // 120

// 斐波那契数列
function fibonacci(n) {
    if (n <= 1) return n
    return fibonacci(n - 1) + fibonacci(n - 2)
}

console.log(fibonacci(10))  // 55

// 树形结构遍历
const tree = {
    name: '根节点',
    children: [
        {
            name: '子节点1',
            children: [
                { name: '孙节点1', children: [] },
                { name: '孙节点2', children: [] }
            ]
        },
        {
            name: '子节点2',
            children: []
        }
    ]
}

function traverse(node, depth = 0) {
    console.log('  '.repeat(depth) + node.name)
    node.children.forEach(child => traverse(child, depth + 1))
}

traverse(tree)
// 根节点
//   子节点1
//     孙节点1
//     孙节点2
//   子节点2

// 递归优化:尾递归(部分引擎支持)
function tailFactorial(n, accumulator = 1) {
    if (n <= 1) return accumulator
    return tailFactorial(n - 1, n * accumulator)
}

八、异步函数

1. 回调函数

JavaScript
// 传统回调
function fetchData(callback) {
    setTimeout(() => {
        const data = { id: 1, name: '张三' }
        callback(data)
    }, 1000)
}

fetchData((data) => {
    console.log('收到数据:', data)
})

// 回调地狱示例
function step1(callback) {
    setTimeout(() => callback('Step 1'), 500)
}

function step2(data, callback) {
    setTimeout(() => callback(`${data} -> Step 2`), 500)
}

function step3(data, callback) {
    setTimeout(() => callback(`${data} -> Step 3`), 500)
}

// 回调地狱
step1((result1) => {
    step2(result1, (result2) => {
        step3(result2, (result3) => {
            console.log(result3)  // Step 1 -> Step 2 -> Step 3
        })
    })
})

2. Promise

JavaScript
// 创建 Promise
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = true
            if (success) {
                resolve({ id: 1, name: '张三' })
            } else {
                reject('获取数据失败')
            }
        }, 1000)
    })
}

// 使用 Promise
fetchData()
    .then(data => {
        console.log('成功:', data)
        return data.name
    })
    .then(name => {
        console.log('姓名:', name)
    })
    .catch(error => {
        console.error('错误:', error)
    })
    .finally(() => {
        console.log('完成')
    })

// Promise 链式调用
function step1() {
    return new Promise(resolve => setTimeout(() => resolve('Step 1'), 500))
}
function step2(prev) {
    return new Promise(resolve => setTimeout(() => resolve(`${prev} -> Step 2`), 500))
}
function step3(prev) {
    return new Promise(resolve => setTimeout(() => resolve(`${prev} -> Step 3`), 500))
}

step1()
    .then(step2)
    .then(step3)
    .then(console.log)  // Step 1 -> Step 2 -> Step 3

// Promise.all
const promises = [
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3)
]
Promise.all(promises).then(results => {
    console.log(results)  // [1, 2, 3]
})

// Promise.race
Promise.race([
    new Promise(resolve => setTimeout(() => resolve('慢'), 1000)),
    new Promise(resolve => setTimeout(() => resolve('快'), 500))
]).then(console.log)  // '快'

3. async/await(ES2017)

JavaScript
// async 函数
async function fetchUserData() {
    try {
        const data = await fetchData()
        console.log('用户数据:', data)
        return data
    } catch (error) {
        console.error('错误:', error)
        throw error
    }
}

// 使用 async/await 处理顺序操作
async function processSteps() {
    const result1 = await step1()
    const result2 = await step2(result1)
    const result3 = await step3(result2)
    console.log(result3)
}

// 并行操作
async function parallelProcess() {
    const [data1, data2, data3] = await Promise.all([
        fetchData(),
        fetchData(),
        fetchData()
    ])
    console.log('所有数据:', data1, data2, data3)
}

// 注意:await 只能在 async 函数中使用
// 顶级 await (ES2022)
// const data = await fetchData()

九、生成器函数

JavaScript
// 基本生成器
function* numberGenerator() {
    yield 1
    yield 2
    yield 3
}

const gen = numberGenerator()
console.log(gen.next())  // { value: 1, done: false }
console.log(gen.next())  // { value: 2, done: false }
console.log(gen.next())  // { value: 3, done: false }
console.log(gen.next())  // { value: undefined, done: true }

// 无限序列
function* infiniteSequence() {
    let i = 0
    while (true) {
        yield i++
    }
}

const infinite = infiniteSequence()
console.log(infinite.next().value)  // 0
console.log(infinite.next().value)  // 1
console.log(infinite.next().value)  // 2

// 生成器传值
function* calculator() {
    const a = yield '请输入第一个数字'
    const b = yield '请输入第二个数字'
    return a + b
}

const calc = calculator()
console.log(calc.next())      // { value: '请输入第一个数字', done: false }
console.log(calc.next(5))     // { value: '请输入第二个数字', done: false }
console.log(calc.next(3))     // { value: 8, done: true }

// 委托生成器
function* generateNumbers() {
    yield* [1, 2, 3]
    yield* 'abc'
    yield* [4, 5, 6]
}

for (const value of generateNumbers()) {
    console.log(value)  // 1,2,3,a,b,c,4,5,6
}

十、函数式编程

JavaScript
// 纯函数
function pureAdd(a, b) {
    return a + b  // 相同输入总是相同输出,无副作用
}

// 不纯函数(有副作用)
let count = 0
function impureAdd(a) {
    count++  // 修改外部变量
    return a + count
}

// 函数组合
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

const addOne = x => x + 1
const double = x => x * 2
const square = x => x * x

const composed = compose(square, double, addOne)
const piped = pipe(addOne, double, square)

console.log(composed(3))  // square(double(addOne(3))) = square(double(4)) = square(8) = 64
console.log(piped(3))     // square(double(addOne(3))) = 64

// 偏函数
function partial(fn, ...args) {
    return function(...moreArgs) {
        return fn(...args, ...moreArgs)
    }
}

const sum = (a, b, c) => a + b + c
const add5 = partial(sum, 5)
console.log(add5(3, 2))  // 10

十一、函数属性和方法

JavaScript
function example(a, b, c) {
    console.log('执行')
}

console.log(example.name)        // 'example'
console.log(example.length)      // 3 (参数个数)

// 自定义属性
function counter() {
    if (counter.count === undefined) {
        counter.count = 0
    }
    counter.count++
    return counter.count
}

console.log(counter())  // 1
console.log(counter())  // 2
console.log(counter.count)  // 2

// call 和 apply
function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`)
}

const person = { name: '张三' }

greet.call(person, '你好', '!')      // 你好, 张三!
greet.apply(person, ['嗨', '~'])     // 嗨, 张三~

// bind(创建新函数,this 永久绑定)
const boundGreet = greet.bind(person, 'Hello')
boundGreet('!!!')  // Hello, 张三!!!

十二、实用函数示例

防抖

JavaScript
function debounce(fn, delay) {
    let timer = null
    return function(...args) {
        clearTimeout(timer)
        timer = setTimeout(() => fn.apply(this, args), delay)
    }
}

节流

JavaScript
function throttle(fn, delay) {
    let lastTime = 0
    return function(...args) {
        const now = Date.now()
        if (now - lastTime >= delay) {
            lastTime = now
            fn.apply(this, args)
        }
    }
}

// 使用示例
const log = () => console.log('执行')
const debouncedLog = debounce(log, 1000)
const throttledLog = throttle(log, 1000)

记忆化

JavaScript
function memoize(fn) {
    const cache = new Map()
    return function(...args) {
        const key = JSON.stringify(args)
        if (cache.has(key)) {
            return cache.get(key)
        }
        const result = fn.apply(this, args)
        cache.set(key, result)
        return result
    }
}

// 耗时函数
const fibonacci = memoize((n) => {
    if (n <= 1) return n
    return fibonacci(n - 1) + fibonacci(n - 2)
})

console.log(fibonacci(40))  // 快速计算

十三、变量赋值与函数声明的提升优先级

JavaScript
console.log(a)            // undefined
var a = 1

console.log(getNum)       // getNum() { a = 3 }  (函数声明)
var getNum = function () {
    a = 2
}
function getNum() {
    a = 3
}

console.log(a)            // 1

getNum()

console.log(a)            // 2

编译阶段后的代码(提升后的效果)

JavaScript
function getNum() {     // 函数声明提升(优先级高)
    a = 3
}
var getNum;             // 变量声明提升(但不会覆盖函数声明)
var a;                  // 变量声明提升

console.log(a)          // undefined
a = 1                   // 赋值操作留在原地

console.log(getNum)     // function getNum() { a = 3; }

getNum = function () {  // 变量赋值(覆盖函数声明)
    a = 2
}

console.log(a)          // 1

getNum()                // 此时 getNum 已经被重新赋值为函数表达式

console.log(a)          // 2

核心规则

  • 函数声明有提升,代码执行前把函数提升到顶部,执行上下文中生成函数定义
  • 同名 var 声明的 getNum 本该提升,但因为函数已经被声明了,就不需要再声明一个同名变量
  • 后续 getNum = function() {...} 是赋值,会覆盖函数声明

十四、传值:能修改和不能修改

能修改对象属性的情况

JavaScript
// 情况1:直接修改对象属性
var person = {
    name: 'Nicholas',
    age: 20
}

function setName(obj) {
    obj.name = 'Greg'  // 直接修改传入对象的属性
}

setName(person)
console.log(person.name)  // 'Greg' 被修改了!

// 为什么?
// obj 和 person 指向同一个对象
// 通过 obj 修改属性,就是在修改 person 指向的对象

不能修改对象引用的情况

JavaScript
// 情况2:重新赋值(你的例子)
var person = {
    name: 'Nicholas',
    age: 20
}

function setName(obj) {
    obj = {}           // 重新赋值,切断联系
    obj.name = 'Greg'
}

setName(person)
console.log(person.name)  // 'Nicholas' 未改变

// 为什么?
// obj 原本指向 person 的对象
// 重新赋值后,obj 指向了新对象
// 后续操作只影响新对象

其他不能修改的例子

JavaScript
// 情况3:数组重新赋值
var arr = [1, 2, 3]

function changeArray(arr) {
    arr = [4, 5, 6]  // 重新赋值
    arr.push(7)
}

changeArray(arr)
console.log(arr)  // [1, 2, 3] 未改变

// 情况4:基本类型无法修改
var num = 10

function changeNum(num) {
    num = 20  // 重新赋值
}

changeNum(num)
console.log(num)  // 10 未改变

十五、总结

JavaScript 函数非常灵活,支持多种编程范式:

  • 面向对象:函数作为方法
  • 函数式:高阶函数、纯函数
  • 异步编程:回调、Promise、async/await
  • 生成器:惰性求值
  • 闭包:数据封装和私有变量

掌握这些函数用法能让你写出更优雅、高效的代码!

十七、箭头函数详解(与普通函数对比)

箭头函数(Arrow Functions)是 ES6 中引入的一种更加简洁的函数书写方式。在面试中,它通常会被拿来与普通(传统)函数进行对比。

1. 更简洁的语法

JavaScript
// 普通函数
const add1 = function(a, b) {
    return a + b
}

// 箭头函数
const add2 = (a, b) => a + b

2. 没有自己的 this(词法作用域的 this

箭头函数没有自己的 this,它会捕获其外层作用域(非箭头函数)的 this,并且这个 this 在箭头函数的整个生命周期内都不会改变。

普通函数的 this 是在调用时根据上下文决定的(new / call / apply / 隐式绑定),而箭头函数则直接继承外层的 this,非常适合作为回调函数(如 setTimeoutmapPromise.then 等)。

3. 没有 arguments 对象

JavaScript
const foo = () => {
    console.log(arguments) // 报错:arguments is not defined
}
foo(1, 2, 3)

箭头函数中可以用剩余参数 ...args 代替:

JavaScript
const foo = (...args) => {
    console.log(args) // [1, 2, 3]
}

4. 不能作为构造函数使用

JavaScript
const Foo = () => {}
new Foo() // 报错:Foo is not a constructor

5. 没有 supernew.target

箭头函数不能用作 super() 的调用方,也不能用 new.target

6. 不能用作 Generator 函数

箭头函数不能作为生成器函数(不能使用 yield)。

适用场景总结

  • ✅ 适合:需要 this 继承自外层(如回调、事件处理)
  • ❌ 不适合:需要动态 this(如对象方法)、构造函数、需要 arguments

十八、解构赋值详解

JavaScript 的解构赋值(Destructuring Assignment)是 ES6 引入的一种极其便利的语法,用于从数组或对象中提取数据,并将其赋值给声明的变量。

数组解构(基于位置)

JavaScript
// 1. 基础用法
const [a, b, c] = [1, 2, 3]

// 2. 忽略某些值
const [x, , y] = [1, 2, 3] // x=1, y=3

// 3. 默认值(当值为 undefined 时生效)
const [m, n = 5] = [1] // m=1, n=5

// 4. 剩余操作符 (Rest)
const [first, ...rest] = [1, 2, 3, 4] // first=1, rest=[2, 3, 4]

// 5. 交换变量(极其方便)
let u = 1, v = 2
;[u, v] = [v, u]

对象解构(基于键名)

JavaScript
// 1. 基础用法
const { name, age } = { name: 'Alice', age: 20 }

// 2. 分配给新变量名(别名)
const { name: myName, age: myAge } = { name: 'Alice', age: 20 }

// 3. 默认值
const { role = 'user' } = {} // role='user'

// 4. 嵌套解构
const user = { info: { id: 1, email: 'a@a.com' } }
const { info: { id, email } } = user

// 5. 剩余属性
const { x, ...others } = { x: 1, y: 2, z: 3 } // others={y: 2, z: 3}

函数参数解构

JavaScript
function printUser({ name, age = 18 }) {
  console.log(name, age)
}
printUser({ name: 'Bob' }) // Bob 18

底层原理

  • 数组解构:本质上是消耗可迭代对象(Iterable)的迭代器(Iterator)。引擎调用 [Symbol.iterator]() 拿迭代器,再依次调用 next()
  • 对象解构:本质上是先将右侧的值转换为对象(ToObject()),然后通过键名去访问属性。null/undefined 会直接抛出 TypeError(因为无法转换为对象)。

推论:你甚至可以对基本数据类型进行对象解构,例如 const { length } = "hello" 成立,是因为 "hello" 被转化为了包装对象 String("hello"),访问到了它的 length 属性。

十九、class 关键易错点

1. 什么情况可以省略 {}return

JavaScript
// 类的 getter 也可以省略 {} 和 return
class Foo {
    get bar() { return 1 } // 必须写 return 和 {}
}

class 中,方法体不能像箭头函数那样省略 {}return。所有方法都必须写完整的 {},有返回值的方法必须写 return

2. 怎么判断箭头函数 this 的指向?以对象 {} 作为一层去判断可以吗?

不能简单以 {} 作为一层去判断。箭头函数 this 指向其词法作用域this,而对象的 {} 不会创建独立作用域。

JavaScript
const obj = {
    foo: () => {
        console.log(this) // window / undefined(取决于严格模式)
    }
}
obj.foo()

判断方法:从箭头函数定义处向外找到第一个普通函数 / 方法,那个普通函数的 this 就是箭头函数的 this

3. 普通函数的 arguments 是怎么用的?

JavaScript
function foo() {
    console.log(arguments) // 类数组对象:{ 0: 'a', 1: 'b', length: 2 }
    console.log(arguments[0]) // 'a'
}
foo('a', 'b')

arguments 是一个类数组对象,包含传入函数的所有参数。可以使用下标访问,但不能直接使用数组方法(如 arguments.forEach 会报错)。如果要用数组方法,可以 [...arguments] 或者 Array.from(arguments) 转为真数组。

4. supernew.target 是什么?

  • super:在类的继承中,super 指向父类的构造函数(用于调用父类构造函数)或者父类的原型(用于调用父类方法)。
  • new.target:指向当前正在被 new 调用的构造函数。常用于抽象类中判断是否通过子类实例化:
JavaScript
class Abstract {
    constructor() {
        if (new.target === Abstract) {
            throw new Error('抽象类不能直接实例化')
        }
    }
}
class Concrete extends Abstract {}
new Abstract() // 报错
new Concrete() // 正常

二十、关联文档