闭包

171 阅读2分钟

一、概念

闭包就是能够读取其他函数内部变量的函数。这是因为内部函数保持了对外部函数作用域的引用,从而使得这些变量不会被垃圾回收机制回收。

闭包的用途和优缺点

二、闭包的用途包括:

  1. 访问内部变量‌:闭包允许在外部访问函数内部的变量,这对于创建私有变量非常有用。
  2. 保持状态‌:闭包可以保持函数的执行环境,即使函数已经执行完毕,其内部变量也不会被释放。

优点:

  • 封装‌:闭包可以封装私有变量,防止外部直接访问。
  • 持久化‌:闭包可以保持函数的执行环境,使得某些状态信息得以保留。

缺点:

  • 内存占用‌:由于闭包会保持外部函数的执行环境,可能会导致内存占用增加。
  • 内存泄漏‌:如果不正确管理闭包,可能会导致内存泄漏问题。

三、Vue中闭包的应用

Vue 源码中,闭包主要用于:

  • 数据封装(如 reactivecomputed
  • 缓存结果(如 computed 避免重复计算)
  • 事件管理(如 EventEmitter 存储事件)
  • 依赖管理(如 Dep 存储 Watcher)

3.1 响应式数据劫持(Reactivity System)

Vue 2 采用 Object.defineProperty 进行响应式处理,而 Vue 3 采用 Proxy,但无论哪种方式,闭包都用于存储依赖和数据。

Vue 3 的 reactive

function createReactiveObject(target: any) {
  const handlers = {
    get(target, key, receiver) {
      console.log(`访问属性:${key}`);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      console.log(`修改属性:${key} = ${value}`);
      return Reflect.set(target, key, value, receiver);
    }
  };
  
  return new Proxy(target, handlers);
}

const state = createReactiveObject({ count: 0 });
  • handlers 这个对象被 Proxy 捕获器(拦截器)所引用,形成了闭包。
  • 这个闭包确保了每次 get/set 都会执行特定的逻辑,而不会污染全局。

3.2 缓存计算属性(Computed)

Vue 3 的 computed 依赖 闭包 来存储计算结果,避免重复计算。

function computed(getter) {
  let cache;
  let dirty = true;  // 是否需要重新计算
  return {
    get value() {
      if (dirty) {
        cache = getter();  // 只在必要时重新计算
        dirty = false;
      }
      return cache;
    }
  };
}

const myComputed = computed(() => Math.random()); 
console.log(myComputed.value);  // 第一次计算
console.log(myComputed.value);  // 直接返回缓存
  • cache 变量存储计算结果,避免重复计算。
  • dirty 变量用于标记是否需要重新计算。

3.3 Vue 事件系统(EventEmitter)

Vue 的事件系统本质上是一个 发布-订阅模式,闭包用于存储事件列表。

function createEventEmitter() {
  const events = {};  // 用于存储事件

  return {
    on(event, fn) {
      if (!events[event]) events[event] = [];
      events[event].push(fn);
    },
    emit(event, ...args) {
      if (events[event]) {
        events[event].forEach(fn => fn(...args));
      }
    }
  };
}

const emitter = createEventEmitter();
emitter.on('click', () => console.log('点击事件触发'));
emitter.emit('click');  // 触发事件
  • events 变量不会暴露到外部,只能通过 onemit 操作,保证数据安全性。