JavaScript 函数
🎯 目标:2-3小时内掌握 JavaScript 函数核心,达到实战水平
目录
1. 核心概念与必须掌握的API
1.1 函数定义方式
函数声明 (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,继承外层作用域的this 
1.2 核心概念
参数 (Parameters)
function add(a, b = 0) { return a + b; } // b 有默认值
- 默认参数:
function fn(x = 1) {} - 剩余参数:
function fn(...args) {}将多余参数收集为数组 
返回值 (Return)
function double(n) { return n * 2; }
- 没有 
return语句时返回undefined return后的代码不会执行
作用域 (Scope) ⭐⭐⭐
let global = 'outside';
function test() {
  let local = 'inside'; // 只在函数内可见
  console.log(global);  // 可访问外层变量
}
- 函数创建独立作用域,内部可访问外部变量,反之不行
 - 闭包:内层函数访问外层函数变量的能力
 
this 关键字 ⭐⭐⭐
const obj = {
  name: 'Alice',
  greet: function() { console.log(this.name); } // this 指向 obj
};
- 普通函数:
this由调用方式决定 - 箭头函数:
this继承定义时的外层作用域 
高阶函数 (Higher-Order Function)
function map(arr, fn) { return arr.map(fn); }
- 接收函数作为参数或返回函数的函数
 - 常见:
map(),filter(),reduce() 
1.3 必须掌握的API
| API | 说明 | 示例 | 
|---|---|---|
call(thisArg, ...args) | 
改变 this 并立即调用函数 | 
fn.call(obj, 1, 2) | 
apply(thisArg, [args]) | 
同 call,但参数以数组形式传递 | 
fn.apply(obj, [1, 2]) | 
bind(thisArg, ...args) | 
返回绑定 this 的新函数,不立即执行 | 
const newFn = fn.bind(obj) | 
... 扩展运算符 | 
展开数组或收集参数 | fn(...[1, 2, 3]) | 
arguments 对象 | 
类数组对象,包含所有传入参数(箭头函数无) | function(){ console.log(arguments) } | 
2. 典型应用场景与设计取舍
2.1 应用场景
| 场景 | 最佳选择 | 原因 | 
|---|---|---|
| 事件处理器 | 箭头函数 | 不绑定 this,避免 this 丢失 | 
| 对象方法 | 普通函数 | 需要动态 this 指向对象 | 
| 回调函数 | 箭头函数 | 简洁,保持外层 this | 
| 构造函数 | 函数声明/表达式 | 箭头函数不能作为构造函数 | 
| 工具函数库 | 函数声明 | 可提升,模块化导出方便 | 
2.2 设计取舍
何时用箭头函数?
- ✅ 需要继承外层 
this(如 React 组件方法) - ✅ 简短的回调函数 (如 
map,filter) - ❌ 需要动态 
this的对象方法 - ❌ 需要 
arguments对象 
何时用普通函数?
- ✅ 对象方法需要访问 
this - ✅ 需要函数提升特性
 - ✅ 需要作为构造函数
 
命名函数 vs 匿名函数
- 命名函数便于调试(堆栈信息清晰)
 - 匿名函数适合一次性使用场景
 
3. 最小可运行示例
3.1 完整示例:任务管理器
/**
 * 任务管理器 - 综合演示函数核心概念
 * 运行环境:浏览器控制台或 Node.js
 */
// ===== 1. 函数声明:创建任务 =====
function createTask(title, priority = 'medium') {
  return {
    id: Date.now(),
    title,
    priority,
    completed: false
  };
}
// ===== 2. 箭头函数:任务过滤器 =====
const filterByPriority = (tasks, priority) => {
  return tasks.filter(task => task.priority === priority);
};
// ===== 3. 闭包:创建计数器 =====
function createCounter() {
  let count = 0; // 私有变量
  return {
    increment: () => ++count,
    getCount: () => count
  };
}
// ===== 4. 高阶函数:任务转换器 =====
function mapTasks(tasks, transformer) {
  return tasks.map(transformer);
}
// ===== 5. this 绑定:任务管理对象 =====
const taskManager = {
  tasks: [],
  // 普通函数:this 指向 taskManager
  addTask: function(title, priority) {
    const task = createTask(title, priority);
    this.tasks.push(task);
    console.log(`✅ 已添加: ${title}`);
  },
  // 箭头函数作为方法(演示陷阱)
  // 错误示例: getTasks: () => this.tasks  // this 不指向 taskManager!
  getTasks: function() {
    return this.tasks;
  },
  // 使用剩余参数
  addMultiple: function(...titles) {
    titles.forEach(title => this.addTask(title, 'low'));
  }
};
// ===== 6. 默认参数与解构 =====
function printTask({ title, priority = 'unknown', completed = false }) {
  const status = completed ? '✓' : '○';
  console.log(`${status} [${priority}] ${title}`);
}
// ===== 运行示例 =====
console.log('=== 任务管理器示例 ===\n');
// 添加任务
taskManager.addTask('学习 JavaScript 函数', 'high');
taskManager.addTask('完成作业');
taskManager.addMultiple('买菜', '运动', '读书');
// 获取所有任务
const allTasks = taskManager.getTasks();
console.log('\n📋 所有任务:');
allTasks.forEach(printTask);
// 过滤高优先级任务
const highPriority = filterByPriority(allTasks, 'high');
console.log('\n🔥 高优先级任务:');
highPriority.forEach(printTask);
// 使用高阶函数转换
const taskTitles = mapTasks(allTasks, task => task.title);
console.log('\n📝 任务标题列表:', taskTitles);
// 闭包计数器
const counter = createCounter();
console.log('\n🔢 计数器演示:');
console.log('计数:', counter.increment()); // 1
console.log('计数:', counter.increment()); // 2
console.log('当前值:', counter.getCount()); // 2
// ===== 7. call/apply/bind 演示 =====
const task = createTask('重要任务', 'high');
function announceTask(prefix) {
  console.log(`${prefix}: ${this.title} (${this.priority})`);
}
console.log('\n🎯 this 绑定演示:');
announceTask.call(task, '提醒');           // call: 逐个传参
announceTask.apply(task, ['警告']);         // apply: 数组传参
const boundFn = announceTask.bind(task);    // bind: 返回新函数
boundFn('绑定调用');
3.2 运行步骤
浏览器环境:
- 打开浏览器开发者工具 (F12)
 - 切换到 Console 面板
 - 复制粘贴代码,回车运行
 
Node.js 环境:
# 保存为 functions.js
node functions.js
预期输出:
=== 任务管理器示例 ===
✅ 已添加: 学习 JavaScript 函数
✅ 已添加: 完成作业
✅ 已添加: 买菜
✅ 已添加: 运动
✅ 已添加: 读书
📋 所有任务:
○ [high] 学习 JavaScript 函数
○ [medium] 完成作业
○ [low] 买菜
○ [low] 运动
○ [low] 读书
🔥 高优先级任务:
○ [high] 学习 JavaScript 函数
📝 任务标题列表: [ '学习 JavaScript 函数', '完成作业', '买菜', '运动', '读书' ]
🔢 计数器演示:
计数: 1
计数: 2
当前值: 2
🎯 this 绑定演示:
提醒: 重要任务 (high)
警告: 重要任务 (high)
绑定调用: 重要任务 (high)
4. 常见坑与调试技巧
4.1 常见陷阱
坑1: 箭头函数的 this 陷阱
// ❌ 错误
const obj = {
  name: 'Alice',
  greet: () => console.log(this.name) // this 不指向 obj!
};
obj.greet(); // undefined
// ✅ 正确
const obj2 = {
  name: 'Alice',
  greet: function() { console.log(this.name); }
};
obj2.greet(); // 'Alice'
坑2: 回调函数丢失 this
const user = {
  name: 'Bob',
  friends: ['Alice', 'Charlie'],
  printFriends: function() {
    // ❌ 错误
    this.friends.forEach(function(friend) {
      console.log(this.name + ' knows ' + friend); // this 是 undefined!
    });
    // ✅ 方法1: 箭头函数
    this.friends.forEach(friend => {
      console.log(this.name + ' knows ' + friend);
    });
    // ✅ 方法2: bind
    this.friends.forEach(function(friend) {
      console.log(this.name + ' knows ' + friend);
    }.bind(this));
  }
};
坑3: 闭包的变量陷阱
// ❌ 错误:循环中的闭包
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出 3 次 3
  }, 100);
}
// ✅ 方法1: 使用 let
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出 0, 1, 2
  }, 100);
}
// ✅ 方法2: IIFE
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 输出 0, 1, 2
    }, 100);
  })(i);
}
坑4: 默认参数的求值时机
let x = 1;
function fn(a = x) {
  let x = 2;
  console.log(a); // 1 (默认参数在函数作用域外求值)
}
fn();
4.2 调试技巧
技巧1: 使用命名函数便于调试
// ❌ 匿名函数:堆栈信息显示 'anonymous'
const data = [1, 2, 3].map(function(x) { 
  throw new Error('test');
});
// ✅ 命名函数:堆栈信息显示 'doubleValue'
const data = [1, 2, 3].map(function doubleValue(x) {
  throw new Error('test');
});
技巧2: 检查函数类型
function isArrowFunction(fn) {
  return !fn.prototype; // 箭头函数没有 prototype
}
const regular = function() {};
const arrow = () => {};
console.log(isArrowFunction(regular)); // false
console.log(isArrowFunction(arrow));   // true
技巧3: 使用 console.trace() 追踪调用栈
function outer() {
  inner();
}
function inner() {
  console.trace('查看调用栈');
}
outer();
技巧4: 参数校验
function divide(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('参数必须是数字');
  }
  if (b === 0) {
    throw new Error('除数不能为零');
  }
  return a / b;
}
5. 分层练习清单
5.1 入门级练习 (30分钟)
练习1: 温度转换器
// 要求:
// 1. 编写 celsiusToFahrenheit 函数(摄氏转华氏)
// 2. 编写 fahrenheitToCelsius 函数(华氏转摄氏)
// 3. 使用默认参数,默认转换 0 度
// 提示:
// 华氏 = 摄氏 * 9/5 + 32
// 摄氏 = (华氏 - 32) * 5/9
练习2: 数组操作
// 要求:
// 1. 使用箭头函数编写 double 函数(数组每项乘2)
// 2. 使用箭头函数编写 filterEven 函数(过滤偶数)
// 3. 测试: [1, 2, 3, 4, 5]
// 提示: 使用 map 和 filter
练习3: 对象方法
// 要求:
// 创建 calculator 对象,包含:
// - add(a, b)
// - subtract(a, b)
// - history 数组记录操作
// - printHistory() 打印历史
// 注意: 使用普通函数确保 this 正确
5.2 巩固级练习 (45分钟)
练习4: 购物车系统
// 要求:
// 1. 创建购物车对象,包含商品数组
// 2. addItem(name, price, quantity) 添加商品
// 3. removeItem(name) 删除商品
// 4. getTotal() 计算总价
// 5. applyDiscount(percent) 应用折扣(返回新函数,闭包实现)
// 测试:
// cart.addItem('苹果', 5, 3);
// cart.addItem('香蕉', 3, 5);
// const discounted = cart.applyDiscount(10); // 9折
// console.log(discounted());
练习5: 防抖函数 (Debounce)
// 要求:
// 实现 debounce(fn, delay) 函数
// - 延迟执行 fn
// - 如果在 delay 时间内再次调用,重置计时器
// 提示:
// 使用闭包保存 timer
// 使用 clearTimeout 和 setTimeout
// 测试:
// const search = debounce(() => console.log('搜索...'), 500);
// search(); search(); search(); // 只执行一次
练习6: 数组去重
// 要求:
// 1. 使用 filter 实现 unique(arr)
// 2. 使用 Set 实现 uniqueSet(arr)
// 3. 比较两种方法性能
// 测试: [1, 2, 2, 3, 3, 3, 4]
5.3 进阶级练习 (45分钟)
练习7: 函数柯里化 (Curry)
// 要求:
// 实现 curry(fn) 函数,将多参数函数转为单参数函数链
// 示例:
// function add(a, b, c) { return a + b + c; }
// const curriedAdd = curry(add);
// console.log(curriedAdd(1)(2)(3)); // 6
// console.log(curriedAdd(1, 2)(3)); // 6
// 提示: 使用闭包和递归
练习8: Promise 重试机制
// 要求:
// 实现 retry(fn, times) 函数
// - fn 返回 Promise
// - 失败时重试 times 次
// - 所有重试失败后抛出错误
// 测试:
// let count = 0;
// const flaky = () => {
//   count++;
//   return count < 3 ? Promise.reject('失败') : Promise.resolve('成功');
// };
// retry(flaky, 5).then(console.log); // '成功'
练习9: 函数组合 (Compose)
// 要求:
// 实现 compose(...fns) 函数,从右到左执行函数
// 示例:
// const addOne = x => x + 1;
// const double = x => x * 2;
// const square = x => x * x;
// const result = compose(square, double, addOne)(2);
// console.log(result); // 36  即: ((2+1)*2)^2
// 提示: 使用 reduce 或 reduceRight
6. 自测清单与闭卷题
6.1 自测清单
在开始测试前,确保你能回答以下问题:
- [ ] 能说出函数声明和函数表达式的区别
 - [ ] 理解箭头函数的 
