javascript:第四章函数
目录
第四章 函数
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中最重要的数据类型。
javascript/第四章函数.txt · 最后更改: 由 127.0.0.1
