前置知识!!!
闭包 是Javascript语言的一个重难点, 也是它的特色, 很多高级应用都要依靠闭包来实现。在各种专业文献上学习"闭包"的时候, 就一个感觉 – “抽象” !
特别是学习内存泄漏的时候, 没想明白为什么使用闭包的时候 不及时清除函数中的元素会导致内存泄漏, 直到我的第一次面试结束之后, 回顾的时候把这几个知识串联了起来, 一切都明朗了。
这里先给大家看个 解除闭包的引用, 释放数据内存 的例子(看不懂没关系, 后面才是正文开始)
function createClosure() {
let externalData = { /* 一些数据 */ };
return function innerFunction() {
// 使用externalData做一些事情
};
}
let myClosure = createClosure();
// ... 使用myClosure做一些事情
// 当你不再需要myClosure时
myClosure = null; // 移除对闭包的引用
// 注意:如果externalData仍然被其他闭包或外部代码引用,则它不会被回收
// 你需要确保没有其他引用指向它,或者它本身也应该被设置为null
这里还要补充一点 垃圾回收机制 的知识, 方便后续理解:
前端中的垃圾回收机制(Garbage Collection, 简称GC)是一种 自动内存管理机制 ,它负责找出并释放那些不再被使用的内存空间,以防止内存泄漏,从而优化程序的性能。在JavaScript等前端技术中,垃圾回收机制扮演着至关重要的角色。
好了, 前面看不懂没关系, 现在正文开始:
一、变量的作用域:
想要理解闭包, 首先必须理解 Javascript 特殊的变量作用域。
变量的作用域分为两种: 全局变量 和 局部变量。
函数内部可以直接读取全局变量, 而函数外部自然无法读取函数内的局部变量
var n=999;
function f1(){
alert(n);
}
f1(); // 999
function f1(){
var n=999;
}
alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){
n=999;
}
f1();
alert(n); // 999
二、如何从外部读取局部变量?
由于业务场景的不同,我们有时候需要得到函数内的局部变量, 由于该局部变量需要收到保护, 不能让外部环境直接更改。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。 如:
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。这就是 Javascript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它的内部变量了!
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
三、闭包的概念
上一节代码中的f2函数, 就是闭包 。
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解总结成一句话就是:
闭包就是在全局环境中, 通过新建对象, 调用函数的方式 已达到读取父对象变量的目的, 而这个调用的函数就是闭包。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
四、闭包的用途
可能这个时候我们就会想到, f1 函数里面的不是局部变量吗? 为什么他在全局环境中, 还能够一直存在?
要彻底理解, 很关键的点的是在 "var result=f1(); "这里!!!
这里的 f1() return返回的是 f2 函数! 而函数是引用数据类型, 内存是放在堆里面的, 而 result 其实只是一个地址, 指向 f2 函数内存存放的地址, 这个时候 f2 函数就成了全局变量 result 的依赖性, f2 函数就必须保存在内存当中, 不被 垃圾回收 清理掉。
而 f1 函数是 f2函数的父对象, f2 存在, f1 就也要存在, 所以 f1 函数的内部变量就不会被 垃圾回收 清理掉!!
所以说, 刚才 “解除闭包的引用, 释放数据内存” 例子中的 "myClosure = null; // 移除对闭包的引用 "就是用来释放 f1 函数的内部变量。提一个极端的例子就可以理解为什么内存泄漏需要被重视:
在一个网页应用中,你可能有一个循环,该循环不断创建新的闭包,而这些闭包都引用了外部作用域中的大型数据结构(比如大型数组或对象)。如果这些闭包被不当地存储(例如,作为全局变量或DOM元素的事件处理器),那么它们将阻止垃圾回收器回收那些大型数据结构的内存,从而导致内存泄漏。
五、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
最后, 这里放我面试中考到的一道闭包代码题给大家练练:
/**
* 不能改动原代码
* f 函数里面的console.log只能执行一次
* 第二次调用开始就只能返回undefined
*/
function once(fn) {
}
var f = function() {
console.log('被执行了')
}
var onceF = once(f)
onceF() // 被执行了
onceF() // undefined
标签:闭包,f1,f2,函数,function,JavaScript,重难点,内存
From: https://blog.csdn.net/Mz0127/article/details/142177921