# JS 中闭包的用途和最佳实践

本节内容围绕以下几方面展开

  • 从什么是闭包 ?
  • 闭包的用途,闭包的应用场景,缺陷或危害
  • 以及常见闭包面试题

# 1、先通过一道题目,感受下闭包现象

// 创建一个函数
function fun() {
  // 定义局部变量
  var name = "arry老师";
  // 返回一个局部函数
  return function () {
    console.log(name);
  };
}

// 调用外部函数,就能得到内部函数,用变量inn来接收
var inn = fun();

// 定义一个全局变量
var name = "icoding";

// 执行inn函数,就相当于在fun函数的外部执行了内部函数
inn(); // 输出 arry老师 成功了

// 虽然定义了全局变量,但依然输出了内部函数变量的值,这种现象就是闭包现象

# 2、什么是闭包 ?

TIP

  • JavaScript 中函数会产生闭包(closure)
  • 闭包 是 函数本身该函数声明时所处的环境状态的组合
  • 下图是模拟闭包的示意图,来辅助我们对抽象的闭包具象化的理解

image-20211224231948096

闭包的特性

  • 函数能够"记忆住"其定义时所处的环境
  • 即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量
// 我们再来观察闭包的特性
function fun() {
  // 定义局部变量
  var name = "arry老师";
  // 定义局部函数,该函数本身也是一个闭包,是由它所处的环境决定的
  function innerFun() {
    console.log(name);
  }
  // 返回了内部函数
  return innerFun;
}

var inn = fun();
// 内部函数被移动到了外部执行
inn();

// 输出 arry老师,依然输出了内部函数变量的值

# 3、仔细观察闭包现象你会发现

TIP

  • 在 JavaScript 中,每次创建函数时都会创建闭包。
  • 但是,闭包特性 往往需要将函数"换一个地方" 执行,才能被观察出来

闭包是非常实用的

  • 闭包很有用,因为它允许我们将数据与操作该数据的函数关联起来。这与 "面向对象编程" 有少许相似之处
  • 闭包的功能:记忆性、模拟私有变量

# 4、闭包的第一个用途:记忆性

TIP

当闭包产生时,函数所处环境的状态 会始终保持在内存中,不会在外层函数调用后被自动清除,这就是闭包的 记忆性。

我们使用闭包的记忆性来实现以下业务:

  • 创建提问检测函数 checkTemp(n),可以检查体温 n 是否正常,函数会被返回布尔值,体温正常会返回true,体温不正常会返回false
  • 但,不同的小区有不同的体温检测标准:
  • 比如:A 小区体温合格线是 37.3℃,而 B 小区体温合格线是 37.0℃,应该怎样编程呢 ?
// 闭包的记忆性(同时这样的函数也叫:高阶函数或偏函数,未来会学习)
// 形参 standardTemp 表示标准线体温
function createCheckTemp(standardTemp) {
  // 定义检测体温函数checkTemp() , 形参n表示用户的体温
  function checkTemp(n) {
    // 如果用户的体温小于等于标准线体温
    if (n <= standardTemp) {
      console.log("你的体温正常");
    } else {
      console.log("你的体温偏高");
    }
  }
  // 返回checkTemp函数
  return checkTemp;
}

// A小区,创建一个checkTemp函数,它以37.3度为标准线
// 当A小区检测体温调用createCheckTemp函数时,给形式参数standardTemp传入的值是 37.3
// 这时,这个checkTemp函数就能一直记忆住它的值是 37.3
// 为什么呢?因为这个形参standardTemp对于函数checkTemp来说,就是checkTemp的闭包
// 换句话说:对于checkTemp来说,它的闭包就是checkTemp本身和形式参数standardTemp,因为我们知道是函数和它所处的环境的组合,因此checkTemp就会一直记忆住传入的值,不会因为createCheckTemp函数执行完毕后,就忘记这个数字了
var checkTemp_A = createCheckTemp(37.3);

// B小区,再创建一个checkTemp函数,它以37.0度为标准线
var checkTemp_B = createCheckTemp(37.0);

checkTemp_A(37.1); // 你的体温正常
checkTemp_A(37.8); // 你的体温偏高

checkTemp_B(36.5); // 你的体温正常
checkTemp_B(37.1); // 你的体温偏高

// 根据以上函数的调用,我们可以看出只需传入一次A小区和B小区体温的标准线,checkTemp函数就会记忆住这个值
// 当用户输入体温后,即可进行判断,A和B小区是不会紊乱的,由此可见闭包是具有记忆性的

# 5、闭包的第二个用途:模拟私有变量

TIP

  • 题目:请定义一个变量 a,要求是能保证这个a只能被进行指定操作(如:加 1、乘 2),而不能进行其他操作,应该怎么编程呢 ?
  • 本质上这个题目的要求就是要让变量a变得安全,即让变量变得私有化
  • 在 Java、C++等语言中,有私有属性的概念,但是JavaScript中只能用闭包来模拟
// 封装一个函数,这个函数的功能就是私有化变量
function fun() {
  // 定义一个局部变量 a
  var a = 0;

  // 返回一个对象
  return {
    getA: function () {
      return a;
    },
    add: function () {
      a++;
    },
    pow: function () {
      a *= 2;
    },
  };
}

var obj = fun();
// 如果想在fun函数外边使用变量a,唯一的方法就是调用getA()方法
console.log(obj.getA()); // 0
// 如果需要变量+1只能调用add方法
obj.add();
obj.add();
obj.add();
console.log(obj.getA()); // 调用三次obj.add(), 输出 3
obj.pow();
obj.pow();
obj.pow();
console.log(obj.getA()); // 调用三次obj.pow() ,输出 24

// 这时候,你会发现你在函数的外部是不能对变量a进行除已经定义过函数的其他操作(比如:对a进行 -1 除2这些操作,因为我们根本就没有定义过这些函数)
// 所以,这个案例就很好的说明了一个事实,就是可以用闭包来模拟私有变量

# 6、使用闭包的注意点,缺点或危害

TIP

  • 不能滥用闭包,否则会造成网页的性能问题,严重时可能会导致内存泄漏
  • 所谓内存泄漏是指程序中已动态分配的内存由于某种原因未释放或无法释放
  • 当然,所谓的内存泄漏是在很多低级别的浏览器(比如 IE 中)中才会出现,高版本的浏览器基本不存在
  • 这也是面试题中常考的题目

# 7、闭包的经典面试题,以下代码的输出结果 ?

function addCount() {
  var count = 0;
  return function () {
    count = count + 1;
    console.log(count);
  };
}

// fun1 和 fun2 是互不影响的,因此count变量都是0,最终各自都输出1
var fun1 = addCount();
var fun2 = addCount();
fun1(); // 1
fun2(); // 1

// 第二次调用,因为fun1 和 fun2是互不影响的,再次调用时由于闭包是有记忆性的,所以会在上一次的结果上再加1,因此输出2
fun2(); // 2
fun1(); // 2
上次更新时间: 6/8/2023, 9:23:17 PM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X