解释javascript关闭的调试值

时间:2018-10-25 12:40:02

标签: javascript debugging closures

我想在执行以下代码期间了解调试内容:

var z = 2;

var SQUAREPLUSOTHER = x => y => ((x*x) + y + z);

var x = 3;

console.log("SQUAREPLUSOTHER", SQUAREPLUSOTHER);

var squareoftwoplusother = (SQUAREPLUSOTHER)(x);

x = 4;
z = 4;
var result = squareoftwoplusother(5);
console.log("result", result);

现在... 致电:

console.log("SQUAREPLUSOTHER", SQUAREPLUSOTHER);

调试清楚显示:

squareoftwoplusother: undefined
SQUAREPLUSOTHER: x=>...
x: 3
z: 2

在以下呼叫时:

var SQUAREPLUSOTHER = x => y => ((x*x) + y + z);

调试显示:

Local:
  x: 3
Closure
  z: 2

在以下呼叫时:

x = 4;
z = 4;

调试显示:

squareoftwoplusother: y=>...
SQUAREPLUSOTHER: x=>...
x: 4
z: 4

在以下呼叫时:

var result = squareoftwoplusother(5);

调试显示:

result: 18
squareoftwoplusother: y=>...
SQUAREPLUSOTHER: x=>...
Local:
  x: 5
Closure
  x: 3
Closure
  z: 4

,在最后一次调用中,调试显示:

result: 18
squareoftwoplusother: y=>...
SQUAREPLUSOTHER: x=>...
x: 4
z: 4

现在的问题: 有多少“关闭”?他们属于? (即如何解释...)

Local:
  x: 5
Closure
  x: 3
Closure
  z: 4

如何在javascript中管理变量的范围? 如何用“上下文”或“无论是什么”的方式对封闭的混乱产生一个“明确的”想法:-)?

预先感谢

Ed

2 个答案:

答案 0 :(得分:0)

每次调用一个函数时,都会为该函数创建一个本地作用域。如果函数返回后仍然有对该本地作用域的引用,则创建一个闭包。一旦引用消失,闭包也将被垃圾回收。

如果将断点添加到每个函数的第一行,您将看到已创建范围,以后可能会变成闭包。

此处http://dmitrysoshnikov.com/ecmascript/javascript-the-core/#activation-object

有很好的解释

我将在您的代码中添加一些括号以使其更容易,并且我将添加自调用函数以添加一定程度的闭包。

(() => {
  var z = 2;

  var SQUAREPLUSOTHER = (x) => {
    debugger;
    return (y) => {
      debugger;
      return (x * x) + y + z;
    }
  };

  var x = 3;

  console.log("SQUAREPLUSOTHER", SQUAREPLUSOTHER);

  var squareoftwoplusother = (SQUAREPLUSOTHER)(x);

  x = 4;
  z = 4;
  var result = squareoftwoplusother(5);
  console.log("result", result);
  debugger
})()

下面的屏幕截图来自Chrome DevTools,它最大程度地减少了关闭次数,即仅保留了对所需内容的引用。过去,整个闭包始终可用,您可以证明这一点,因为它显示在调试器的闭包窗格中。有关更多详细信息,请参见Javascript closures performance

First breakpoint

Second Breakpoint

End of code

答案 1 :(得分:0)

  

范围

您的代码中只有三个不同的作用域。一个是全局作用域,您在其中声明了所有变量,另外两个在这里:

 var SQUAREPLUSOTHER = x => y => ((x*x) + y + z);

有两个函数范围,一个包含变量x,另一个函数范围包含变量y。如果将箭头功能替换为function,可能会更容易:

 // global scope
 function SQUAREPLUSOTHER(x) {
   // function scope containing x
   return function(y) {
     // function scope containing y
     return (x*x) + y + z;
   };
 }

范围界定的工作原理非常简单:

  • 每个{都会启动一个新作用域,其计数器}将其关闭,并在其中用letconst(或var声明的变量但那稍微复杂一点)是范围的一部分。如果它是函数的范围,则参数(例如xy)就是该范围的一部分。

  • 从代码中,可以访问当前作用域或其父作用域中的所有变量,如果有多个具有相同名称的变量,则获得的是最内部的变量。因此,x函数内部的SQUAREPLUSOTHER引用了SQUAREPLUSOTHER范围的变量,而对于它外部的代码,x是全局变量,而函数变量不能被访问。

变量的作用域在运行时不会更改,您始终可以通过查看周围的{ .. }直接查看变量属于tp的作用域。

现在,不同作用域中的不同变量必须在运行时保存值,这就是我们要做的:

  

环境记录

调用函数时,JavaScript引擎会创建一个新的“ EnvironmentRecord”(类似于内部对象),其中包含您调用的函数的所有变量,例如在这种情况下:

  function test(a) {
    let b;
  }

然后,如果您调用该函数(test(1)),则会创建一个包含ab的新环境记录。现在,函数中的代码开始运行,并在其中查找每个变量。如果有两个函数嵌套在一起,则调用内部函数将创建一个环境记录,其中包含对外部函数的引用:

 function test(a) {
   function test2(b) {
   }

   test2(5);
 }

现在调用test(1)将创建一条记录,其中a1。如果引擎随后执行第二个调用(test2(5)),则它将创建另一个包含b的记录,其中包含5,并且该记录包含对包含a的记录的引用。现在,如果您在a内使用test2,引擎将在当前环境记录中查找它,找不到它,然后在找到a的父对象中查找它是1

  

关闭

通常,当执行到达}时,这些记录将被删除,但是,如果有另一条记录将当前记录作为父记录,则不会将其删除。它会一直存在,直到删除所有子记录,然后还将删除父记录。这种行为(变量的生存期更长,因为可以从内部函数体内访问它们)被称为闭包。