前 言
在前端开发中闭包是一个很重要的知识点,是面试中一定会被问到的内容。
一、闭包的定义
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
简单来说,闭包 就是一个定义在函数中的函数。
例如:
function Clos(){
var age = 32;
function getNum(){
console.log(age);//32
return age;
}
return getNum;
}
var get_A = Clos();//get_A将会得到Clos()最后的运行返回的结果.即getNum()函数体
get_A();//拿到了变量age的值:32
console.log(age);//报错:ReferenceError: age is not defined
上面这段代码中的getNum()函数
就是一个闭包,它定义在名为Clos()函数体中。
二、闭包的作用
函数内部定义的变量属于局部变量,局部变量的生命周期是:当它所在的函数被调用的时候,就是开始,当调用执行一旦结束,局部变量就会被释放,当我们需要函数内部变量时,他已经被释放了,读取不到了,这个时候怎么解决?我们就要想办法延长他的生命周期。
闭包的目的也可以说就是这个,延长局部变量的生命周期,当函数执行完毕以后,局部变量不可以被内存释放,然后让外部可以访问到这个变量。
我们还是先回到上面的代码进行分析:
代码在运行到
console.log(age);
这句话本意是我们本想调用变量age
进行输出。但是编译结果报错:age未定义。
这是因为变量age
定义在Clos()函数
中,作用域也就局限于Clos()函数中。变量age
相当于Clos()函数
的 私有属性(private value)。
那么我们想要调用age就没有办法了吗?
这时,闭包的作用就体现出来了。Clos()函数
的返回值指向闭包函数getNum()
。因为闭包函数getNum()
定义在Clos()函数
中,所以我们可以用它来获取变量age
的值。闭包就相当于公有方法(Public Method) 用于给外界提供接口来访问内部数据。
三、闭包的利与弊
先来说说弊端
也正是由于闭包机制,使得JavaScript的垃圾回收机制不会收回Clos()函数
所占的资源,因为闭包执行依赖的变量在它里面。如果代码中闭包过多,会对内存造成溢出,对页面的加载造成影响,所以对于闭包的使用需要谨慎。
再来看看好处
JavaScript在多人协作开发项目时,如果定义过多的全局变量,有可能造成全局变量命名冲突,使用闭包可以很好的解决。
//公司程序员小李和小王一起协作开发项目
var name = '小李';//小李定义了一个全局变量
//var name = '小王'; //如果小王继续定义相同名字变量就会覆盖小李写的变量name的值
var private = (function(){
var name ='小王';
function getName() {
return name;
}
return getName;
}());
console.log("小王定义的name:"+private());
console.log("小李定义的name:"+name);
上面代码中,第一个var name='小李'
定义在window全局作用域中。第二个var name = '小王'
定义在函数体中,作用域互不影响,这就避免了全局污染。
总结
闭包主要有以下几个特点:
-
函数套函数,闭包一定有嵌套函数。闭包的目的就是访问函数内部的局部变量,如果不定义局部变量,那就达不到我们的目的——延长变量生命周期。
-
外层函数一定有局部变量,且内层函数一定操作了外层函数的这个变量.
-
外层函数一定把内层函数返回外部,使用return。
在做闭包的问题时,我们还需要注意以下两点:
- 外层函数被多次调用,都会创建新的作用域,也就是说内层函数操作的外层函数的局部变量之间是不会影响的
- 外层函数返回的内层函数被调用几次,内层函数操作的外层函数的局部变量就会变化几次
//模拟了用户去银行存钱并查询余额的功能
function Person(name){
var money = 0;
function setMoney(m){
money = m;
}
function getMoney(){
return money;
}
return {
name:name,
setMoney:setMoney,
getMoney:getMoney
}
}
//第一次调用
var LiLei = person('李磊');
LiLei.setMoney(4500);//用户李磊存入4500元
//第二次调用
var WangWu = person('王武');
WangWu.setMoney(7000);//用户王武存入7000元
console.log(LiLei.getMoney());//得到并输出账户余额:4500
console.log(WangWu.getMoney());//得到并输出账户余额:7000
var LiLei = person('李磊');
var WangWu = person('王武');
这句代码模拟了两个不同用户开户的过程,他们共同调用一个函数,操作变量money
值的修改。正因为每次重复调用会开辟新的作用域,所以李磊和王武的钱不会存到一起去。李磊和王武的钱分别保存在两个不同的内存空间中。