这一次,彻底搞懂闭包

788 阅读4分钟

什么是闭包?

在了解闭包之前,我们首先需要搞懂什么是闭包,闭包指的是一个有权访问另一个函数作用域中变量的函数。

为什么需要闭包?

我们知道在JS中,变量分为全局变量和局部变量,全局变量的作用域为全局作用域,局部变量的作用域为局部作用域,我们可以在函数内部访问全局变量,但是无法在全局环境中访问函数的局部变量,闭包可以帮助我们访问到这个局部变量。

闭包的应用场景

1. 数据私有化 / 模块化模式

这是最经典的应用,用来创建私有变量:

function createCounter() {
  let count = 0;  // 私有变量,外部无法直接访问
  
  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();  // 1
counter.increment();  // 2
console.log(counter.count);  // undefined - 无法直接访问
console.log(counter.getCount());  // 2 - 只能通过方法访问

实际应用:比如购物车功能、状态管理器等,需要保护内部数据不被外部随意修改。


2. 防抖(Debounce)和节流(Throttle)

这是性能优化中非常常用的场景:

// 防抖:搜索框输入
function debounce(fn, delay) {
  let timer = null;  // 闭包保存 timer
  
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用场景
const searchInput = document.getElementById('search');
const handleSearch = debounce(function(e) {
  console.log('发起搜索请求:', e.target.value);
}, 500);

searchInput.addEventListener('input', handleSearch);
// 节流:滚动事件
function throttle(fn, interval) {
  let lastTime = 0;
  
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 使用场景
window.addEventListener('scroll', throttle(function() {
  console.log('处理滚动事件');
}, 200));

实际应用:搜索框输入联想、页面滚动加载、窗口 resize 事件等。


3. 函数柯里化(Currying)

将多参数函数转换为单参数函数序列:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

// 实际应用:日志记录
function log(level, time, message) {
  console.log(`[${level}] ${time}: ${message}`);
}

const curriedLog = curry(log);
const errorLog = curriedLog('ERROR');
const errorLogNow = errorLog(new Date().toISOString());

errorLogNow('数据库连接失败');  // [ERROR] 2025-11-18T...: 数据库连接失败
errorLogNow('API 请求超时');    // [ERROR] 2025-11-18T...: API 请求超时

实际应用:参数复用、函数式编程、配置预设等。


4. React Hooks(useState, useEffect 等)

React 的 Hooks 底层就是基于闭包实现的:

// 简化版 useState 实现原理
function useState(initialValue) {
  let state = initialValue;  // 闭包保存状态
  
  function setState(newValue) {
    state = newValue;
    render();  // 触发重新渲染
  }
  
  return [state, setState];
}

// 实际使用
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

实际应用:React 组件状态管理、副作用处理等。


5. 延迟执行 / 偏函数应用

function partial(fn, ...presetArgs) {
  return function(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  };
}

// 实际应用:API 请求封装
function request(method, url, data) {
  return fetch(url, { method, body: JSON.stringify(data) });
}

const get = partial(request, 'GET');
const post = partial(request, 'POST');

get('/api/users');
post('/api/users', { name: 'John' });

6. 定时器管理

function createTimer() {
  let timerId = null;
  
  return {
    start(callback, delay) {
      this.stop();
      timerId = setInterval(callback, delay);
    },
    stop() {
      if (timerId) {
        clearInterval(timerId);
        timerId = null;
      }
    }
  };
}

const timer = createTimer();
timer.start(() => console.log('tick'), 1000);
// 稍后
timer.stop();

总结

闭包的核心价值在于:

  1. 数据持久化:在函数执行完后仍然保留变量
  2. 数据私有化:创建私有作用域,保护数据
  3. 参数预设:创建特定配置的函数

在实际开发中,几乎每天都在使用闭包,只是有时候我们没有意识到。像事件处理、定时器、回调函数、Promise、async/await 等,背后都涉及到闭包的概念。

闭包解决了什么问题?(闭包的作用)

  1. 闭包可以缓存上级作用域,使得函数外部打破了函数作用域的束缚,可以访问函数内部的变量。
  2. 让变量的值始终保持在内存中。

闭包带来了什么问题?(闭包的缺陷)

闭包会导致函数的变量一直保存在内存中,过多的闭包会导致内存泄露。

数组的哪些方法用到了闭包?

  • 例如forEach
const arr = [1,2,3];

arr.forEach((item,index) => {
    setTimeout(() => {
        console.log(item);
    },1000)
})

只要是符合闭包的定义,一个有权访问另一个函数作用域中变量的函数就是闭包。权威指南中说,严格意义来说JS中的函数都是闭包。

参考资料