this继承规则 - [ ] 能解释闭包的原理和应用场景
 - [ ] 掌握 
call、apply、bind的区别 - [ ] 理解高阶函数的概念
 - [ ] 能正确使用默认参数和剩余参数
 - [ ] 知道何时使用箭头函数,何时使用普通函数
 - [ ] 能识别和避免常见的 
this陷阱 - [ ] 理解函数作用域和变量提升
 - [ ] 能使用闭包实现私有变量
 
6.2 闭卷测试题
第1题:代码输出 (30分)
var name = 'Global';
const obj = {
  name: 'Object',
  method1: function() {
    console.log(this.name);
  },
  method2: () => {
    console.log(this.name);
  },
  method3: function() {
    const inner = () => {
      console.log(this.name);
    };
    inner();
  }
};
obj.method1();
obj.method2();
obj.method3();
const fn = obj.method1;
fn();
问题: 请写出每个 console.log 的输出结果,并解释原因。
第2题:实现函数 (40分)
// 实现一个 once 函数,确保传入的函数只执行一次
function once(fn) {
  // 在此实现
}
// 测试
const initialize = once(() => console.log('初始化'));
initialize(); // 输出: 初始化
initialize(); // 不输出
initialize(); // 不输出
要求:
- 使用闭包实现
 - 函数应该能接收参数并返回结果
 - 多次调用返回第一次的结果
 
