0
点赞
收藏
分享

微信扫一扫

JS闭包知识

秀妮_5519 2022-05-02 阅读 141

一、 引出闭包

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
//需求: 点击某个按钮, 提示"点击的是第n个按钮"
<script type="text/javascript">
 var btns = document.getElementsByTagName('button')
 //注意[btns]不是一个数组,它是一个伪数组
 //每次获取[btns.length]其实都是需要进行计算的(因为它是伪数组)
 //所以为了性能更好,在此处赋值,就只需要计算一次
 for (var i = 0,length=btns.length; i < length; i++) {
   var btn = btns[i]
   btn.onclick = function () {  //遍历加监听
     alert('第'+(i+1)+'个')     //结果 全是[4]
   }
 }
</script>   

        可得点击每个按钮的结果都是4。原因:此处的【i】是一个全局变量,点击函数执行的时候,for循环已经结束,此时i等于3,最后加1得4。

        将变量挂载到自身来解决

        解决方式1:将btn所对应的下标保存在btn上。

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>

//需求: 点击某个按钮, 提示"点击的是第n个按钮"
<script type="text/javascript">
 var btns = document.getElementsByTagName('button')
 for (var i = 0,length=btns.length; i < length; i++) {
   var btn = btns[i]
   //将btn所对应的下标保存在btn上
   btn.index = i
   btn.onclick = function () { 
     alert('第'+(this.index+1)+'个')  //注意此处this.index   
   }
 }
</script>   

        解决方式2:利用闭包。

var btns = document.getElementsByTagName('button')
//利用闭包
for (var i = 0,length=btns.length; i < length; i++) {
    //此处的j是局部的,它将传入的[i]存入局部的[j]中,这样就能实现效果
   (function (j) {
       var btn = btns[j]
       btn.onclick = function () {
            alert('第'+(j+1)+'个')
       }
    })(i)//此处的i读取的全局的
}

二、 闭包的含义

        闭包是指有权访问另外一个函数作用域中的局部变量的函数。声明在一个函数中的函数,叫做闭包函数。而且内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

三、产生闭包的条件

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

        (1)函数嵌套

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

function fn1(){
    var a= 2;
    var b = 'abc';
    function fn2(){//执行函数定义就会产生闭包(不用调用内部函数)
        console.log(a)
    }
    fn2()
}
fn1()

四、常见的闭包

        (1)将函数作为另一个函数的返回值

// 1. 将函数作为另一个函数的返回值
 function fn1() {
   var a = 2//此时已经有闭包了,函数定义已经执行过了(函数提升)
   function fn2() {
     a++
     console.log(a)
   }
   return fn2
 }
 var f = fn1()
 f() // 3
 f() // 4
 //创建内部函数对象时产生闭包,整个过程创建了一个内部函数对象,只有一个闭包
 fn1()//此时产生两个闭包,共调用了两次外部函数

        (2)将函数作为实参传递给另一个函数调用

// 2. 将函数作为实参传递给另一个函数调用
 function showDelay(msg, time) {
   setTimeout(function () {
     alert(msg)
   }, time)
 }
 showDelay('atguigu', 2000)

        (3)高阶函数与柯里化(待编辑)

五、闭包的作用

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

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

        问题:

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

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

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

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

六、闭包的生命周期

        1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)

        2. 死亡: 在嵌套的内部函数成为垃圾对象时。即没有人指向它时死亡,通常置为[null],当然指向其他也行,但不安全(容易污染变量)

//闭包的生命周期
function fn1() {
   //此时闭包就已经产生了(函数提升,实际上[fn2]提升到了第一行, 内部函数对象已经创建了)
   var a = 2
   function fn2 () { //如果时[let fn2=function(){}],那么在这行才会产生闭包
     a++
     console.log(a)
   }
   return fn2
 }
 var f = fn1()
 f() // 3
 f() // 4
 f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

七、 闭包的应用场景

        最常见的是函数封装的时候,再就是在使用定时器的时候,即:当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

        闭包的应用 : 定义JS模块

        (1)具有特定功能的js文件

        (2)将所有的数据和功能都封装在一个函数内部(私有的)

        (3)只向外暴露一个包含n个方法的对象或函数

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

模块定义

//myModule.js
function myModule() {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }
                            
  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}
                            
-----------------------------------------------------------------
// myModule2.js   
(function () {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }
                            
  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()    

模块调用

//调用示例
------------  模块调用1 --------------------------------------------
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>
------------  模块调用2 --------------------------------------------
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>

八、 闭包的缺点及解决

        1. 缺点:

        (1)函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长

        (2)容易造成内存泄露

        2. 解决:

        (1)能不用闭包就不用

        (2)及时释放

function fn1() {
  var arr = new Array(100000)
  function fn2() {
    console.log(arr.length)
  }
  return fn2
}
var f = fn1()
f()
f = null //让内部函数成为垃圾对象-->回收闭包

九、 内存溢出与内存泄漏

        1. 内存溢出

        (1)一种程序运行出现的错误

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

        2. 内存泄露

        (1)占用的内存没有及时释放

        (2)内存泄露积累多了就容易导致内存溢出

        常见的内存泄露:

        (1)意外的全局变量

        (2)没有及时清理的计时器或回调函数

        (3)闭包

<script type="text/javascript">
 // 1. 内存溢出
 var obj = {}
 for (var i = 0; i < 10000; i++) {
   obj[i] = new Array(10000000)
   console.log('-----')
 }

 // 2. 内存泄露
   // 意外的全局变量
 function fn() {
   a = new Array(10000000)  //不使用var let const去承接
   console.log(a)
 }
 fn()

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

 // clearInterval(intervalId)

   // 闭包
 function fn1() {
   var a = 4
   function fn2() {
     console.log(++a)
   }
   return fn2
 }
 var f = fn1()
 f()
 // f = null

</script>

十、 常见闭包面试题

//代码片段一  -->没有产生闭包:因为内部函数没有调用外部变量
var name = "The Window";
var object = {
 name : "My Object",
 getNameFunc : function(){
   return function(){
     return this.name;
   };
 }
};
alert(object.getNameFunc()());  //?  the window
//函数体的this是window

//代码片段二
var name2 = "The Window";
var object2 = {
 name2 : "My Object",
 getNameFunc : function(){
 //此处的this指向是[getNameFunc],他是对象中的属性,所以this指向就是object
   var that = this;
   return function(){
     //此处用的是保存的  that
     return that.name2;
   };
 }
};
alert(object2.getNameFunc()()); //?  my object
function fun(n,o) {
 console.log(o)
 return {
   fun:function(m){
     return fun(m,n)
   }
 }
}
var a = fun(0) //undefined
a.fun(1)  //0
a.fun(2)  //0	
a.fun(3)  //0

var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2

var c = fun(0).fun(1) //undefined  0
c.fun(2)//1 -->经过上方定义后 n固定为1
c.fun(3)//1 -->此处是陷阱!!!  一直没有改到n,所以一直是1
举报

相关推荐

0 条评论