js闭包的理解

125 阅读5分钟
  • 如果在内部函数使用了外部函数的变量,就会形成闭包。闭包保留了外部环境的引用
  • 如果内部函数被返回到了外部函数的外面,在外部函数执行完之后,依然可以使用闭包里的值

闭包的形成

在内部函数使用外部函数的变量,就会形成闭包,闭包是当前作用域的延伸

//例1:
function a(){
var aa = 100;
function b(){
console.log(aa);//代码进入到这一行时,会产生闭包,但是一旦b()调用结束,该闭包也结束了
}
b();
}
a();
//所以这个例子就是产生了闭包,但是没有保持住

  • 执行到13行会形成闭包,闭包对象里会存放使用的变量aa。也就是在形成闭包中,访问了aa,那么aa就会被闭包对象当成属性存放进来。此时闭包对象里存放的就是a函数的AO对象的属性aa和属性值100。

  • 当函数调用完(执行到15行时),闭包消失并没有保持住,说明闭包对象里的数据没有被a这个外部函数返回到全局环境GO里

  • 闭包对象里包含了内部函数b中使用了外部函数a中的变量,这个变量就会作为闭包对象的属性
2:
function a(){
var aa = 100;
function b(){
console.log(b);//此时在aAO里找到了b变量(函数),说明产生了闭包,但还是没有保持住
}
b();
}
a();

分析过程:1.产生了aAO对象
	预编译:aAO:{aa:100,b:function}

		2.产生了bAO对象
	预编译:bAO:{}
	作用域链:
	`[[scopes]]`- 0:bAO
				- 1:aAO
				- 2:GO

//该例子答案是:产生了闭包,闭包对象里保存了一个属性,这个属性是b,这个属性的值是function,最后该闭包没有保持住,调用完后消失了

  • 此处有争议:关于是否形成闭包,因为闭包没有返回到函数外部,没保持住,但调试时的确是产生了闭包,所以见仁见智
3:
function a(){
var aa = 100;
function b(){
var b = 200;
console.log(b);//不会形成闭包
}
b();
}
a();

分析过程://1. 产生了aAO
		aAO:{aa:100,b: function}
		
		   2. 产生了bAO
		bAO:{b200}
		[[scopes]] 0:bAO
				   1:aAO
				   2:GO

  • 不会形成闭包,由于在b函数内部定义了变量b,打印时直接使用的是内部函数的变量b,不会形成闭包

闭包的销毁

  • 当函数调用完之后,闭包就会被销毁掉

闭包的保持

  • 如果希望在函数调用后,闭包依然保持,就需要将内部函数的数据(变量/函数)返回到外部函数的外部(把这个数据从里面函数能够拿出来,拿到外面来使用,把这个数据延续下去)
例:
function a(){
var num = 0;
function b(){
console.log(num++); // 形成闭包
}
return b;
}
var demo =a();
console.dir(demo);
demo();// b()  0
demo();// b()  1

执行过程:
GO:{demo:b, a:function}

1.产生aAO
aAO:{num:2,b:function}

2.产生bAO------产生后在执行一个demo()后被销毁
bAO:{}
[[scopes]] 0:bAO
		   1:aAO
		   2:GO

2.产生一个新的bAO
bAO:{}
[[scopes]] 0:bAO
		   1:aAO
		   2:GO

  • 调用a函数,将内部函数b返回,保存在函数a的外部
  • 调用demo函数,实质上时候调用内部函数,在函数b的[[scopes]]属性中可以找到闭包对象,从而访问到里面的值

总结

使用闭包要满足两个条件:

  • 闭包要形成:在内部函数使用外部函数的变量
  • 闭包要保持:内部函数要返回到外部函数的外面

闭包的应用

闭包的两面性

  • 好处:在函数外部是没办法访问函数内部的变量的,设计闭包最主要就是解决这个问题
  • 坏处:有时候不注意使用了闭包,会导致出现意想不到的结果

闭包的应用

  • 在函数外部访问私有变量
    • 本来在函数a的外部(全局)不能直接访问内部变量num,通过闭包就可以使用num变量了
function a(){
var num = 0;
function b(){
console.log(num++); // 形成闭包
}
return b;
}
var demo =a();
console.dir(demo);
demo();
  • 实现封装
    • 定义了一个函数Person,一个内部变量uname,两个内部函数,返回内部函数,是利用闭包的特性。这样在Person函数外部,通过get和set两个方法(接口)对变量uname进行操作,这里是面向对象里的封装思想
function Person(){
var uname;
function setName(uname){
this.uname=uname;//产生了闭包
}
function getName(){
return this.uname;//产生了闭包
}
return{
getName:getName,
setName:setName,//通过闭包返回,保持了闭包
}
}
var xiaopang = Person();//Person函数对外暴露了两个接口(方法)
xiaopang.setName('xiaopang');
var uname = xiaopang.getName();
console.log(uname);
  • 防止污染全局变量

闭包机制

  • 函数执行一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为'闭包'
  • 形成一个不销毁的私有作用域(私有栈内存)才是闭包(绝大多数人的理解)-不推荐用该概念

闭包写法

大部分开发者写闭包的方式

  • 柯理化函数
function fn(){
return function(){

}
}
var f = fn();
  • 惰性函数
var utils = (function (){
return{   }
})();

闭包实战应用

真实项目开发中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用。因为产生一个闭包就会产生一个不销毁的栈内存,就会存在一个不销毁的堆内存,会消耗性能。

闭包的作用

  1. 闭包具有保护作用:保护私有变量不受外界干扰
    1. 在真实项目中,尤其是团队协助开发的时候,应当尽可能的减少全局变量的,以防止相互之间得到冲突,全局变量的污染。那么此时我们可以把自己这一部分内容封装到一个闭包中,让全局变量转化为私有变量
    (function (){
    var n = 12;
    function fn(){
    ...
    }
    })();
    
    1. 当我们封装类库插件函数的时候,也会把自己的程序都存放到闭包中保护起来,防止和用户程序冲突,但是我们又需要暴露一些方法给客户使用
  2. 闭包具有保存作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用