前端基础之闭包

168 阅读3分钟

在前端开发中,作为一个前端开发人员。必然要懂闭包。闭包在javascript的这门语言中使用得非常普遍。它可以用来模拟class类的私有属性,传递变量等等用途。那么什么是闭包呢,圣经(javascript权威指南)中记载函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包。简单的理解可以认为是能够访问函数内部变量的函数。通过闭包这一个特性我们可以实现一些高级的编程。涉及到闭包,就不免涉及到作用域的问题,在js 中,变量的作用域是让开发这比较头大的问题,而学习闭包则难免会和这些问题打交道。下面看例子:

var name = "global name";
function wrapper(_name){
    var name = _name;    
    function closure(){
        console.log(name) ;//此处输出 local name
    }
    closure() ;
}
wrapper("local name") ;

这里可以看到 wrapper()输出了 local name;为什么是输出了local name而不是global name 呢,这里可以简单的理解为局部变量优先;在closure()方法执行的时候会在当前的closure()的作用域内查找变量name,如果找不到就向上一级查找。以此类推,如果找到了就输出来,找不到的话就会报错了。

我们把这个例子再变一下,看看下面的输出会是什么

var name = "global name";
function wrapper(_name){
    var name = _name;    
    function closure(){
        console.log(name) ;//此处仍然输出 local name
    }
    return closure;
}
var _closure = wrapper("local name") ;
_closure();

在这里我们把closure函数返回来了,并且执行了返回的函数。但是仍然输出了local name,这是因为javascript函数的执行用到了作用链域,作用链域是在函数定义的时候创建的。并且函数定义时的作用链域到函数执行时仍然有效。简单来时函数访问的作用链域在你定义函数的时候就已经确定了,后续不管怎么调用都是你现在定义的这个作用链域。这个就是闭包的强大之处,下面一个例子:

    function User(name,age){
        var privateName = name ;
        this.setName = function(_name){
            privateName = _name ;
        }
        this.getName = function(){
            return privateName ;
        }
        this.age = age ;
    }
var user = new User("javascript权威指南",6);
console.log(user.name); // undefined
console.log(user.age); // 6
console.log(user.getName()); // javascript权威指南
user.setName("java从入门到精通")
console.log(user.getName()); //java从入门到精通

在这里我们通过闭包来模拟了user的私有变量。可以看到直接通过user.name是访问不到name属性的,结果自然就输出undefined。这个就是闭包的一个使用。

我们还可以通过闭包来保存传递参数,下面的例子再使用中比较常见:

var list = [] ;
for(var i = 0 ;i<10 ; i++){
    list[i] = function(){
        console.log(i) ;
    }
}
console.log(list[5]()) // 10

这里将会输出10 ,如果想要输出对应的下标可以有两种解决方法,一个是动态的传参,一个是使用ES6 let 语法。

var list = [] ;
for(let i = 0 ;i<10 ; i++){
    list[i] = (function(i){
        return function(){
            console.log(i) ;
        }
    })(i);// 这是用了一个立即执行函数并且动态传入循环的参数 i
}
console.log(list[5]()) // 5
var list = [] ;
for(let i = 0 ;i<10 ; i++){ //注意此处用了ES6 let 语法
    list[i] = function(){
        console.log(i) ;
    }
}
console.log(list[5]()) // 5

我们还可用闭包来动态的注入参数,比如

function wrapper(module){
	return function (e){
		console.log(module+"模块的"+e.target.tagName+"被点击了"); // 登录模块的DIV被点击了
	}
}
document.addEventListener("click",wrapper("登录"));

这样我们通过wrapper 方法来生成了一个函数,并且向这个函数里面注入module字段。这些都是闭包的一些常用方法。简单来说,闭包就是可以访问函数的函数。理解这些之后,应付日常的开发就游刃有余了。