背景
闭包是为了解决,子函数未被调用而父函数已经执行完毕了,需要销毁时发现子函数引用了自己作用域的变量
实现原理
咱一点点解释
- 为什么子函数没调用而父函数却执行完了?
// 正常来说 顺序执行 没问题
function father(){
const a = 1;
function child(){
console.log(a);
};
child();
}
// 但是js 里 函数可以return出去!
function father(){
const a = 1;
return function child(){
console.log(a);
};
}
let child = father();
child 就可以任意时候执行了
那么问题来了father 销毁吗? 销毁了的话,执行child()时, console.log(a) 打印 undefined 了, 这可不行啊.
于是便有了闭包
- 通俗理解就是: 父函数:" 你需要a? 那我打包给你,你存起来带着走吧,我销毁了,不多占内存了".
- 专业的术语就是: 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一 起,这样的组合就是闭包(closure)
闭包们储存在属性[[scopes]]里,复数也可说明打包的闭包并不会合并,如果你用到了父作用域的a,爷的x,祖师爷的y,那就会产生三个记录.
(图源自开头文章)
而需要注意的是无论是否引用外部变量,Global全局作用域总是会打包进去,这也是为什么函数无论在哪调用,都能访问到全局变量的原因
(图源自开头文章)
2.不销毁父函数行吗?
不行,性能消耗太多,虽然闭包也会消耗内存,但相对而言肯定更优化,具体闭包是存在 堆 中的,所以使用不当可能会造成内存泄漏
3.都打包哪些变量?
只会打包在静态作用域链中用到的变量.这里的静态作用域链不是啥高级东西,解释一下,首先明确每一个函数,代码块等会形成一个作用域,而类似于函数的互相嵌套,作用域链也分父子,当在自己的作用域中找不到某个变量,就去问爸爸要,还没有就去问爷爷要,一直找到全局作用域,这就是作用域链.
4.Eval
eval()函数会打包所有作用域,因此会产生性能问题,这是因为无法检测会用到哪些调用,毕竟你传入一个字符串是动态的,只能全部打包,因此少用\
闭包的具体应用
防抖节流函数
闭包可以用于在对象中创建私有变量
let 的实现原理,let用babel转换后就是用闭包实现的
如有错误,欢迎指出