闭包:闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量
闭包三要素:
-
嵌套结构的函数
-
内部函数访问了外部函数的变量
-
在外部函数的外面,调用内部函数
/*闭包的常见写法*/
function fun(){
var name = "haha";
function innerFun(){
console.log(name);
}
return innerFun;
}
fun()();
/*显然当执行fun()();语句的时候,控制台会打印"haha"出来*/
我们再来看看另外有段代码,这段代码跟上面的代码很相似,但这段代码并没有突出闭包的特性。
function fun(){
var name = "hahahah";
function innerFun(){
console.log(name);
}
innerFun();
}
fun();
/*显然当执行fun()的时候,控制台会打印"hahahah"出来*/
/*虽然能打印结果,但这段代码中其实是在fun函数执行的时候innerFun函数在fun函数内部执行的*/
因为innerFun
函数是在fun
函数里面执行的,所以innerFun
函数所引用的fun
函数里面的name
变量其实是在fun作用域里面的,所以这段代码并没有突出闭包的特点。
在分析代码段之前,我们先回顾两点知识:
-
从之前的学到的课程中我们了解到
js
的作用域分两种,全局和局部,在js
作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的。简言之,小范围的作用域可以访问到大范围的作用域,但大范围的作用域不能够访问小范围的作用域。 -
我们所经常用到的
return
可能没太注意它所返回的任何类型的数据是返回到哪里的, 在js
中其实return
所返回的数据是return
到window
对象下面的。
这样我们可以重新分析第一段代码:
-
我们执行
fun
函数 -->fun()();
-
fun
函数里面,先定义了name
变量,所以name
的作用域就在fun
函数里面 -
然后定义了一个
innerFun
函数,在innerFun
函数里面运用到了fun
函数里的name
变量,这看似没有问题,因为小范围能访问大范围的变量。 -
然后
fun
函数把innerFun
函数return
出来了。 -
当我们先调用
fun()
时,fun
函数已经把innerFun
函数return
到了window
对象下面来了,这时候再执行fun()(),
这样理论上window
是大范围的作用域,是不能访问到fun
小范围的作用域中的name
变量的。
那么既然理论上大范围不能访问小范围,那为什么结果还是能打印出"haha"
的结果呢?这就是闭包的,应该说的js
机制的一大特点。js
中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。
所以闭包的特性:
-
函数内部可以引用外部的参数变量
-
起到了内部变量能长存的作用,不会快速被垃圾回收机制回收
-
简易类特性
简易类特性:根据课程所讲的,我所理解到的就是 可以把闭包函数看成是类,类就相当于对象制造工厂,而闭包则是闭包制造闭包对象工厂,执行闭包函数,就会产生一个独立的闭包对象,每次执行闭包函数,它所产生的闭包对象都不同,这就时简易类特性。
function fun(){
var num = 1;
function innerFun(){
console.log(num++);
}
return innerFun;
}
var f1 = fun();
var f2 = fun();
f1();
f2();
/*显然执行了f1();f2();后的结果是控制台输出两个1,
这两个1分别代表着不同闭包对象产生的num的值,
如果f1,f2是同一对象的话那执行f1()f2()就相当于执行了同一函数两次,
那结果输出应该是1,2而非两个1,所以闭包产生了两个不同的闭包对象*/
使用闭包常见的错误
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
function fun(){
var li = document.getElementsByTagName('li');
for(var i=0;i<li.length;i++){
li[i].onclick = function(){
console.log("li:"+i);
}
}
}
fun();
</script>
</body>
从结果看到,其实我们是想要点击li
的时候输出对应的i值,而不是点击每个li
都是输出同一个结果。那解决的办法可以是这样:
-
使用多个闭包
function fun(){
var li = document.getElementsByTagName('li');
for(var i=0;i<li.length;i++){
li[i].onclick = f(i);
}
}
function f(i){
return function(){
console.log("li:"+i);
}
}
fun();
这样输出的结果就是我们想要的效果了,这就是因为使用闭包函数使在内部的f(i)
函数使用外部fun
函数的i
变量,i
独立传进f闭包函数里面,使每个传进去的i
的值都不一样。
-
使用立即执行函数
function fun(){
var li = document.getElementsByTagName('li');
for(var i=0;i<li.length;i++){
(function(){
var id = i;
li[id].onclick = function(){
console.log("li :"+id);
};
})()
}
}
fun();
闭包一定要有返回值吗?
闭包不一定要有返回值,只要在闭包函数里面定义了一个函数再执行完闭包函数,在内存中就存在了闭包函数内部定义的函数,然后我们可以在闭包函数外面调用其内部函数了
var innerFun;
function fun(){
var num = "xixix";
innerFun = function (){
console.log(num);
}
}
fun();
innerFun();
不过这种写法得把函数名先定义在全局作用域里面,然后才能调用闭包函数fun
内部写的函数innerFun
,否则控制台会报错显示innerFun
未定义。
性能问题
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。所以不是特殊的情况我们可以减少使用或避免使用会消耗内存和处理速度的闭包。