一、闭包(Closure)
1.什么是闭包?
闭包是指函数能够记住并访问其词法作用域,即使函数在其词法作用域之外执行。简单说,就是内部函数可以访问外部函数的变量。
2.理解作用域
JavaScript 是 词法作用域(Lexical Scope) :
变量的作用域在定义时决定,而不是在调用时决定。
function outer() {
let a = 10;
function inner() {
console.log(a);
}
inner();
}
outer(); // 10
inner 能访问 outer 里的 a,因为它定义在 outer 里面。
闭包的基本示例
function createCounter() {
let count = 0; // 私有变量
return function inner() {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter1()); // 3
const counter2 = createCounter();
console.log(counter2()); // 1 (独立的计数器)
打印结果
发生了什么?
当 createCounter() 执行结束后:
- 按理说
count应该被销毁 - 但是
inner还在引用它 - JS 引擎不会回收这个变量
👉 因为 inner 形成了闭包
3. 闭包的执行过程(原理)
当函数创建时,会:
- 创建一个执行上下文
- 生成一个词法环境
- 内部函数会保存对这个词法环境的引用
只要内部函数还存在,这个环境就不会被垃圾回收。
4. 闭包的关键特征
- 函数嵌套函数:通常是一个外层函数包裹一个内层函数。
- 内层函数引用外层函数的变量:这是形成闭包的必要条件。
- 外层函数将内层函数返回(或传递出去) :使得内层函数可以在其原始作用域之外被调用。
5. 闭包的常见用途
1.数据私有化(封装)
function createUser() {
let password = "123456";
return {
checkPassword(input) {
return input === password;
}
};
}
const user = createUser();
console.log(user.checkPassword("123456")); // true
打印结果:
外部访问不到 password
2. 防抖/节流
function debounce(fn, delay) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(fn, delay);
};
}
timer就是被闭包保存的
3.循环中的经典坑(经典面试题)
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
打印结果:
原因:
var 声明的 i 是函数作用域(或全局作用域),3个定时器回调共享同一个 i。当循环结束时,i 已经是 3,所以每个回调都打印 3。
解决方案:
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
使用let:
let 声明的变量是块级作用域,每次循环都会创建一个新的 i 绑定,效果等同于用闭包保存了状态。
或者使用
for (var i = 0; i < 3; i++) {
(function (i) {
setTimeout(() => {
console.log(i);
}, 1000);
})(i);
}
打印结果:
这个就是利用闭包锁住变量
6.闭包的优缺点
优点
- 数据私有化
- 模块化开发
- 避免全局变量污染
- 实现函数式编程模式
缺点
- 容易造成内存泄漏(长期引用大对象)
- 不合理使用会影响性能
- 变量长期存在难以调试
7.面试总结版
- 闭包是指函数可以访问其定义时的词法作用域,即使外部函数已经执行结束。
- 本质是函数持有对外部变量的引用。
- 常见应用场景包括数据私有化、计数器、防抖节流等。
二、高阶函数
1.什么是高阶函数?
在 JavaScript 里,函数是一等公民(First-Class Function) :
- 可以赋值给变量
- 可以作为参数传入
- 可以作为返回值返回
高阶函数是指至少满足以下条件之一的函数:
- 接受一个或多个函数作为参数
- 返回一个函数作为结果
简单说:操作其他函数的函数。
2. 常见的高阶函数示例
示例1:函数作为参数(回调函数)
function greet(name) {
return "Hello " + name;
}
function processUserInput(callback) {
const name = "Mike";
console.log(callback(name));
}
processUserInput(greet);
打印结果:
示例2:函数作为返回值
function multiplier(factor) {
console.log("打印factor:", factor); //打印 2 3
return function (number) {
console.log("打印number:", number); //打印 5 6
return number * factor;
};
}
const double = multiplier(2);
const triole = multiplier(3);
console.log(double(5)); //10
console.log(triole(6)); //18
打印结果:
示例3.经典数组方法
map() - 映射/转换
const numbers = [1, 2, 3, 4, 5];
// 每个元素乘以2
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 转换为对象数组
const objects = numbers.map(num => ({ value: num }));
console.log(objects);
// [{value: 1}, {value: 2}, {value: 3}, {value: 4}, {value: 5}]
filter() - 过滤
const numbers = [1, 2, 3, 4, 5, 6];
// 获取偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6]
// 获取大于3的数
const greaterThan3 = numbers.filter(num => num > 3);
console.log(greaterThan3); // [4, 5, 6]
reduce() - 归约/累积
const numbers = [1, 2, 3, 4, 5];
// 求和
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum); // 15
// 求最大值
const max = numbers.reduce((acc, curr) => Math.max(acc, curr), -Infinity);
console.log(max); // 5
// 数组转对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const usersById = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
console.log(usersById);
// {
// 1: { id: 1, name: 'Alice' },
// 2: { id: 2, name: 'Bob' }
// }
forEach() - 遍历
const fruits = ['apple', 'banana', 'orange'];
fruits.forEach((fruit, index) => {
console.log(`${index + 1}. ${fruit}`);
});
// 1. apple
// 2. banana
// 3. orange
find() / findIndex() - 查找
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Alice' }
];
const alice = users.find(user => user.name === 'Alice');
console.log(alice); // { id: 1, name: 'Alice' }
const aliceIndex = users.findIndex(user => user.name === 'Alice');
console.log(aliceIndex); // 0
some() / every() - 条件检查
const numbers = [1, 2, 3, 4, 5];
// 是否有偶数?
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true
// 是否所有数都大于0?
const allPositive = numbers.every(num => num > 0);
console.log(allPositive); // true
3.自定义高阶函数
1.什么是自定义高阶函数
自定义高阶函数,本质就是:
用函数去增强、控制、复用另一个函数
一般分三类:
- 函数增强(装饰)
- 行为抽象(解耦)
- 执行控制(节流、防抖等)
2.最经典:函数增强(装饰器思想)
function before(fn, beforeFn) {
return function (...args) {
beforeFn.apply(this, args); // 先执行 beforeFn
return fn.apply(this, args); // 再执行原始函数 fn
};
}
function after(fn, afterFn) {
return function (...args) {
const result = fn.apply(this, args); // 先执行原始函数 fn
afterFn.apply(this, args); // 再执行 afterFn
return result; // 返回原始函数的结果
};
}
function say(name) {
console.log("Hello " + name);
}
const newSay = before(say, function () {
console.log("准备执行...");
});
newSay("Jake");
Before函数:
**调用 newSay("Jake") 时:
- 执行 beforeFn() → "准备执行..."
- 执行 say("Jake") → "Hello Jake"
- 返回 say 的返回值(undefined)**
After 函数:
**调用 afterFn 装饰的函数时:
- 执行原始函数 fn → 得到结果
- 执行 afterFn()
- 返回 fn 的结果**
打印结果:
3.执行控制型高阶函数
执行控制型高阶函数是指那些能够控制函数执行时机、频率或条件的高阶函数,它们在前端开发中非常实用。
3.1 自定义节流函数
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime > delay) {
lastTime = now;
fn.apply(this, args);
}
};
}
window.addEventListener(
"scroll",
throttle(() => {
console.log("滚动触发");
}, 1000)
);
3.2 自定义防抖函数
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
4.行为抽象型高阶函数
指将程序中具体的操作逻辑(行为)提取出来,使其不依赖于具体的数据,而是通过参数(特别是函数参数)来定义。这使得代码更通用、更可复用。
4.1 抽象重复逻辑
function withLoading(fn) {
return async function (...args) {
console.log("loading...");
try {
const result = await fn(...args);
return result;
} finally {
console.log("loading end");
}
};
}
async function fetchData() {
return "数据";
}
const newFetch = withLoading(fetchData);
newFetch();
打印结果:
5.函数柯里化(进阶自定义高阶函数)
函数柯里化是一种将接受多个参数的函数转换为接受单个参数(或更少参数)的函数序列的技术。
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return function (...nextArgs) {
return curried(...args, ...nextArgs);
};
}
};
}
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
打印结果:
6.链式增强
链式增强是 JavaScript 中一种强大的编程模式,它通过方法链(Method Chaining)让代码更加流畅、可读。这种模式特别适用于数据处理、DOM 操作和构建复杂配置。
Function.prototype.before = function (beforeFn) {
const self = this;
return function (...args) {
beforeFn.apply(this, args);
return self.apply(this, args);
};
};
function say() {
console.log("hello");
}
say = say.before(function () {
console.log("before...");
});
say();
打印结果:
7.自定义高阶函数的本质结构
function higherOrder(fn) {
return function (...args) {
// 1. 执行前逻辑
const result = fn.apply(this, args);
// 2. 执行后逻辑
return result;
};
}
8.作为前端开发你必须掌握的几个自定义高阶函数
- before / after
- throttle
- debounce
- curry
- once(只执行一次)
- memoize(缓存结果)
比如 memoize:
function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = fn.apply(this, args);
cache[key] = result;
return result;
};
}
9.终极理解(非常重要)
高阶函数解决的问题是:
- 不修改原函数
- 不侵入原逻辑
- 动态增强功能
这就是:
- AOP
- 装饰器模式
- 中间件机制
- React HOC
- Vue 插件机制
的底层原理。
🌱 写在最后
写作这些技术文章的过程,也是我重新梳理知识体系的过程。
编程不仅是实现功能,更是一种思考方式。希望这篇文章不仅能帮你解决眼前的问题,更能激发你对[某个技术点]的更深层思考。
最后:保持好奇,保持热爱。