深入理解js闭包
在面试中遇到两个闭包的题目:
let total = 0;
let result = [];
let a = 3;
function foo(a){
let i = 0;
for(;i<3;i++){
result[i] = function(){
total += i*a;
console.log(total);
}
}
}
foo(1);
result[0]();
result[1]();
result[2]();
手写代码,实现单次执行a()就输出1;偶次执行a()就输出2;不可用全局变量;
a() ; //1
a() ; //2
a() ; //1
a() ; //2
第一题的答案是:3,6,9;执行foo(1)的时候,传入的数据a = 1;执行for循环,i最终变成了3;result里面存放的是一个个function;当执行时,i和a始终是1和3;
第二题的思路:用一个标识符i来标示执行的次数;用(i%2)来判断奇偶,由于i不能是全局变量,为了保存住i的值,所以要用到闭包;
最初学习闭包的时候,是从AO、GO的角度去理解闭包的,但是这样理解方式是ES3的,已经不能解释现在很多语法了。
在学习闭包之前,要明白两个知识点:作用域、作用域链
作用域
变量作用域两种: 全局变量和局部变量
- 函数内部可以直接读取全局变量
- 函数外部无法读取函数内部的局部变量
作用域链
简单说就是作用域集合 ,当前作用域 -> 父级作用域 -> … -> 全局作用域 形成的作用域链条 。他保证了变量对象的有序访问。
闭包的概念
《你不知道的Js》解释:当函数可以记住并访问所在的词法作用域,即使函数是在当前记法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
闭包就是能够读取其他函数内部变量的函数,函数没有被释放,整条作用域链上的局部变量都将得到保留 ; 闭包其实就是将函数内部和函数外部链接的一座桥梁
特性
- 闭包的外层是个函数,闭包内部有函数
- 闭包会return内部函数,闭包返回的函数内部不能有return
- 执行闭包后,闭包的内部变量会存在,闭包内部函数的内部变量会回收
闭包好处和用途
好处:
- 希望一个变量长期驻扎在内存中
- 避免全局变量污染
用途: 匿名的自执行函数 (封装工具类的时候可以运用)、对数据进行缓存
注意事项
滥用闭包,会造成内存泄露的问题;内存泄露的意思就是变量无法正常地从内存里面释放掉。
垃圾回收机制
垃圾回收的两种实现方式垃圾回收有两种实现方式,分别是标记清除和引用计数
标记清除
当变量进入执行环境时标记为“进入环境”,当变量离开执行环境时则标记为“离开环境”,被标记为“进入环境”的变量是不能被回收的,因为它们正在被使用,而标记为“离开环境”的变量则可以被回收;
function func3 () {
const a = 1
const b = 2
// 函数执行时,a b 分别被标记 进入环境
}
func3() // 函数执行结束,a b 被标记 离开环境,被回收
引用计数统计
引用类型变量声明后被引用的次数,当次数为 0 时,该变量将被回收
function func4 () {
const c = {} // 引用类型变量 c的引用计数为 0
let d = c // c 被 d 引用 c的引用计数为 1
let e = c // c 被 e 引用 c的引用计数为 2
d = {} // d 不再引用c c的引用计数减为 1
e = null // e 不再引用 c c的引用计数减为 0 将被回收
}
但是引用计数的方式,有一个相对明显的缺点——循环引用
function func5 () {
let f = {}
let g = {}
f.prop = g
g.prop = f
// 由于 f 和 g 互相引用,计数永远不可能为 0
}
像上面这种情况就需要手动将变量的内存释放
f.prop = null
g.prop = null