JavaScript 函数完全指南

小飞兽 JavaScript 414 次阅读 2026-05-15

JavaScript 函数完全指南

Introduction(引言)

函数是 JavaScript 编程的核心构造单元,是组织代码、实现逻辑复用的基本单位。从最初的函数声明与函数表达式,到箭头函数的诞生,再到高阶函数、闭包、回调、Promise 异步函数,JavaScript 的函数体系在 ES6 之后变得前所未有的强大与灵活。本篇文章将全面讲解 JavaScript 函数的方方面面:基础定义与调用方式、参数处理机制、返回值、箭头函数与普通函数的区别、IIFE 立即执行函数、回调函数、高阶函数、闭包、递归,以及异步函数与生成器函数,帮助你建立完整的 JavaScript 函数知识体系。

一、函数的定义与调用

1.1 四种定义方式

// 方式一:函数声明(Function Declaration)
function greet(name) {
  return '你好,' + name + '!';
}

// 方式二:函数表达式(Function Expression)
const sayHello = function(name) {
return 'Hello, ' + name;
};

// 方式三:箭头函数(Arrow Function,ES6+)
const add = (a, b) => a + b;
const greetArrow = name => '你好,' + name; // 单参数可省略括号
const noReturn = () => { console.log('无返回值'); };

// 方式四:Function 构造函数(不推荐)
const multiply = new Function('a', 'b', 'return a * b');
console.log(multiply(3, 4)); // 12

1.2 函数的调用与 this 绑定

// 普通函数调用
function showThis() {
  console.log(this);
}
showThis(); // 浏览器中输出 Window(严格模式下为 undefined)

// 作为对象方法调用
const person = {
name: '小明',
introduce() {
// this 指向调用该方法的对象(person)
return '我是' + this.name;
}
};
console.log(person.introduce()); // "我是小明"

// 构造器调用(new)
function Car(brand, model) {
this.brand = brand;
this.model = model;
}
const myCar = new Car('Tesla', 'Model 3');
console.log(myCar.brand); // "Tesla"

// call / apply / bind 显式绑定
function speak(words) {
return this.name + ' 说:' + words;
}
const obj1 = { name: '小红' };
const obj2 = { name: '小华' };
console.log(speak.call(obj1, '你好')); // "小红 说:你好"
console.log(speak.apply(obj2, ['再见'])); // "小华 说:再见"
const boundSpeak = speak.bind(obj1);
console.log(boundSpeak('很高兴认识你')); // "小红 说:很高兴认识你"

二、参数处理机制

2.1 默认参数、剩余参数与解构

// 默认参数
function connect(host = 'localhost', port = 3306, user = 'root') {
  return 连接 ${host}:${port},用户:${user};
}
console.log(connect());                         // 使用全部默认值
console.log(connect('127.0.0.1'));             // 只改host
console.log(connect('db.example.com', 5432));  // 改host和port

// 剩余参数(Rest Parameters)
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

function printAll(first, ...rest) {
console.log('第一个:', first);
console.log('剩余:', rest);
}
printAll('a', 'b', 'c', 'd'); // 第一个: a,剩余: ['b', 'c', 'd']

// 参数解构
function createUser({ name, age, gender = '未知' } = {}) {
return { name, age, gender };
}
console.log(createUser({ name: '小明', age: 20 }));
console.log(createUser({ name: '小红', age: 18, gender: '女' }));
console.log(createUser()); // 默认空对象,所有字段使用默认值

2.2 arguments 对象(仅普通函数可用)

// 箭头函数没有 arguments 对象
function legacySum() {
  console.log('arguments 是 Array-like 对象:', arguments);
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}
console.log(legacySum(10, 20, 30)); // 60

// 箭头函数使用剩余参数代替
const modernSum = (...nums) => nums.reduce((a, b) => a + b, 0);
console.log(modernSum(10, 20, 30)); // 60

三、箭头函数的特殊行为

// 箭头函数不创建自己的 this
function Timer() {
  this.seconds = 0;
  
  // 普通函数:setInterval 的回调里 this 指向 Window(严格模式undefined)
  // setInterval(function() { this.seconds++; }, 1000);
  
  // 箭头函数:继承外层 this
  setInterval(() => {
    this.seconds++;
    console.log('已过秒数:', this.seconds);
  }, 1000);
}

