首页 纸飞机TG账号批发老号购买内容详情

闭包:那个“赖着不走”的家伙,到底有什么用?

2026-03-22 4 纸飞机账号购买

昨儿个,我们知晓了闭包,就是那个有着“虽已离开家庭,却仍记得家中密码”这般奇妙特性的函数。今儿咱们要深入探究一番:闭包此等事物究竟能够达成怎样的功用?是否存在副作用?又该如何避免它将内存耗尽?读完这篇内容,你不但会明白闭包的应用方式,还能够在面试场景中面对面试官时侃侃而谈。前言。

闭包如同那样一个租客,好似“赖着不走”一般。你原本觉得这人已经离开了,然而实际上他依旧留存着你的钥匙,还时不时回来取用一些物品。这种情况在JavaScript当中,有时会显得格外好用,可有时又会特别坑人。

咱们今儿就来清点一下闭包的若干经典运用场景,顺带说一说怎样能促使它“得体地退场”,莫要使汝的内存被吃尽。

一、闭包的应用场景,这个有着“赖着不走”特性的家伙居然蛮有用处,它可用于模块化,形成私有变量以及公共方法。

JavaScript在没有ES6模块的时候,闭包可是实现模块化的主要方式呢。它能够将内部的细节给隐藏起来,仅仅去暴露那些需要公开的接口呀。

const counter = (function() {
  let count = 0; // 私有变量,外面访问不到
  
  function increment() {
    count++;
    console.log(count);
  }
  
  function decrement() {
    count--;
    console.log(count);
  }
  
  function getCount() {
    return count;
  }
  
  return {
    increment,
    decrement,
    getCount
  };
})();
counter.increment(); // 1
counter.increment(); // 2
console.log(counter.count); // undefined,拿不到
console.log(counter.getCount()); // 2

此模式称作 IIFE(立即执行函数),其创建出一个闭包,于其中的 count 变量,被返回的方法“记住”,外部不能够直接进行修改,仅可依照所提供的接口来操作。这是不是类似一个“保险箱”?钥匙仅给予了几个特定的人。

2. 函数工厂:批量生产定制函数

闭包能够被用以创建具备特定“预设”的函数,举例来说,存在一个能够记录调用次数的函数。

function createCounter(initial = 0) {
  let count = initial;
  return function() {
    count++;
    return count;
  };
}
const counterA = createCounter(10);
console.log(counterA()); // 11
console.log(counterA()); // 12
const counterB = createCounter(0);
console.log(counterB()); // 1

每一个计数器,都单独有着自身的count变量,彼此之间不会产生干扰。这座工厂恰似制作定制蛋糕,每一位客户所拿到的,皆是属于自己独一无二的那一份。

3. 防抖与节流:控制函数执行频率

前端性能优化里较为常见的手法是防抖以及节流,它们的核心均借助闭包去对计时器以及状态进行保存。

用户连续触发事件之际,存在防抖情况,即唯有最后一次等待终结之后,才会予以执行这一操作,就像在搜索框进行输入时的那般情况。

function debounce(fn, delay) {
  let timer = null; // 闭包保存timer
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, delay);
  };
}
// 使用
const search = debounce(() => console.log('搜索中...'), 500);

节流:限制函数在单位时间内最多执行一次(比如滚动事件)。

function throttle(fn, delay) {
  let last = 0;
  return function(...args) {
    const now = Date.now();
    if (now - last >= delay) {
      last = now;
      fn.apply(this, args);
    }
  };
}

这两个函数返回的皆是闭包,当中的timer或者last被“记住”了,因而每次调用的时候均能够访问到上一次的状态。

4. 柯里化:提前固定参数

