前言:闭包到底是啥?别怕,今天用段子讲明白!
闭包,这个让无数前端新手头秃的词,究竟是天使还是魔鬼?你以为它只是面试官的“灵魂拷问”,其实它是JS世界的“超级英雄”——能守护变量、能制造私有空间,也能让你一不小心掉进内存泄漏的“深坑”。今天,我们就用最幽默的方式,带你彻底搞懂闭包的技术细节和应用场景!
一、闭包的本质:作用域链的“超级外挂”
1. 作用域链和声明提升
JS引擎会先编译代码,再执行。变量和函数声明会被提升到作用域顶部,这就是“声明提升”。比如:
showName()
console.log(myname);
var myname = '路明非'
function showName() {
console.log('函数showName 执行了');
}
输出:
函数showName 执行了
undefined
技术点:函数声明优先于变量声明,变量提升但不赋值。
2. 作用域链和outer指针
JS查找变量时,会先从当前作用域找,找不到就向上一级找,直到全局作用域。这条“查找链”就是作用域链。每个函数都有一个[[scope]](也叫outer指针),指向它定义时的外部作用域。
二、闭包的诞生:函数里的“变量守护者”
1. 闭包的定义
当一个函数A内部声明了另一个函数B,并且B被带到A的外部执行时,A的变量不会被销毁,而是被B“守护”了下来,这个“守护变量的集合”就是闭包。
function foo() {
var myname = '路明非'
var age = 18
return function bar() {
console.log(myname);
}
}
var baz = foo()
baz() // 输出:路明非
技术点:bar函数在foo执行完后还能访问myname,这就是闭包的魔力。
2. 闭包的经典应用:制造私有变量
闭包可以用来制造“私有变量”,比如计数器、缓存等。
三、作用域链的“宫斗剧”:变量查找的那些坑
1. 变量查找优先级
function a() {
var num = 10
function b() {
var num = 20
c()
}
function c() {
console.log(num);
}
b()
}
a() // 输出:10
技术点:c函数的作用域链在定义时就确定了,和调用位置无关。
2. 块级作用域与let/const
function foo() {
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a); // 1
console.log(b); // 3
}
console.log(b); // 2
console.log(c); // 4
// console.log(d); // 报错
}
foo()
技术点:let/const有块级作用域,var没有。
四、闭包的“副作用”:内存泄漏与性能陷阱
1. 闭包导致的内存泄漏
闭包会让本该销毁的变量常驻内存,如果滥用,容易造成内存泄漏。
var arr = []
for (var i = 0; i < 5; i++) {
(function(j) {
arr.push(function () {
console.log(j);
})
})(i)
}
for (var j = 0; j < arr.length; j++) {
arr[j]()
}
输出:
0
1
2
3
4
技术点:每个函数都“记住”了自己的j,闭包让变量“长生不老”。
2. 调用栈与递归
function runStack(n) {
if (n === 0) return 100
return runStack(n - 2)
}
runStack(50000)
技术点:调用栈太深会爆栈,闭包和递归要慎用。
五、闭包的实战应用:高阶函数与回调
闭包是高阶函数、回调、定时器、事件监听等场景的“幕后英雄”。比如:
- 封装私有变量
- 实现防抖/节流
- 记忆化缓存
六、图解闭包:一图胜千言
七、面试官的灵魂拷问
- “什么是闭包?有什么用?”
- “闭包会导致什么问题?”
- “如何避免闭包带来的内存泄漏?”
答题锦囊:闭包是函数和其词法作用域的组合,能让函数在外部访问内部变量。用得好是神器,用不好是“内存炸弹”!
八、总结:闭包,JS世界的“超级英雄”
闭包让JS拥有了强大的变量保护和私有化能力,也带来了内存管理的挑战。理解闭包的本质和作用域链,你就能写出更优雅、更高效的JS代码。下次再遇到闭包,别再害怕,勇敢地用段子和技术征服它吧!
项目源码已开源,欢迎点赞、收藏、评论,和我一起用段子学闭包,快乐进阶前端江湖!