第3题:代码分析与修复 (30分)
function createButtons() {
  const buttons = [];
  for (var i = 0; i < 3; i++) {
    buttons.push({
      click: function() {
        console.log('Button ' + i);
      }
    });
  }
  return buttons;
}
const buttons = createButtons();
buttons[0].click(); // 期望: Button 0, 实际: ?
buttons[1].click(); // 期望: Button 1, 实际: ?
buttons[2].click(); // 期望: Button 2, 实际: ?
问题:
- 实际输出是什么?为什么?
 - 提供至少两种修复方法
 
6.3 标准答案
第1题答案:
obj.method1();  // 输出: 'Object'
// 原因: 普通函数,this 指向调用对象 obj
obj.method2();  // 输出: 'Global' (浏览器) 或 undefined (严格模式)
// 原因: 箭头函数继承外层 this,此处是全局对象
obj.method3();  // 输出: 'Object'
// 原因: 箭头函数 inner 继承 method3 的 this,指向 obj
const fn = obj.method1;
fn();  // 输出: 'Global' (浏览器) 或 undefined (严格模式)
// 原因: 普通函数调用,this 指向全局对象(非严格)或 undefined(严格)
评分标准:
- 每个输出正确 3分 (共15分)
 - 每个解释正确 3分 (共15分)
 
