跳到主要内容

JavaScript 函数

函数是组织代码的基本单元,可以提高代码的复用性和可读性。

函数声明

函数声明(Function Declaration)

// 基本语法
function greet() {
console.log("你好!");
}

// 调用函数
greet(); // 输出:你好!

// 带参数的函数
function greet(name) {
console.log("你好," + name + "!");
}

greet("张三"); // 输出:你好,张三!

函数表达式(Function Expression)

// 将函数赋值给变量
const greet = function(name) {
console.log("你好," + name + "!");
};

greet("张三"); // 输出:你好,张三!

// 函数表达式可以添加函数名(用于递归)
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};

console.log(factorial(5)); // 120

箭头函数(Arrow Function,ES6)

// 基本语法
const greet = (name) => {
console.log("你好," + name + "!");
};

// 单参数可以省略括号
const greet = name => {
console.log("你好," + name + "!");
};

// 单行函数体可以省略大括号和 return
const add = (a, b) => a + b;

// 返回对象字面量需要用括号包裹
const createPerson = (name, age) => ({
name: name,
age: age
});

箭头函数与普通函数的区别

  1. 没有自己的 this
  2. 没有 arguments 对象
  3. 不能用作构造函数
  4. 没有 prototype 属性
// 箭头函数没有自己的 this
const obj = {
name: "张三",
// 普通函数
sayHello: function() {
console.log("你好," + this.name);
},
// 箭头函数
sayHi: () => {
// 这里的 this 指向外部作用域
console.log("你好," + this.name);
}
};

obj.sayHello(); // 你好,张三
obj.sayHi(); // 你好,undefined(箭头函数的 this 不会绑定)

函数参数

默认参数

function greet(name = "游客", greeting = "你好") {
console.log(greeting + "," + name + "!");
}

greet(); // 你好,游客!
greet("张三"); // 你好,张三!
greet("张三", "欢迎"); // 欢迎,张三!

剩余参数(Rest Parameters)

使用 ... 收集剩余参数:

function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

// 与其他参数配合
function multiply(factor, ...numbers) {
return numbers.map(num => num * factor);
}

console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]

arguments 对象

在普通函数中访问所有参数:

function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}

console.log(sum(1, 2, 3)); // 6

注意:箭头函数没有 arguments 对象。

参数解构

// 解构对象参数
function createUser({ name, age, city = "北京" }) {
console.log(`姓名:${name}`);
console.log(`年龄:${age}`);
console.log(`城市:${city}`);
}

createUser({ name: "张三", age: 20 });
// 姓名:张三
// 年龄:20
// 城市:北京

// 解构数组参数
function getScores([first, second, ...rest]) {
console.log(`第一名:${first}`);
console.log(`第二名:${second}`);
console.log(`其他:${rest}`);
}

getScores([95, 88, 76, 92, 85]);
// 第一名:95
// 第二名:88
// 其他:76,92,85

函数返回值

return 语句

function add(a, b) {
return a + b;
}

const result = add(3, 5);
console.log(result); // 8

返回多个值

// 使用数组
function getMinMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax([3, 1, 4, 1, 5, 9, 2, 6]);
console.log(`最小值:${min},最大值:${max}`);

// 使用对象
function getStats(numbers) {
return {
min: Math.min(...numbers),
max: Math.max(...numbers),
avg: numbers.reduce((a, b) => a + b) / numbers.length
};
}

const stats = getStats([3, 1, 4, 1, 5, 9, 2, 6]);
console.log(stats); // {min: 1, max: 9, avg: 3.875}

提前返回

function processUser(user) {
// 验证参数
if (!user) {
return null;
}

if (!user.name) {
return { error: "用户名不能为空" };
}

// 正常处理逻辑
return { success: true, user: user };
}

无返回值函数

没有 return 语句或 return 空值的函数返回 undefined:

function printHello() {
console.log("Hello");
}

console.log(printHello()); // undefined

函数调用

调用模式

// 作为函数调用
function greet() {
console.log(this);
}
greet(); // this 指向全局对象(浏览器中为 window)

