了解闭包与内存泄漏以及垃圾回收机制
什么是闭包
MDN中的解释:闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
我的理解:
- 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包
- 从广义的角度来说:JavaScript中的函数都可能是闭包
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,就会形成闭包
// 闭包不一定需要return
function outer(){
let a = 1
function foo(){
console.log(a)
}
foo()
}
let a = 2
// 在函数定义的地方开始向外查找:一定要注意是定义的地方!!!!
outer()// 1
————————————————————————————
function outer(){
function foo(){
console.log(a)
}
foo()
}
let a = 2
outer()// 2
—————————————————————————————
function foo(fn){
const a = 1
fn()
}
const a = 2
function fn(){
console.log(a)
}
foo(fn)// 2
// 但是当我们想在外部使用这个局部变量 a 的时候,我们就需要将foo给return出去
function outer(){
let a = 10
function foo(){
console.log(a)
}
return foo
}
const fn = outer()
fn()
闭包的作用
防止变量和参数被垃圾回收机制回收(变量持久化)
防止变量和参数被外部污染(变量只在闭包内部可访问)
实现数据的私有化
let count = 1
function foo(){
count++
console.log(`函数被调用了${count}次`)
}
fn()// 2
fn()// 3
count = 100 // 这个count是全局变量,任意被修改
fn() // 101
function fn(){
let count = 1
function foo(){
count++
console.log(`函数被调用了${count}次`)
}
return foo
}
const result = fn()
result()// 2
result()// 3
count = 100
result()// 4
闭包的风险
闭包会导致外部函数的变量无法被垃圾回收,从而增加内存占用。
如果滥用闭包,可能导致内存泄漏问题(注意是可能,闭包不是一定会有内存泄漏的)
了解内存泄漏
内存泄露 是指当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或者内存池的现象。内存泄漏可能会导致应用程序卡顿或者崩溃。
闭包中的内存泄漏指的是在闭包函数中,由于对外部变量的引用而导致这些变量无法被垃圾回收机制释放的情况。当一个函数内部定义了一个闭包,并且这个闭包引用了外部变量时,如果这个闭包被其他地方持有,就会导致外部变量无法被正常释放,从而造成内存泄漏。
用途
- 防抖,节流等
- vue :响应式原理
- react :hooks原理
了解垃圾回收机制
概念
- 因为内存的大小是有限的,所以当内存不再需要的时候,我们要对复进行释放,以便腾出更多的内存空间。
- 大部分现代的编程语言都是有自己的垃圾回收机制:
- 垃圾回收的英文是Garbage Collection,简称GC
- 对于那些不再使用的对象,我们都称之为是垃圾,它需要被回收,以释放更多的内存空间
- 而我们的语言运行环境,比如Java的运行环境JVM,JavaScript的运行环境js引擎都会内存垃圾回收器
- 垃圾回收器我们也会简称为GC,所以在很多地方你看到GC其实指的是垃圾回收器
- 但是这里又出现了另外一个很关键的问题:GC怎么知道哪些对象是不再使用的呢?
- 这里就要用到GC的实现以及对应的算法
常见GC算法
引用计数(了解)
- 当一个对象有一个引用指向它时,那么这个对象的引用就+1
- 当一个对象的引用为0时,这个对象就可以被销毁掉
- 这个算法有一个很大的弊端就是会产生循环引用
标记清除(我们的js引擎用的就是这个GC算法)
- 标记清除的核心思路是可达性(Reachability)
- 这个算法是设置一个根对象(其实在js中,这个根对象就是window),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于那些没有引用到的对象,就认为是不可用的对象
- 这个算法可以很好的解决循环引用的问题
了解V8的GC算法
- JS引擎比较广泛的采用的就是可达性中的标记清除算法,当然类似于V8引擎为了进行更好的优化,它在算法的实现细节上也会结合一些其他的算法
- 标记整理(Mark-Compact)
- 和“标记–清除”相似
- 不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化
- 分代收集(Generational collection)
- 对象被分成两组:“新的”和“旧的”
- 许多对象出现,完成它们的工作并很快死去,它们可以很快被清理
- 那些长期存活的对象会变得“老旧”,而且被检查的频次也会减少
- 增量收集(Incremental collection)
- 如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
- 所以引擎试图将垃圾收集工作分成几部分来做,然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的延迟
- 闲时收集(ldle-time collection)
- 垃圾收集器只会在CPU空闲时尝试运行,以减少可能对代码执行的影响。