====== 第四章 函数 ====== ===== 4.1 函数概述 ===== 函数是JavaScript中最重要的概念之一。函数是可重复使用的代码块,用于执行特定任务。JavaScript中的函数是**一等公民**(First-class Citizen),这意味着函数可以: * 赋值给变量 * 作为参数传递给其他函数 * 作为其他函数的返回值 * 存储在数据结构中 ===== 4.2 函数定义方式 ===== ==== 4.2.1 函数声明(Function Declaration) ==== // 函数声明语法 function greet(name) { return "Hello, " + name + "!"; } // 调用函数 console.log(greet("Alice")); // "Hello, Alice!" // 函数声明会被提升(hoisting) console.log(sayHi()); // "Hi!"(可以在声明前调用) function sayHi() { return "Hi!"; } **函数声明的特点**: * 有函数名 * 会被提升到作用域顶部 * 不能在语句块中使用(在非严格模式下可以,但不推荐) ==== 4.2.2 函数表达式(Function Expression) ==== // 匿名函数表达式 const greet = function(name) { return "Hello, " + name + "!"; }; // 命名函数表达式(函数名在函数内部可用) const factorial = function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); // 在内部可以用fact调用自己 }; // 函数表达式不会被提升 // console.log(greet2()); // TypeError: greet2 is not a function const greet2 = function(name) { return "Hello!"; }; ==== 4.2.3 箭头函数(Arrow Function,ES6) ==== // 基本语法 const greet = (name) => { return "Hello, " + name + "!"; }; // 单参数可以省略括号 const double = x => { return x * 2; }; // 单表达式可以省略花括号和return(隐式返回) const triple = x => x * 3; // 多参数 const add = (a, b) => a + b; // 返回对象需要括号 const createUser = (name, age) => ({ name, age }); // 无参数 const getTime = () => new Date().toLocaleTimeString(); **箭头函数的特点**: * 语法简洁 * 没有自己的this、arguments、super或new.target * 不能用作构造函数 * 适合回调函数和简单的函数表达式 ==== 4.2.4 Function构造函数 ==== // 使用Function构造函数(不推荐) const add = new Function('a', 'b', 'return a + b'); console.log(add(2, 3)); // 5 // 等同于 const add2 = function(a, b) { return a + b; }; 不推荐使用的理由: * 性能较差(需要动态解析) * 安全性问题(如果参数来自用户输入) * 代码可读性差 ===== 4.3 函数参数 ===== ==== 4.3.1 形参与实参 ==== // 形参(形式参数):函数定义时的参数 function greet(name, greeting) { return `${greeting}, ${name}!`; } // 实参(实际参数):函数调用时传入的参数 greet("Alice", "Hello"); // "Hello, Alice!" // 实参多于形参 function sum(a, b) { console.log(arguments); // [3, 5, 7, 9] return a + b; // 只使用前两个参数 } sum(3, 5, 7, 9); // 8 // 实参少于形参 function greet(name, greeting) { return `${greeting || "Hello"}, ${name}!`; } greet("Bob"); // "Hello, Bob!"(greeting是undefined) ==== 4.3.2 默认参数(ES6) ==== // 基本默认参数 function greet(name, greeting = "Hello") { return `${greeting}, ${name}!`; } greet("Alice"); // "Hello, Alice!" greet("Bob", "Hi"); // "Hi, Bob!" // 默认参数可以是表达式 function getTimestamp() { return Date.now(); } function log(message, timestamp = getTimestamp()) { console.log(`[${timestamp}] ${message}`); } // 默认参数可以引用前面的参数 function createUser(name, role = "user", fullName = `${role}: ${name}`) { return { name, role, fullName }; } // 默认参数与解构结合 function createPoint({ x = 0, y = 0 } = {}) { return { x, y }; } createPoint(); // { x: 0, y: 0 } createPoint({ x: 5 }); // { x: 5, y: 0 } ==== 4.3.3 剩余参数(Rest Parameters,ES6) ==== // 使用剩余参数收集所有额外参数 function sum(first, ...numbers) { console.log(first); // 第一个参数 console.log(numbers); // 其余参数组成的数组 return numbers.reduce((a, b) => a + b, first); } sum(1, 2, 3, 4, 5); // 15 // 剩余参数必须是最后一个参数 function process(a, ...rest, b) { } // SyntaxError // 与展开运算符的区别 function multiply(multiplier, ...numbers) { return numbers.map(n => n * multiplier); } multiply(2, 1, 2, 3); // [2, 4, 6] ==== 4.3.4 arguments对象 ==== // 传统方式:使用arguments对象 function sum() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; } sum(1, 2, 3, 4, 5); // 15 // arguments是类数组对象 function test() { console.log(arguments.length); // 3 console.log(arguments[0]); // "a" console.log(typeof arguments); // "object" console.log(Array.isArray(arguments)); // false } test("a", "b", "c"); // 将arguments转为数组 function oldStyle() { const args = Array.prototype.slice.call(arguments); // 或使用ES6方式 const args2 = [...arguments]; // 或使用Array.from const args3 = Array.from(arguments); } **箭头函数与arguments**: // 箭头函数没有自己的arguments const arrow = () => { console.log(arguments); // 引用外部函数的arguments }; // 在箭头函数中获取参数 const arrow2 = (...args) => { console.log(args); // 真正的数组 }; ==== 4.3.5 参数解构 ==== // 对象解构 function createUser({ name, age, email = "N/A" }) { return { name, age, email }; } createUser({ name: "Alice", age: 25 }); // { name: "Alice", age: 25, email: "N/A" } // 数组解构 function getCoordinates([x, y, z = 0]) { return { x, y, z }; } getCoordinates([10, 20]); // { x: 10, y: 20, z: 0 } getCoordinates([10, 20, 30]); // { x: 10, y: 20, z: 30 } // 嵌套解构 function displayPerson({ name, address: { city, country } }) { console.log(`${name} lives in ${city}, ${country}`); } displayPerson({ name: "Bob", address: { city: "北京", country: "中国" } }); ===== 4.4 函数返回值 ===== ==== 4.4.1 return语句 ==== // 返回单个值 function add(a, b) { return a + b; } // 无返回值(返回undefined) function log(message) { console.log(message); // 隐式返回undefined } // 显式返回undefined function doNothing() { return; } // 提前返回 function divide(a, b) { if (b === 0) { return null; // 提前返回 } return a / b; } ==== 4.4.2 返回多个值 ==== // 使用数组返回多个值 function getMinMax(arr) { return [Math.min(...arr), Math.max(...arr)]; } const [min, max] = getMinMax([3, 1, 4, 1, 5]); // 使用对象返回多个值(推荐,更清晰) function getUserStats(user) { return { name: user.name, age: calculateAge(user.birthDate), membershipDays: getMembershipDays(user.joinDate) }; } const { name, age } = getUserStats(user); ===== 4.5 箭头函数详解 ===== ==== 4.5.1 箭头函数与普通函数的区别 ==== | 特性 | 普通函数 | 箭头函数 | |------|----------|----------| | this | 动态绑定 | 词法绑定(继承外层) | | arguments | 有自己的arguments | 没有,使用...args | | 构造函数 | 可以 | 不可以 | | prototype | 有 | 没有 | | 简写语法 | 无 | 支持 | ==== 4.5.2 this的词法绑定 ==== // 普通函数的this问题 const obj = { name: "Alice", friends: ["Bob", "Carol"], greetFriends: function() { this.friends.forEach(function(friend) { // 这里的this不是obj! // console.log(this.name + " says hi to " + friend); // undefined }); } }; // 传统解决方案:保存this const obj2 = { name: "Alice", friends: ["Bob", "Carol"], greetFriends: function() { const self = this; this.friends.forEach(function(friend) { console.log(self.name + " says hi to " + friend); }); } }; // 使用bind const obj3 = { name: "Alice", friends: ["Bob", "Carol"], greetFriends: function() { this.friends.forEach(function(friend) { console.log(this.name + " says hi to " + friend); }.bind(this)); } }; // 箭头函数解决方案(推荐) const obj4 = { name: "Alice", friends: ["Bob", "Carol"], greetFriends: function() { this.friends.forEach(friend => { console.log(this.name + " says hi to " + friend); }); } }; ==== 4.5.3 箭头函数的适用场景 ==== // 1. 回调函数 const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); const evens = numbers.filter(n => n % 2 === 0); const sum = numbers.reduce((a, b) => a + b, 0); // 2. 链式调用 const result = users .filter(u => u.active) .map(u => u.name) .sort((a, b) => a.localeCompare(b)); // 3. Promise回调 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)); // 4. 对象字面量中的方法(注意this问题) const counter = { count: 0, // 箭头函数在这里不合适,因为this不会指向counter increment: () => { this.count++; // 错误!this不是counter }, // 应该使用普通函数或方法简写 incrementCorrect() { this.count++; } }; ===== 4.6 高阶函数 ===== 高阶函数是接收函数作为参数或返回函数的函数。 ==== 4.6.1 回调函数 ==== // 异步回调 setTimeout(() => { console.log("1秒后执行"); }, 1000); // 数组方法回调 [1, 2, 3].forEach(item => console.log(item)); // 自定义高阶函数 function processData(data, callback) { const result = data.map(item => item * 2); callback(result); } processData([1, 2, 3], result => { console.log(result); // [2, 4, 6] }); ==== 4.6.2 函数作为返回值(闭包) ==== // 创建计数器 function createCounter() { let count = 0; return function() { return ++count; }; } const counter1 = createCounter(); const counter2 = createCounter(); console.log(counter1()); // 1 console.log(counter1()); // 2 console.log(counter2()); // 1(独立的计数器) // 创建乘法函数 function createMultiplier(factor) { return function(number) { return number * factor; }; } const double = createMultiplier(2); const triple = createMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15 ==== 4.6.3 函数组合 ==== // 简单组合 const compose = (f, g) => x => f(g(x)); const add1 = x => x + 1; const double = x => x * 2; const add1ThenDouble = compose(double, add1); console.log(add1ThenDouble(5)); // 12((5 + 1) * 2) // 多函数组合 const composeMany = (...fns) => x => fns.reduceRight((v, f) => f(v), x); const pipeline = composeMany( x => x + 1, x => x * 2, x => x - 3 ); console.log(pipeline(5)); // 5(((5 - 3) * 2) + 1) // 管道(从左到右) const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); ==== 4.6.4 偏函数和柯里化 ==== // 偏函数:固定部分参数 function partial(fn, ...fixedArgs) { return function(...remainingArgs) { return fn(...fixedArgs, ...remainingArgs); }; } function greet(greeting, name) { return `${greeting}, ${name}!`; } const sayHello = partial(greet, "Hello"); console.log(sayHello("Alice")); // "Hello, Alice!" // 柯里化:将多参数函数转为单参数链 function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); }; } }; } function sum(a, b, c) { return a + b + c; } const curriedSum = curry(sum); console.log(curriedSum(1)(2)(3)); // 6 console.log(curriedSum(1, 2)(3)); // 6 console.log(curriedSum(1)(2, 3)); // 6 ===== 4.7 递归函数 ===== 函数调用自身的编程技巧。 // 阶乘 function factorial(n) { if (n <= 1) return 1; return n * factorial(n - 1); } // 斐波那契数列 function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } // 数组求和 function sumArray(arr) { if (arr.length === 0) return 0; return arr[0] + sumArray(arr.slice(1)); } // 深拷贝 function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj); } if (Array.isArray(obj)) { return obj.map(item => deepClone(item)); } const cloned = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]); } } return cloned; } // 尾递归优化(ES6支持,但浏览器实现不一) function factorialTail(n, acc = 1) { if (n <= 1) return acc; return factorialTail(n - 1, n * acc); } ===== 4.8 立即执行函数(IIFE) ===== // 基本IIFE (function() { console.log("立即执行"); })(); // 带参数的IIFE (function(name) { console.log("Hello, " + name); })("World"); // 箭头函数版本 (() => { console.log("IIFE with arrow"); })(); // IIFE的使用场景:创建私有作用域 const counter = (function() { let count = 0; return { increment() { return ++count; }, decrement() { return --count; }, getCount() { return count; } }; })(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.getCount()); // 2 // count变量无法从外部直接访问 // 模块化模式(ES6模块之前) var myModule = (function() { var privateVar = "private"; function privateMethod() { return privateVar; } return { publicMethod: function() { return privateMethod(); } }; })(); ===== 4.9 函数式编程基础 ===== ==== 4.9.1 纯函数 ==== // 纯函数:相同输入始终产生相同输出,无副作用 function add(a, b) { return a + b; } // 非纯函数:依赖外部状态 let count = 0; function increment() { return ++count; // 修改外部状态 } // 非纯函数:产生副作用 function logMessage(msg) { console.log(msg); // IO操作 } // 非纯函数:依赖外部可变数据 const users = []; function addUser(user) { users.push(user); // 修改外部数据 return users; } ==== 4.9.2 不可变性 ==== // 可变操作(不推荐) const arr = [1, 2, 3]; arr.push(4); // 修改原数组 // 不可变操作(推荐) const newArr = [...arr, 4]; // 对象不可变更新 const user = { name: "Alice", age: 25 }; const updatedUser = { ...user, age: 26 }; // 数组方法对比 const numbers = [1, 2, 3]; // 可变 numbers.push(4); // 修改原数组 numbers.splice(1, 1); // 修改原数组 // 不可变 const newNumbers = [...numbers, 4]; const filtered = numbers.filter(n => n !== 2); const mapped = numbers.map(n => n * 2); ===== 4.10 练习题 ===== ==== 练习1:函数定义 ==== 编写以下函数的不同定义方式: // 1. 使用函数声明定义一个计算圆面积的函数 // 2. 使用函数表达式定义一个检查字符串是否为回文的函数 // 3. 使用箭头函数定义一个计算数组平均值的函数 // 4. 将上述函数改写为使用默认参数和剩余参数 ==== 练习2:高阶函数 ==== // 1. 实现一个map函数(模拟Array.prototype.map) function myMap(array, callback) { // 你的代码 } // 2. 实现一个filter函数 function myFilter(array, callback) { // 你的代码 } // 3. 实现一个once函数,确保函数只执行一次 function once(fn) { // 你的代码 } // 4. 实现一个memoize函数,缓存函数结果 function memoize(fn) { // 你的代码 } ==== 练习3:递归 ==== // 1. 使用递归实现数组扁平化 function flatten(arr) { // 你的代码 } // flatten([1, [2, [3, 4]], 5]) => [1, 2, 3, 4, 5] // 2. 使用递归实现对象属性路径获取 function get(obj, path) { // 你的代码 } // get({ a: { b: { c: 1 } } }, 'a.b.c') => 1 // 3. 实现快速排序 function quickSort(arr) { // 你的代码 } ==== 练习4:综合应用 ==== 实现一个函数组合管道,能够: // 1. 创建一个管道函数,可以链式组合多个函数 const pipeline = createPipeline( x => x + 1, x => x * 2, x => x.toString() ); pipeline(5); // "12" // 2. 实现一个节流(throttle)函数 function throttle(fn, delay) { // 你的代码 } // 3. 实现一个防抖(debounce)函数 function debounce(fn, delay) { // 你的代码 } ===== 参考答案 ===== **练习1答案**: // 1. 函数声明 function circleArea(radius) { return Math.PI * radius ** 2; } // 2. 函数表达式 const isPalindrome = function(str) { const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, ''); return cleaned === cleaned.split('').reverse().join(''); }; // 3. 箭头函数 const average = arr => arr.reduce((a, b) => a + b, 0) / arr.length; // 4. 使用默认参数和剩余参数 const circleArea2 = (radius = 1) => Math.PI * radius ** 2; const sumAll = (first = 0, ...numbers) => numbers.reduce((a, b) => a + b, first); **练习2答案**: // 1. myMap function myMap(array, callback) { const result = []; for (let i = 0; i < array.length; i++) { result.push(callback(array[i], i, array)); } return result; } // 2. myFilter function myFilter(array, callback) { const result = []; for (let i = 0; i < array.length; i++) { if (callback(array[i], i, array)) { result.push(array[i]); } } return result; } // 3. once function once(fn) { let called = false; let result; return function(...args) { if (!called) { called = true; result = fn.apply(this, args); } return result; }; } // 4. memoize 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; }; } **练习3答案**: // 1. flatten function flatten(arr) { return arr.reduce((flat, item) => { return flat.concat(Array.isArray(item) ? flatten(item) : item); }, []); } // 迭代版本 function flattenIterative(arr) { const result = []; const stack = [...arr]; while (stack.length) { const item = stack.pop(); if (Array.isArray(item)) { stack.push(...item); } else { result.unshift(item); } } return result; } // 2. get function get(obj, path) { const keys = path.split('.'); let result = obj; for (const key of keys) { if (result === null || result === undefined) { return undefined; } result = result[key]; } return result; } // 递归版本 function getRecursive(obj, path) { const keys = path.split('.'); if (keys.length === 1) { return obj?.[keys[0]]; } return getRecursive(obj?.[keys[0]], keys.slice(1).join('.')); } // 3. quickSort function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[Math.floor(arr.length / 2)]; const left = arr.filter(x => x < pivot); const middle = arr.filter(x => x === pivot); const right = arr.filter(x => x > pivot); return [...quickSort(left), ...middle, ...quickSort(right)]; } **练习4答案**: // 1. createPipeline function createPipeline(...fns) { return function(input) { return fns.reduce((result, fn) => fn(result), input); }; } // 使用 const pipeline = createPipeline( x => x + 1, x => x * 2, x => x.toString() ); console.log(pipeline(5)); // "12" // 2. throttle function throttle(fn, delay) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= delay) { lastTime = now; return fn.apply(this, args); } }; } // 3. debounce function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; } ===== 本章小结 ===== 本章深入学习了JavaScript函数: * 四种函数定义方式:声明、表达式、箭头函数、Function构造器 * 灵活的参数处理:默认参数、剩余参数、解构 * 箭头函数的词法this绑定 * 高阶函数、回调、闭包、函数组合 * 递归与IIFE * 函数式编程基础概念 函数是JavaScript的核心,掌握函数的各种特性和模式对于编写高质量代码至关重要。 下一章将学习对象,这是JavaScript中最重要的数据类型。