闭包
前言(你必须了解与之关联的知识)
提到闭包不得不提到,作用域链,更不得不提起词法作用域,学习闭包的时候总把自己搞得晕头转向?是不是觉得外部函数定义的局部变量在函数返回后就不存在了,之所以有这种想法,是因为我们忽略了作用域链的存在,晕头转向是因为我们忽略了词法作用域的特点。在Javascript并非函数执行结束后与之关联的作用域链就不存在了。
定义(通过了解了与之关联的知识来思考闭包的存在)
函数对象可以通过作用域链相互关联起来,函数体内的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为"闭包";
理解(知识串联)
粗略的理解,所有JavaScript函数都是闭包。我们知道函数都是对象,变量保存在函数的作用域内,函数对象通过作用域链关联了起来。嵌套函数可以访问到父函数定义的变量,词法作用域就是JS查找某个变量的规则,词法作用域规则而指出了函数作用域是在函数定义的时候就确定了,而非运行的时候;作用域链则是JS寻找某个变量的链条,如果找到链条的末端都没找到就会返回referenceError的错误。
案例
简单的案例
var scpoe = "global";
function fun1(){
var scope = "local";
return function (){
console.log(scope)
}
}
var fun = fun1();
fun()
fun1 返回一个function对象后看似乎fun1就运行结束,存放在fun1的变量,按道理是会消失的。但是看运行结果却以然能够被返回的这个function对象访问。而且控制台输出的是fun1函数内定义的变量,而非全局变量。
其实如果你知道词法作用域规则,和作用域链你就能明白闭包是什么了。
我们来看,每次调用JavaScript函数的时候,都会 为之创建一个新的对象用来保存局部变量,并且把这个对象保存到作用域链中,运行结束后如果这个函数没有被其他引用指向这个对象,它就会被当作垃圾回收,如果定义了嵌套函数,并且将它当作返回值返回那么存在一个变量存放了该函数对象引用,他就不会被当作垃圾回收,并且它所指向的变量绑定对象也不会被当作垃圾回收。所以闭包使用不当的话是会造成内存泄漏的。
简单的应用(JS绑定事件示例,加深认识)
没有使用闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tbounditle</title>
</head>
<body>
<input type="button" id="btn1" value="按钮1">
<input type="button" id="btn2" value="按钮2">
<input type="button" id="btn3" value="按钮3">
</body>
<style type="text/css"></style>
<script type="text/javascript">
var buttons = document.getElementsByTagName("input")
for (var i = 0; i<buttons.length; i++){
buttons[i].addEventListener("click",function (){
alert(i)
})
}
</script>
</html>
结果:每个按钮alert的都是3;
为什么呢?
绑定的时候是i的值0,1,2分别进行绑定的没错,但是这个方法最终是触发按钮的时候才调用了这个回调函数,对于这个函数所处的词法作用域上的值此时已经累加到3了;所以每次点击按钮只会读到3这个值;
使用了闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tbounditle</title>
</head>
<body>
<input type="button" id="btn1" value="按钮1">
<input type="button" id="btn2" value="按钮2">
<input type="button" id="btn3" value="按钮3">
</body>
<style type="text/css"></style>
<script type="text/javascript">
var buttons = document.getElementsByTagName("input")
for (var i = 0; i<buttons.length; i++){
(function (i){
buttons[i].addEventListener("click",function (){
alert(i)
})
})(i)
}
</script>
</html>
结果:每个按钮alert的分别是0,1,2
为什么不一样?
前面提到过的:每次调用JavaScript函数的时候,都会 为之创建一个新的对象用来保存局部变量;
我们通过自调用函数,然后把i传递给该函数,他就会在每次调用的到时候把这个i值保存一份到该函数作用域中(或者理解成闭包),循环了多少次就执行多少次函数,也就创建了多少个闭包,每个闭包都保存了相应的值,闭包的特性就是即使函数运行结束了,只要我有引用指向该返回的函数(这里指回调函数),那么等到事件触发的时候,他就会调用该函数,从而获取到闭包当时独立保存的变量。