第2题答案:
function once(fn) {
  let called = false;  // 标记是否已调用
  let result;          // 缓存结果
  return function(...args) {
    if (!called) {
      called = true;
      result = fn.apply(this, args);  // 保持 this 和参数
    }
    return result;
  };
}
// 或使用箭头函数(不考虑 this)
function once(fn) {
  let called = false;
  let result;
  return (...args) => {
    if (!called) {
      called = true;
      result = fn(...args);
    }
    return result;
  };
}
评分标准:
- 使用闭包保存状态 (10分)
 - 正确判断首次调用 (10分)
 - 缓存并返回结果 (10分)
 - 正确传递参数 (10分)
 
第3题答案:
1. 实际输出:
buttons[0].click(); // 输出: Button 3
buttons[1].click(); // 输出: Button 3
buttons[2].click(); // 输出: Button 3
原因: var 声明的 i 是函数作用域,循环结束后 i = 3,所有闭包共享同一个 i。
2. 修复方法:
方法1: 使用 let
function createButtons() {
  const buttons = [];
  for (let i = 0; i < 3; i++) {  // 改用 let
    buttons.push({
      click: function() {
        console.log('Button ' + i);
      }
    });
  }
  return buttons;
}
方法2: IIFE (立即执行函数)
function createButtons() {
  const buttons = [];
  for (var i = 0; i < 3; i++) {
    (function(j) {  // 创建新作用域
      buttons.push({
        click: function() {
          console.log('Button ' + j);
        }
      });
    })(i);
  }
  return buttons;
}
方法3: 箭头函数 + forEach
function createButtons() {
  return [0, 1, 2].map(i => ({
    click: () => console.log('Button ' + i)
  }));
}
评分标准:
- 正确分析输出 (5分)
 - 解释原因 (10分)
 - 每个修复方法 (7.5分,共15分)
 
📚 推荐资源
- 
官方文档:
 - 
进阶阅读:
- 《你不知道的JavaScript(上卷)》- 作用域与闭包
 - 《JavaScript高级程序设计》- 第10章(函数)
 
 - 
在线练习:
 
🎯 学习检查点
完成本教程后,你应该能够:
- ✅ 在实际项目中正确选择函数定义方式
 - ✅ 理解并避免 
this绑定陷阱 - ✅ 使用闭包实现数据封装和高级功能
 - ✅ 编写清晰、可维护的函数式代码
 - ✅ 调试函数相关的常见问题
 
下一步学习方向:
- 异步函数 (async/await)
 - 函数式编程范式
 - 设计模式中的函数应用
 
祝学习顺利! 🚀
