作为前端er,在学习JS过程中,对于闭包的理解是很重要也是很基础的一件事。本篇主要从闭包的定义、原理、优缺点以及应用场景说一下个人对闭包的理解。
定义: 有权访问另一个函数作用域的函数。
原理: 一个函数创建时,会创建一个包含全局变量对象的作用域链保存在内部的scope属性中。函数调用时,通过复制scope中的对象构建执行环境的作用域链。然后,自身的活动对象被推至执行环境作用链的顶端。闭包是在函数被返回时,初始化外部函数活动对象及全局变量对象进作用域链。外部函数执行完毕后,作用域链会被销毁,但活动对象依然留存在内存中,因为闭包还有对它的引用。
优缺点: 优点在应用场景中一起合并介绍,缺点主要是闭包可能会引起内存泄漏(原因是在IE9之前,IE的Java引擎使用的是COM对象,并且使用的垃圾回收机制是引用计数。因此,如果在闭包的作用域内如果存在一个HTML元素,那就有可能出现内存泄漏);并且由于携带了外部函数的作用域,会比其它函数占用更多的内存。
内存泄漏:
function assignHandler(){
var ele = document.getElementById('element');
ele.onclick = function(){ //ele的onclik属性指向闭包
console.log(ele.id); //闭包指向ele的id属性,形成循环引用,造成内存泄漏
}
}
使用场景:
- 如果将函数当作值类型到处传递,会看到闭包。在定时器,事件监听器等,只要使用了回调函数,就在使用闭包。
function wait(message){
setTimeout(function timer(){
console.log(message);
}, 1000);
}
- 模块。隐藏函数的私有变量,通过公共的API来访问或修改内部变量。
function CoolModule(){
let something = 'cool'; //私有变量
let another = [1, 2, 3]; //私有变量
function doSomething(){
console.log(something);
}
function doAnother(){
console.log(another);
}
return {
doSomething,
doAnother
}
}
let foo = CoolMoudule(); //外部函数已经执行完毕,但内部变量还没被回收,因为doSomething和doAnother函数还持有该变量
foo.doSomething();
foo.doAnother();
- 避免全局变量的污染。
var a = 1;
function B(){
a ++;
}
function B(){
var a = 1;
return function(){
return a++;
}
}
- 使用 I I F E构造独立作用域。
for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
}, 1000); //最终打印5个5
}
//正确打印0,1,2,3,4
for(var i = 0; i < 5; i++){
(function(j){ //构造一个独立作用域
setTimeout(function(){
console.log(j);
}, 1000)
})(i);
}