// 箭头函数没有 prototype 属性(不能用作构造器)
const Arrow = () => {};
// console.log(new Arrow()); // TypeError: Arrow is not a constructor

// 箭头函数不能用作对象方法(this不会绑定到对象)
const counter = {
count: 0,
// 错误用法:箭头函数的 this 指向外层(Window/undefined),不会绑定到 counter
// inc: () => this.count++,

// 正确用法:方法使用普通函数
inc() { this.count++; },
dec() { this.count--; },
getCount() { return this.count; }
};
counter.inc();
counter.inc();
console.log(counter.getCount()); // 2

// 箭头函数与 yield
// 箭头函数不能用作生成器(Generator)
// const gen = *() => {}; // 语法错误

四、IIFE 立即执行函数

// 经典 IIFE(Immediately Invoked Function Expression)
(function() {
  const secret = '这是私有变量';
  console.log('IIFE 执行了,secret =', secret);
})();

// 带参数的 IIFE
(function(global, name) {
global.appName = name;
console.log('应用初始化:', name);
})(window, 'MyApp');

// 箭头函数形式的 IIFE(ES6+)
(() => {
const env = process.env.NODE_ENV || 'development';
console.log('运行环境:', env);
})();

((msg) => console.log('私有消息:', msg))('Hello from IIFE');

// IIFE 的作用:创建独立作用域,避免全局污染
(function() {
// 在此定义的变量不会泄露到全局
let privateCounter = 0;
window.increment = () => ++privateCounter;
window.getCount = () => privateCounter;
})();

// console.log(privateCounter); // ReferenceError
console.log(window.increment()); // 1
console.log(window.getCount()); // 1

五、回调函数与高阶函数

5.1 回调函数

// 回调函数:把函数作为参数传入另一个函数
function processData(data, callback) {
  console.log('处理数据:', data);
  const result = data * 2;
  // 调用回调,传入结果
  callback(result);
}

processData(5, function(result) {
console.log('回调收到结果:', result); // 10
});

// 数组方法的回调
const scores = [85, 92, 78, 96, 61];
const passed = scores.filter(function(score) {
return score >= 75;
});
console.log('及格分数:', passed); // [85, 92, 78, 96]

// 箭头函数简化回调
const passedArrow = scores.filter(score => score >= 75);
const grade = scores.map(score => score >= 90 ? 'A' : score >= 75 ? 'B' : 'C');
console.log('等级:', grade); // ["B", "A", "B", "A", "C"]

5.2 高阶函数

// 高阶函数:接受函数为参数 or 返回函数的函数

// 1. 接受函数为参数
function mapArray(arr, transform) {
const result = [];
for (const item of arr) {
result.push(transform(item));
}
return result;
}
const doubled = mapArray([1, 2, 3], x => x * 2);
console.log('翻倍:', doubled); // [2, 4, 6]

// 2. 返回函数
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(multiplier(4)(5)); // 20(函数组合调用)

// 3. 函数柯里化(Currying)
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6

// 4. compose(函数组合)
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
const addOne = x => x + 1;
const double2 = x => x * 2;
const square = x => x * x;

const compute = compose(addOne, double2, square);
console.log(compute(3)); // (3*3)*2+1 = 19

六、闭包与词法作用域

// 闭包:函数能访问其词法作用域外部的变量
function createCounter() {
  let count = 0; // 私有变量,被闭包引用,不会被垃圾回收
  
  return function() {
    count++;
    return count;
  };
}

const counterA = createCounter();
const counterB = createCounter();

console.log(counterA()); // 1
console.log(counterA()); // 2
console.log(counterA()); // 3
console.log(counterB()); // 1(独立闭包,count重置)
console.log(counterA()); // 4

// 经典闭包问题:循环中的var
function createPrinters1() {
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function() { console.log(i); });
}
return funcs;
}
createPrinters1()[0](); // 3(所有函数共享同一个i,循环结束后i=3)
createPrinters1()[1](); // 3
createPrinters1()[2](); // 3

// 解决方案一:使用 let(块级作用域,每次循环新变量)
function createPrinters2() {
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(function() { console.log(i); });
}
return funcs;
}
createPrinters2()[0](); // 0
createPrinters2()[1](); // 1
createPrinters2()[2](); // 2

