浅谈闭包
如何产生闭包?
为了在函数外部访问函数内局部变量,就在父函数中定义一个子函数并让它访问了父函数的局部变量,再通过父函数返回该子函数就实现了调用局部变量的效果。
简单举例
function fun1() {
let num = 1;
return function fun2() {
console.log(num++);
}
}
const add = fun1();
add();
形成的条件
- 函数嵌套(函数作参数,函数作返回值)
- 内部函数访问外部函数的局部变量
可以把闭包理解成一个容器保存了这些值(调试时候看到的closure,里面有key: value保存闭包的值)
在案例中fun2这个函数访问了父函数的num,函数销毁前,num一直存在,我们每次调用fun2(),num值+1
闭包是什么?
这是我从网上找到的一些的解释。
闭包,不同于一般的函数,它允许一个函数在立即词法作用域外调用时,仍可访问非本地变量。 –维基百科
闭包就是能够读取其他函数内部变量的函数,闭包就是将函数内部和函数外部连接起来的一座桥梁。 –阮一峰
闭包就是对函数写作时的环境引用。
闭包是通过JS回收机制保留某段作用域的一种手段。
两点知识:
- Javascript函数内部可以直接读取全局变量
- 在JS中,变量查找遵循就近原则,如果同级没有该变量,则就一层一层向父级层查找。—“链式作用域”。
闭包与GC垃圾回收
JavaScript的垃圾回收机制,简单来说就是:固定时间间隔,周期性的释放不在使用的变量所占内存。全局变量的生命周期直至浏览器卸载页面才会结束,局部变量只在函数的执行过程中存在。
垃圾回收有两个办法:标记清除和引用计数(具体不懂)。
简单点来说就是有没有被引用,没有被引用的就会被回收。
那么在闭包中,函数调用了父函数作用域的值,然后被return出去了,这个函数中使用的外部值也就没有被回收,直至函数销毁。
function fun1() {
let num = 1;
return function fun2() {
console.log(num++);
}
}
const add = fun1();
add();
结合上面的例子,父函数中的变量被子函数引用,子函数又被外部变量引用,可以说明闭包的产生和他们不被回收的原因。上面的列子中将全局的add=null就可以回收fun2了。
闭包的优缺点
闭包的优点:可以在内存中维持一个变量,由于闭包,定义一个父函数中的变量只有通过子函数访问到,无法通过其他途径访问修改,从而达到了保护变量安全的效果。
闭包的缺点:容易造成内存泄漏,所以不能滥用闭包。
闭包的场景
看网上还有一些场景:私有变量,安全,让变量始终保持在内存中,可以避免全局使用的变量都在window上等等,实际中的应用还不太清楚,网上资料也不多,还需磨练自己在实践中体会。
在之前写过的防抖和节流中,共同维护了一个定时器对象timer,其中产生了闭包,使得我们每次频繁操作的时候,先判断定时器状态,再执行业务逻辑,达到防抖和节流的效果。
题目
第一道
下面两段代码的运行结果是什么?------来自阮一峰的网络日志
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc: function () {
return function () {
console.log(this.name);
};
}
};
object.getNameFunc()(); // The window
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc: function () {
var that = this;
return function () {
console.log(that.name);
};
}
};
object.getNameFunc()(); // My Object
// 使用了父级函数的that变量,形成闭包
第二道
以下代码的输出结果是什么?
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
};
}
console.log('-----a-----');
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
console.log('-----b-----');
var b = fun(0).fun(1).fun(2).fun(3);
console.log('-----c-----');
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);
运行结果