004-函数式编程[FP]深度解析

函数式编程(FP)深度解析

函数式编程核心理解

函数式编程是一种声明式编程范式,核心思想是”通过纯函数组合构建程序”,主要特点:

特性 说明 示例
纯函数 相同输入永远返回相同输出,无副作用(不改变外部状态) const add = (a,b) => a + b;
不可变性 数据创建后不可修改,通过生成新数据实现变更 使用map代替for循环+push
函数一等公民 函数可作为参数/返回值/变量 const logger = fn => (...args) => { console.log(...args); return fn(...args); }
引用透明 函数调用可直接替换为其返回值 add(2,3) 可替换为 5
高阶函数 接收或返回函数的函数 map, filter, reduce
函数组合 将多个函数组合成新函数(f(g(x)) const process = compose(validate, format, save);

函数柯里化(Currying)详解

概念:将多参数函数转换为一系列单参数函数的过程
f(a, b, c)f(a)(b)(c)

实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 基础柯里化实现
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return (...nextArgs) => curried.apply(this, [...args, ...nextArgs]);
}
};
}

// 使用示例
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6


#### 柯里化特点

1. **参数延迟传递**:分步传入参数
2. **函数复用性**:基于部分参数创建新函数
3. **动态函数生成**:按需组合函数逻辑

### 实际应用场景

#### 场景1:参数复用

```javascript
// 通用URL构建器
const buildUrl = curry((protocol, domain, path) =>
`${protocol}://${domain}/${path}`
);

// 创建特定环境URL生成器
const createHttpsUrl = buildUrl('https');
const githubUrl = createHttpsUrl('github.com');
const apiUrl = createHttpsUrl('api.example.com');

console.log(githubUrl('user/repo')); // https://github.com/user/repo
console.log(apiUrl('v1/data')); // https://api.example.com/v1/data

场景2:事件处理

1
2
3
4
5
6
7
8
9
10
11
// 通用事件监听器
const addListener = curry((eventType, element, handler) =>
element.addEventListener(eventType, handler)
);

// 创建特定元素监听器
const addButtonListener = addListener('click')(document.querySelector('button'));

// 添加不同按钮处理逻辑
addButtonListener(handleLogin);
addButtonListener(handleLogout);

场景3:数据校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 校验规则组合
const validate = curry((rules, value) =>
rules.every(rule => rule(value))
);

// 创建特定校验器
const isString = v => typeof v === 'string';
const minLength = len => v => v.length >= len;
const hasNumber = v => /\d/.test(v);

// 密码校验器
const isValidPassword = validate([
isString,
minLength(8),
hasNumber
]);

console.log(isValidPassword('abc123')); // false (长度不足)
console.log(isValidPassword('securePwd1')); // true

场景4:React高阶组件

1
2
3
4
5
6
7
8
9
10
11
12
// 柯里化HOC
const withFeatures = curry((features, Component) =>
props => <Component {...props} features={features} />
);

// 创建特性注入器
const withAnalytics = withFeatures(['analytics']);
const withLogger = withFeatures(['logger']);

// 应用特性
const DashboardWithAnalytics = withAnalytics(Dashboard);
const AppWithLogger = withLogger(App);

函数式编程实践价值

  1. 可维护性:纯函数+不可变性使代码更可预测
  2. 可测试性:无副作用函数易于单元测试
  3. 并发安全:避免共享状态带来的竞态条件
  4. 模块化:函数组合实现高度复用
  5. 调试友好:引用透明性简化问题追踪

使用注意事项

1
2
3
4
5
6
7
8
// 避免过度柯里化
// 反模式:curried(1)(2)(3)(4)(5)...
// 解决方案:合理设计参数分组

// 性能考量:深度柯里化可能创建大量闭包
// 优化方案:限制柯里化深度,关键路径避免使用

// 团队协作:在非FP团队中适度使用,避免认知负担

现代JS中的FP支持

工具 用途 示例
Lodash/fp 函数式工具库 _.map(_.trim, strings)
Ramda 纯函数式工具库 R.compose(R.filter(f), R.map(g))
React Hooks 函数组件+状态管理 useMemo, useCallback
Redux 不可变状态管理 reducer(state, action) => newState
1

005-防抖与节流概念-场景-实现

防抖与节流:原理、实现与应用场景

防抖(Debounce)

核心原理

在事件被触发 n 秒后再执行回调,若在 n 秒内事件再次被触发,则重新计时
确保函数只在最后一次触发后执行一次

使用场景

场景 说明
搜索框输入 用户停止输入后再发送请求,避免频繁请求
窗口大小调整 窗口调整结束后再计算布局,避免重复渲染
表单验证 用户输入完成后再进行验证,减少实时验证开销
按钮防重 防止用户快速重复点击导致多次提交
实时保存 文档编辑时,停止输入一段时间后自动保存

基础实现