// 解决方案二:使用 IIFE 捕获当前值
function createPrinters3() {
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push((function(captured) {
return function() { console.log(captured); };
})(i));
}
return funcs;
}
createPrinters3()[0](); // 0
createPrinters3()[2](); // 2

// 闭包应用:私有数据
function createBankAccount(initialBalance) {
let balance = initialBalance; // 私有变量

return {
deposit(amount) {
if (amount <= 0) throw new Error('存款金额必须为正');
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) throw new Error('余额不足');
balance -= amount;
return balance;
},
getBalance() {
return balance;
}
};
}

const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // 1500
console.log(account.withdraw(200)); // 1300
// account.balance; // undefined(外部无法访问)

七、递归函数

// 递归:函数调用自身
// 经典案例:斐波那契数列
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log('斐波那契前10项:', [...Array(10)].map((_, i) => fibonacci(i)));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// 优化:记忆化递归(Memoization)
function fibonacciMemo(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 1) return n;
memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
return memo[n];
}
console.log('斐波那契第40项:', fibonacciMemo(40)); // 102334155

// 递归遍历树形结构
const tree = {
name: 'root',
children: [
{
name: 'src',
children: [
{ name: 'index.js' },
{ name: 'app.js' }
]
},
{ name: 'package.json' }
]
};

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

八、异步函数 Async/Await

// 异步函数:Promise 的语法糖
async function fetchData() {
  return '数据加载完成';
}
// 等价于:
// function fetchData() { return Promise.resolve('数据加载完成'); }

fetchData().then(console.log); // "数据加载完成"

// await 等待 Promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function sequential() {
console.log('开始...');
await delay(1000);
console.log('1秒后...');
await delay(1000);
console.log('再1秒后结束');
}
sequential();

// 并行执行:Promise.all
async function parallel() {
const [user, posts] = await Promise.all([
new Promise(r => setTimeout(() => r({ id: 1, name: '小明' }), 500)),
new Promise(r => setTimeout(() => r(['文章1', '文章2']), 800))
]);
console.log('用户:', user);
console.log('文章:', posts);
}
parallel();

// 错误处理
async function safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(HTTP ${response.status});
return await response.json();
} catch (error) {
console.error('请求失败:', error.message);
return null;
}
}

// for await...of 遍历异步迭代器
async function demoAsyncIterator() {
function createAsyncGenerator(values) {
return {
[Symbol.asyncIterator]() {
let i = 0;
return {
async next() {
await delay(100);
if (i < values.length) {
return { value: values[i++], done: false };
}
return { done: true };
}
};
}
};
}

for await (const val of createAsyncGenerator(['a', 'b', 'c'])) {
console.log('异步值:', val);
}
}
demoAsyncIterator();

九、生成器函数

// 生成器函数:可暂停、恢复执行的特殊函数
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* fibonacciGenerator() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

const fibGen = fibonacciGenerator();
console.log([...Array(10)].map(() => fibGen.next().value));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// 生成器作为默认迭代器
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range(1, 5)]); // [1, 2, 3, 4, 5]

// yield* 委托生成器
function* genA() { yield 'A1'; yield 'A2'; }
function* genB() { yield 'B1'; yield* genA(); yield 'B2'; }
console.log([...genB()]); // ["B1", "A1", "A2", "B2"]

// 生成器的 return 和 throw
function* simpleGen() {
try {
yield '第一步';
yield '第二步';
} finally {
console.log('清理工作');
}
yield '不会执行'; // return之后不会再执行
}
const sg = simpleGen();
console.log(sg.next()); // { value: '第一步', done: false }
console.log(sg.return('结束')); // { value: '结束', done: true } 并打印"清理工作"

十、运行效果说明

将上述所有代码粘贴到浏览器控制台(按 F12 → Console)或 Node.js 环境中运行。预期输出摘要:

// 部分运行结果
greet('小明')              // "你好,小明!"
add(3, 5)                  // 8
person.introduce()         // "我是小明"
speak.call(obj1, '你好')   // "小红 说:你好"
sum(1, 2, 3, 4, 5)         // 15
createUser({name:'小红', age:18}).gender  // "女"
createPrinters2()[0]()     // 0
fibonacci(10)              // 55
fibonacciMemo(40)          // 102334155
[...range(1, 5)]           // [1, 2, 3, 4, 5]

十一、常见问题与解决方案

Q1:箭头函数和普通函数哪个更好?

