闭包实际项目中应用场景有哪些举例

什么是闭包&&举例

在编程中,闭包(Closure) 是指一个函数能够 "记住" 其定义时所处的作用域(即使该作用域已经销毁),并可以访问和操作该作用域中的变量。简单来说,闭包是 "函数 + 其捆绑的周边状态(词法环境)" 的组合。

核心特点:

  1. 函数嵌套:闭包通常由嵌套函数实现,内部函数引用了外部函数的变量。
  2. 作用域保留:外部函数执行结束后,其作用域不会被销毁(因为内部函数仍在引用其中的变量)。
  3. 变量私有化:外部函数的变量可以被内部函数访问,但无法被外部直接修改,实现了 "私有变量" 的效果。

举个例子:

javascript 复制代码
function outer() {
  let count = 0; // 外部函数的变量

  // 内部函数,引用了外部的count
  function inner() {
    count++;
    return count;
  }

  return inner; // 返回内部函数
}

// 调用外部函数,得到闭包(inner函数 + 其捆绑的count)
const closure = outer();

console.log(closure()); // 1(count从0变为1)
console.log(closure()); // 2(count从1变为2)
console.log(closure()); // 3(count持续被保留和修改)

在这个例子中:

  • outer 函数执行后,理论上其作用域(包括 count)应被销毁,但由于 inner 函数引用了 countouter 的作用域被 "保留" 了下来。
  • closure 变量持有 inner 函数,每次调用 closure() 时,都能操作 outer 中定义的 count,这就是闭包的效果。

在 Vue 项目中, 闭包的应用场景非常广泛,核心是利用其 "保存词法环境(状态)并允许外部访问内部变量" 的特性,解决状态隔离、逻辑封装、异步上下文保持等问题。 以下是具体场景及示例:

1. 组件生命周期与异步操作中的状态留存

组件的生命周期钩子(如 mountedbeforeDestroy)和异步操作(定时器、接口请求)中,闭包用于留存组件实例或局部变量,确保异步回调能正确访问上下文。

示例:组件内定时器的清理

vue

xml 复制代码
<template>
  <div>{{ time }}</div>
</template>
<script>
export default {
  data() { return { time: 0 } },
  mounted() {
    // 定时器回调是闭包,保存了组件实例 this
    const timer = setInterval(() => {
      this.time++; // 访问组件 data 中的 time
    }, 1000);

    // beforeDestroy 钩子回调是闭包,保存了 timer 变量
    this.$on('hook:beforeDestroy', () => {
      clearInterval(timer); // 组件销毁时清理定时器
    });
  }
};
</script>
  • 闭包确保异步回调(定时器、销毁钩子)能访问 this(组件实例)和 timer(局部变量),避免状态丢失。

2. 事件处理与防抖 / 节流逻辑

事件处理函数(如 @click@input)中,闭包可封装防抖、节流等逻辑,保存中间状态(如定时器 ID),避免污染组件数据。

示例:输入框防抖

vue

xml 复制代码
<template>
  <input @input="handleInput" placeholder="搜索...">
</template>
<script>
export default {
  methods: {
    handleInput() {
      let timer; // 闭包保存定时器状态
      return (e) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log('搜索:', e.target.value); // 防抖后执行
        }, 500);
      };
    }() // 立即执行,返回闭包函数作为事件处理函数
  }
};
</script>
  • 闭包将 timer 隔离在事件处理逻辑内部,避免多个输入框共享状态冲突。

3. 自定义指令的私有状态管理

自定义指令中,闭包用于保存指令实例的私有状态(如绑定值、临时变量),确保多个指令实例间状态隔离。

示例:权限控制指令

vue

xml 复制代码
<script>
export default {
  directives: {
    permission: {
      inserted(el, binding) {
        const requiredPerm = binding.value; // 闭包保存当前指令需要的权限
        // 检查权限的函数(闭包访问 requiredPerm)
        const checkPerm = () => {
          if (!hasPermission(requiredPerm)) { // 假设 hasPermission 是权限工具
            el.style.display = 'none'; // 无权限则隐藏元素
          }
        };
        checkPerm();
        // 监听权限变化(闭包确保能访问 checkPerm 和 requiredPerm)
        el._permWatcher = window.addEventListener('permChange', checkPerm);
      },
      unbind(el) {
        // 清理监听(闭包访问 el._permWatcher)
        window.removeEventListener('permChange', el._permWatcher);
      }
    }
  }
};
</script>
<template>
  <button v-permission="'delete'">删除按钮</button>
</template>
  • 闭包使指令的 insertedunbind 钩子能共享 requiredPermcheckPerm,且每个指令实例的状态独立。

4. Vue 3 Composition API 与组合式函数

