引例
实现功能:3个按钮,点击按钮显示是第几个按钮。 实现:
<body>
<button>button1</button>
<button>button2</button>
<button>button3</button>
<script>
var btns = document.getElementsByTagName('button')
var length = btns.length
for (var i = 0; i < length ; i++) {
var btn = btns[i]
btn.index = i
btn.onclick = function (){
alert("第"+(this.index+1)+'个按钮')
}
}
</script>
</body>
注意不能使用如下方式实现:
<body>
<button>button1</button>
<button>button2</button>
<button>button3</button>
<script>
var btns = document.getElementsByTagName('button')
var length = btns.length
for (var i = 0; i < length ; i++) {
var btn = btns[i]
btn.onclick = function (){
alert("第"+(i+1)+'个按钮')
}
}
</script>
</body>
这样不管点击哪一个按钮都会显示是第4个按钮,这是因为,绑定事件和触发事件不是同时执行的。 代码运行的时候为按钮绑定事件,i 变为3;但是当用户点击的时候才会触发事件,这时候i 已经是3,所一不管点击哪一个按钮都输出4。
但是却可以这样写
<body>
<button>button1</button>
<button>button2</button>
<button>button3</button>
<script>
var btns = document.getElementsByTagName('button')
var length = btns.length
for (var i = 0; i < length ; i++) {
(function (i){
var btn = btns[i]
btn.onclick = function (){
alert("第"+(i+1)+"个按钮")
}
})(i)
}
</script>
</body>
运行结果正确,这就是利用了闭包。
闭包的定义
闭包初始
<body>
<script>
function fn1 (){
var a =2
function fn2 (){
console.log(a)
}
return fn2
}
fn1()
</script>
</body>
有如下内容:
闭包定义
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的==变量(函数)==时,就产生了闭包。 闭包的理解: 理解一:闭包是嵌套的内部函数(绝大部分人) 理解二:闭包是内部函数对象中,包含被引用变量(函数)的对象(极少数人)
以上面的例子为例。 注意:
- 不管怎么说,闭包存在于嵌套的内部函数中
- 闭包可以使用chrome调试查看
闭包产生的条件
- 函数嵌套
- 内部函数引用了外部函数的数据
变量/函数 - 执行外部函数
(因为闭包在内部函数对象中,必须产生了内部函数对象才可能产生闭包,而执行外部函数才能执行内部函数定义,从而产生内部函数对象——调用外部函数,不用调用内部函数) —— 注意:这样说的前提是函数内部函数使用的是声明的方式定义函数,而不能使用变量的方式,因为声明的方式才能使函数声明提升。
常见的闭包
将函数作为另一个函数的返回值
function fn1 (){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f()
f()
输出:
3
4
有两个问题:
- 产生了几个闭包?
- 该过程中只创建了
一个 闭包 闭包在产生内部函数对象时进行产生:上面的代码只是调用了一次fn1() ,即函数执行上下文只产生一次,即对局部数据只进行一次预处理,函数内部函数对象自然也只产生一次,所以说只产生一个闭包。 f()执行两次只是 fn2()执行了两次,和产生闭包没关系。 - 所以说如果想要产生两个闭包,就需要执行两次
fn1() ,产生两个内部函数对象 有结论:外部函数执行几次就产生几个闭包
- 为什么输出3、4?
-
我们知道a是函数 fn1() 的局部变量,执行 fn1() 时产生,fn1() 调用结束后销毁。所以执行完var f=fn1() 语句后fn1() 就调用结束,按理说应该销毁a。但是上述代码,a不但正常输出,调用两次fn2() 还进行了累加,这说明 a没有被销毁的,或者说 a在在销毁前就被存在另一个地方,存在该地方后再使用a就可以直接在这里面拿。 -
那这个地方是哪里呢,这个地方就是闭包。闭包在创建内部函数对象时产生,即外部函数执行时产生,并且当闭包所在的函数对象成为垃圾对象的时候才会消失。而 a 存放在闭包中自然也不会销毁。而闭包又在内部函数中,所以在内部函数中就可以对a进行操作。 -
多个闭包之间不会冲突,多个闭包中存放的变量自然也不会冲突。eg:
function fn1 (){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f1=fn1()
f1()
f1()
var f2=fn1()
f2()
f1()
f2()
输出:
补充:为什么上面说 执行完var f=fn1() 语句后fn1() 调用结束?
首先说一下调用堆栈的含义 调用堆栈是在调试程序时的一项内容,里面存放的是当前调用的函数,类似于执行上下文栈。 它的栈底存放的是全局的对象(匿名),栈顶存放的时当前调用的函数。 我在14、16、21行设置断点,进行逐句执行有如下结果: (21行是代码执行的开始处) 执行顺序:21-14-19-22-16-17-18-23-16-17-18-24 执行的过程中观察右侧的调用堆栈,发现当执行到14-19 行的时候,调用堆栈有(匿名)和fn1,且栈顶是fn1() :
执行 22 时,调用堆栈只有(匿名),即全局对象 执行16-17时调用堆栈有(匿名)和fn2,且栈顶是fn2() : 即执行fn2()的时候,没有 fn1 ,说明此时 fn1已经调用执行完了。 即 执行完var f=fn1() 语句后fn1() 调用结束。
将函数作为实参传递给另一个函数调用
function showDelay (msg,time){
setTimeout(function (){
alert(msg)
},time)
}
showDelay('baidu',2000)
输出:延迟两秒输出baidu弹窗 分析:闭包中只有msg
闭包的作用
- 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以间接操作(读写)到函数内部的数据(变量/函数)
eg:
function fn1(){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
f()
输出: 延长了局部变量的生命周期:指的是fn1() 调用结束之后,通过 fn2() 任然可以访问fn1() 的局部变量a 。 函数外部可以间接操作(读写)到函数内部的数据:指的是 f() 在函数fn1() 的外面,但是仍然可以通过 f() 对a 进行操作。
应用
通过上面闭包的两个作用,可以通过闭包实现如下功能: 如果我们不希望将一个变量完全暴露出去,想读就读想改就改,而是想要对该变量进行有限的操作,即只能对该变量执行我们规定的操作,就就可以使用闭包实现。 eg:上面的例子
function fn1(){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
该代码就实现了只能对变量a执行++操作,执行不了其他操作。 如果我们想让变量a只读,就可以修改成如下代码:
function fn1(){
var a =2
function fn2(){
return a;
}
return fn2
}
var f = fn1()
console.log('a='+f())
输出:
两个问题
- 函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在,存在于闭包的变量才可能存在。 为什么说是可能存在,是因为如果内部函数对象成为垃圾对象之后(即没有变量指向它),就会被回收,回收后内部函数中的闭包也会消失,闭包都不在了,变量自然也不能存在。 eg:
function fn1(){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
fn1()
fn1()没有变量指向,即 fn2成为来及对象,即闭包所在的函数对象会被回收,纵然就不能保存局部变量了。
- 在函数外部能直接访问函数内部的局部变量吗?
不能,但我们可以通过闭包让外部操作它。 但是通过闭包操作函数内部变量的前提是:外部函数的返回值一般是内部函数,内部函数来对变量进行操作 eg:上面的代码就是一个例子
function fn1(){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
分析: 分析如下代码,当代码执行到var f = fn1() 时,谁会被释放,谁不会被释放
function fn1(){
var a =2
function fn2(){
a++
console.log(a)
}
function fn3(){
a--
console.log(a)
}
return fn3
}
var f = fn1()
- a不会被释放:有 f 指向 fn3对应的函数对象,fn3的闭包就不会消失,变量a就不会被释放。
- 变量fn3被释放,但是它所对应的对象不会被释放: fn3对应的函数对象有变量 f 指向了自然不会被释放,但是 变量fn3 就没用了,会被释放掉。
- 变量fn2被释放,它所对应的对象也会被释放 :外部没有变量指向fn2对应的对象,会成为垃圾对象被回收,变量fn2自然也没用了,也会被释放掉。
闭包的声明周期
- 产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡:在嵌套的内部函数成为垃圾对象时
因为闭包在内部函数对象中。
eg:
function fn1(){
var a =2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f = fn1()
f=null
闭包应用
自定义JS模块
补充: JS模块的定义:具有特定动能的js文件称为JS模块。 JS模块的规范:
- 将所有的数据和功能都封装在一个函数内部 (函数内部的属性和方法私有的)
- 只向外暴露一个或n个方法的函数或对象 (相当于将方法变成共有的)
(暴露n个方法需要封装成对象) JS模块的调用:模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。
使用闭包实现JS模块 eg: myModule.js文件:
function myModule() {
var msg = 'baiDu'
function doSomething (){
console.log('doSomthing():' + msg.toUpperCase())
}
function doOtherthing (){
console.log('doOtherthing():' + msg.toLowerCase())
}
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
引入: .html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type='text/javascript' src='./myModule.js'></script>
<script>
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
</body>
</html>
输出: 该JS模块使用了闭包 eg2:也可以使用匿名函数的自调用实现
myModule2.js文件:
(function (window){
var msg = 'baiDu'
function doSomething (){
console.log('doSomthing():' + msg.toUpperCase())
}
function doOtherthing (){
console.log('doOtherthing():' + msg.toLowerCase())
}
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})(window)
.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type='text/javascript' src='./myModule2.js'></script>
<script>
myModule2.doSomething()
myModule2.doOtherthing()
</script>
</body>
</html>
也使用了闭包:匿名函数的自调用已经调用了函数了,无需再手动调用 我们一般使用匿名函数的自调用这种方式。
闭包的缺点
缺点
- 函数执行完后,函数内的局部变量没有释放,占用内存时间会变长,并且如果占用内存很大的话就会造成严重的空间浪费
- 容易造成内存泄露
解决
eg:
function fn1 (){
var arr = new Array(100000)
function fn2 (){
console.log(arr.length)
}
return fn2
}
var f =fn1()
f()
f=null
习题
题1
var name = "The Window";
var object = {
name:"My Object",
getNameFunc : function (){
return function(){
return this.name;
}
}
}
alert(object.getNameFunc()())
var name2 = "The Window";
var object2 = {
name2:"My Object",
getNameFunc : function (){
var that = this
return function(){
return that.name2;
}
}
}
alert(object2.getNameFunc()())
题2
function fun (n,o){
console.log(o)
return{
fun:function(m){
return fun(m,n)
}
}
}
问:return fun(m,n)的fun是谁? 是外层函数function fun (n,o) 原因:
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
},
};
}
var a = fun(0); a.fun(1);a.fun(1);a.fun(1);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
分析:var a = fun(0); a.fun(1);a.fun(1);a.fun(1);//undefined 0 0 0
- 传参理解输出:
a.fun(1)传递的参数是(1,0)所以输出 0 a.fun(2)传递的参数是(2,0)所以输出 0 a.fun(3)传递的参数是(3,0)所以输出 0 - 闭包理解输出
fun(0)输出undefined var a = fun(0) ,调用外层函数产生一个闭包,闭包里存放 n=0 ,而且有变量指向所以闭包不会消失。 即 a 的闭包中存放的n一直等于0 ,又 a.fun(x) 后执行的语句是 console.log(n),所以不管x为几,console.log(n)只会取a的闭包中的n的值,即0。 分析一下 a.fun(x) 的闭包情况 接着使用 a.fun(1) ,又调用了外部函数,又会产生一个闭包,闭包里存放n=1 ,但是由于没有变量指向,所以闭包很快消失。
分析:var b = fun(0).fun(1).fun(2).fun(3);
- 闭包理解输出
fun(0)输出undefined var b = fun(0) ,调用外层函数产生一个闭包,闭包里存放 n=0 ,而且有变量指向所以闭包不会消失。 紧接着 .fun(1) ,输出闭包中的n,即 0 同时 .fun(1) ,又调用了外部函数,产生一个新闭包,闭包里存放 n=1 ,有变量指向所以闭包不会消失。 紧接着 .fun(2) ,输出闭包中的n,即 1 同时 .fun(2) ,又调用了外部函数,产生一个新闭包,闭包里存放 n=2 ,有变量指向所以闭包不会消失。 紧接着 .fun(3) ,输出闭包中的n,即 2 同时 .fun(3) ,又调用了外部函数,产生一个新闭包,闭包里存放 n=3 ,有变量指向所以闭包不会消失。
分析:var c = fun(0).fun(1); c.fun(2); c.fun(3); 这条语句其实就是上面两条语句的结合:
- c = fun(0).fun(1):
fun(0)输出undefined var c = fun(0) ,调用外层函数产生一个闭包,闭包里存放 n=0 ,而且有变量指向所以闭包不会消失。 紧接着 .fun(1) ,输出闭包中的n,即 0 同时 .fun(1) ,又调用了外部函数,产生一个新闭包,闭包里存放 n=1 ,有变量指向所以闭包不会消失。 - c.fun(2);c.fun(3);
即 c 的闭包中存放的n一直等于1 ,又 c.fun(x) 后执行的语句是 console.log(n),所以不管 x为几,console.log(n)只会取c 的闭包中 n 的值,几为1。
分析引例
我们再来分析一下开头得引例分析一下闭包结构
<body>
<button>button1</button>
<button>button2</button>
<button>button3</button>
<script>
var btns = document.getElementsByTagName('button')
var length = btns.length
for (var i = 0; i < length ; i++) {
(function (i){
var btn = btns[i]
btn.onclick = function (){
alert("第"+(i+1)+"个按钮")
}
})(i)
}
</script>
</body>
函数嵌套,内部函数引用外部函数变量,外部函数执行,所以产生了闭包: 数组长为3,执行了3次外部函数,产生了3个闭包,每个闭包中存放自己得 i 的值,所以输出的时候直接调用自己闭包中的 i 值,这样就是我们想要的输出结果了。
问题: 我们说如果包含闭包的对象函数被销毁,闭包就会死亡。 注意看这个自调用函数,没有变量指向外部函数,说明外部函数会成为垃圾对象,那内部函数对象是不是也销毁了呢? 注意 内部函数被 btn.onclick 指向,而 btns是一个全局变量不会被销毁(页面不关闭,按钮不释放),所以内部函数对象不hi被销毁,闭包也不会死亡。 如果想要释放设置:btn.οnclick=null
|