函数大全
涵盖 JS 函数定义方式、参数、返回值、高阶函数、闭包、IIFE、递归、异步、生成器、函数式编程、防抖节流、记忆化。原文件"函数"整章 + "变量赋值覆盖函数声明" + "传值引用" + "this 隐式丢失"中闭包代码整合到本文(闭包原理见 01)。
一、函数定义方式
1. 函数声明
// 函数声明(会提升)
function greet(name) {
return `Hello, ${name}!`
}
console.log(greet('张三')) // Hello, 张三!
// 函数声明提升
console.log(sayHi('李四')) // Hello, 李四! (可以在声明前调用)
function sayHi(name) {
return `Hello, ${name}!`
}2. 函数表达式
// 匿名函数表达式
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)
// 基本语法
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 构造函数(不推荐)
// 动态创建函数
const sum = new Function('a', 'b', 'return a + b')
console.log(sum(3, 5)) // 8
// 不推荐使用,因为性能差且不安全二、函数的参数
1. 默认参数
// 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)) // 1002. 剩余参数(Rest Parameters)
// 收集剩余参数为数组
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 对象(类数组)
// 传统方式获取所有参数
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. 基本返回
// 返回多个值(通过对象或数组)
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')) // undefined2. 提前返回
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. 函数作为参数
// 回调函数
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. 函数作为返回值
// 函数工厂
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)
// 基本 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七、递归函数
// 阶乘
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. 回调函数
// 传统回调
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
// 创建 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)
// 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()九、生成器函数
// 基本生成器
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
}十、函数式编程
// 纯函数
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十一、函数属性和方法
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, 张三!!!十二、实用函数示例
防抖
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}节流
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)记忆化
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)) // 快速计算十三、变量赋值与函数声明的提升优先级
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编译阶段后的代码(提升后的效果):
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() {...}是赋值,会覆盖函数声明
十四、传值:能修改和不能修改
能修改对象属性的情况
// 情况1:直接修改对象属性
var person = {
name: 'Nicholas',
age: 20
}
function setName(obj) {
obj.name = 'Greg' // 直接修改传入对象的属性
}
setName(person)
console.log(person.name) // 'Greg' 被修改了!
// 为什么?
// obj 和 person 指向同一个对象
// 通过 obj 修改属性,就是在修改 person 指向的对象不能修改对象引用的情况
// 情况2:重新赋值(你的例子)
var person = {
name: 'Nicholas',
age: 20
}
function setName(obj) {
obj = {} // 重新赋值,切断联系
obj.name = 'Greg'
}
setName(person)
console.log(person.name) // 'Nicholas' 未改变
// 为什么?
// obj 原本指向 person 的对象
// 重新赋值后,obj 指向了新对象
// 后续操作只影响新对象其他不能修改的例子
// 情况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. 更简洁的语法
// 普通函数
const add1 = function(a, b) {
return a + b
}
// 箭头函数
const add2 = (a, b) => a + b2. 没有自己的 this(词法作用域的 this)
箭头函数没有自己的 this,它会捕获其外层作用域(非箭头函数)的 this,并且这个 this 在箭头函数的整个生命周期内都不会改变。
普通函数的 this 是在调用时根据上下文决定的(new / call / apply / 隐式绑定),而箭头函数则直接继承外层的 this,非常适合作为回调函数(如 setTimeout、map、Promise.then 等)。
3. 没有 arguments 对象
const foo = () => {
console.log(arguments) // 报错:arguments is not defined
}
foo(1, 2, 3)箭头函数中可以用剩余参数 ...args 代替:
const foo = (...args) => {
console.log(args) // [1, 2, 3]
}4. 不能作为构造函数使用
const Foo = () => {}
new Foo() // 报错:Foo is not a constructor5. 没有 super 和 new.target
箭头函数不能用作 super() 的调用方,也不能用 new.target。
6. 不能用作 Generator 函数
箭头函数不能作为生成器函数(不能使用 yield)。
适用场景总结
- ✅ 适合:需要
this继承自外层(如回调、事件处理) - ❌ 不适合:需要动态
this(如对象方法)、构造函数、需要arguments
十八、解构赋值详解
JavaScript 的解构赋值(Destructuring Assignment)是 ES6 引入的一种极其便利的语法,用于从数组或对象中提取数据,并将其赋值给声明的变量。
数组解构(基于位置)
// 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]对象解构(基于键名)
// 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}函数参数解构
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?
// 类的 getter 也可以省略 {} 和 return
class Foo {
get bar() { return 1 } // 必须写 return 和 {}
}在 class 中,方法体不能像箭头函数那样省略 {} 和 return。所有方法都必须写完整的 {},有返回值的方法必须写 return。
2. 怎么判断箭头函数 this 的指向?以对象 {} 作为一层去判断可以吗?
不能简单以 {} 作为一层去判断。箭头函数 this 指向其词法作用域的 this,而对象的 {} 不会创建独立作用域。
const obj = {
foo: () => {
console.log(this) // window / undefined(取决于严格模式)
}
}
obj.foo()判断方法:从箭头函数定义处向外找到第一个普通函数 / 方法,那个普通函数的 this 就是箭头函数的 this。
3. 普通函数的 arguments 是怎么用的?
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. super 和 new.target 是什么?
super:在类的继承中,super指向父类的构造函数(用于调用父类构造函数)或者父类的原型(用于调用父类方法)。new.target:指向当前正在被new调用的构造函数。常用于抽象类中判断是否通过子类实例化:
class Abstract {
constructor() {
if (new.target === Abstract) {
throw new Error('抽象类不能直接实例化')
}
}
}
class Concrete extends Abstract {}
new Abstract() // 报错
new Concrete() // 正常二十、关联文档
- 01-闭包与作用域(var/let/const 作用域)
- 02-this 绑定与隐式丢失
- 03-原型与继承
- 05-异步与事件循环(Promise / async-await / 事件循环)
- 06-迭代器与生成器