js闭包初理解

目录

 

目录

1、闭包的简介

2、闭包的特点

3、闭包的形成 

4、闭包的作用

5、闭包的构成

6、闭包的缺点

7、使用闭包的注意事项


 


最近关于js闭包的文章看了好多,主要有两点认识:(1)闭包很重要:前端面试,必问闭包;(2)彻底把闭包搞清楚很不容易。但是我还是想写一下我对闭包的理解,不久之前,我写过一篇函数预编译的文章链接是:https://blog.csdn.net/weixin_44164982/article/details/107314659 主要介绍了函数执行期上下文的建立过程,如果有需要了解之前知识的朋友,可以先去看一下。

今天这篇文章主要是实现对闭包的初步理解:

1、闭包的简介

课本上对闭包的描述:

  1. 闭包,就是有权访问外部作用域中的变量和参数函数。
  2. 闭包是能够读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,在本质上,闭包是函数内部和函数外部连接起来的桥梁。
  3. 当函数可以记住并访问所在词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

     4.在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

2、闭包的特点

  1. 内部函数被返回到外部并保存时,会生成闭包(重点)
  2. 可以读取自身函数外部的变量(沿着作用域链寻找)先从自身开始查找,如果自身没有才会继续往上级查找,自身如果拥有将直接调用。(哪个离的最近就用哪一个)
  3. 可以延长内部变量的生命周期(产生在作用域上,不能被销毁)
  4. 函数fn2嵌套在函数fn1内部
  5. 函数fn1返回函数fn2

3、闭包的形成 

 如下方代码所示:

function fn1() {      //外部函数fn1
	var a=123;
    function fn2(){   //内部函数fn2
        var b=234;
	    console.log(a)
	}
	return fn2;     //外部函数fn1返回内部函数fn2	
  }

var glob=100;
var demo=fn1();
demo();

执行结果如下:

在上面代码中,fn2函数就是闭包。

在上面的代码中,函数fn2就被包括在函数fn1内部,这时fn1内部的所有局部变量,对fn2都是可见的。但是反过来就不行,fn2内部的局部变量,对fn1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然fn2可以读取fn1中的局部变量,那么只要把fn2作为返回值,我们就可以在f1外部读取它的内部变量。

接下来我们在讨论具体执行过程之前先了解一下什么是作用域链?

作用域链:[[scope]]中存储的执行期上下文对象的集合,这个链呈链式链接,把这种链式链接叫做作用域链。作用域链的查找顺序是从作用域链的顶端依次向下查找。

运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。

上面函数的具体执行过程为

(1)fn1函数定义,[[scope]]全局执行期上下文GO,如下图所示

(2)fn1函数执行,[[scope]]又存入fn1函数执行期新建的上下文AO,第0位是AO,在作用域链顶端;第1位是GO。这里强调:作用域链的查找顺序是从作用域链的顶端依次向下查找。

(3)fn1函数执行过程中触发fn2函数的定义, fn2刚被定义时,是直接引用的是fn1函数的作用域链,直接存入fn2的[[scope]]中。

这里我们要理解具体发生了什么: 

(4)fn2函数执行,生成fn2函数自己的执行期上下文AO,并存入fn2[[scope]]的最顶端。

(5) fn2函数执行完之后,销毁fn2自己生成的执行期上下文AO。

 此时看代码,发现fn1其实也执行完了,fn1自己的执行期上下文此时就要销毁,要回归到被定义状态。但是:,fn2函数在fn1执行完之前被保存到全局,fn2的[[scope]]中还有之前引用的fn1的AO。因此,fn1的执行期上下文实际上是无法销毁的。


 

 然后我们发现,两个函数类似这种结构的,都能形成闭包,比如:

function test(){
	var temp=1000;
	function fn1(){
	    console.log(temp);
	}
	return fn1;
}
var demo=test();
console.log(demo);

 

4、闭包的作用

一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

在本文例子中,在函数fn1执行完并返回后,闭包使得JavaScript的垃圾回收机制不会收回fn1所占用的资源,因为fn1的内部函数fn2的执行需要依赖fn1中的变量,闭包需要循序渐进的过程。
常用场景:

  1. 实现公有变量   eg:函数累加器
  2. 可以做缓存(存储结构)  eg;eater
  3. 可以实现封装,属性私有化  eg: Person);
  4. 模块化开发,防止污染全局变量

5、闭包的构成

闭包由两部分构成:

  • 函数
  • 以及创建该函数的环境

6、闭包的缺点

  • 闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。
  • 滥用闭包会造成内存泄露。因为闭包中引用到的包裹函数中定义的变量都永远不会被释放,所以我们应该在必要的时候,及时释放这个闭包函数。

      什么是内存泄露?

      内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。

7、使用闭包的注意事项

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

例子面包店销售

<script>
    function breadMgr(num){
        var breadNum=arguments[0]||10;//实参没给值默认是10个
        function supply(){
            breadNum+=10;
            console.log(breadNum);
        }
        function sell(){
            breadNum--;//卖出一个,减少一个
            console.log(breadNum);
        }
        return[supply,sell];//内部函数被返回外部,形成闭包。埋了个钩子,抓住内部函数,也是数据缓存
    }
   var breadMgr=breadMgr(50);
   breadMgr[0]();//60
   breadMgr[1]();//59
   breadMgr[1]();//58
   breadMgr[1]();//57
   breadMgr[1]();//56
</script>

例子:

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

在for循环里面的匿名函数执行 return i 语句的时候,由于匿名函数里面没有i这个变量,所以这个i他要从父级函数中寻找i,而父级函数中的i在for循环中,当找到这个i的时候,是for循环完毕的i,也就是5,所以这个box得到的是一个数组里面是5个5。

function box(){
    var arr = [];
    for(var i=0;i<5;i++){
        arr[i] = function(){
            return i;  //由于这个闭包的关系,他是循环完毕之后才返回,最终结果是4++是5
        }              //这个匿名函数里面根本没有i这个变量,所以匿名函数会从父级函数中去找i,
    }                  //当找到这个i的时候,for循环已经循环完毕了,所以最终会返回5
    return arr;
}
console.log(box()[0]()); 

同理 ,下面是另外一个例子。

function test(){
    var arr=[]; 
    for(var i=0;i<10;i++){
        arr[i]=function(){//funcition并没有执行,返回到全局,被return arr;保存了,
            console.log(i);
        }
    }

    return arr;//返回数组,此时i=10
}
var myArr=test();
console.log(myArr);//形成闭包,把里面的arr传到了外面 [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
for(var j=0;j<myArr.length;j++){
   myArr[j]();//循环执行,打印10个10
}

常见:解决闭包问题

// 解决 用立即执行函数解决。在外面加立即执行函数,保存i
for(var i=0;i<10;i++){
   (function(j){
        arr[j]=function(){ //funcition并没有执行,只是i++
              console.log(j);//0 1 2 3....9
        }
    })(i)//保存到循环中每一次i的值
}

看一道面试题

    <ul>
        <li>a</li>
        <li>b</li>
        <li>c</li>
        <li>d</li>
    </ul>
    <script>
        var list=document.getElementsByTagName("li");
        for(var i=0;i<list.length;i++){
            list[i].addEventListener("click",function(){  //形成闭包,函数写在循环里要考虑是否出现闭包,改为立即执行函数
                    console.log(i);//只输出结果4
                    },false);
        }

//改为:
        for(var i=0;i<list.length;i++){
            (function(i){
                    list[i].addEventListener("click",function(){  //闭包,函数写在循环里要考虑是否出现闭包,改为立即执行函数
                    console.log(i);//正确输出次序
                    },false);
                 }(i))
            }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值