🙉初识闭包
闭包可谓是JS的一大难点也是面试中常见的问题之一,今天开始梳理一下闭包的知识,请诸君品鉴。
🍇什么是闭包
闭包是嵌套的内部函数;内部函数包含被引用变量(函数)的对象。闭包存在于嵌套的内部函数中,例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来。当然如何直观的查看闭包可以通过chrome来查看,这里有个坑需要反馈一下,新版的chrome需要先调用fun2()才允许debugger,这样才能显示闭包。
<script>functionfn1(){ vara=2; functionfn2(){//执行函数定义就会产生闭包(不用调用内部函数)console.log(a); } //新版的chrome需要返回一下内部函数才会显示闭包returnfn2() } fn1() </script>
🍈如何产生闭包
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
<script>// 将函数作为另一个函数的返回值functionfn1(){ vara=2; functionfn2(){//a++; console.log(a); } returnfn2//将一个内部函数作为一个外部函数的返回值返回 } varf=fn1() //整个过程产生了一个闭包,主要看你产生了几个内部函数对象,调用了几次外部函数//闭包的特点就是函数内部的变量会一直存在于内存中,不会立即释放。f()//3 这里的f()是调用了内部函数f()//4</script>
<script>// 将函数作为实参传递给另一个函数调用functionshowDelay(msg,time){ //setTimeout 的第一个参数是函数,符合闭包的规则setTimeout(function(){ alert(msg) },time) } showDelay('张三',2000) </script>
🍉产生闭包条件
函数嵌套;内部函数引用了外部函数的数据(变量/函数)。
🍊闭包的作用
使用函数内部的变量在函数执行完毕后,仍然存活在内存中(延长了局部变量的生命周期);让函数外部可以操作(读写)到函数内部的数据(变量/函数)。
🍋闭包的生命周期
产生:在嵌套的内部函数定义执行完时就产生了(不是在调用),死亡:在嵌套的内部函数称为垃圾对象时就死亡了。
<script>functionfn1 () { //此时闭包就已经产生了(函数提升,内部函数对象已经创建了)vara=2; functionfn2 () {//a++; console.log(a); } returnfn2//将一个内部函数作为一个外部函数的返回值返回 } varf=fn1() f()//3 f()//4f=null//闭包死亡(包含闭包的函数对象成为垃圾对象)</script>
🍌闭包的应用
定义JS模块(具有特定功能的js文件),将所有的数据和功能都封装在一个函数的内部(私有的),只向外暴露一个包含n个方法的对象和函数;模块的使用者只需要通过模块暴露的对象调用方法来实现对应的功能。
//myModule.js 文件functionmyModule(){ // 私有数据varmsg='My Module'functionshowUpper(){ console.log('showUpper'+msg.toUpperCase()); } functionshowLow(){ console.log('showLow'+msg.toLowerCase()); } //向外暴露对象(给外部使用的方法)return { showUpper:showUpper, showLow:showLow } } //index.html文件<scriptsrc="./myModule.js"></script><script>varmodule=myModule() module.showUpper() module.showLow() </script>
我们也可以通过匿名函数来实现闭包,这样能很便捷的调用闭包里面的属性,虽然会达到我们想要的效果,但是可能会造成全局的变量名污染,建议使用第一种。
//myModule2.js文件(function(){ // 私有数据varmsg='My Module'// 操作数据的函数functionshowUpper(){ console.log('showUpper'+msg.toUpperCase()); } functionshowLow(){ console.log('showLow'+msg.toLowerCase()); } //向外暴露对象(给外部使用的方法)window.myModule2= { showUpper:showUpper, showLow:showLow } })() //index.js文件<scriptsrc="./myModule2.js"></script><script>myModule2.showUpper() myModule2.showLow() </script
🍍闭包的缺点及解决方法
在我们使用闭包过程中,函数执行完后,函数内部的局部变量没有释放,占用内存时间会变长,容易造成内存泄漏,所以在日常开发中,尽量避免闭包的出现,或者要对局部变量及时释放。
<script>functionfn1(){ vararr=newArray[100000] functionfn2(){ console.log(arr.length); } returnfn2 } varf=fn1() f() //不用闭包或者回收闭包f=null//让内部函数成为垃圾对象 --> 回收闭包</script>
内存溢出:一种程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误。
<script>varobj= {} for(vari=0;i<10000;i++){ obj[i]=newArray(1000000) console.log('------'); } </script>
内存泄漏:占用的内存没有及时释放,内存泄漏积累多了就容易导致内存溢出。常见的内存泄漏:意外的全局变量、没有及时清理的计时器或回调函数、闭包。
<script>//意外的全局变量functionfn(){ a=10; console.log(a); } // 调用函数虽然能打印a,但是a并没有被释放掉。一不注意就设置了一个全局变量fn() //没有及时清理计时器或回调函数varintervalId=setInterval(function(){ //启动循环定时器后不清理console.log('--------'); },2000) // clearInterval(intervalId)//闭包functionfn1(){ vara=2//闭包 a 并没有被释放掉functionfn2(){ console.log(++a) } returnfn2 } varf=fn1() f() // f = null 不执行这条语句,a的值一直在</script>
🥭闭包案例
<script>// 案例一:varname="this is Window"varobject= { name:"this is Object", getName:function(){ returnfunction(){ returnthis.name } } } //闭包的this只能是全局,若在当前作用域中定义了this,就直接使用定义的this,若没定义,则需要一层层向外找,直到全局为止//本题是没有闭包的alert(object.getName()())//this is Window// 案例二:varname1="this is Window"varobject1= { name1:"this is Object", getName:function(){ //定义的that形成了闭包,内部函数引用了外部函数的变量,而this指向的是object,所以返回的是object中的name1varthat=this; returnfunction(){ returnthat.name1 } } } alert(object1.getName()())// this is Object</script>
<script>//没有使用闭包的话,数据是没有保留的,所以n传递给o之后,下次运算o值还是上次的值不会发生改变functionfun(n,o){ console.log(o); return{ fun:function(m){ returnfun(m,n) } } } //在执行fun(0)之后,n被之前的n=0,一直被调用vara=fun(0); //闭包里面的n传入了0a.fun(1); a.fun(2); a.fun(3)//undefined,0,0,0//链式执行会导致n的改变,n是前面函数执行的形参varb=fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2//c.fun(2)、c.fun(3)都调用了fun(1)留下的闭包nvarc=fun(0).fun(1); c.fun(2); c.fun(3)//undefined,0,1,1</script>