垃圾回收机制(GC)
JS引擎会在一定的时间间隔来自动对内存进行回收(把内存释放)
JS垃圾回收机制有两种: 标记清除和引用计数
1.标记清除
工作原理: js会对变量做一个标记Yes or No的标记以供js引擎来处理, 当变量在某个环境下被使用则标记为yes, 当超出该环境(可以理解为超出作用域)则标记为no, js引擎会在一定时间间隔来进行扫描, 会对有no标签的变量进行释放(将该变量所占的内存释放掉)
工作流程:
- 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
- 去掉环境中的变量以及被环境中的变量引用的变量的标记。
- 加上标记的会被视为准备删除的变量。
- 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
2.引用计数
工作原理:对于js中引用类型的变量, 采用引用计数的内存回收机制, 当一个引用类型的变量赋值给另一个变量时, 引用计数会+1, 而当其中有一个变量不再等于这个引用类型的值时, 引用计数会-1, 如果引用计数为0, 则js引擎会将其释放掉
工作流程:
- 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。
- 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.
- 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.
- 当引用次数变成0时,说明没办法访问这个值了。
- 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
内存泄漏和内存溢出
- 内存泄漏(memory leak):是指程序在申请内存空间后,无法释放已申请的内存空间。严格来说:只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。但实际情况很多时候一些不太好的实践(或者疏忽)会导致对象的生命周期变得很长甚至导致OOM(内存溢出),也可以叫做宽泛意义上的“内存泄漏”。一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
- 内存溢出(out of memory):指程序申请内存时,没有足够的内存供申请者使用,程序执行需要的内存空间大于系统能提供的内存空间。
闭包概述
我们知道一般每次执行函数,里面的变量或者参数都会重新声明,也就意味着每次里面的变量或参数都是一个新的变量。那么如果我想让他里面的变量不回收,是不是只需要保持对函数内部声明的变量的引用,函数内部声明的变量就不会被gc回收。
根据上述代码 就是如果我的函数里面返回一个引用,这个引用内包含了外部函数的参数或变量,那么我的这个外部函数的参数他不会随着函数里面代码的块的销毁而销毁。那么我同时又知道函数也是引用数据类型,那么我们是不是可以直接返回函数(方便调用)。内部函数包含外部函数的变量,这个时候对应的变量也不会被销毁了。
简单的说,javascript允许使用内部函数——即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
创建闭包的方式,就是在一个函数内部返回一个匿名函数,该匿名函数中保持了对外部函数参数或变量或其它内部函数的引用。
function reduce(){
var i = 0
return function(){
i++
return i
}
}
//调用
let fn = reduce()
console.log(fn()) //1
console.log(fn()) //2
闭包的特性
-
可以通过内部函数的引用 ,在调用内部函数的过程中访问外部函数的参数和变量。
-
外部函数的参数和变量不会被gc 回收。
-
内部函数时刻保持对应外部函数的引用。
闭包的优点
-
不会被gc回收
-
扩大外部函数内变量的使用范围
-
避免函数内的变量被外部所污染
闭包的缺点
-
消耗内存,会导致内存泄漏,甚至内存溢出
-
内部函数要时刻保持对应外部函数的引用
闭包的实现
function reduce(){
var i = 0
return function(){
i++
return i
}
}
//调用
let fn = reduce()
console.log(fn()) //1
console.log(fn()) //2
通过以上示例我们可以看到,执行 reduce 函数返回了一个内部函数,保持了对外部函数的变量 i 的引用,用一个变量接住返回的函数,经过两次调用,可以看到,改变的是同一个值(外部函数中的变量 i)