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
});
箭头函数与普通函数的区别
- 没有自己的
this - 没有
arguments对象 - 不能用作构造函数
- 没有
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 指向全局对象。在严格模式下,this 是 undefined。
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 的特点:
- 没有自己的
this,继承外层作用域的this call、apply、bind无法改变箭头函数的this- 不能作为构造函数(不能用
new调用) - 没有
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
小结
本章我们学习了:
- 函数定义:函数声明、函数表达式和箭头函数
- 函数参数:默认参数、剩余参数、参数解构
- 函数返回值:return 语句、返回多个值、提前返回
- 函数调用:call、apply、bind 的使用
- 作用域:全局作用域、函数作用域、块级作用域
- 闭包:概念、应用场景、注意事项
- 递归函数:递归的概念和实现
- 高阶函数:接受或返回函数的函数
- this 绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定、箭头函数的 this
- 函数柯里化:将多参数函数转换为单参数函数链
- 偏函数应用:预设部分参数返回新函数
- 函数组合:将多个函数组合成数据处理管道
- 函数式编程方法:map、filter、reduce 等数组方法
练习
- 编写一个函数,计算两个数的最大公约数(欧几里得算法)
- 编写一个函数,接受任意数量的数字,返回最大值和最小值
- 使用闭包创建一个缓存函数,缓存函数的计算结果
- 编写一个递归函数,实现数组扁平化
- 使用函数式方法计算一组学生成绩的平均分(过滤不及格的)
- 编写一个通用的柯里化函数,并用于实现一个可配置的请求函数
- 使用偏函数创建一个带默认参数的日志函数(支持不同日志级别)
- 实现一个 pipe 函数,组合以下操作:过滤偶数、翻倍、求和
- 分析以下代码的 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()();