研究一下闭包和递归

317 阅读3分钟

一.闭包

1.闭包(closure)是什么 :

  • 闭包 是一个 访问其他函数内部变量 的 函数
  • 闭包 = 函数 + 上下文引用

2.闭包作用 : 解决变量污染

一般用于回调函数

3.可以在浏览器中调试闭包

    function fn() {
      let num = 10
      // fn1 + 访问num 组合才叫闭包
      function fn1() {
        console.log(num)
      }
      fn1()
    }
    fn()

4.代码示例

function a(){

let i = 0;

function b(){

alert(++i);

}

return b;

}
let c = a();

c();

这段代码有两个特点:

1、函数b嵌套在函数a内部;

2、函数a返回函数b。

这样在执行完let c = a( )后,变量c实际上是指向了函数b,再执行c( )后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,这是因为函数a外面的变量c引用了函数a内的函数b。也就是说,当函数a的内部函数b被函数a外面的一个变量引用的时候,就创建了一个闭包。

5.闭包案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>闭包案例</title>
</head>

<body>
    <input type="text" placeholder="请输入搜索内容">
    <button class="btn">点我搜索</button>

    <script>
        /* 
        1.闭包(closure)是什么 : 
            a. 闭包 是一个 访问其他函数内部变量 的 函数
            b. 闭包 = 函数 + 上下文引用

        2.闭包作用 : 解决变量污染
            * 一般用于回调函数

        3.在浏览器中调试闭包 :
        */

        /*  document.querySelector('.btn').onclick = function(){
             //1.获取用户搜索的内容
             let txt = document.querySelector('input').value
             //2.网络请求 : 不是立即就能出结果的,网络请求需要时间的。
             //使用定时器来模拟请求
             setTimeout(function(){
                 alert(`${txt}的搜索结果如下:123条`)
             },1000)
         } */


        /* 自己写
     1.闭包是什么:闭包是一个访问其他函数内部变量的函数
       闭包组合 = 函数 + 上下文引用
     2.闭包作用:解决变量污染
        闭包一般用于回调函数
     3.可以在浏览器控制台调试闭包 */

        // 点击搜索按钮, 获取输入框内容, 发送服务器
        document.querySelector('button').onclick = function () {
            // (1)获取输入框内容
            let input = document.querySelector('input')
            // (2)发送服务器: 使用定时器模拟
            setTimeout(function () {
                alert(`${input.value}的搜索结果为:189条`)
                alert(22222222222)
            }, 1000)
        }
    </script>
</body>

</html>

6. 应用场景

1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i一直存在于内存中,因此每次执行c(),都会给i自加1。

7. 使用注意点

