8JavaScript高级程序设计(第4版)

函数

代码复用与抽象

函数定义参数闭包箭头函数

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第8章:函数(Functions)

一、导读

1.1 本章学习目标

第8章深入讲解JavaScript的函数。本章核心目标:

  • 掌握函数的各种定义方式:声明、表达式、箭头函数
  • 理解闭包:函数和作用域的结合
  • 掌握this关键字:在不同上下文中的指向
  • 学会使用call、apply、bind
  • 理解高阶函数

1.2 为什么本章如此重要

函数是JavaScript中最重要的特性。JavaScript的函数是"一等公民",可以像值一样传递和使用。闭包是JavaScript最强大的特性之一,理解闭包是掌握JavaScript的关键。

1.3 核心问题引导

函数声明和函数表达式有什么区别?

箭头函数和普通函数有什么本质区别?

闭包是什么?有什么实际应用?

this在箭头函数和普通函数中有什么不同?


二、核心概念详解

8.1 函数定义

javascript
// 函数声明(有提升)
function add(a, b) {
  return a + b;
}

// 函数表达式
const multiply = function(a, b) {
  return a * b;
};

// 箭头函数
const subtract = (a, b) => a - b;
const square = x => x * x;

// 默认参数
function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

// 剩余参数
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4));  // 10

8.2 闭包

javascript
// 闭包:函数记住并访问其词法作用域
function counter() {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  };
}

const c = counter();
c.increment();
c.increment();
console.log(c.getCount());  // 2

// 闭包实现私有变量
function createBankAccount(initialBalance) {
  let balance = initialBalance;
  
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) throw new Error("Insufficient funds");
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(1000);
account.deposit(500);
console.log(account.getBalance());  // 1500

// 闭包在循环中的应用
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);  // 0, 1, 2(let有块级作用域)
}

// 使用IIFE创建模块
const module = (function() {
  let privateVar = 0;
  
  return {
    increment() { privateVar++; },
    getValue() { return privateVar; }
  };
})();

8.3 this关键字

javascript
// 全局上下文
console.log(this);  // 浏览器中是window

// 对象方法
let obj = {
  name: "Alice",
  greet() {
    console.log(`Hello, ${this.name}`);
  },
  greetArrow: () => {
    console.log(`Hello, ${this.name}`);  // this指向外层
  }
};
obj.greet();       // "Hello, Alice"
obj.greetArrow();  // "Hello, undefined"

// call, apply, bind
function introduce(greeting) {
  console.log(`${greeting}, I'm ${this.name}`);
}

let person = {name: "Bob"};
introduce.call(person, "Hi");       // "Hi, I'm Bob"
introduce.apply(person, ["Hey"]);   // "Hey, I'm Bob"

let boundIntroduce = introduce.bind(person);
boundIntroduce("Hello");  // "Hello, I'm Bob"

// 在事件处理中
class Button {
  constructor(element) {
    this.clickCount = 0;
    element.addEventListener('click', () => {
      this.clickCount++;  // 箭头函数保持this
    });
  }
}

8.4 高阶函数

javascript
// 接受函数作为参数
function applyOperation(a, b, operation) {
  return operation(a, b);
}
console.log(applyOperation(5, 3, (a, b) => a + b));  // 8

// 返回函数
function multiplier(factor) {
  return function(x) {
    return x * factor;
  };
}
const triple = multiplier(3);
console.log(triple(5));  // 15

// 函数组合
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

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

const transform = pipe(addOne, double, square);
console.log(transform(3));  // (3+1)*2=8, 8*8=64

// 柯里化
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...args2) {
      return curried.apply(this, [...args, ...args2]);
    };
  };
}

const curriedAdd = curry((a, b, c) => a + b + c);
console.log(curriedAdd(1)(2)(3));  // 6
console.log(curriedAdd(1, 2)(3));  // 6

三、实战案例

3.1 函数式数据处理

javascript
const data = [
  {name: "Alice", department: "Engineering", salary: 90000},
  {name: "Bob", department: "Marketing", salary: 75000},
  {name: "Charlie", department: "Engineering", salary: 95000},
  {name: "David", department: "Marketing", salary: 80000},
  {name: "Eve", department: "Engineering", salary: 85000}
];

// 函数式数据处理管道
const result = data
  .filter(emp => emp.department === "Engineering")
  .map(emp => ({...emp, bonus: emp.salary * 0.1}))
  .sort((a, b) => b.salary - a.salary);

console.log(result);

// 分组函数
function groupBy(arr, keyFn) {
  return arr.reduce((acc, item) => {
    const key = keyFn(item);
    (acc[key] = acc[key] || []).push(item);
    return acc;
  }, {});
}

const byDept = groupBy(data, emp => emp.department);
console.log(byDept);

四、本章小结

第8章深入讲解了JavaScript函数:定义方式、闭包、this关键字、call/apply/bind、高阶函数。函数是JavaScript的核心,闭包是最强大的特性。


五、思考与练习

思考题

函数声明的提升和变量的提升有什么区别?

闭包会导致内存泄漏吗?如何避免?

箭头函数没有自己的this,那它的this是什么?

什么是函数式编程?JavaScript支持函数式编程吗?

编程练习

实现一个memoize函数(带缓存的函数)。

实现一个debounce函数(防抖)。

使用闭包实现一个简单的状态机。


8/14函数
0%