// 作为方法调用
const obj = {
name: "张三",
greet() {
console.log(this.name);
}
};
obj.greet(); // this 指向 obj

// 作为构造函数调用
function Person(name) {
this.name = name;
}
const person = new Person("李四"); // this 指向新创建的对象

// 使用 call/apply/bind 调用
function greet() {
console.log(this.name);
}
const obj1 = { name: "张三" };
const obj2 = { name: "李四" };
greet.call(obj1); // 张三
greet.apply(obj2); // 李四
const boundGreet = greet.bind(obj1);
boundGreet(); // 张三

call、apply、bind

const person = {
name: "张三",
greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
};

const anotherPerson = { name: "李四" };

// call - 参数逐个传递
person.greet.call(anotherPerson, "Hello", "!"); // Hello, 李四!

// apply - 参数以数组传递
person.greet.apply(anotherPerson, ["Hi", "."]); // Hi, 李四.

// bind - 返回绑定 this 的新函数
const boundGreet = person.greet.bind(anotherPerson);
boundGreet("Hey", "?"); // Hey, 李四?

// 部分应用参数
const greetHello = person.greet.bind(anotherPerson, "Hello");
greetHello("~"); // Hello, 李四~

作用域

全局作用域

在函数外部声明的变量:

const globalVar = "全局变量";

function test() {
console.log(globalVar); // 可以访问
}

函数作用域

在函数内部声明的变量:

function test() {
const localVar = "局部变量";
console.log(localVar); // 可以访问
}
// console.log(localVar); // 错误:localVar 未定义

块级作用域(let/const)

使用 let 和 const 声明的变量只在当前块内有效:

{
let blockVar = "块级变量";
const CONSTANT = "常量";
}
// console.log(blockVar); // 错误
// console.log(CONSTANT); // 错误

if (true) {
let x = 10;
}
// console.log(x); // 错误

作用域链

const globalVar = "全局";

function outer() {
const outerVar = "外层";

function inner() {
const innerVar = "内层";
console.log(globalVar); // 全局
console.log(outerVar); // 外层
console.log(innerVar); // 内层
}

inner();
}

闭包

闭包是指函数可以访问其词法作用域之外的变量的能力。

基本闭包

function createCounter() {
let count = 0;

return function() {
count++;
return count;
};
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// 多个计数器互不影响
const counter2 = createCounter();
console.log(counter2()); // 1

闭包的应用

1. 数据私有化

function createBankAccount(initialBalance) {
let balance = initialBalance;

return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return `存款成功,当前余额:${balance}`;
}
return "存款金额必须为正数";
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return `取款成功,当前余额:${balance}`;
}
return "余额不足或金额无效";
},
getBalance() {
return balance;
}
};
}

const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // 存款成功,当前余额:1500
console.log(account.withdraw(200)); // 取款成功,当前余额:1300
console.log(account.balance); // undefined(无法直接访问)

2. 函数工厂

function multiply(factor) {
return function(number) {
return number * factor;
};
}

const double = multiply(2);
const triple = multiply(3);
const quadruple = multiply(4);

console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20

3. 延迟执行

function delayedLog(message, delay) {
setTimeout(() => {
console.log(message);
}, delay);
}

delayedLog("这条消息 2 秒后显示", 2000);

闭包的注意事项

// 常见错误:循环中的闭包
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出:3, 3, 3(var 没有块级作用域)
}, 100);
}

// 解决方法 1:使用 let(推荐)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出:0, 1, 2
}, 100);
}

// 解决方法 2:使用闭包
for (var i = 0; i < 3; i++) {
((index) => {
setTimeout(() => {
console.log(index); // 输出:0, 1, 2
}, 100);
})(i);
}

递归函数

函数调用自身:

// 阶乘
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

// 斐波那契数列
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 (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;
}

高阶函数

接受函数作为参数或返回函数的函数:

// 接受函数作为参数
function applyOperation(numbers, operation) {
return numbers.map(operation);
}

const numbers = [1, 2, 3, 4, 5];
console.log(applyOperation(numbers, x => x * 2)); // [2, 4, 6, 8, 10]
console.log(applyOperation(numbers, x => x ** 2)); // [1, 4, 9, 16, 25]

