乐趣区

关于程序员:看完这个再不懂什么是闭包准备卷铺盖走人吧

在本文中,咱们将具体解说 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 开发者至关重要。

创作不易,期待你的点赞珍藏加关注。也能够关注我的微信公众号:前端干活分享
每天第一工夫接管最新文章推送。

本文由 mdnice 多平台公布

退出移动版