没有绝对答案,根据场景选择。箭头函数适合:回调函数(如数组方法)、不需要自己的 this/arguments 的场景、不需要用作构造器的场景。普通函数(函数声明/表达式)适合:对象方法、构造器、需要 arguments 对象的场景、需要递归调用的场景。

Q2:如何避免回调地狱(Callback Hell)?

// 回调地狱示例
// doSomething(function(result) {
//   doSomethingElse(result, function(newResult) {
//     doThirdThing(newResult, function(finalResult) {
//       console.log(finalResult);
//     });
//   });
// });

// 解决方案一:Promise 链式调用
doSomethingP()
.then(result => doSomethingElseP(result))
.then(newResult => doThirdThingP(newResult))
.then(finalResult => console.log(finalResult))
.catch(error => console.error(error));

// 解决方案二:async/await(最清晰)
async function main() {
try {
const result = await doSomethingP();
const newResult = await doSomethingElseP(result);
const finalResult = await doThirdThingP(newResult);
console.log(finalResult);
} catch (error) {
console.error(error);
}
}
main();

Q3:函数声明 vs 函数表达式,哪个会被提升?

// 函数声明:完全提升(声明和定义都提升),可以在定义前调用
sayHello(); // ✅ "Hello!"
function sayHello() { console.log('Hello!'); }

// 函数表达式:只提升变量声明,不提升定义(赋值)
// sayHi(); // ❌ TypeError: sayHi is not a function
var sayHi = function() { console.log('Hi!'); };
sayHi(); // ✅ "Hi!"

// 具名函数表达式:只有变量提升,函数名不能在外部访问
var factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // fact 在内部可见,用于递归
};
console.log(factorial(5)); // 120
// fact(5); // ReferenceError: fact is not defined

Q4:如何实现函数参数的记忆化(Memoization)?

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 expensiveCalc = memoize(function(n) {
console.log('正在计算...');
return n * n;
});
console.log(expensiveCalc(5)); // 正在计算... 25
console.log(expensiveCalc(5)); // 直接从缓存返回,无"正在计算..."
console.log(expensiveCalc(6)); // 正在计算... 36

Q5:为什么函数调用要使用 call/apply/bind?

// bind 返回新函数,永久绑定 this
const module = {
  x: 42,
  getX() { return this.x; }
};
const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined(this丢失)

const boundGetX = module.getX.bind(module);
console.log(boundGetX()); // 42

// apply 与 call 的区别:参数形式
function introduce(greeting, punctuation) {
return greeting + ', 我是' + this.name + punctuation;
}
const dev = { name: '开发者' };

console.log(introduce.call(dev, '你好', '!')); // 你好, 我是开发者!
console.log(introduce.apply(dev, ['你好', '!'])); // 你好, 我是开发者!

// apply 常用于将数组展开为参数
const numbers = [3, 1, 4, 1, 5, 9];
console.log(Math.max.apply(null, numbers)); // 9(ES6前常用)
console.log(Math.max(...numbers)); // 9(ES6+更简洁)

十二、延伸阅读

    • 《You Don't Know JS Yet》- Scope & Closures:Kyle Simpson 深入讲解闭包与作用域,是理解 JavaScript 函数机制的必读经典
    • 《You Don't Know JS Yet》- Async & Performance:深入讲解 Promise、async/await、生成器的异步模型
    • JavaScript 柯里化与函数组合:了解 lodash 的 curry、compose、pipe 等函数式编程工具的实现原理
    • Deep JavaScript - Dr. Axel Rauschmayerexploringjs.com — 涵盖 JavaScript 函数的所有高级主题
  • TC39 提案:关注 decorators、record & tuple 等未来函数相关提案的发展
  • 函数式编程风格:学习纯函数、高阶函数、Compose、Functor、Monad 等函数式概念在 JavaScript 中的实践

总结

JavaScript 函数是这门语言最强大、最灵活的特性之一。从基础的函数声明与调用,到箭头函数带来的简洁语法,再到闭包、高阶函数、回调、异步函数、生成器等高级特性,构成了一个完整且深邃的函数体系。掌握这些概念,不仅是写出优雅 JavaScript 代码的前提,也为学习 TypeScript、React、Node.js 等现代框架与运行时技术打下不可或缺的根基。多动手实践,多在控制台中探索函数的运行机制,你会逐渐发现 JavaScript 函数的魅力所在。