// 返回函数
function createGreeting(prefix) {
return function(name) {
return `${prefix}, ${name}!`;
};
}

const sayHello = createGreeting("Hello");
const sayHi = createGreeting("Hi");

console.log(sayHello("张三")); // Hello, 张三!
console.log(sayHi("李四")); // Hi, 李四!

this 绑定规则详解

理解 this 是掌握 JavaScript 的关键之一。与其他语言不同,JavaScript 的 this 值不是在函数定义时确定的,而是在函数调用时确定的。这意味着同一个函数,以不同的方式调用,this 的值可能完全不同。

this 绑定的四种规则

规则一:默认绑定

当函数独立调用(不作为对象的方法)时,this 指向全局对象。在严格模式下,thisundefined

function showThis() {
console.log(this);
}

showThis(); // 非严格模式:window,严格模式:undefined

// 严格模式示例
function showThisStrict() {
"use strict";
console.log(this);
}

showThisStrict(); // undefined

默认绑定是最基础的情况,理解它有助于排查 this 丢失的问题。

规则二:隐式绑定

当函数作为对象的方法调用时,this 指向调用该函数的对象。

const person = {
name: "张三",
greet() {
console.log(`你好,我是${this.name}`);
}
};

person.greet(); // 你好,我是张三

// this 指向调用者
const anotherPerson = { name: "李四", greet: person.greet };
anotherPerson.greet(); // 你好,我是李四

// 隐式丢失问题
const greetFunc = person.greet;
greetFunc(); // 你好,我是undefined(默认绑定)

隐式丢失是一个常见的陷阱。当把对象的方法赋值给变量后调用,this 会丢失。这在回调函数中经常发生:

const obj = {
name: "张三",
greet() {
console.log(`你好,${this.name}`);
}
};

// 回调中 this 丢失
setTimeout(obj.greet, 100); // 你好,undefined

// 解决方案1:使用箭头函数
setTimeout(() => obj.greet(), 100); // 你好,张三

// 解决方案2:使用 bind
setTimeout(obj.greet.bind(obj), 100); // 你好,张三

规则三:显式绑定

使用 call()apply()bind() 方法显式指定 this 的值。

function introduce(age, city) {
console.log(`我是${this.name}${age}岁,来自${city}`);
}

const person = { name: "张三" };

// call:参数逐个传递
introduce.call(person, 25, "北京"); // 我是张三,25岁,来自北京

// apply:参数以数组传递
introduce.apply(person, [25, "北京"]); // 我是张三,25岁,来自北京

// bind:返回一个新函数,this 被永久绑定
const boundIntroduce = introduce.bind(person, 25);
boundIntroduce("北京"); // 我是张三,25岁,来自北京

// bind 只能绑定一次
const reboundIntroduce = boundIntroduce.bind({ name: "李四" });
reboundIntroduce("上海"); // 我是张三,25岁,来自上海(仍然是张三)

call、apply、bind 的区别:

方法执行方式参数形式返回值
call()立即执行逐个传递函数返回值
apply()立即执行数组传递函数返回值
bind()返回新函数逐个传递绑定后的函数

实际应用场景:

// 1. 借用方法
const arrayLike = { 0: "a", 1: "b", length: 2 };
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ["a", "b"]

// 2. 数组最大值
const max = Math.max.apply(null, [1, 5, 3, 9, 2]);
console.log(max); // 9

// 3. 事件处理中的 this 绑定
class Button {
constructor(text) {
this.text = text;
// 绑定 this,避免回调中丢失
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
console.log(`点击了:${this.text}`);
}
}

规则四:new 绑定

使用 new 关键字调用函数时,会创建一个新对象,this 指向这个新对象。

function Person(name, age) {
// this 指向新创建的对象
this.name = name;
this.age = age;
}

const person = new Person("张三", 25);
console.log(person.name); // 张三
console.log(person.age); // 25

// new 执行过程:
// 1. 创建一个新对象
// 2. 将新对象的 __proto__ 指向构造函数的 prototype
// 3. 执行构造函数,this 指向新对象
// 4. 返回新对象(如果构造函数没有显式返回对象)

