汇总一下容易忽视的闭包在项目中的使用场景

203 阅读3分钟

前言

如今这年头早已不是靠八股文就能横扫求职战场的年代了,所有的问题都应该会归于项目中,闭包问题作为一个老生常谈的问题,在面试中屡见不鲜,然而我们往往很容易忽视这个问题在项目中的使用,一味的八股文恐怕很难获得面试官的青睐~

1.数据封装和信息隐蔽

创建私有变量,这些变量无法被外部直接访问。这是实现模块化或者构造函数中数据隐蔽的方式。也是最常见的闭包使用场景:

function createCounter() {
  let count = 0;
  return function increment() {
    count ++;
    return count;
  }
}
const fn = createCounter();

console.log(fn()); // 1
console.log(fn()); // 2
console.log(fn()); // 3

2.缓存函数

函数能够缓存记忆先前的计算结果,减少计算从而优化性能

function memorize(fn) {
  let cache = {}; // 定义对象进行缓存数据的存储
  return function(...args) {
    console.log('获取参数', args)
    let n = args[0];
    if(n in cache) {
      console.log('从缓存中取值', cache[n])
      return cache[n];
    } else {
      let result = fn(n);
      cache[n] = result;
      console.log('执行计算取值', result)
      return result;
    }
  }
}

function getInfn(num) {
  console.log('执行复杂运算',)
  return num * 2000;
}

const cacheFn = memorize(getInfn);

cacheFn(3);
cacheFn(3);
cacheFn(3);
cacheFn(3);

// 输出结果
取参数 [ 3 ]
执行复杂运算
执行计算取值 6000
获取参数 [ 3 ]
从缓存中取值 6000
获取参数 [ 3 ]
从缓存中取值 6000
获取参数 [ 3 ]
从缓存中取值 6000

3. 事件处理器中维持状态

用于在不同的事件调用中保存状态

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="myBtn">点我吧哈哈</button>
</body>
<script>
  function eventFn() {
    let clickTimes = 0;
    document.getElementById('myBtn').addEventListener('click', () => {
      clickTimes ++;
      console.log('记录点击次数', clickTimes)
    })
  }
  eventFn()
</script>
</html>

4. 函数的防抖和节流

必包实现节流函数 —— 在一定时间范围内,只执行第一次函数回调

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流</title>
<body>
  <button id="btn">点我试试</button>
  <script>
    const btn = document.querySelector("#btn");
    // 要求: 3s内只输出一次hi
    function sayHi() {
      console.log("hi")
    }
    // 在一定时间范围内,只执行第一次函数回调
    function throttle(fn, wait = 3000) {
      let flag = true;
      return () => {
        if (flag) {
          flag = false;
          fn();
          setTimeout(() => {
            flag = true;
          }, wait)
        }
      }
    }
    btn.addEventListener("click", throttle(sayHi, 3000))
  </script>
</body>

</html>

防抖 —— 在一定时间范围内,只执行最后一次函数回调

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

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

<body>
  <button id="btn">点我试试</button>
  <script>
    const btn = document.querySelector("#btn")
    function sayHi() {
      console.log("Hi");
    }
    // 在一定时间范围内,只执行最后一次函数回调
    function debounce(fn, delay) {
      let timer = null;
      return () => {
        if (timer) {
          clearTimeout(timer);
        }
        timer = setTimeout(() => {
          fn();
        }, delay);
      }
    }
    btn.addEventListener("click", debounce(sayHi, 1000));
  </script>
</body>

</html>

5. 函数柯里化

通过闭包记录每次调用的参数,并最终累计求值

function sum(a) {
  return function(b) {
    return b ? sum(a + b) : a;
  }
}

console.log(sum(1)(2)(3)(4)())

6.维护链式调用的状态

闭包在构建支持链式调用的库中很有用,例如 jQuery 中,可以在调用链中维持状态

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <h3 class="selector">hello</h3>
    <h3 class="selector">world</h3>
  </div>
</body>
<script>
  // 闭包实现一个链式调用的函数
  function query(selector) {
    let ele = document.querySelectorAll(selector);
    return {
      hide: function() {
        ele.forEach(val => val.style.display = 'none');
        return this;
      },
      show: function() {
        ele.forEach(val => val.style.display = '');
        return this;
      }
    }
  }

  let time = 0;
  // 定时器累积为偶数时隐藏,为奇数时显示
  setInterval(() => {
    if(time % 2 === 0) {
      query('.selector').hide();
    } else {
      query('.selector').show();
    }
    time ++;
  }, 2000)
</script>
</html>

7.利用立即执行函数和闭包实现模块化,封装私有状态和方法

const myModule = (function() {
  let _var = "private value";
  function privateFn() {
    console.log(_var);
  }
  return {
    publicMethod: function() {
      privateFn()
    } 
  }
})()

myModule.publicMethod();