什么是闭包
MDN的解释:闭包是函数和声明该函数的词法环境的组合。
简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的所有局部变量组成。
理解闭包的关键在于:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。
如何产生一个闭包函数
创建闭包最常见方式,就是在一个函数内部创建另一个函数。
function outer() {
let name = "hello"; // 闭包创建时所能访问的局部变量
function sayHello() { // 闭包函数
alert(name);
}
return sayHello; // 返回闭包函数
}
let myFunc = outer();
myFunc();
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。
outer有了myFunc的引用,内存一直得不到释放,咋办呢?这样的函数多了是不是会造成内存溢出?
手动释放一下:
myFunc = null;
闭包的注意事项(如何防止内存泄漏)
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
let add5 = makeAdder(5);
let add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
add5 = null;
add10 = null;
add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。
最后通过 null 释放了 add5 和 add10 对闭包的引用。
在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。
闭包中的this对象
let name = "window";
let obj = {
name: 'object',
getName: function() {
return function() {
return this.name;
}
}
}
obj.getName()(); // window
在上面这段代码中,obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。
window才是匿名函数功能执行的环境。
如果想使this指向外部函数的执行环境,可以这样改写:
let name = "window";
let obj = {
name: 'object',
getName: function() {
var that = this;
return function() {
return that.name;
}
}
}
obj.getName()();
函数内部的定时器
当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。
(function() {
let a = 0;
setInterval(function(){
console.log(a++);
}, 1000)
})()
闭包的用途
模拟块级作用域
var isShow = true;
if(isShow){
var a=1000;
console.log(a);
}
console.log(a); // 在if定义的变量在外部可以访问
(function(){ // a在外部就不认识啦
var isShow = true;
if(isShow){
var a=10000;
console.log(a);
}
})();
console.log(a); // 报错,无法访问
让变量的值始终保持在内存中,对结果进行缓存
function fn(){
let count = 0;
return function(){
count++;
return count;
}
}
let add=fn();
add(); // 1
add(); // 2
add(); // 3
封装工具函数
let counter = (function(){
let privateCounter = 0; // 私有变量
function change(val){
privateCounter += val;
}
return {
increment:function(){ // 三个闭包共享一个词法环境
change(1);
},
decrement:function(){
change(-1);
},
value:function(){
return privateCounter;
}
};
})();
counter.value(); // 0
counter.increment();
counter.value(); // 1