绑定规则的优先级

当多种规则同时出现时,优先级从高到低为:

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
function foo() {
console.log(this.name);
}

const obj = { name: "对象", foo };
const boundFoo = foo.bind({ name: "绑定" });

// 隐式绑定 vs 显式绑定
obj.foo.call({ name: "显式" }); // 显式(显式 > 隐式)

// 显式绑定 vs new 绑定
new boundFoo(); // undefined(new > 显式,this 是新对象,没有 name 属性)

箭头函数的 this

箭头函数没有自己的 this,它会捕获定义时外层作用域的 this 值。这个特性让箭头函数特别适合作为回调函数。

const person = {
name: "张三",

// 普通函数:this 指向调用者
greetRegular() {
setTimeout(function() {
console.log(this.name); // undefined(回调中 this 丢失)
}, 100);
},

// 箭头函数:继承外层 this
greetArrow() {
setTimeout(() => {
console.log(this.name); // 张三(继承 greetArrow 的 this)
}, 100);
}
};

person.greetRegular();
person.greetArrow();

箭头函数 this 的特点:

  1. 没有自己的 this,继承外层作用域的 this
  2. callapplybind 无法改变箭头函数的 this
  3. 不能作为构造函数(不能用 new 调用)
  4. 没有 arguments 对象
const arrow = () => {
console.log(this);
};

// 这些操作对箭头函数无效
arrow.call({ name: "测试" }); // 仍然是外层的 this
arrow.bind({ name: "测试" })(); // 仍然是外层的 this

函数柯里化

柯里化(Currying)是将接受多个参数的函数转换为一系列接受单个参数的函数的技术。每个函数只接受一个参数,并返回下一个函数,直到所有参数都传入后返回最终结果。

柯里化的概念

// 普通函数
function add(a, b, c) {
return a + b + c;
}

add(1, 2, 3); // 6

// 柯里化后
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}

curriedAdd(1)(2)(3); // 6

// 可以分步调用
const addOne = curriedAdd(1);
const addOneAndTwo = addOne(2);
console.log(addOneAndTwo(3)); // 6

通用柯里化函数

function curry(fn) {
return function curried(...args) {
// 如果参数足够,直接调用原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 否则返回一个函数继续收集参数
return function(...moreArgs) {
return curried.apply(this, [...args, ...moreArgs]);
};
};
}

// 使用示例
function multiply(a, b, c) {
return a * b * c;
}

const curriedMultiply = curry(multiply);

console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
console.log(curriedMultiply(2, 3, 4)); // 24

柯里化的实际应用

1. 参数复用

// 创建特定基数的日志函数
function log(base, message) {
console.log(`[${base}] ${message}`);
}

const curriedLog = curry(log);

// 创建特定前缀的日志器
const errorLog = curriedLog("ERROR");
const infoLog = curriedLog("INFO");
const debugLog = curriedLog("DEBUG");

errorLog("用户未找到"); // [ERROR] 用户未找到
infoLog("操作成功"); // [INFO] 操作成功
debugLog("变量值:42"); // [DEBUG] 变量值:42

2. 延迟执行

// 创建可配置的请求函数
function request(method, url, data) {
console.log(`${method} ${url}`, data);
}

const curriedRequest = curry(request);

// 预设请求方法
const get = curriedRequest("GET");
const post = curriedRequest("POST");

// 预设 URL
const apiGet = get("/api/data");
const apiPost = post("/api/data");

// 最终调用
apiGet(); // GET /api/data undefined
apiPost({ name: "张三" }); // POST /api/data {name: "张三"}

3. 函数组合的基础

// 柯里化的 map 函数
const map = curry((fn, arr) => arr.map(fn));

// 柯里化的 filter 函数
const filter = curry((fn, arr) => arr.filter(fn));

// 柯里化的 reduce 函数
const reduce = curry((fn, init, arr) => arr.reduce(fn, init));

// 使用
const double = x => x * 2;
const isEven = x => x % 2 === 0;

const numbers = [1, 2, 3, 4, 5, 6];

