“这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战”
引子
很多初学者都会觉得闭包的概念很麻烦复杂,甚至还有人说闭包会引起内存泄露,让人对闭包即好奇又畏惧,其实闭包一点都不神秘也不难,甚至可以说在js中所有的函数都是闭包,闭包是平易近人的。
我所见过的一些关于闭包比较生动形象的比喻
1.闭包是穷人的对象。
2.闭包是间谍,打入了敌方的内部阵营,为我方带来情报。
3.闭包是爱丽丝梦游仙境,去过大臣们永远没有去过的远方,归来后,给大臣讲述他们永远没有
见过的仙境。
4.闭包是富二代,继承了父辈的资产,任期挥霍,可是就算是从石头缝里面蹦出来的小孩,也有空气自由呼吸,阳光自由享受,而空气和阳光才是最重要的东西,从这个角度而言,所有的人都是闭包。
5.大自然界水都是从高处往低处流的, 而聪明又调皮的人类有一天却希望水可以从低处流向高处,终于有一天人们发明了水泵,从而人们用水更加的自由,更加的随心所欲。这里的水泵就是闭包。
闭包是什么?
1.闭包通过对变量的引用,从而阻止垃圾回收的机制。
2.函数如果引用了外部作用域的自由变量就构成了一个闭包。
个人的理解,闭包的包指的函数的小背包,背包里面有着外层作用域的变量,关键是如何去理解这个闭字,闭真的很像php和java中类的私有变量,该变量只能通过引用了该变量的闭包去读取或改变,这里的引用了该变量的闭包的方法可以是多个,对应于php或java类中的多个方法,每个闭包代表类中的一个方法,所以有人称闭包为穷人的对象。
function foo(){
let test =1;
const bar1=function(){
test ++;
console.log(test);
}
const bar2 = function(){
test = test+100;
console.log(test);
}
return [bar1, bar2];
}
let arr = foo();
arr[0]()//2
arr[1]()//102
arr[0]()//103
arr[1]()//203
词法作用域和动态作用域
闭包是词法作用域的必然结果,理解了词法作用域,也就基本理解了闭包,作用域有两种,一种是词法作用域,另一种是动态作用域。词法作用域是在写代码或者定义时确定的,而动态作用域是在运行时确定的。词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
在js这门语言中,只有词法作用域,没有动态作用域。使用动态作用域的语言有perl、lisp等。
var name = '小明';
function foo() {
console.log(name); // 会输出小明还是小红?
}
function bar() {
var name = '小红';
foo();
}
bar();
打印出了小明, 说明foo函数调用时变量name使用的是定义时的作用域,而不是调用时所处的作用域, 这就是词法作用域。
为什么很多人觉得闭包很难理解?
个人觉得主要有以下两个原因:
1.一些书和文章对闭包解释的过于晦涩难懂。
2.虽然js语言里面只有词法作用域没有动态作用域,但是js中的this、eval、with的行为却很像动态作用域,尤其是this,指向调用他的对象,而与定义时所处的对象没有关系,使用的频率还很高,这很容易让初学者搞混,从而加大了对闭包的理解的难度。
闭包的用途
1.封装私有变量
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName()); //default
person.setName("abruzzi");
print(person.getName()); //abruzzi
2.模仿块级作用域
var arr = ["a", "b", "c", "d", "e"];
for (var i = 0; i < arr.length; i++) {
(function(j) {
var item = arr[j];
setTimeout(function() {
console.log(item);
}, 1000 * (i + 1));
})(i);
}
当然,因为setTimeout是异步任务,如果不加闭包,打印出来的全部是e, 这里使用闭包强制使得函数记得当时的i变量,当然在ES6推出let关键字之后,用let更加的简便。
3.储存变量
闭包的另一个特点是可以保存外部函数的变量,内部函数保留了对外部函数的活动变量的引用,所以变量不会被释放。比如防抖的实现就依赖于闭包
function debounce(fn, wait) {
var timeout //闭包的方式定义一个全局变量,是实现防抖函数的核心
return function () {
var context = this,
args = arguments
clearTimeout(timeout)
timeout = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}
window.addEventListener('scroll', debounce(scrollHandler, 500))
这个防抖函数实现的核心是把变量timeout储存起来,如果不使用闭包,每次scroll事件触发时,都会重新初始化timeout变量,就不可能达到防抖的效果。
对闭包的误解,闭包是否真的引起内存泄露?
闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集,从而导致内存无法进行回收,这是IE的问题,闭包和内存泄漏没半毛钱关系。更多详情可以参考闭包会造成内存泄漏吗?
总结
闭包是词法作用域的必然结果,引用了外部自由变量的函数就是闭包,闭包一点都不神秘更不可怕。