1

1 闭包的定义

维持了自由变量不被释放的函数, 称为闭包,(自由变量指不在自身上下文,也不在全局上下文中的变量)。

那么闭包函数的特点在哪里,我们知道函数在创建的时候,它的[[scope]]属性就已经确定并不可以改变,所以闭包函数在创建的时候就保存了上级的作用域链,闭包函数通过作用域链去寻找使用到的变量,正常情况下,函数在执行完毕后,将销毁函数的执行上下文,但是由于闭包函数的存在,包含闭包函数的上级函数执行完毕后,如果闭包函数还存在,那么这个上级函数的作用域中的变量仍然保留在内存中供闭包函数访问。

注意:
由定义可以知道,闭包函数肯定是定义在函数中,才可能有上级的函数作用域可以访问,否则上级作用域就是全局作用域。全局作用域中的变量本身就一直在内存中,所以访问全局作用域中变量的函数不能称为闭包。

var name = 'global';   
function func1() {
    var name1 = 'func1';
    console.log(name);
    console.log(name);
    function func2() {
        console.log(name);
        var name2 = 'func2';
        function func3() {
            console.log(name2);
        }
        func3();
    }
    func2();
}
func1();

上面代码中,func3为闭包函数,因为它访问了上级函数作用域中的变量name2,func2不能称为闭包函数,因为它们访问的是全局作用域中的变量name。

2 常见的闭包场景

2.1 维持变量的闭包

var person = (function(){
    var _name = 'yangyiliang';
    var _age = 18;
    return {
        getName: function () {
            return _name;
        },
        getAge: function () {
            return _age;
        },
        addAge: function (num) {
            return _age += num;
        },
        reduceAge: function (num) {
            return _age -= num;
        }
    }
})();
 
console.log(person.addAge(5)); // 23
console.log(person.reduceAge(3)); //20

上面的代码中,首先外层包裹了一个匿名立即执行函数,创造了一个上级函数作用域,getName和getAge方法都是在其内部,并且访问了上级函数作用域中的变量,所以是闭包,所当匿名函数执行完毕后,本该销毁的执行上下文,却因为闭包函数而保留了作用域中的_name和 _age变量, 通过addAge 和reduceAge的结果发现,两个闭包共用保留的作用域。

2.2 维持参数的闭包

function makeSizer(size) {
    return function() {
        document.body.style.fontSize = size + 'px';
    };
}
 
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
 
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

上面的代码中,makerSizer函数的返回值即为闭包函数,闭包函数访问上级函数作用域中的参数。
2.3 循环创建闭包常见错误

function bind() {
    var arr = document.getElementsByTagName("p");
    for(var i = 0; i < arr.length;i++){
       arr[i].onclick = function(){
            alert(i);
       }
    }
}  
bind();

上面的代码中,假设arr的length为5,想要实现的功能是点击5个P标签分别alert 0,1,2,3,4。但是事实上得到的结果却是都alert 5。

造成这种结果的原因就是绑定的多个onclick函数是闭包函数,他们共同使用保留的上级函数作用域中的变量 i 。 for循环执行结束后 i 的值即为5。

要想解决这种错误,就让这些闭包保存不同的上级作用域即可。

function bind() {
    var arr = document.getElementsByTagName("p");
    for(var i = 0; i < arr.length;i++){
        (function (i) {
            arr[i].onclick = function(){
            alert(i);
            }
        })(i);
    }
}  
bind();
或者
function bind() {
    var arr = document.getElementsByTagName("p");
    for(var i = 0; i < arr.length;i++){
        arr[i].onclick = (function(i){
            return function () {
                alert(i);
            }
        })(i);
    }
}  
bind();

如果觉得有收获请关注微信公众号 前端良文 每周都会分享前端开发中的干货知识点。


传播正能量
238 声望30 粉丝