// 先过滤偶数,再翻倍
const result = map(double)(filter(isEven)(numbers));
console.log(result); // [4, 8, 12]

偏函数应用

偏函数应用(Partial Application)与柯里化相似,但它允许一次传入部分参数(不限于一个),返回一个接受剩余参数的函数。

偏函数与柯里化的区别

// 柯里化:每次只接受一个参数
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
curriedAdd(1)(2)(3); // 必须分三次调用

// 偏函数:可以一次接受多个参数
function partialAdd(a, b, c) {
return function(d) {
return a + b + c + d;
};
}
partialAdd(1, 2, 3)(4); // 一次传入多个参数

实现偏函数

function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}

// 使用示例
function greet(greeting, punctuation, name) {
return `${greeting}, ${name}${punctuation}`;
}

// 预设问候语
const sayHello = partial(greet, "Hello", "!");
console.log(sayHello("张三")); // Hello, 张三!

// 预设部分参数
const sayHi = partial(greet, "Hi");
console.log(sayHi(".", "李四")); // Hi, 李四.

使用占位符的偏函数

更灵活的偏函数可以使用占位符来指定哪些位置需要后续填充:

const _ = Symbol("placeholder");

function partialWithPlaceholder(fn, ...presetArgs) {
return function(...laterArgs) {
const args = presetArgs.map(arg =>
arg === _ ? laterArgs.shift() : arg
);
return fn(...args, ...laterArgs);
};
}

// 使用示例
function formatName(lastName, firstName, middleName) {
return `${lastName} ${middleName ? middleName + " " : ""}${firstName}`;
}

// 使用占位符指定填充位置
const formatWithMiddleName = partialWithPlaceholder(formatName, _, _, "William");
console.log(formatWithMiddleName("张", "三")); // 张 William 三

const formatLastName = partialWithPlaceholder(formatName, "李", _);
console.log(formatLastName("四")); // 李 四

偏函数的实际应用

// 1. 预设配置
function createRequest(baseUrl, timeout, endpoint) {
return fetch(`${baseUrl}${endpoint}`, {
signal: AbortSignal.timeout(timeout)
});
}

const apiRequest = partial(createRequest, "https://api.example.com", 5000);

// 2. 事件处理器
function handleEvent(action, event, target) {
console.log(`${action} on ${target}:`, event.type);
}

const handleClick = partial(handleEvent, "点击", _, "按钮");
element.addEventListener("click", handleClick);

// 3. 数学运算
function calculate(op, a, b) {
switch(op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
}
}

const add5 = partial(calculate, '+', 5);
console.log(add5(3)); // 8

const multiplyBy2 = partial(calculate, '*', _, 2);
console.log(multiplyBy2(6)); // 12

函数组合

函数组合是将多个函数组合成一个新函数的技术。数据从右向左流经每个函数,前一个函数的输出成为后一个函数的输入。

基本概念

// 两个函数的组合
function compose(f, g) {
return function(x) {
return f(g(x));
};
}

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

// 先加一,再翻倍
const doubleAfterAddOne = compose(double, addOne);
console.log(doubleAfterAddOne(5)); // 12((5 + 1) * 2)

// 先翻倍,再加一
const addOneAfterDouble = compose(addOne, double);
console.log(addOneAfterDouble(5)); // 11(5 * 2 + 1)

通用组合函数

// 从右向左组合
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}

// 从左向右组合(管道)
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}

// 使用示例
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const split = str => str.split(' ');

const processString = pipe(trim, toLowerCase, split);
console.log(processString(" Hello World ")); // ["hello", "world"]

// 多参数版本
function pipeMulti(...fns) {
return function(...args) {
return fns.reduce((acc, fn) => fn(acc), fns[0](...args));
};
}

实际应用示例

1. 数据处理管道

// 数据转换管道
const users = [
{ name: "张三", age: 25, active: true },
{ name: "李四", age: 30, active: false },
{ name: "王五", age: 28, active: true }
];

const filter = curry((fn, arr) => arr.filter(fn));
const map = curry((fn, arr) => arr.map(fn));
const sortBy = curry((key, arr) => [...arr].sort((a, b) => a[key] - b[key]));

