====== 第四章 函数 ======
===== 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中最重要的数据类型。