js中的函数嵌套和闭包详情

编辑: admin 分类: javascript 发布时间: 2021-12-12 来源:互联网
目录
  • 一、作用域
  • 二、函数的返回值
  • 三、函数嵌套
  • 四、闭包
  • 五、闭包的实际应用
    • 1、隐藏内部变量名称和函数执行暂停
    • 2、setTimeout函数传递参数
    • 3、回调
    • 4、函数防抖
  • 六、使用类实现类似闭包中隐藏内部变量功能

    前言:

    今天就先和大家一起聊一聊我理解的闭包。在聊这个问题之前,先了解一下变量的定义域。
    在js中,变量定义域有全局作用域和局部作用域之说。es6中新出现的变量声明关键字,就是为了解决部分变量作用域混乱引入的。全局作用域在这就不谈了。主要说说函数的作用域。

    一、作用域

    简单一点说,函数的作用域,就是函数的花括号内部,先看两个例子,或许能对这个概念理解的更好一点

    function f1(){
      let n = 999
      console.log(n)
    }
    f1() // 999
    
    function f2(){
      let n = 999
    }
    alert(n); // 报错
    
    

    二、函数的返回值

    要说闭包之前,我得先说一下函数返回值。关于函数的返回值,小编也是年初才有了一个更深层次的理解。没有返回值的函数,执行之后会返回undefined,有返回值的函数,执行之后就变成了对应的返回值。就像这样

    // 没有返回值的函数
    function f1(){
      alert(666)
    }
    console.log(f1()) // 出现弹窗之后,在控制台输出undefind
    
    // 存在返回值
    function f2(){
      alert(666)
      return 'over'
    }
    console.log(f2()) // 出现弹窗之后,在控制台输出over。当然,可以返回字符串,也可以返回Bealon,还可以返回函数。
    
    

    三、函数嵌套

    在《重构——改善既有代码的设计》中,提出了js语法是允许函数内部嵌套函数的,但并不是所有的编程语言都可以的,所谓代码嵌套,就是在函数内部又有函数声明,

    就像这样:

    function outer(){
      let name = 'lilei'
      function inner(){
        console.log(name)
      }
    }  
    
    

    四、闭包

    前面说明了在js中的局部变量作用域的问题,在实际项目中,就是需要在函数外部,访问函数内部的变量,这个时候,按照局部变量作用域的问题。似乎是不可能的,闭包的出现,解决了这个问题。

    function outer(){
      let name = 'lilei'
      function inner(){
        return name
      }
      return inner
    }
    
    

    上面是一个典型的闭包函数,在使用这个闭包函数的时候,我们可以这样:

    let g = outer()
    console.log(g()) // lilei
    
    
    

    至此,已经解决了在全局访问函数内的局部变量。但是小编在回家的路上在想,为了实现这个功能,是不是不用这个麻烦,我通过这样的函数,也是可以满足需求的。

    function outer(){
      let name = 'lilei'
      return name
    }
    
    console.log(outer()) // lilei  
    
    
    

    确实上面的代码和通过闭包最终在控制台输出的内容是一样的,那为什么还要引入闭包呢?小编也是想了接近一周才明白的,这就好比变量->函数->类,每层往上都是逐步提升的过程,通过函数可以实现更多的逻辑,比如对数据进行处理,仅仅靠变量是不能实现的。

    五、闭包的实际应用

    上面小编介绍了闭包,那么在实际项目中有什么应用呢?先看下面代码:

    1、隐藏内部变量名称和函数执行暂停

    function outer() {
        let name = 1
        function inner() {
            return name ++
        }
        return inner
    }
    let g = outer()
    console.log(g()) // 2
    console.log(g()) // 3
    console.log(g()) // 4
    console.log(g()) // 5
    
    

    2、setTimeout函数传递参数

    默认的setTimeout是这样的:

    小编也曾经这样试验过

    function f1(p) {
        console.log(p)
    }
    setTimeout(f1(666),3000) // 并没有延时,直接输出666
    
    

    要想通过延时来实现对函数传递参数,这时候,闭包的作用就显现出来了。

    function f1(a) {
        function f2() {
            console.log(a);
        }
        return f2;
    }
    var fun = f1(1);
    setTimeout(fun,1000); // 一秒之后打印出1
    
    

    3、回调

      定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。就像下面这块代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>测试</title>
    </head>
    <body>
        <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  id="size-12">12</a>
        <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  id="size-20">20</a>
        <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  id="size-30">30</a>
    
        <script type="text/javascript">
            function changeSize(size){
                return function(){
                    document.body.style.fontSize = size + 'px';
                };
            }
    
            var size12 = changeSize(12);
            var size14 = changeSize(20);
            var size16 = changeSize(30);
    
            document.getElementById('size-12').onclick = size12;
            document.getElementById('size-20').onclick = size14;
            document.getElementById('size-30').onclick = size16;
    </script>
    </body>
    </html>
    
    

    4、函数防抖

      在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

       实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。就像下面这样:

    /*
    * fn [function] 需要防抖的函数
    * delay [number] 毫秒,防抖期限值
    */
    function debounce(fn,delay){
        let timer = null    //借助闭包
        return function() {
            if(timer){
                clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
                timer = setTimeOut(fn,delay) 
            }else{
                timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
            }
        }
    }
    
    

    六、使用类实现类似闭包中隐藏内部变量功能

    上面是一个关于闭包的实际应用,小编在晚上睡不着觉的时候,想起同样的需求,是不是也可以通过类来实现呢?最后经过一顿折腾,答案是肯定的,就像这样:

    class Adder{
        constructor(c){
            this._c = c
        }
        increace(){
            this._c ++ 
        }
        decreace(){
            this._c --
        }
        get finalNum(){
            return this._c
        }
    }
    let c = new Adder(1)
    c.increace()
    console.log(c.finalNum) // 2
    c.increace()
    console.log(c.finalNum) // 3
    c.increace()
    console.log(c.finalNum) // 4
    c.decreace()
    console.log(c.finalNum) // 3
    
    

    参考文章:

    https://www.cnblogs.com/gg-qq...

    https://www.cnblogs.com/pikac...

    https://developer.mozilla.org...

    【文章出处:cc防御 转载请说明出处】