闭包:函数嵌套函数,内部函数就是闭包 正常情况下,函数执行完成,内部变量会销毁。在闭包中,外部函数已经执行完毕但是内部函数引用外部变量所以外部变量不会被销毁。
一个函数内部声明另一个函数,内部函数使用外部函数的变量
什么是闭包
MDN 定义:闭包是指能够访问自由变量的函数。
自由变量 是指在函数中使用,但既不是函数参数也不是函数的局部变量的变量。闭包 = 函数 + 函数能够访问的自由变量。
所有的函数都是闭包。用为它们在创建的时候就将上层上下文的数据保存起来。在函数中访问全局变量就相当于访问自由变量,这时使用最外层的作用域。可以访问其他函数作用域的内部函数叫做闭包。闭包让你可以在一个内层函数中访问到其外层函数的作用域。
从实践角度定义:以下函数才算闭包:
- 即使创建它的上下文已销毁,它任然存在。(比如内部函数从父函数中返回)。
- 代码中引用了自由变量。
一个函数内部有外部变量的引用,比如函数嵌套函数时,内层函数引用了外层函数作用域下的变量,即形成了闭包。最常见的场景为:函数作为一个函数的参数,或函数作为一个函数的返回值时
可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。而正因闭包会把函数中的变量值存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内层函数对象的变量赋值为 null。
闭包的特性
- 函数嵌套函数。
- 函数内部可以引用外部的参数和变量。
- 参数和变量不会被垃圾回收机制回收。
闭包的使用场景
- setTimeout:原生的 setTImeout 传递的第一个函数不能带参数,通过闭包可以实现传参效果。
- 回调:定义行为,然后关联到用户事件上,代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
- 防抖函数、记忆函数等。
- 私有变量的数据封装。
- 延长变量的生命周期。
- 防止全局变量污染。典型应用是模块封装,在各模块规范出现之前,都是用这样的方式防止变量污染全局
- 在循环中创建闭包,防止取到意外的值。
js
export const debounce = (fn, time) => {
let info = {
arr: new Array(10 * 1024 * 1024).fill(1),
timer: null
};
return function (...args) {
info.timer && clearTimeout(info.timer);
info.timer = setTimeout(() => {
fn.apply(this, args);
}, time);
};
};
如果把 info 变量放到 debounce 函数外部,从 A 页面跳转到 B 页面后,该 info 变量所占的内存会被释放掉吗?
let info = {
arr: new Array(10 * 1024 * 1024).fill(1),
timer: null
};
export const debounce = (fn, time) => {
return function (...args) {
info.timer && clearTimeout(info.timer);
info.timer = setTimeout(() => {
fn.apply(this, args);
}, time);
};
};
info 变量所占的内存也不会被释放掉
// vue 页面使用
import { debounce } from './util';
mounted() {
this.debounceFn = debounce(() => {
console.log('1');
}, 1000)
}
函数内部声明一个局部变量(包含闭包本身),把它传递给一个不被回收的对象以产生引用即可,如全局变量、模块局部变量、setInterval、给某个不打算删除的 html 元素加个监听事件等,都能引起内存溢出
闭包的优点与缺点
优点:
- 变量长期存储在内存中,实现变量数据共享。
- 避免全局变量的污染。
- 把变量存到独立的作用域,作为私有成员存在。
缺点:
- 常驻内存,增加内存使用量。
- 使用不当会很容易造成内存泄露。
- 闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
注意:需要明确的是,大量使用闭包,并不会造成内存泄漏。只要闭包使用得当,只要引用闭包的函数被正常销毁,闭包所占的内存都会被 gc 自动回收,并不会造成内存泄漏。