程序员A:"为什么我的变量总是乱跑?"
程序员B:"因为它买了张'闭包'的环球旅行票!"
今天我们要探索JavaScript中两个神奇的概念:作用域和闭包。它们就像编程世界的"空间折叠术"——明明变量在函数里出生,却能在千里之外被召唤出来。准备好进入这个奇幻世界了吗?
🎪 作用域:变量的"活动范围"
想象变量是不同社交圈的人士:
// 1. 全局作用域 - 社交名流
var n = 999; // 所有人都认识我!
function f1() {
// 2. 函数作用域 - 公司内部
b = 123; // 没var声明?糟糕,我成网红了(全局变量)!
{
// 3. 块级作用域(ES6) - 部门小团体
let a = 1; // 只有我们组认识我
}
console.log(n); // 名流谁不认识?999
}
f1();
console.log(b); // 网红果然被围观了:123
作用域链的潜规则:
- 内部可访问外部(小弟认识大哥)
- 外部不能访问内部(大哥不认识小弟)
var是社交恐惧症(函数作用域)let/const是社恐晚期(块级作用域)
注意
当我们在函数作用域内,没有用var、let、const声明变量而是直接像上述代码由于 b=123会变成全局变量,在外部可以直接访问,不需要借助闭包
垃圾回收机制
内存的生命周期 JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
- 说明:
- 全局变量一般不会回收(关闭页面回收);
- 一般情况下局部变量的值, 不用了, 会被自动回收掉
🎒 闭包:神奇的"背包"
闭包不是魔术,但比魔术更神奇!它能让局部变量"偷渡"到外部:
function f1() {
var n = 999; // 本该消失的局部变量
function f2() {
console.log(n); // 抓住n不放!
}
return f2; // 把f2当"人质"带出去
}
// 执行魔法
const secretBag = f1();
secretBag(); // 999!变量复活了!
但是在闭包中,我们f1执行完,其变量n为什么没有被回收呢?那是因为我们
f2还存在对f1函数中变量n的引用,简单来说闭包会阻止外部还是变量的回收
闭包三要素:
- 函数嵌套函数(背包套小包)
- 内部函数访问外部变量(小包装了大包的东西)
- 外部函数返回内部函数(把背包递出去)
🧪 闭包实验室:长生不老的变量
// 3.js
function f1() {
var n = 999;
// 全局函数偷偷修改闭包变量
nAdd = function() { n += 1 };
function f2() { console.log(n); }
return f2;
}
const result = f1();
result(); // 999
nAdd(); // 幕后黑手操作
result(); // 1000!变量竟然记住了变化!
实验结论:
- 闭包让局部变量"长生不老"
- 被引用的变量不会被垃圾回收
- 可随时通过闭包函数访问
- 甚至能被外部函数修改(
nAdd)
同时这也充分的说明了外部函数的自由变量不会被销毁
💡 闭包为什么叫"背包"?
想象外部函数是游客,内部函数是背包:
- 游客走了(函数执行完毕)
- 但背包被留下了(返回的函数)
- 背包里装着游客的纪念品(局部变量)
- 纪念品随时可取出(通过闭包访问)
希望这个比喻能帮助你更好地理解闭包阻止了变量地销毁
🎯 this指向:闭包里的"身份迷失"
<!-- 4.html -->
<script>
var name = 'The Window';
var obj = {
name: 'My object',
getNameFunc: function() {
var that = this; // 快照!保存当前身份
return function() {
return that.name; // 正确:My object
// return this.name; // 错误:The Window
};
}
};
console.log(obj.getNameFunc()());
</script>
this的犯罪现场分析:
- 直接返回
this时:匿名函数的this指向window - 闭包导致
this丢失原对象绑定 - 破案技巧:用
that保存正确的this
返回的匿名函数调用,相当于普通函数的执行,this是指向最后调用它的,而普通函数的this是指向window的,所以this就是window,所以这里我们打印的是全局变量
如果大家对this有疑惑的,可以看看我的文章JavaScript中的this指向:从懵圈到豁然开朗的奇幻之旅 相信看完大家心中的疑虑会解决不少
程序员吐槽:"闭包里的this,就像在迪士尼乐园穿西装的打工人——外表光鲜,实际早已迷失自我!"
🚨 闭包的危险:内存泄露的"锅"
闭包虽好,但可能变"内存杀手":
// 危险操作示范
function createHeavyClosure() {
const bigData = new Array(1000000); // 超大数组
return function() {
console.log(bigData.length);
};
}
const heavyBag = createHeavyClosure();
// 即使不再需要,bigData仍被闭包引用!
内存泄露预防指南:
- 及时清理:
heavyBag = null - 避免循环引用
- 使用弱引用:
WeakMap/WeakSet - 模块化设计,限制闭包范围
💎 总结:闭包使用三原则
- 必要才用:如非必须,勿增闭包
- 及时清理:
= null是好习惯 - 明确边界:避免无节制暴露内部状态
最后友情提示:闭包就像辣椒——适量提味,过量烧胃。现在,是时候打开你的编辑器,用闭包写点"魔法代码"了!毕竟,不会用闭包的JavaScript程序员,就像不会用筷子的中餐厨师——能活,但憋屈!