SSH seal the most essential closure article [eat melon].

Read on to answer your questions:

  • The difference between static and dynamic scoped chains
  • Why do closures exist
  • When are closures created
  • What is the [[scopes]] attribute
  • What does a closure hold
  • Where are closures stored
  • Why is eval poor
  • When does EVAL create closures

The body of the

In JavaScript, functions, blocks, and modules can form scopes (separate Spaces for variables). They can be nested within each other, and they can form references to each other. This chain is called a scope chain.

What does a scope chain look like?

Static scope chains

Take a code like this

  function func() {
    const guang = 'guang';
    function func2() {
      const ssh = 'ssh';
      {
        function func3 () {
          const suzhe = 'suzhe'; }}}}Copy the code

There are three variables (guang, SSH, suzhe), three functions (func, func2, func3), and one block. You can use Babel to view the scope chain between them.

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const code = ` function func() { const guang = 'guang'; function func2() { const ssh = 'ssh'; { function func3 () { const suzhe = 'suzhe'; }}}} ';

const ast = parser.parse(code);

traverse(ast, {
  FunctionDeclaration (path) {
    if (path.get('id.name').node === 'func3') {
      console.log(path.scope.dump()); }}})Copy the code

As a result,

This is what it looks like when you visualize it

The variable declarations within the scope of functions and blocks create a binding (the variable name is bound to a specific value, known as a binding) within the scope, and the rest of the block can refer to this binding. This is the order in which variables are accessed in the static scope chain.

Why is it called static?

Because this nesting relationship can be obtained by analyzing the code, without running it, the chain of accessing variables in this order is the static scope chain. The advantage of this chain is that the reference relationship between variables can be intuitively known.

In contrast, there is a dynamic scope chain, which means that the reference relationships of scopes are independent of the nesting relationship, and are dependent on the order of execution. The scope references of different functions and blocks are dynamically created at execution time. The disadvantage is that it’s not intuitive and can’t be analyzed statically.

Static scope chains can do static analysis, such as the scope chains we just Babel analyzed. So most programming languages are designed to scope chains in a static order.

However, another feature of JavaScript besides static scope chains is that functions can act as return values. Such as

function func () {
  const a = 1;
  return function () {
    console.log(a); }}const f2 = func();
Copy the code

This leads to a problem, original layers according to the order create call function, in order to create and destroy the scope is quite good, but if the inner function returns or through other exposed out, then the outer function, the inner function but have not destroyed, then how to deal with scope, the parent scope pin not destroyed? (Such as whether to destroy scope at the end of the func call)

Function calls and closures out of order

Return the inner function and then call it from the outside:

function func() {
  const guang = 'guang';
  function func2() {
    const ssh = 'ssh';
    function func3 () {
      const suzhe = 'suzhe';
    }
    return func3;
  }
  return func2;
}

const func2 = func();
Copy the code

When func1 is finished executing when func2 is called, is the pin destroyed? JavaScript then designed the mechanism for closures.

How are closures designed?

Instead of looking at the answer, consider how we solve the problem of destroying the parent before the child in this static scope chain.

First, should the parent scope be destroyed? Is the parent scope not destroyed?

No, there are a lot of things in the parent scope that have nothing to do with the child function, so why is it that the child function stays in memory until it ends. There must be a performance problem, so destroy it anyway. However, destroying the parent scope does not affect the child functions, so create a new object, and pack the variables in the parent scope of the reference in the child function to the child function.

How do you pack a subfunction away?

Design a unique property, such as [[Scopes]], and use this to set the environment in which the function will be packaged. And this property has to be a stack, because functions have subfunctions, subfunctions and maybe subfunctions, and each package has to be one package here, so it has to be a stack structure, just like a lunchbox has multiple layers.

The solution we are considering is that after destroying the parent scope, we wrap the variables we use, package them to the child functions, and put them on a property. This is the mechanism of closures.

Let’s experiment with the properties of closures:

Does Func3 need to pack anything? Will there be closures?

There are closures, which at least contain global scope.

But why not Guang, SSH, and Suzhe? Suzhe is generated because it is not external, only when external variables are generated. For example, if we change the code, print these three variables.

Run the [[Scopes]] command again (Scopes) :

Now you have two closures. Why? Where’s Suzhe?

First, we need to package only what is not in the environment, that is, the closure only holds external references. And then when you create the function, you save it to the property of the function, and when the function is returned, you package it to the function, but how does the JS engine know what external references it’s going to use? You have to do AST scanning, and many of the JS engines do Lazy Parsing. You’ll also know what external references it’s using, and then pack those in Closure and add them to [[scopes]].

So, a Closure is to scan the identifier references in the function when returning it, print the variable in the scope as a Closure and put it in [[Scopes]].

When func3 returns, the function scans for identifiers in the function, scans for SSH and guang variables, filters them down the scope chain, and packs them into two Closure (because they are in two scopes, so generate two Closure). Add the outermost Global and set the [[scopes]] property to func3 and let it pack.

When func3 is called, the JS engine will pull out the Closure + Global chain in the [[Scopes]] package, set it to the new scope chain, and that’s all the external context the function will use, so it will run.

Here’s a question to consider: When debugging code, why do you come across a variable that is accessible in scope, but has no information about it?

Why doesn’t this traverse, when it can be visited, display any messages? Did the debugger suck?

No, if you don’t know why, it’s because you don’t understand closures yet. Since the FunctionDeclaration function is a callback function, which is obviously called inside another function, it needs to be packaged with the contents of the environment when it is created, according to the principle of packaging only the necessary environment (don’t waste memory). Traverses are not referred, so naturally they are not packed. It’s not that the debugger has a bug.

So if we just access it, we can access it while debugging.

Do you suddenly know why you can’t see some variable information when debugging? If you can explain this phenomenon, you can understand the closure.

eval

Another question to consider: closures need to scan identifiers in the function to do static analysis, but what about eval, which might be content recorded from the network, read from disk, etc., and the content is dynamic. It is impossible to analyze dynamics statically without bugs. How to do?

Yes, eval can’t parse external references, so you can’t package closures. This is a special case where you can package the entire scope.

Verify:

This, as mentioned above, packages the external reference as a closure

This is the eval implementation, because there is no way to statically analyze dynamic content, so it is packaged as a closure. Originally, closures are designed to not save all the contents of the scope chain. As a result, eval will save all the contents, so try not to use eval. Can cause closures to save too much.

However, the JS engine only deals with direct calls, which means that a direct call to eval will package the entire scope. If you don’t call eval directly, you won’t be able to parse the reference and form a closure.

This special case can sometimes be used to perform some dark magic, such as using the feature that if you don’t call Eval directly, the closure will not be generated and will be executed in a global context.

Define a closure

To define closures, use the experiment we just did:

Closures are what a function is packaged with when it is created to filter the rest of the scope chain based on external references within the function. It is a subset of the scope chain generated when the function is created, and is the outer environment for packaging. Evel doesn’t parse content, so calling it directly packs the entire scope (so try not to use eval, it’s easy to store too many useless variables in closures), and not calling it directly leaves no closures.

Filtering rules:

  1. The global scope is not filtered out and must be included. So you can access the function wherever you call it.

  2. The rest of the scope is filtered out based on whether any internal variables are referenced by the current function. Not every returned subfunction generates a closure.

  3. Referenced scopes also filter out unreferenced binding (variable declarations). Just pack the variables you need.

Disadvantages of closures

JavaScript is statically scoped, and closures are designed to solve the problem of child functions being destroyed later than their parent functions. When the parent function is destroyed, we’ll put the variable referenced by the child function in a Closure package on the function [[Scopes]] and let it calculate that the parent function is destroyed and have access to the external environment at any time.

It does solve the problem, but is there a downside?

The problem is the [[Scopes]] attribute

We know that the JavaScript engine divides memory into a function call stack, a global scope, and a heap. The heap is used to hold some dynamic objects. Each stack frame of the call stack is used to hold a function execution context, and there is a local variable environment for internally declared variables. The reference is then saved in the local environment of the stack frame. The global scope is the same, but it’s usually used to put static things, sometimes called static fields.

The execution context of each stack frame contains all the environments that the function needs to access for execution, including the local environment, scope chain, this, and so on.

So what happens if the subfunction returns?

First, the stack frame of the parent function is destroyed. The child function has not actually been called yet, so it is still an object in the heap and has no stack frame. In this case, the parent function filters out the scope chain and sets the closure chain on the [[Scopes]] property of the child function.

The parent function is destroyed, the memory corresponding to the stack frame is immediately freed, the SSH Obj used is collected by GC, and the returned function filters out the scope chain to form a closure chain in the heap. This leads to a problem: if a large object is referred to by a function that would otherwise be destroyed at the end of the call, but now the reference is stored in the heap through closures and is never used, the heap memory will remain unusable, which can be considered a memory leak. So don’t mess with closures and pack less stuff into the heap.

conclusion

We started talking about static scope, made clear what is the scope, through the Babel static analyses the scope, understand the static and the dynamic scope, then introduces the child function before the parent function problem, thinking under the scheme, and then introduced the concept of closure, analysis the process generated by the closure, the location of the saved. We also used the closure feature to look at why variable information is sometimes not available for debugging, and then looked at why eval can’t precisely generate closures, when to pack all scopes, when not to generate closures, and why eval can cause excessive memory usage. After analyzing the characteristics of the following functions with closures in memory, explains why the memory leak is possible.

Closures are created in order to save a snapshot of the environment downloaded when returning a function, tree shking the scope chain, leaving only the necessary closure chain in the heap as the [[scopes]] property of the object, and giving the function access to the external environment wherever it goes. When this function is executed, the “snapshot” is used to restore the chain of scopes.

Because the function has not yet been executed, the identifier reference is statically analyzed. Statically analyzing dynamics has been proven impossible by countless frameworks, so return functions such as eval can only be fully packaged or not generate closures. Dynamic import like WebPack cannot be analyzed.