闭包、递归函数

220 阅读3分钟

1、闭包

1)闭包的定义

闭包(closure)是什么?一个作用域可以访问另一个函数内部的局部变量。此时就会有闭包产生,那么这个闭包所在的函数我们就称之为闭包函数。

概括:1.闭包 : 是一个可以访问其他函数作用域的函数

function fn(){
    let a = 1
    function fn1() {
        console.log(a)
    }
    fn1()
}

执行函数 fn1 用到了另一个函数fn中的 a 这个变量,所以 fn1 + a 构成了闭包。

2)闭包的作用(优缺点)

闭包的优点和缺点:

  • 1.延长了变量的生命周期
  1. 优点:执行空间不会销毁,变量也不会被销毁
  2. 缺点:执行空间不会销毁,会一直存在内存中,占用内存,影响性能
  • 2.可以访问函数内部的私有变量或者私有数据
  1. 优点:可以利用闭包访问函数内的私有变量
  2. 缺点:占用内存,影响性能
  • 3. 保护私有变量(只要是函数都有这个特点)
  1. 优点:保护私有变量不被外界访问
  2. 缺点:如果想要访问私有变量,必须要利用闭包函数

闭包的致命缺点:

因为当一段内存在内存空间一直存在,不被销毁,就会出现内存占用,当占用过多时,导致内存溢出,结果就容易造成内存泄漏。

闭包的作用:

  • 当你需要延长变量的生命周期或者需要访问函数里面的私有变量或者私有数据,可以使用闭包,如果有别的办法,就尽量不使用闭包,慎用闭包,因为容易造成内存泄漏。 直接作用:解决变量污染问题,让变量被函数保护起来。

案例演示

let count = 0
setInterval(function () {
  console.log(count++)
}, 1000)

以上代码中的 count 是一个使用频率很高的变量名,为了避免和其他位置的代码冲突,可以再使用一个函数把以上代码包裹起来,起到保护作用。

function fn() {
  let count = 0

  setInterval(function () {
    console.log(count++)
  }, 1000)
}

以上代码中,setInterval 第一个参数的匿名函数count 构成了闭包。 将以上代码改写如下:

function fn() {
  let count = 0
  function add() {
    console.log(count++)
  }
  
  setInterval(add, 1000)
}

以上代码中,add + count 构成了闭包。

结论:一个函数内使用了外部的变量,那这个函数和被使用的外部变量一起被称为闭包结构,在实际开发中,通常会再使用一个函数包裹住闭包结构,以起到对变量保护的作用。

注意

  • 闭包会在父函数外部,改变父函数内部变量的值。因此如果把父函数当作(object)使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这是一定要小心,不要随便改变父函数内部变量的值

3)闭包的案例

案例需求:在输入框输入搜索文字,点击百度一下按钮,用定时器模拟网络请求,1 秒之后显示搜索结果;

  • 页面结构
<div class="box">
  <input type="search" name="" id="">
  <button>百度一下</button>
</div>
  • 代码如下
// 1. 获取元素
let search = document.querySelector('.box input')
let btn = document.querySelector('.box button')

// 2. 添加点击事件
btn.onclick = function () {
  // 获取搜索的文字
  let text = search.value

  // 模拟发送网络请求
  setTimeout(function () {
    alert(`您搜索的内容是 ${text} 共搜索到 12345 条结果`)
  }, 1000)
}

4)闭包总结

闭包 = 函数 + 上下文的引用

闭包的作用:解决变量污染问题,让变量被函数保护起来。

-   在 ES5 时代,闭包可以解决一些其他 JavaScript 的小 BUG,但随着 ES6 `let` 等新语法的诞生,之前一些闭包的使用场景已经不再需要

2、递归函数

1)递归介绍

  • 1.递归函数:一个函数自己调用自己

  • 2.递归函数特点

    • a.一定要有结束条件,否则会导致死循环
    • b.能用递归函数实现的需求,就一定可以用循环调用函数来解决,只是代码简洁与性能不同而已
<!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. 递归特点
            a. 能用递归实现的功能一定可以用循环,只是语法不同
            b. 递归一定要有结束的条件,否则会导致死循环
         */

        //一个函数递归
        // function fn(){
        //     console.log('哈哈');
        //     fn();
            
        // };

        // fn();

        //两个函数递归
        // function fn1(){
        //     console.log('哈哈');
        //     fn2();
            
        // };

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


        //需求:写一个函数,打印三次 今天天气真好

        let i = 1;
        function fn(){
            console.log('今天天气真好');
            i++;
            if(i <= 3){
                fn();
            };
            
            //循环实现
            // for(let i = 1;i<=3;i++){
            //     console.log('今天天气真好');
                
            // };
        };

        fn();
    </script>
</body>
</html>

2)递归应用场景

  1. 深拷贝(前面的文章讲过) 02)有这样的案例 服务器返回一个不确定的数据结构。 是一个多级菜单,这个数据是不确定的。我们需要根据服务器返回的数据,来生成对应的页面结构,可以利用递归来实现遍历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: ['雀巢咖啡']
            }
          ]
        }
      ]

      /* 使用递归遍历数组 */
      function addElement (arr, father) {
        for (let i = 0; i < arr.length; i++) {
          let div = document.createElement('div')
          div.innerHTML = `<p>${arr[i].type || arr[i] }</p>`
          father.appendChild(div)
          if( arr[i].data ){
            addElement(arr[i].data,div)
          }
        }
      }

      //调用递归函数
      addElement(arr,document.querySelector('.menu'))
    </script>
  </body>
</html>

后记

image.png