(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

二.递归

1.递归 : 在函数内部调用自己

递归作用和循环类似的,也需要有结束条件

2.递归作用 :

  • 浅拷贝与深拷贝
  • 遍历dom树

3.代码示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script>
        /* 
        1.递归函数:  一个函数 在内部 调用自己
            * 递归作用和循环类似的,也需要有结束条件

        2.递归应用:
        
        */

        /*  function fn(){
            console.log('今天学得很开心')
            fn()
        }

         fn() */

        //双函数递归 : 两个函数互相调用
         function fn1(){
            console.log('哈哈')
            fn2()
        }

        function fn2(){
            console.log('呵呵')
            fn1()
        }

         fn2()  

       /*  自己写
        递归函数: 在函数内部调用自己
        递归函数功能和循环类似的.(优先用循环, 递归少用)

        单函数递归 */
        /* function fn() {
            console.log('哈哈')
            fn()
        } */
    </script>
</body>

</html>

4.构成递归需具备的条件:

  1. 子问题须与原始问题为同样的事,且更为简单;

  2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。

5.浅拷贝与深拷贝:递归实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script>
        /* 
        1.递归函数:  一个函数 在内部 调用自己
            * 递归作用和循环类似的,也需要有结束条件

        2.递归应用:
            浅拷贝与深拷贝 : 
                方式一(推荐) : JSON方式实现
                    * let newObj = JSON.parse( JSON.stringify( obj ) )
                方式二(递归) : 了解
            遍历dom树
        */

        /* let obj = {
            name:'张三',
            age:20,
            sex:'男',
            hobby:['吃饭','睡觉','学习'],
            student:{
                name:"班长",
                score:90
            }
        }

        //使用递归函数
        function kaobei(obj,newObj){
            for(let key in obj){
                if( obj[key] instanceof Array ){
                    //声明一个空数组,然后继续拷贝数组里面的数据
                    newObj[key] = []
                    //递归调用继续拷贝 数组
                    kaobei(obj[key],newObj[key])
                }else if(  obj[key] instanceof Object ){
                     //声明一个空对象
                     newObj[key] = {}
                    //递归调用继续拷贝 对象
                    kaobei(obj[key],newObj[key])
                }else{
                    newObj[key] = obj[key]
                }
            }
        }
        //创建一个空对象,然后深拷贝
        let newObj = {}
        kaobei(obj,newObj)

        newObj.name = '李四'
        newObj.hobby[0] = '摸鱼'
        newObj.student.name = '欧阳'
        console.log( obj,newObj) */

        // 自己写
        let obj = {
            name: '张三',
            age: 33,
            hobby: ['学习', '上课', '干饭'],
            student: {
                name: '尼古拉斯凯奇',
                age: 66
            }
        }
        // 深拷贝函数
        function copy(obj, newObj) {
            for (let key in obj) {
                if (obj[key] instanceof Array) {
                    newObj[key] = []
                    // 递归调用,继续深拷贝数组
                    copy(obj[key],newObj[key])
                }else if(obj[key] instanceof Object){
                    newObj[key] = {}
                    // 递归调用,继续深拷贝对象
                    copy(obj[key],newObj[key])
                }else{
                    newObj[key] = obj[key] 
                }
            }
        }
        // 开始拷贝
        let newObj = {}
        copy(obj,newObj)
        newObj.name = '嗯嗯嗯'
        newObj.hobby[0] = '电影'
        newObj.student.name = 'uuuuuuu'
        console.log(obj,newObj)
    </script>
</body>

</html>

6.递归遍历dom树

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>Document</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    .menu p {
      width: 100px;
      border: 3px solid;
      margin: 5px;
    }

    .menu>div p {
      margin-left: 10px;
      border-color: red;
    }

    .menu>div>div p {
      margin-left: 20px;
      border-color: green;
    }

    .menu>div>div>div p {
      margin-left: 30px;
      border-color: yellow;
    }
  </style>
</head>

<body>
  <div class="menu">
    <!-- <div>
        <p>第一级菜单</p>
        <div>
          <p>第二级菜单</p>
          <div>
            <p>第三级菜单</p>
          </div>
        </div>
      </div> -->
  </div>
  <script>
    //服务器返回一个不确定的数据结构,涉及到多重数组嵌套
    let arr = [
      {
        type: "电子产品",
        data: [
          {
            type: "手机",
            data: ["iPhone手机", "小米手机", "华为手机"]
          },
          {
            type: "平板",
            data: ["iPad", "平板小米", "平板华为"]
          },
          {
            type: "智能手表",
            data: []
          }
        ]
      },
      {
        type: "生活家居",
        data: [
          {
            type: "沙发",
            data: ["真皮沙发", "布沙发"]
          },
          {
            type: "椅子",
            data: ["餐椅", "电脑椅", "办公椅", "休闲椅"]
          },
          {
            type: "桌子",
            data: ["办公桌"]
          }
        ]
      },
      {
        type: "零食",
        data: [
          {
            type: "水果",
            data: []
          },
          {
            type: "咖啡",
            data: ["雀巢咖啡"]
          }
        ]
      }
    ]

    //封装一个遍历dom树函数
    /* function addElement(arr, father) {
      //遍历数组
      for (let i = 0; i < arr.length; i++) {
        //(1)创建空标签
        let div = document.createElement("div")
        //(2)设置内容
        div.innerHTML = `<p>${arr[i].type || arr[i]}</p>`
        //(3)添加到父盒子
        father.appendChild(div)
        //如果元素还有data属性,则需要使用递归继续添加下级菜单
        if (arr[i].data) {
          addElement(arr[i].data, div)
        }
      }
    }

    addElement(arr, document.querySelector(".menu")) */

    // 自己写
    function addElement(arr, father) {
      for (let i = 0; i < arr.length; i++) {
        // (1)创建标签
        let div = document.createElement('div')
          // (2)设置内容
        div.innerHTML = `<p>${arr[i].type || arr[i]}</p>`
          // (3)添加到父元素上
        father.appendChild(div)
        // 如果有下级菜单, 继续递归添加
        if (arr[i].data) {
          addElement(arr[i].data, div)
        }
      }
    }
    // 调用函数
    addElement(arr,document.querySelector('.menu'))

  </script>
</body>

</html>

三.总结

闭包和递归是函数中的两个比较特殊的情况,所以人们要慎用。闭包相当于函数的返回值,而递归则相当于函数的参数