JavaScript之闭包
什么是闭包
闭包是JavaScript的一个显著特性。简单地说,闭包是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域。这意味着当前作用域总是能够访问外部作用域中的变量。 因为函数是JavaScript中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。
闭包的作用
- 闭包能创建独立的作用域,并且保持该作用域中即将被垃圾回收的变量。
<button>1</button>
<button>2</button>
<button>3</button>
<script type="text/javascript">
const btns = document.querySelectorAll('button');
for(var i = 0; i < btns.length; i++) {
const btn = btns[i];
btn.addEventListener('click', function() {
console.log(i);
}, false);
}
</script>
这是一个很经典的问题,因为var关键字声明的变量属于全局变量,所有的click事件都指向同一个变量i,不管你点击哪个按钮,控制台打印出来的结果都是i的最终结果3。而这个问题有两种简单的解决方案:let关键字 和 闭包。
let关键字是ES6的特性,它所声明的变量属于块级作用域,作用域结束后便销毁,在作用域外直径引用便会报错。回到刚刚的栗子中,如果用let关键字声明变量i,每个click事件都对应一个独立的i,因此打印出来的值分别为:0、1、2。
同样,这个栗子还能用闭包来解决这个问题:
const btns = document.querySelectorAll('button');
for(var i = 0; i < btns.length; i++) {
(function(index) {
const btn = btns[i];
btn.addEventListener('click', function() {
console.log(index);
}, false);
})(i);
}
在for函数循环体内,每次将i值赋值到闭包内的变量index上,所以click事件所指向的值不再是全局变量i了,而是各自闭包内的index,打印的结果自然也变成了0、1、2。
值得注意的是该index变量并没有因为循环体的结束而被销毁,而是因为index变量仍被click事件引用,而被闭包保存了下来。
- 封装对象私有变量
众所周知,JavaScript并不具备用以将成员声明为公有或私有的任何内置机制。然而可以利用闭包实现这种特性。
在JavaScript中,只有函数具有作用域,在一个函数内部声明的变量是无法在函数外部直接访问的。正是因为这样的特性,可以使得我们实现“私有成员变量”的特性。
function Counter() {
let result = 0;
let count = 0; // 调用次数
this.add = function(num) {
result = result + num;
count++;
};
this.getResult = function() {
return result;
};
}
let counter = new Counter();
counter.add(1);
counter.add(2);
counter.add(3);
counter.getResult(); // 6
在这个栗子中,Counter对象中声明的result、count变量就是私有变量,counter实例无法直接访问和改变这两个变量,若想获取私有变量的值只能通过getResult这种特权方法才能拿到。
闭包带来的问题
因为闭包会使得变量被保留在内存中,对内存的开销会增大,使用不当会造成内存浪费甚至内存泄漏。