什么是闭包&&举例
在编程中,闭包(Closure) 是指一个函数能够 "记住" 其定义时所处的作用域(即使该作用域已经销毁),并可以访问和操作该作用域中的变量。简单来说,闭包是 "函数 + 其捆绑的周边状态(词法环境)" 的组合。
核心特点:
- 函数嵌套:闭包通常由嵌套函数实现,内部函数引用了外部函数的变量。
- 作用域保留:外部函数执行结束后,其作用域不会被销毁(因为内部函数仍在引用其中的变量)。
- 变量私有化:外部函数的变量可以被内部函数访问,但无法被外部直接修改,实现了 "私有变量" 的效果。
举个例子:
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函数引用了count,outer的作用域被 "保留" 了下来。closure变量持有inner函数,每次调用closure()时,都能操作outer中定义的count,这就是闭包的效果。
在 Vue 项目中, 闭包的应用场景非常广泛,核心是利用其 "保存词法环境(状态)并允许外部访问内部变量" 的特性,解决状态隔离、逻辑封装、异步上下文保持等问题。 以下是具体场景及示例:
1. 组件生命周期与异步操作中的状态留存
组件的生命周期钩子(如 mounted、beforeDestroy)和异步操作(定时器、接口请求)中,闭包用于留存组件实例或局部变量,确保异步回调能正确访问上下文。
示例:组件内定时器的清理
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>
- 闭包使指令的
inserted和unbind钩子能共享requiredPerm和checkPerm,且每个指令实例的状态独立。
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时,内部的form、errors与validate形成闭包,不同实例的状态互不干扰。
5. 状态管理(Vuex/Pinia)中的 getter 与 action
Vuex/Pinia 的 getter 函数通过闭包访问 state,action 则通过闭包维护异步操作中的上下文(如 commit、dispatch)。
示例: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 实例;
- 封装逻辑:将相关状态与方法捆绑,避免全局污染,提升复用性。
所以上面的几个举例只是日常中大家经常见到的,肯定不止这些例子,不管什么项目,只要体现出函数内部调用函数外部的常量就行,确保常量不被修改,体现出闭包的特性就行
使用时需注意:闭包可能导致变量长期驻留内存,需及时清理(如组件销毁时清除定时器、解绑事件),避免内存泄漏。