编程网

深入理解JavaScript闭包函数

闭包是 JavaScript 语言的一个特色,很多高级应用都要依靠闭包实现。

变量的作用域

要学习闭包,首先我们需要了解变量的作用域。关于作用域,我们在前面深入 JavaScript 系列已经介绍过了,这里就不再赘述了。

我们先来看下面这段代码:

const a = 1;

function fn() {
  console.log(a); // 1
}

fn();

这段代码可以打印出 1,因为 fn 函数作用域可以访问全局作用域上的 a 变量。

function fn() {
  const a = 1;
}

console.log(a); // error

这段代码会报错,因为外部作用域无法访问内部作用域的变量。

那么有没有什么方式能让外部作用域访问内部作用域的变量呢?

有,答案是:闭包!

function f1() {
  const a = 1;

  function f2() {
    return a;
  }

  return f2;
}

const f3 = f1();
const result = f3();
console.log(result); // 1

这是什么原因呢?

f1 执行结束返回了内部函数 f2 的引用,f3 全局变量保存了内部函数 f2 的引用,f2 函数作用域链是可以访问 a 变量的,所以 f3 执行后 result 变量可以获取 a 的值。

闭包的概念

闭包是能够读取其他函数内部变量的函数。上例中 f2 函数就是闭包,闭包是函数内部和函数外部连接的一座桥梁。

闭包的用途

闭包的最大用处有两个:

  • 1.前面提到的可以读取函数内部的变量
  • 2.让这些变量的值始终保持在内存中

我们来看个变量的值始终保持在内存中的例子

function f1() {
  let a = 1;

  function add() {
    a += 1;
  }

  function f2() {
    return a;
  }

  return {
    f2,
    add,
  };
}

const { f2: f3, add } = f1();

add();
add();
const result = f3();
console.log(result); // 3

打印的结果是 3。这就证明了,函数 f1 中的局部变量 a 一直保存在内存中,没有在 f1 调用后清除。

为什么会这样呢?

原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量 f3,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

使用闭包的注意点

1、闭包有内存消耗,所以不能滥用闭包。2、如果你把父函数当做对象来使用,把闭包当作对象的公用方法,把内部变量当作对象的私有属性,注意不要随便改变父函数内部变量的值。

经典必考题

const list = [];

for (var i = 0; i < 3; i++) {
  list[i] = function () {
    console.log(i);
  };
}

list[0]();
list[1]();
list[2]();

答案是:都是 3

那么如何打印出 0、1、2

方法 1——使用立即执行函数

const data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
    return function () {
      console.log(i);
    };
  })(i);
}

data[0]();
data[1]();
data[2]();

方法 2——使用块级作用域

ES6 中新增了块级作用域的概念,{}里的区域可以是块级作用域( {},if{},else{},while{},for{},swicth{}…),使用 let 关键字声明变量,可以添加块级作用域。

const list = [];

for (let i = 0; i < 3; i++) {
  list[i] = function () {
    console.log(i);
  };
}

list[0]();
list[1]();
list[2]();

参考

热门内容