关于 js 闭包(stale closure)问题的疑惑?

小夜勃
  • 221

网上看到如下两段代码:

function createIncrement(i) {
  let value = 0;
  function increment() {
    value += i;
    console.log(value);
    const message = `Current value is ${value}`;
    return function logValue() {
      console.log(message);
    };
  }
  
  return increment;
}

const inc = createIncrement(1);
const log = inc(); // 1
inc();             // 2
inc();             // 3
// Does not work!
log();             // "Current value is 1"
function createIncrementFixed(i) {
  let value = 0;
  function increment() {
    value += i;
    console.log(value);
    return function logValue() {
      const message = `Current value is ${value}`;
      console.log(message);
    };
  }
  
  return increment;
}

const inc = createIncrementFixed(1);
const log = inc(); // 1
inc();             // 2
inc();             // 3
// Works!
log();             // "Current value is 3"

第二段代码与第一段代码的唯一区别仅仅在于, 将message变量移入了 logValue()函数中, 似乎就能"正确"读取到value的值

两端代码均存在嵌套的闭包, 我个人分析的时候比较困惑, 个人理解为: value由于inc()的调用应该是不断变化的, 而由于存在最顶端的createIncrement()/createIncrementFixed()这个函数造成的闭包, 所读取的 value 值应该总是最新的即为 3? 但是似乎和实际结果有些不太符合, 不知道哪里理解错了, 恳请知道的前辈能详细分析一下? 不胜感激!

回复
阅读 1.2k
1 个回答

从下面几个部分分析:

第一个误区:

  1. value 这个的变量的声明 被执行了几次? 只有一次对吧
  2. message 这个变量的声明,被执行了几次? 是不是 inc() 函数被执行几次就会声明几次

因此value 只被声明了一次,而且是在 局部作用域(闭包中),所以后续的访问都是同一个地方

message虽然也在局部作用域中,但是每次都被重新声明(而不是重新赋值);所以语法上看是 logValue 里面的 使用的是同一个,但是其实内存中不是同一个;因此 第一种写法,log 执行但结果都是当时 inc()执行时的值

第二个误区:

在第二种写法中不仅仅是代码的位置发生了变化,同时值的引用也发生了变化; 当你任何时候执行 log 时,他都是访问 value 的值而和 message 没关系了,然而我们之前说了 value 会不断被重新赋值,因此 log 每次执行都是拿到最新的。

第三 换个写法实现,让message 也只声明一次,代码如下

function createIncrement(i) {
  let message;
  let value = 0;
  function increment() {
    value += i;
    console.log(value);
    message = `Current value is ${value}`;
    return function logValue() {
      console.log(message);
    };
  }
  return increment;
}

这个时候 任何时候执行 log 也是一样 拿到最新的值

希望对你有帮助

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