发表于: 2019-04-21 19:56:32

1 261


今天学习到的知识

闭包

闭包就是能够读取其他函数内部数据(变量/函数)的函数。

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

上面这两句话,是阮一峰的文章里的,你不一定能理解,来看下面的讲解和举例。

如何产生闭包

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时, 就产生了闭包。

闭包到底是什么?

使用chrome调试查看

  • 理解一: 闭包是嵌套的内部函数(绝大部分人)

  • 理解二: 包含被引用变量 or 函数的对象(极少数人)

注意: 闭包存在于嵌套的内部函数中。

产生闭包的条件

  • 1.函数嵌套

  • 2.内部函数引用了外部函数的数据(变量/函数)。

来看看条件2:

   function fn1({        function fn2({        }        return fn2;    }    fn1();

上面的代码不会产生闭包,因为内部函数fn2并没有引用外部函数fn1的变量。

PS:还有一个条件是外部函数被调用,内部函数被声明。比如:

   function fn1({        var a = 2        var b = 'abc'        function fn2(//fn2内部函数被提前声明,就会产生闭包(不用调用内部函数)            console.log(a)        }    }    fn1();    function fn3({        var a = 3        var fun4 = function ({  //fun4采用的是“函数表达式”创建的函数,此时内部函数的声明并没有提前            console.log(a)        }    }    fn3();

常见的闭包

    1. 将一个函数作为另一个函数的返回值
    1. 将函数作为实参传递给另一个函数调用。

闭包1:将一个函数作为另一个函数的返回值

   function fn1({      var a = 2      function fn2({        a++        console.log(a)      }      return fn2    }    var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2    f() // 3       //执行fn2    f() // 4       //再次执行fn2

当f()第二次执行的时候,a加1了,也就说明了:闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。

上面的代码中,虽然调用了内部函数两次,但是,闭包对象只创建了一个。

也就是说,要看闭包对象创建了一个,就看:外部函数执行了几次(与内部函数执行几次无关)。

闭包2. 将函数作为实参传递给另一个函数调用

   function showDelay(msg, time{      setTimeout(function({  //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg        alert(msg)      }, time)    }    showDelay('atguigu'2000)

上面的代码中,闭包是里面的funciton,因为它是嵌套的子函数,而且引用了外部函数的变量msg。

闭包的作用

  • 作用1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)

  • 作用2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

我们让然拿这段代码来分析:

   function fn1({      var a = 2      function fn2({        a++        console.log(a)      }      return fn2    }    var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2    f() // 3       //执行fn2    f() // 4       //再次执行fn2

作用1分析

上方代码中,外部函数fn1执行完毕后,变量a并没有立即消失,而是保存在内存当中。

作用2分析:

函数fn1中的变量a,是在fn1这个函数作用域内,因此外部无法访问。但是通过闭包,外部就可以操作到变量a。

达到的效果是:外界看不到变量a,但可以操作a

比如上面达到的效果是:我看不到变量a,但是每次执行函数后,让a加1。当然,如果我真想看到a,我可以在fn2中将a返回即可。

回答几个问题:

  • 问题1. 函数执行完后, 函数内部声明的局部变量是否还存在?

答案:一般是不存在, 存在于闭中的变量才可能存在。

闭包能够一直存在的根本原因是f,因为f接收了fn1(),这个是闭包,闭包里有a。注意,此时,fn2并不存在了,但是里面的对象(即闭包)依然存在,因为用f接收了。

  • 问题2. 在函数外部能直接访问函数内部的局部变量吗?

不能,但我们可以通过闭包让外部操作它。

闭包的生命周期

  1. 产生: 嵌套内部函数fn2被声明时就产生了(不是在调用)

  2. 死亡: 嵌套的内部函数成为垃圾对象时。(比如f = null,就可以让f成为垃圾对象。意思是,此时f不再引用闭包这个对象了)

闭包的应用:定义具有特定功能的js模块

  • 将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或函数。

  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能。

方式一

(1)myModule.js:(定义一个模块,向外暴露多个函数,供外界调用)

function myModule({    //私有数据    var msg = 'Smyhvae Haha'    //操作私有数据的函数    function doSomething({        console.log('doSomething() ' + msg.toUpperCase()); //字符串大写    }    function doOtherthing({        console.log('doOtherthing() ' + msg.toLowerCase()) //字符串小写    }    //通过【对象字面量】的形式进行包裹,向外暴露多个函数    return {        doSomething1: doSomething,        doOtherthing2: doOtherthing    } }

上方代码中,外界可以通过doSomething1和doOtherthing2来操作里面的数据,但不让外界看到。

(2)index.html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>05_闭包的应用_自定义JS模块</title></head><body><!-- 闭包的应用 : 定义JS模块  * 具有特定功能的js文件  * 将所有的数据和功能都封装在一个函数内部(私有的)  * 【重要】只向外暴露一个包含n个方法的对象或函数  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能 --><script type="text/javascript" src="myModule.js"></script><script type="text/javascript">    var module = myModule();    module.doSomething1();    module.doOtherthing2();</script></body></html>

方式二

同样是实现方式一种的功能,这里我们采取另外一种方式。

(1)myModule2.js:(是一个立即执行的匿名函数)

(function ({    //私有数据    var msg = 'Smyhvae Haha'    //操作私有数据的函数    function doSomething({        console.log('doSomething() ' + msg.toUpperCase())    }    function doOtherthing({        console.log('doOtherthing() ' + msg.toLowerCase())    }    //外部函数是即使运行的匿名函数,我们可以把两个方法直接传给window对象    window.myModule = {        doSomething1: doSomething,        doOtherthing2: doOtherthing    } })()

(2)index.html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>05_闭包的应用_自定义JS模块2</title></head><body><!-- 闭包的应用2 : 定义JS模块  * 具有特定功能的js文件  * 将所有的数据和功能都封装在一个函数内部(私有的)  * 只向外暴露一个包信n个方法的对象或函数  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能 --><!--引入myModule文件--><script type="text/javascript" src="myModule2.js"></script><script type="text/javascript">    myModule.doSomething1()    myModule.doOtherthing2()</script></body></html>

上方两个文件中,我们在myModule2.js里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。

总结:

当然,方式一和方式二对比后,我们更建议采用方式二,因为很方便。

但无论如何,两种方式都采用了闭包。

闭包的缺点及解决

缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。

解决:能不用闭包就不用,及时释放。比如:

   f = null;  // 让内部函数成为垃圾对象 -->回收闭包

总而言之,你需要它,就是优点;你不需要它,就成了缺点。

内存泄漏内存溢出

内存泄漏

内存泄漏:占用的内存没有及时释放。内存泄露积累多了就容易导致内存溢出。

常见的内存泄露:

  • 1.意外的全局变量

  • 2.没有及时清理的计时器或回调函数

  • 3.闭包

情况1举例:

   // 意外的全局变量    function fn({        a = new Array(10000000);        console.log(a);    }    fn();

情况2举例:

   // 没有及时清理的计时器或回调函数    var intervalId = setInterval(function (//启动循环定时器后不清理        console.log('----')    }, 1000)    // clearInterval(intervalId);  //清理定时器

情况3举例:

<script type="text/javascript">  function fn1({    var arr = new Array[100000];   //这个数组占用了很大的内存空间    function fn2({      console.log(arr.length)    }    return fn2  }  var f = fn1()  f()  f = null //让内部函数成为垃圾对象-->回收闭包</script>

内存溢出(一种程序运行出现的错误)

内存溢出:当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误。

 //内存溢出  var obj = {}  for (var i = 0; i < 10000; i++) {    obj[i] = new Array(10000000);   //把所有的数组内容都放到obj里保存,导致obj占用了很大的内存空间    console.log('-----')  }

明天的事情

主要把精力放到任务中!!!!



返回列表 返回列表
评论

    分享到