// 组合多个操作
const getActiveUserNames = pipe(
filter(u => u.active),
map(u => u.name),
names => names.join(", ")
);

console.log(getActiveUserNames(users)); // "张三, 王五"

2. 字符串处理

const exclaim = str => str + "!";
const upperFirst = str => str.charAt(0).toUpperCase() + str.slice(1);
const upperEveryWord = str => str.split(' ').map(upperFirst).join(' ');

const emphasize = pipe(
str => str.trim(),
upperEveryWord,
exclaim
);

console.log(emphasize("hello world")); // "Hello World!"

3. 数学计算

const add = curry((a, b) => a + b);
const multiply = curry((a, b) => a * b);
const subtract = curry((a, b) => a - b);

const calculate = pipe(
add(10), // 先加 10
multiply(2), // 再乘 2
subtract(5) // 最后减 5
);

console.log(calculate(3)); // ((3 + 10) * 2) - 5 = 21

Point-Free 风格

Point-Free(无参数风格)是指定义函数时不显式提及参数。这种风格通常与函数组合和柯里化结合使用。

// 非 Point-Free 风格
const getActiveUserNames = users =>
users
.filter(u => u.active)
.map(u => u.name);

// Point-Free 风格
const isActive = u => u.active;
const getName = u => u.name;

const getActiveUserNamesPF = pipe(
filter(isActive),
map(getName)
);

Point-Free 风格的优点是代码更声明式,关注"做什么"而非"怎么做"。但过度使用可能导致代码难以理解,需要权衡。

函数式编程方法

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// map - 转换每个元素
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// filter - 过滤元素
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// reduce - 汇总元素
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 55

const product = numbers.reduce((acc, n) => acc * n, 1);
console.log(product); // 3628800

// find - 查找第一个匹配元素
const firstEven = numbers.find(n => n % 2 === 0);
console.log(firstEven); // 2

// findIndex - 查找第一个匹配元素的索引
const firstEvenIndex = numbers.findIndex(n => n % 2 === 0);
console.log(firstEvenIndex); // 1

// some - 检查是否有任何元素满足条件
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true

// every - 检查是否所有元素都满足条件
const allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true

// 链式调用
const result = numbers
.filter(n => n > 5) // [6, 7, 8, 9, 10]
.map(n => n * 2) // [12, 14, 16, 18, 20]
.reduce((acc, n) => acc + n, 0); // 80
console.log(result); // 80

小结

本章我们学习了:

  1. 函数定义:函数声明、函数表达式和箭头函数
  2. 函数参数:默认参数、剩余参数、参数解构
  3. 函数返回值:return 语句、返回多个值、提前返回
  4. 函数调用:call、apply、bind 的使用
  5. 作用域:全局作用域、函数作用域、块级作用域
  6. 闭包:概念、应用场景、注意事项
  7. 递归函数:递归的概念和实现
  8. 高阶函数:接受或返回函数的函数
  9. this 绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定、箭头函数的 this
  10. 函数柯里化:将多参数函数转换为单参数函数链
  11. 偏函数应用:预设部分参数返回新函数
  12. 函数组合:将多个函数组合成数据处理管道
  13. 函数式编程方法:map、filter、reduce 等数组方法

练习

  1. 编写一个函数,计算两个数的最大公约数(欧几里得算法)
  2. 编写一个函数,接受任意数量的数字,返回最大值和最小值
  3. 使用闭包创建一个缓存函数,缓存函数的计算结果
  4. 编写一个递归函数,实现数组扁平化
  5. 使用函数式方法计算一组学生成绩的平均分(过滤不及格的)
  6. 编写一个通用的柯里化函数,并用于实现一个可配置的请求函数
  7. 使用偏函数创建一个带默认参数的日志函数(支持不同日志级别)
  8. 实现一个 pipe 函数,组合以下操作:过滤偶数、翻倍、求和
  9. 分析以下代码的 this 指向:
const obj = {
name: "张三",
greet: () => console.log(this.name),
sayHello() {
const fn = () => console.log(this.name);
return fn;
}
};

obj.greet();
obj.sayHello()();
const fn = obj.sayHello;
fn()();