看完这个再不懂什么是闭包,准备卷铺盖走人吧

在本文中,我们将详细讲解 JavaScript 闭包的概念、原理和应用。闭包是 JavaScript 中一个非常重要的概念,理解闭包对于编写高效、可维护的代码至关重要。

什么是闭包?

闭包(Closure)是指一个函数可以访问并操作其外部作用域中的变量。换句话说,闭包使得一个函数可以“记住”它所在的词法环境,即使该函数在其原始词法环境之外执行。
在 JavaScript 中,闭包通常由一个函数和一个函数所在的作用域对象组成。当函数被创建时,它会创建一个闭包,并将其与当前的作用域对象绑定在一起。在函数执行期间,闭包会持续存在,即使函数执行完毕,它也仍然存在。这就允许函数访问并操作其创建时所在的作用域对象中的变量。

闭包的原理

JavaScript 采用词法作用域(lexical scoping),也就是说,函数的作用域在函数定义时就已经确定。当一个函数嵌套在另一个函数内部时,内部函数可以访问外部函数的变量。这种能力就是闭包。

闭包的原理可以归结为以下两点:

  1. 函数可以作为参数或返回值传递。
  2. 函数可以访问其外部作用域的变量。

闭包的应用

闭包在 JavaScript 中有很多应用场景,以下是一些常见的例子:

  1. 模块化:通过闭包,我们可以创建私有变量和方法,从而实现模块化。这有助于保护内部实现细节,避免全局变量污染。
function createCounter() {
  let count = 0;

  return {
    increment: function () {
      count++;
    },
    getCount: function () {
      return count;
    },
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1

上面的例子中,我们不用关注count的具体实现逻辑,我们通过调用方法来获取count的值,外部无法直接操作count,从而对count起到了保护作用。

  1. 事件处理:闭包可以用于在事件处理程序中保存状态信息。
function createButton(text) {
  const button = document.createElement("button");
  button.textContent = text;
  let clickCount = 0;

  button.addEventListener("click", function () {
    clickCount++;
    console.log(`${text} button clicked ${clickCount} times.`);
  });

  return button;
}

document.body.appendChild(createButton("Click me!"));
  1. 函数柯里化(Currying):闭包可以用于实现函数柯里化,将多参数函数转换为一系列单参数函数。
function curry(fn) {
  const arity = fn.length;

  return function curried(...args) {
    if (args.length >= arity) {
      return fn.apply(this, args);
    } else {
      return function (...moreArgs) {
        return curried.apply(this, args.concat(moreArgs));
      };
    }
  };
}

function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出 6

上例中,fn.length能得出一个函数的参数个数。当你传入的参数小于原函数参数个数时,会把参数concat起来并返回curried函数给你让你可以接着调用,直到你的参数大于等于原函数参数个数是,才调用原函数。

  1. 延迟执行:通过闭包来延迟执行函数,可以优化代码性能,避免在短时间内频繁执行同一个函数
function debounce(fn, delay) {
  var timeoutId;
  return function() {
    var context = this;
    var args = arguments;
    clearTimeout(timeoutId);
    timeoutId = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

function doSomething() {
  console.log('Doing something...');
}

var debouncedDoSomething = debounce(doSomething, 1000);

debouncedDoSomething(); // 在1000ms后输出 "Doing something..."
debouncedDoSomething(); // 取消前一个定时器并在1000ms后输出 "Doing something..."

上面的例子就是一个经典的防抖函数。我们定义了一个 debounce 函数,该函数接受一个函数和延迟时间作为参数,并返回一个闭包。在闭包内部,我们使用 setTimeout 延迟执行函数,并使用 clearTimeout 取消前一个定时器,从而避免在短时间内频繁执行同一个函数。
使用闭包来实现防抖可以避免在短时间内频繁执行同一个函数,从而提高代码的性能和响应速度。需要注意的是,使用防抖时,需要合理设置延迟时间,避免影响用户体验。

  1. 缓存数据:通过闭包来缓存数据,可以避免重复计算,提高代码性能
function fibonacci() {
  var cache = {};
  function fib(n) {
    if (n < 2) {
      return n;
    }
    if (cache[n]) {
      return cache[n];
    }
    cache[n] = fib(n - 1) + fib(n - 2);
    return cache[n];
  }
  return fib;
}

var fib = fibonacci();

console.log(fib(10)); // 55
console.log(fib(20)); // 6765
console.log(fib(20)); // 6765
console.log(fib(30)); // 832040

上面是一个经典的算法题,斐波拉契数列的实现。我们定义了一个 fibonacci 函数,该函数返回一个闭包 fib。在闭包内部,我们使用一个 cache 对象来缓存已经计算过的斐波拉契数列值,如果已经缓存过了,则直接返回缓存值,否则计算并缓存该值。通过使用闭包来缓存斐波拉契数列值,可以避免在多次计算相同的斐波拉契数列值时重复计算,从而提高代码的性能。

闭包的注意事项

虽然闭包非常有用,但也需要注意以下几点:

  1. 内存泄漏:由于闭包会引用外部作用域的变量,这可能导致内存泄漏。当不再需要使用闭包时,应确保解除对外部变量的引用,以便垃圾回收器回收内存。

  2. 性能:过度使用闭包可能导致性能下降。在性能关键的场景中,应谨慎使用闭包。

如何避免闭包内存泄漏

闭包会引用外部函数中的变量和函数,如果这些变量和函数没有及时释放,就可能导致内存泄漏的问题。因此,在使用闭包时,我们需要注意一些清除变量和函数的方法,以避免内存泄漏的问题。

下面是一些清除闭包变量和函数的方法:

  1. 手动清除:在闭包的最后,手动将外部变量和函数置为 null,以释放内存。例如:
function outer() {
  let a = 1;
  return function inner() {
    console.log(a);
    a = null; // 手动清除外部变量
    inner = null; // 手动清除内部函数
  }
}

在上面的例子中,我们在 inner 函数的最后手动将 a 变量和 inner 函数置为 null,以释放内存。

  1. 使用 IIFE:可以使用立即执行函数表达式(Immediately Invoked Function Expression,IIFE)来创建一个独立的作用域,并在作用域结束时自动清除变量和函数。例如:
function outer() {
  let a = 1;
  return (function() {
    console.log(a);
  }());
}
  1. 避免循环引用:当闭包和外部对象之间存在循环引用时,需要特别注意变量的清除。可以在外部对象中定义一个方法来清除闭包变量。例如:
function outer() {
  let a = 1;
  const obj = {
    inner: function() {
      console.log(a);
    },
    clear: function() {
      a = null;
      obj.inner = null;
    }
  };
  return obj;
}

在上面的例子中,我们在 obj 对象中定义了一个 clear 方法,用于清除闭包变量 a 和 inner 函数,推荐使用第三种方式。前面2种都会在我们调用闭包函数后,释放内存,但这也导致我们无法控制。第三种方式提供一个函数来手动释放。我们一般可以在页面离开,或者弹框关闭时手动触发来释放内存。

总结

需要注意的是,在使用闭包时,我们需要避免滥用闭包,避免创建过多的闭包,从而造成内存泄漏和性能问题。在需要使用闭包时,需要合理使用闭包,并注意变量的清除问题,以确保代码的正确性和性能。
总之,闭包是 JavaScript 中一个非常强大的概念,可以帮助我们编写更加模块化、可维护的代码。理解闭包的原理和应用场景对于成为一名高效的 JavaScript 开发者至关重要。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容