柯里化,是一项技术,这项技术能将多参数函数,转变为一系列单参数函数,其本质,同样也是闭包。

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...more) {
        return curried.apply(this, args.concat(more));
      };
    }
  };
}
function add(a, b, c) {
  return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

依照返回新设函数的每一回,原本的那些args会被闭包留存下来,一直到参数全都凑齐了才会执行。恰似你向一家餐厅留下了订单,每一次打电话去添加菜品,最后一同进行结算。

5. 事件监听中的回调

事件回调之中去访问外部变量,实际上它就是闭包,举例来说,有一个简单的计数器按钮。

let count = 0;
document.getElementById('btn').addEventListener('click', function() {
  count++;
  console.log(count);
});

此处在的匿名函数铭记了外部的count变量,每一次点击都能够访问到最新的数值。

二、闭包的“阴暗面”:内存泄漏与性能

明明闭包这般香,为何却有人讲它不好呢?原因在于它会“赖着不走”,就是那些被记住的变量,哪怕外部函数已然执行完毕,也不会被进行垃圾回收,只要闭包函数还处于存活状态,它们便会一直存在。

1. 什么是内存泄漏?

程序使用完内存后,系统却未及时进行回收,致使内存占用持续增大,最终造成浏览器出现卡顿现象,甚至走向崩溃,这便是内存泄漏。

闭包导致泄漏的典型场景:

function leak() {
  let bigData = new Array(1000000).fill('leak');
  return function() {
    console.log('I am a closure');
    // 虽然没有直接使用bigData,但闭包还是引用了它
  };
}
const closureFn = leak(); // 泄漏了100万个元素的数组

上边所举的那个例子里头,返回的那个函数尽管并未用到bigData,然而由于bigData跟它处于同一个作用域,闭包会留存整个作用域链上的全部变量。所以要是闭包始终存在,那些没有用处的变量也会一直占据内存。

2. 如何避免闭包导致的内存泄漏?

closureFn = null; // 这样bigData就可以被回收了

function good() {
  let bigData = new Array(1000000).fill('data');
  let needed = 'only me';
  return function() {
    console.log(needed); // 只引用needed,bigData会被回收
  };
}

由于闭包仅仅引用了所需的,所以引擎能够进行优化,进而将大数据标记为不可达的。

3. 弱引用:救星Map和Set

ES6引入了WeakMap,以及WeakSet。它们有着这样的特性,其键是弱引用的。也就是说啊,要是键对象不再被其他处引用,那么即便它还存在于耳WeakMap里,依旧会被垃圾回收。

这在闭包中可以用来缓存数据,而不阻止回收。

const cache = new WeakMap();
function process(obj) {
  if (!cache.has(obj)) {
    const result = heavyComputation(obj);
    cache.set(obj, result);
  }
  return cache.get(obj);
}

要是obj于别的地方被销毁掉了,cache当中的键值对也会自顾自地消失掉,不会导致泄漏情况的发生。

三、以实际操作来说:将闭包用于封装私有数据的最佳做法是,在并非需要完全隔离的情形下,闭包是有助于实现模块化的得力助手。然而在现代开发过程中,能够运用ES6模块(import/export)去替换IIFE,如此会更加清晰明了。防抖节流借助闭包来保存状态:这属于闭包的经典运用方式,并没有什么值得纠结之处。要谨慎地使用返回闭包的高阶函数:当闭包持有数量众多的数据时,务必要保证及时进行清理。善于运用let来替代var:let具备块级作用域,能够防止一些意外出现的闭包问题。于DevTools之中对内存加以监控,借助Chrome的Memory面板,能够拍摄快照,瞧瞧哪些闭包对象始终留存,用于辅助定位泄漏之处。四、进行总结,闭包乃是优秀员工,然而切勿使其996。

JavaScript里的闭包乃是强大特性,它赋予函数以“记忆”,可去实现模块化、柯里化、防抖节流这类高级功能。然而,还需留意它存在的副作用:被记住的变量并不会自动消逝,要是不加留意,极易引发内存泄漏。

记住几个原则:

要是掌握了闭包,那你便掌握了成为 JS 高级编程熟手的关键之道;明天,咱将会踏入 JS 的另外那极具灵性的领域,也就是原型跟原型链,瞧瞧那个能让初涉者心生怯意的概念,究竟是怎样些情况,到底是怎么一回事。

如若你认为今日所讲的闭包应用以及内存管理讲解得极为透彻,那就点个赞,以便让更多的人能够看到。要是存在疑问,就在评论区相见,我们明天再会!

闭包:那个“赖着不走”的家伙,到底有什么用?

相关标签: # 闭包 # JavaScript # 内存管理 # 模块化 # 性能优化