js基础之闭包

2023-11-17

作为前端开发,闭包是时时刻刻都在使用的,理解闭包是十分重要的,下面从闭包的定义,使用场景,及优缺点进行总结,帮助大家更好的理解闭包。

什么是闭包

引用自 MDN关于闭包的描述

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

闭包 = 函数 + 函数内部能访问的所有变量

换句话说:闭包是指在函数内部定义的函数可以访问到外部函数的变量,即使外部函数已经执行完毕退出了,内部函数仍然可以访问到外部函数的变量。如下

function outerFunction() {
  const outerVariable = "Hello, world!";

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

const closure = outerFunction();
closure(); // 输出 "Hello, world!"

使用场景

封装私有变量

// count变量只能在createCounter内部使用
function createCounter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  }
}

延迟执行

function delay(func, time) {
  return function() {
    setTimeout(func, time);
  }
}

const delayedFunc = delay(function() {
  console.log('Hi!');
}, 1000);

delayedFunc(); // 1 秒后输出 "Hi!"
// delay 函数返回一个内部函数,该函数在被调用时才会执行 func 函数,
// 并且在 time 毫秒后才会执行,达到了延迟执行的目的

常说的防抖节流中

// 防抖
function debounce(fn,delay) {
  let timer = null
  return function() {
    let args = arguments
    if(timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(()=>{
      fn.apply(this,args)
    },delay)
  }
}
// debounce 函数返回一个内部函数,该函数在被连续调用时会清除之前的定时器,
// 延迟一段时间后再执行 func 函数。
// 由于需要在延迟执行的过程中访问外部变量 timer,因此使用了闭包
// 节流
function throttle(fn,delay) {
  let record = Date.now()
  return function(...args) {
    let now = Date.now()
    const interval = now - record
    if(interval>delay) {
      fn(...args)
      record = now
    }
  }
}
// 由于需要在内部函数中保存上一次执行的时间record,因此使用了闭包。

缓存数据

function memoize(func) {
  const cache = {};
  return function(arg) {
    if (arg in cache) {
      return cache[arg];
    } else {
      const result = func(arg);
      cache[arg] = result;
      return result;
    }
  }
}

function expensiveCalculation(n) {
  console.log('Calculating...');
  return n * 2;
}

const memoizedCalculation = memoize(expensiveCalculation);
memoizedCalculation(5); // 输出 "Calculating..." 和 10
memoizedCalculation(5); // 直接输出 10,不再计算

模块化开发

const module = (function() {
  let privateVar = 0;
  function privateFunc() {
    console.log('Private function');
  }
  return {
    publicVar: 1,
    publicFunc: function() {
      console.log('Public function');
    }
  }
})();

console.log(module.publicVar); // 输出 1
module.publicFunc(); // 输出 "Public function"
console.log(module.privateVar); // 输出 undefined
module.privateFunc(); // 报错,因为无法访问私有函数
// 使用立即执行函数和闭包的方式实现了模块化开发,将私有变量和函数封装在了内部,
// 对外暴露了公共接口,提高了代码的可维护性

实现回调函数

function fetchData(url, callback) {
  fetch(url)
    .then(response => response.json())
    .then(data => callback(data))
    .catch(error => console.error(error));
}

function displayData(data) {
  console.log(data);
}

fetchData('https://jsonplaceholder.typicode.com/todos/1', displayData);
// 将函数作为参数传递给另一个函数,并在需要的时候调用

优点

一句话:私有化数据,并且在私有化数据的基础上可以保存数据

问题

  1. 内存泄漏

    闭包会导致一些变量一直被引用,而无法被垃圾回收器回收,从而导致内存泄漏。一般来说,在使用闭包时,要确保不再需要某个变量时,要将其设置为null,以便垃圾回收器回收。

  2. 性能问题

    由于闭包会在函数执行完毕后继续占用内存,因此使用闭包会对性能造成一定的影响。在需要频繁调用的函数中,使用闭包可能会导致函数执行速度变慢。

综上,我们在使用闭包的时候,需要注意避免闭包中的变量一直被引用,可以在不需要使用闭包的时候,将闭包设置为null,以便垃圾回收器回收,避免内存占用过高的问题。

补充:垃圾回收机制

垃圾回收机制是一种自动化的内存管理技术,用于检测和回收不再被程序使用的内存。在 JavaScript 中,垃圾回收机制是由 JavaScript 引擎自动执行的,程序员无需手动管理内存。

垃圾回收机制的基本原理是标记清除和引用计数

  1. 标记清除:垃圾回收器会在内存中维护一个“根集合”,它包含了一些全局变量和当前调用栈中的变量。垃圾回收器会从根集合开始遍历所有变量,并标记它们是否可达。不可达的变量就会被回收。
  2. 引用计数:垃圾回收器会维护每个变量的引用计数,即记录有多少个变量引用了该变量。当引用计数为 0 时,说明该变量不再被使用,就会被回收。
    在实际应用中,大多数 JavaScript 引擎采用标记清除算法,辅以引用计数算法来处理循环引用的情况。

在 JavaScript 中,垃圾回收机制的具体实现由 JavaScript 引擎负责。不同的JavaScript 引擎有不同的垃圾回收机制,例如 V8 引擎采用了分代垃圾回收机制,将内存分为新生代和老生代两个区域,并对它们采用不同的垃圾回收策略。

总之,垃圾回收机制是一种自动化的内存管理技术,可以帮助程序员避免内存泄漏和内存溢出等问题,提高代码的可靠性和性能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

js基础之闭包 的相关文章