闭包

发布时间 2023-12-30 17:08:45作者: 前端自信逐梦者

什么是闭包?

闭包就是有权访问另一个函数作用域中变量的函数。
简单理解就是:闭包是用来连接函数外部和内部的桥梁。

如何从函数外部来访问另一个函数内部的变量呢?

举个例子

// 当函数外部想要访问另一函数内部的变量时,需要使用return
function outer() {
  let n = 10;
  return function () {
    console.log(10);
  };
}
const fn = outer();
fn();

闭包的优缺点

优点:

  • 私有化数据,在私有化的基础上可以保持数据

缺点:

  • 使用不恰当,可能会导致内存泄漏,在不需要使用的时候,即及时将变量置为null

闭包的应用(手写)

  • 防抖(保存数据状态和私有化变量和函数)
const debounce = function (func, delay) {
  let timer;
  // 注意点1:这里就不能使用箭头函数,因为箭头函数中没有this
  return function () {
    let agrs = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    // 注意点2:这里就需要写箭头函数,因为要保证func中的this是外城function中的this
    timer = setTimeout(() => {
      // 注意点3:将外部的this传递到func中的this中,使用apply
      // 注意点4:将外部的参数头传给func,使用arguments
      func.apply(this, agrs);
    }, delay);
  };
};

// 假设业务中需要执行的函数名称为doLayout(), 想使用上面的防抖函数
const do_layout = debounce(doLayout(), 1000);
window.onresize = do_layout;
/* 
    要保证do_layout中的this和debounce函数中func的this指向相同
*/
  • 实现数据缓存

假设我们在业务中处理一个很耗时的函数对象,每次调用都会花费很长的时间,那我们我们可以将计算出来的值保存起来,当调用这个函数的时候,先从缓存中查找,如果找不到,就计算,并更新缓存的值,如果找到了,直接返回从缓存中查找出来的值即可。这就可以使用闭包,因为闭包它是不会释放外部的引用,使得函数内部的值可以保留。

function configCache() {
  // 设置一个内部的对象,用来存储缓存数据,这个属性是私有的
  let cache = {};
  return {
    setCache(k, v) {
      cache[k] = v;
    },
    getCache(k) {
      return cache[k];
    },
  };
}
let cacheFunc = configCache();
cacheFunc.setCache('name', '张三');
console.log(cacheFunc.getCache('name')); // 张三

// configCache().setCache('name', '张三');
// console.log(configCache().getCache('name')); // undefined
/* 
这里为什么是undefined呢?因为configCache()执行两次,会产生两种不同的执行环境
*/
  • 用于实现柯里化和函数式变编程
    什么是函数柯里化?
    函数柯里化:指的是一个函数,它接收函数作为参数,运行后能够返回一个新的函数,并且这个新的函数能够处理外面函数的剩余参数。

  • 封装类和模块
    类似与上面的实现缓存

  • 用于解决循环中异步问题
    这个问题是我以前没怎么注意到的问题
    首先我写了下面一个代码

for (let i = 0; i < 4; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}
// 依次输出 0 1 2 3

直到后来我看见了这个

for (var i = 0; i < 4; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}
// 输出结果 4 4 4 4 

乍眼一看,没啥区别啊,但是i使用的声明变量不同
说一下这个问题吧。

// 使用var定义循环变量i
for (var i = 0; i < 4; i++) {
  setTimeout(function () {
    console.log('i=', i);
  }, 1000);
}
console.log(i);
/* 
输出结果 4 i= 4 i= 4 i= 4 i= 4
由于setTimeout是异步,还没有执行完毕,for循环已经执行完了
感受下执行过程吧:
这段代码 for循环里面嵌套了setTimeout,外层有个console.log
js代码的执行顺序是同步 > 异步 > 回调
那么 先执行for循环,发现里面有个setTimeout,将其放到宏任务队列中,
再执行外部的console.log,输出4
这时候执行栈中的同步任务都执行完毕了,将拿取宏任务队列中的任务回调函数放到
执行栈中去执行,这时候i已经全是4了,所以输出4遍i=4
*/

那么怎么解决这个问题哦呢

  • 方法1:使用let
/* 
    在es6中,let声明个变量可以创建一个自己的块级作用域
    在这里这个块级作用域也就是这个循环体
    那么这里let本质上就形成了一个闭包
*/

垃圾回收机制

总结

当被问到什么是闭包、谈谈你对闭包的理解,这样回答。
首先闭包的指有权访问另一个函数作用域中变量的函数。闭包的原理它就是,在内存中创建一个包含函数和变量的环境,当函数返回后,这个环境还仍然存在于内存中,因此他可以被外部访问。
后面就是涉及到优缺点、应用场景、垃圾回收机制等等。问到就展开回答。