Vue 3 的 setup 函数和组合式函数(如 useXXX)重度依赖闭包封装响应式状态和逻辑,实现逻辑复用且状态隔离。

示例:封装表单验证逻辑

vue

ini 复制代码
<script setup>
import { ref } from 'vue';

// 组合式函数:闭包封装表单状态和验证逻辑
function useFormValidator(initialValues) {
  const form = ref(initialValues);
  const errors = ref({});

  const validate = () => {
    errors.value = {};
    // 验证逻辑(闭包访问 form 和 errors)
    if (!form.value.name) errors.value.name = '必填';
    return Object.keys(errors.value).length === 0;
  };

  return { form, errors, validate }; // 暴露闭包中保存的状态和方法
}

// 组件中使用:两个表单实例状态完全隔离
const userForm = useFormValidator({ name: '' });
const addressForm = useFormValidator({ city: '' });
</script>
  • 每次调用 useFormValidator 时,内部的 formerrorsvalidate 形成闭包,不同实例的状态互不干扰。

5. 状态管理(Vuex/Pinia)中的 getter 与 action

Vuex/Pinia 的 getter 函数通过闭包访问 state,action 则通过闭包维护异步操作中的上下文(如 commitdispatch)。

示例:Pinia 的 getter 与 action

javascript

运行

javascript 复制代码
// store/user.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({ token: null, info: null }),
  getters: {
    isLogin(state) {
      // getter 是闭包,访问外部 state
      return !!state.token;
    }
  },
  actions: {
    async fetchUserInfo() {
      // action 闭包访问 state 和 this(store 实例)
      const res = await api.getUser(this.token); // 用当前 token 发请求
      this.info = res.data; // 更新状态
    }
  }
});
  • getter 和 action 本质是闭包,确保能访问最新的 state 和 store 实例方法。

6. 高阶函数与逻辑复用

通过闭包创建高阶函数(返回函数的函数),封装通用逻辑(如权限校验、参数预设),实现代码复用。

示例:封装带 loading 状态的请求

vue

xml 复制代码
<script setup>
import { ref } from 'vue';

// 高阶函数:闭包保存 loading 状态
function withLoading(apiFn) {
  const loading = ref(false);
  const wrappedFn = async (...args) => {
    loading.value = true;
    try {
      return await apiFn(...args); // 闭包调用传入的接口函数
    } finally {
      loading.value = false;
    }
  };
  return { wrappedFn, loading }; // 暴露闭包中的状态和包装函数
}

// 使用:获取用户列表(自带 loading 状态)
const { wrappedFn: fetchUsers, loading } = withLoading(api.getUsers);
</script>
<template>
  <button @click="fetchUsers" :disabled="loading">
    {{ loading ? '加载中' : '获取用户' }}
  </button>
</template>
  • 闭包使 wrappedFn 能访问 loading 状态,且每个 withLoading 调用的状态独立。

总结

闭包在 Vue 中的核心作用是:

  • 隔离状态:如组合式函数、指令实例的独立状态;
  • 保存上下文:确保异步操作、事件回调能访问正确的组件 / Store 实例;
  • 封装逻辑:将相关状态与方法捆绑,避免全局污染,提升复用性。

所以上面的几个举例只是日常中大家经常见到的,肯定不止这些例子,不管什么项目,只要体现出函数内部调用函数外部的常量就行,确保常量不被修改,体现出闭包的特性就行

使用时需注意:闭包可能导致变量长期驻留内存,需及时清理(如组件销毁时清除定时器、解绑事件),避免内存泄漏。

相关推荐
专注前端30年5 小时前
【Vue2】基础知识汇总与实战指南
开发语言·前端·vue
懵圈5 小时前
第02章:使用Vite初始化项目
前端
CodeCraft Studio5 小时前
前端表格工具AG Grid 34.3 发布:重磅引入AI工具包,全面支持 React 19.2!
前端·人工智能·react.js·angular·ag grid·前端表格工具·透视分析
剑小麟5 小时前
maven中properties和dependencys标签的区别
java·前端·maven
西洼工作室5 小时前
优化网页性能指标:提升用户体验的关键
前端·css
掘金一周6 小时前
第一台 Andriod XR 设备发布,Jetpack Compose XR 有什么不同?对原生开发有何影响? | 掘金一周 10.30
前端·人工智能·后端
_大学牲6 小时前
Flutter 之魂 Dio🔥:四两拨千斤的网络库
前端·数据库·flutter
一枚前端小能手6 小时前
🌐 HTML DOM API全攻略(上篇)- 从基础操作到高级技巧的完整指南
前端·javascript·html
黄毛火烧雪下6 小时前
使用 Ant Design Pro CLI 快速创建前端中台项目
前端