1
2
3
4
5
6
7
8
9
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}

增强版(支持立即执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function debounce(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
clearTimeout(timer);

// 立即执行模式
if (immediate && !timer) {
fn.apply(this, args);
}

timer = setTimeout(() => {
// 非立即执行模式
if (!immediate) {
fn.apply(this, args);
}
timer = null;
}, delay);
};
}

应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 搜索框防抖
const searchInput = document.getElementById('search');
const handleSearch = debounce(query => {
console.log(`搜索: ${query}`);
// 实际发送搜索请求...
}, 500);

searchInput.addEventListener('input', e => handleSearch(e.target.value));

// 窗口调整防抖
const handleResize = debounce(() => {
console.log('窗口调整完成');
// 重新计算布局...
}, 300);

window.addEventListener('resize', handleResize);

节流(Throttle)

核心原理

在单位时间内,无论事件触发多少次,只执行一次回调函数
确保函数以固定频率执行

使用场景

场景 说明
滚动事件 滚动页面时触发加载更多内容,避免高频执行
鼠标移动 跟踪鼠标位置,限制触发频率
游戏操作 限制角色移动或攻击频率
高频点击 限制按钮点击频率,防止误操作
动画渲染 保证动画帧率稳定,避免过度渲染

时间戳实现

1
2
3
4
5
6
7
8
9
10
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}

定时器实现(支持尾执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function throttle(fn, interval) {
let timer = null;
let lastTime = 0;

return function(...args) {
const now = Date.now();
const remaining = interval - (now - lastTime);

clearTimeout(timer);

// 达到执行间隔
if (remaining <= 0) {
fn.apply(this, args);
lastTime = now;
}
// 设置尾执行
else {
timer = setTimeout(() => {
fn.apply(this, args);
lastTime = Date.now();
}, remaining);
}
};
}

应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 滚动加载节流
let page = 1;
const loadMore = throttle(() => {
const scrollBottom = document.documentElement.scrollHeight -
window.innerHeight -
window.scrollY;

if (scrollBottom < 500) {
console.log(`加载第 ${page} 页数据`);
// 实际加载数据...
page++;
}
}, 500);

window.addEventListener('scroll', loadMore);

// 游戏攻击节流
const playerAttack = throttle(() => {
console.log('玩家发动攻击');
// 实际攻击逻辑...
}, 1000);

attackButton.addEventListener('click', playerAttack);

对比总结

特性 防抖 节流
核心思想 延迟执行,只执行最后一次 固定频率执行
执行时机 停止触发后执行 间隔时间内执行
执行次数 多次触发只执行一次 多次触发按固定频率执行
实现关键 setTimeout + clearTimeout 时间戳或定时器
适用场景 输入类事件 高频触发事件
用户感知 响应最后操作结果 保持操作流畅性

可视化对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph LR
A[事件触发] --> B{防抖模式}
A --> C{节流模式}

B --> D[触发1]
D --> E[重置计时]
E --> F[触发2]
F --> G[重置计时]
G --> H[触发3]
H --> I[执行回调]

C --> J[触发1-立即执行]
J --> K[冷却期]
K --> L[触发2-忽略]
L --> M[冷却结束]
M --> N[触发3-执行]

现代开发实践

使用 Lodash 实现

1
2
3
4
5
6
7
8
9
10
11
import { debounce, throttle } from 'lodash';

// 防抖
const searchHandler = debounce(query => {
// 搜索逻辑
}, 500);

// 节流
const scrollHandler = throttle(() => {
// 滚动处理
}, 300);

React Hooks 封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 防抖Hook
function useDebounce(callback, delay) {
const callbackRef = useRef(callback);

useEffect(() => {
callbackRef.current = callback;
});

return useMemo(() => {
const handler = (...args) => {
clearTimeout(handler.timeout);
handler.timeout = setTimeout(() => {
callbackRef.current(...args);
}, delay);
};
return handler;
}, [delay]);
}

// 节流Hook
function useThrottle(callback, interval) {
const callbackRef = useRef(callback);
const lastTimeRef = useRef(0);

useEffect(() => {
callbackRef.current = callback;
});

return useCallback((...args) => {
const now = Date.now();
if (now - lastTimeRef.current >= interval) {
callbackRef.current(...args);
lastTimeRef.current = now;
}
}, [interval]);
}

性能优化建议

  1. 合理设置延迟时间:防抖250-500ms,节流100-300ms
  2. 避免过度使用:只在必要时应用
  3. 及时清理:组件卸载时清除定时器
  4. 结合RAF:动画场景使用requestAnimationFrame
1
2
3
4
5
6
7
8
9
10
11
12
function rafThrottle(fn) {
let ticking = false;
return function(...args) {
if (!ticking) {
requestAnimationFrame(() => {
fn.apply(this, args);
ticking = false;
});
ticking = true;
}
};
}