JavaScript 函数高级技巧(系列02)
本文内容基于 AI 生成结果整理,可能包含不准确信息,仅供参考使用。
一、箭头函数特性
1. 与传统函数区别
javascript
// 传统函数
function sum(a, b) {
return a + b;
}
// 箭头函数
const sum = (a, b) => a + b;
关键差异 :
- 没有自己的 this(继承父级作用域)
- 不能用作构造函数(不能 new)
- 没有 arguments 对象
2. 适合场景举例
javascript
// 1. 数组操作
const nums = [1, 2, 3];
const doubled = nums.map((n) => n * 2);
// 2. 定时器
setTimeout(() => {
console.log("延迟执行");
}, 1000);
二、闭包实战应用
1. 基础闭包示例
javascript
function createCounter() {
let count = 0;
return function () {
return ++count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
2. 封装私有变量
javascript
function createUser(name) {
let age = 20; // 私有变量
return {
getName: () => name,
getAge: () => age,
setAge: (newAge) => {
age = newAge;
},
};
}
const user = createUser("小明");
console.log(user.getAge()); // 20
user.setAge(21);
console.log(user.age); // undefined(无法直接访问)
三、递归优化策略
1. 基础递归(存在栈溢出风险)
javascript
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
2. 尾递归优化(ES6)
javascript
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
console.log(factorial(5)); // 120
优化原理 :
- 最后一步只调用自身,不保留栈帧
- 需在严格模式下生效(‘use strict’)
四、函数柯里化
柯里化(Currying)是一种将多参数函数转换为一系列单参数函数的技术,使得函数可以逐步接收参数,并在所有参数收集完毕后执行。
柯里化的核心价值:
- 参数复用: 提前固定部分参数,减少重复代码。
- 函数组合: 便于构建高阶函数(如 createLogger、createApiRequest)。
- 延迟执行: 分步传入参数,最后触发计算。
1. 基础柯里化实现
javascript
function curry(fn) {
return function curried(...args) {
return args.length >= fn.length
? fn(...args)
: (...moreArgs) => curried(...args, ...moreArgs);
};
}
// 使用示例
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
2. 实际应用场景
示例 1:创建特定前缀的日志函数
javascript
// 创建特定前缀的日志函数
const createLogger = (prefix) => (message) => {
console.log(`[${prefix}] ${message}`);
};
const appLogger = createLogger("APP");
appLogger("启动成功"); // [APP] 启动成功
作用 :
- 提前固定 prefix(如 “APP”),后续调用只需传 message。
- 避免重复写 console.log("[APP] …"),提升代码复用性。
示例 2:动态生成 API 请求函数
javascript
const createApiRequest = (baseUrl) => (endpoint) => (params) => {
return fetch(`${baseUrl}/${endpoint}`, { body: JSON.stringify(params) });
};
// 固定 baseUrl
const myApi = createApiRequest("https://api.example.com");
// 固定 endpoint
const getUser = myApi("user");
const getPosts = myApi("posts");
// 最终调用
getUser({ id: 123 }); // 请求 https://api.example.com/user
getPosts({ page: 1 }); // 请求 https://api.example.com/posts
作用 :
- 分步固定 baseUrl 和 endpoint,最终只需传 params。
- 避免重复写完整的 URL,减少硬编码。
示例 3:校验表单字段(复用校验规则)
javascript
const validateField = (rule) => (value) => {
return rule.test(value);
};
// 固定校验规则
const isEmail = validateField(/^\S+@\S+\.\S+$/);
const isPhone = validateField(/^1\d{10}$/);
// 使用
console.log(isEmail("test@example.com")); // true
console.log(isPhone("13800138000")); // true
作用 :
- 提前绑定校验规则(如邮箱、手机号),后续直接校验值。
- 避免重复写正则表达式。
示例 4:动态生成 CSS 类名(React/Vue 中常用)
javascript
const addClassName = (baseClass) => (modifier) => {
return `${baseClass} ${baseClass}--${modifier}`;
};
// 固定基础类名
const btnClass = addClassName("btn");
// 生成具体类名
btnClass("primary"); // "btn btn--primary"
btnClass("danger"); // "btn btn--danger"
作用 :
- 在 UI 组件中快速生成 BEM 风格的类名。
- 避免手动拼接字符串。
示例 5:计算折扣价格(电商场景)
javascript
const applyDiscount = (discount) => (price) => {
return price * (1 - discount);
};
// 固定折扣率
const summerSale = applyDiscount(0.2); // 8 折
const blackFriday = applyDiscount(0.5); // 5 折
// 计算实际价格
summerSale(100); // 80 (100 * 0.8)
blackFriday(100); // 50 (100 * 0.5)
作用 :
- 预定义不同促销活动的折扣率,后续只需传原价。
- 逻辑清晰,避免重复计算。
示例 6:权限检查(前端鉴权)
javascript
const checkPermission = (role) => (resource) => {
return role === "admin" || resource.accessibleTo.includes(role);
};
// 固定用户角色
const userCanAccess = checkPermission("user");
const adminCanAccess = checkPermission("admin");
// 检查权限
userCanAccess({ accessibleTo: ["user", "admin"] }); // true
adminCanAccess({ accessibleTo: ["admin"] }); // true
作用 :
- 提前绑定用户角色(如 “user”),后续检查资源是否可访问。
- 替代冗长的 if-else 权限判断。
五、性能优化技巧
1. 记忆化缓存
javascript
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
return cache.has(key)
? cache.get(key)
: cache.set(key, fn(...args)).get(key);
};
}
// 使用
const fib = memoize((n) => (n <= 1 ? 1 : fib(n - 1) + fib(n - 2)));
console.log(fib(50)); // 快速计算
2. 防抖与节流
特性 | 防抖(Debounce) | 节流(Throttle) |
---|---|---|
触发时机 | 停止操作后 delay 毫秒执行 | 固定间隔 interval 毫秒执行一次 |
重置计时 | 每次新触发会重置计时 | 不会重置计时 |
适用场景 | 搜索联想、窗口调整 | 滚动、鼠标移动、游戏按键 |
极端情况 | 如果一直触发,则永远不会执行 | 至少每隔 interval 执行一次 |
防抖
作用 : 事件触发后,等待 delay 毫秒再执行,如果期间再次触发,则重新计时。
适用场景 : 搜索框输入联想、窗口大小调整、避免按钮重复提交。
javascript
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer); // 清除之前的计时
timer = setTimeout(() => fn(...args), delay); // 重新开始计时
};
}
// 使用示例:搜索框输入联想
const searchInput = document.getElementById("search");
const fetchResults = (query) => {
console.log("搜索:", query);
// 实际场景中这里可能是 AJAX 请求
};
// 防抖后的搜索函数(停止输入 500ms 后才会触发)
const debouncedSearch = debounce(fetchResults, 500);
searchInput.addEventListener("input", (e) => debouncedSearch(e.target.value));
// 测试输入 "hello"(快速连续输入时,只会在停止后打印一次)
// 输入:h -> e -> l -> l -> o (500ms 内无新输入)
// 输出:搜索:hello
节流
作用 : 固定时间间隔(interval)内最多执行一次,稀释事件触发频率。
适用场景 : 滚动事件监听、鼠标移动跟踪、游戏中的按键响应。
javascript
function throttle(fn, interval) {
let lastTime = 0;
return (...args) => {
const now = Date.now();
if (now - lastTime >= interval) {
// 如果距离上次执行已超过间隔
fn(...args);
lastTime = now; // 更新最后执行时间
}
};
}
// 使用示例:滚动事件监听
const handleScroll = () => {
console.log("滚动位置:", window.scrollY);
};
// 节流后的滚动函数(每 200ms 最多触发一次)
const throttledScroll = throttle(handleScroll, 200);
window.addEventListener("scroll", throttledScroll);
// 测试快速滚动页面
// 实际输出(假设用户疯狂滚动):
// 滚动位置:100
// 滚动位置:300 (200ms 后)
// 滚动位置:500 (再过 200ms 后)
// ...(不会因为高频滚动导致回调爆炸)
高级用法(结合二者)
javascript
// 增强版:结合防抖和节流(先节流,最后一次触发防抖)
function hybrid(fn, delay) {
let lastTime = 0;
let timer;
return (...args) => {
const now = Date.now();
if (now - lastTime < delay) {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
lastTime = now;
}, delay);
} else {
fn(...args);
lastTime = now;
}
};
}
// 使用场景:既希望限制频率,又希望最终触发一次(如实时保存表单)
const saveForm = hybrid(() => console.log("保存数据"), 1000);
实际应用场景
场景 1:按钮防重复提交
javascript
const submitOrder = debounce(() => {
console.log("订单提交中...");
// 实际发送请求
}, 1000);
document.getElementById("buy-btn").addEventListener("click", submitOrder);
// 用户疯狂点击按钮时,只会提交一次
场景 2:无限滚动加载(节流)
javascript
const checkScrollBottom = throttle(() => {
if (window.scrollY + window.innerHeight >= document.body.scrollHeight) {
console.log("加载更多数据...");
}
}, 500);
window.addEventListener("scroll", checkScrollBottom);
// 滚动到底部时,最多每 500ms 检查一次