JS 对于闭包的理解不够彻底?

JS代码

function fn() {
  var num = 3;
  return function() {
      var n = 0;
      n++;
      num++;
      console.log('n=' + n);
      console.log('num=' + num);
  }
}

var fn1 = fn();
fn1(); // 1 4
console.log("------");
fn1(); // 1 5

变量 num 在函数执行完毕后没有被销毁,当我第二次调用 fun1 的时候输出 num=5,而变量 n 每次执行时都会创建一个新的,导致两次调用输出都是 1,这是为什么?同样都是函数内部的变量
变量 num 在执行过程中是如何被保留下来的?

阅读 1.5k
avatarAI BotBETA

在 JavaScript 中,函数内部定义的变量具有闭包。闭包是一种函数,它能够记住并访问其词法作用域,即使该函数在其词法作用域之外执行。这意味着,当函数返回另一个函数时,外部函数的作用域被封装在内部函数中,形成一个闭包。

在你的例子中,fn 函数返回了一个匿名函数,这个匿名函数创建了自己的作用域并引用了 num 变量。即使 fn 函数已经执行完毕,这个匿名函数仍然保持了对 num 变量的引用,因此 num 变量不会被垃圾回收。

每次你调用 fn1() 时,实际上是调用了匿名函数。这个匿名函数创建了自己的作用域,并且有自己的 n 变量。每次调用时,n 都会被初始化为 0,然后自增 1,同时 num 也自增 1。由于 n 是在匿名函数的作用域内定义的,所以每次调用时都会创建一个新的 n 变量。而 num 变量由于闭包的作用被保留了下来。

所以,第一次调用 fn1() 时,输出的是 n=1num=4。第二次调用时,由于 num 已经被闭包保留下来并自增了 1,所以输出的是 n=1num=5

4 个回答
当一个函数可以通过某种方式访问其它函数内部变量时,闭包就形成了

你每次执行fn1,里面的var n = 0,都把n初始化为0了。

fn其实也遵循这个规律,函数都这样。

但是,你的fn在复赋值给fn1之后,就没有再执行了。所以里面的num没有被重新赋值,又由于闭包的作用让它不会被销毁(语言引擎特性),使得你在每次调用fn1都可以访问到它累积起来的值。

回到开头的话,你的fun1就有能力访问fn内部的num。也就形成了闭包。闭包的特性就使得num不会被销毁(因为外面有函数可能会访问num,不销毁才合理)。

function fn() {
  var num = 3;
  // 这里加一个console.log
  console.log('outer num', num);
  return function() {
      var n = 0;
      n++;
      num++;
      console.log('n=' + n);
      console.log('num=' + num);
  }
}

// 连续调用两次看看,num的表现和n是一样的
fn()
fn()

附一下阮一峰的闭包文章(阮一峰,我的hero!)

学习Javascript闭包(Closure)

可以看看MDN权威的文档,我觉得讲的很清楚:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
定义上来说闭包是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。闭包函数就是定义在一个函数里面的函数,这个函数(内部函数)可以访问到外部函数的作用域。回到题目中:

function fn() {
  var num = 3;
  return function() { //这个匿名函数就是闭包函数,它可以访问到外部函数fn()的作用域
      var n = 0;
      n++;
      num++;
      console.log('n=' + n);
      console.log('num=' + num);
  }
}

一个函数执行完后,垃圾回收器会回收已经没有被引用的对象实例

我们注意到

var fn1 = fn();

这个fn1是执行外部函数fn()时创建的匿名函数(内部函数)实例的引用,那么当外部函数fn()执行完后,这个引用的匿名函数(内部函数)对象实例并不会被回收,所以它的状态就一直被保持了下来,下次再调用fn()的时候引用指向的还是同一个匿名函数(内部函数)对象实例,对于外部函数里定义的

var num = 3;

被这个匿名函数引用了,所以同样也不会被回收,下次再执行fn()时是不会重新创建num这个变量的,还是对同一个num进行操作
对于内部的匿名函数里定义的

var n = 0;

在内部的匿名函数执行完后就被回收了,因为没有人对它持有引用,所以下次再执行fn()时会重新创建一个新的n并且初始化为0
所以闭包是有内存泄露的风险的,如果一个闭包函数引用外部函数的对象引用,而它自己本身一直被引用没有释放,那么外部函数的对象实例就不会被回收。

😅看你21年就开始写js代码了,24年在这钻牛角尖是吧?
fn函数根据你写的逻辑
你把fn函数想象成一个妈妈,执行它可以生很多小孩,
生小孩的时候这个小孩身高是3(var num = 3)

  1. 这个小孩每一年(调用一次)都会从0(var n = 0)开始存钱,每次存1块钱(n++)
  2. 这个小孩从3开始长高,每一年(调用一次)这个小孩长高1(num++)
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题