Re learn JavaScript scope and closure

Time:2021-10-21

1、 Understand the scope, scope chain and internal principles of JavaScript

1.1 scope

JavaScript has a set of well-designed rules to store variables, and these variables can be easily found later. This set of rules is calledScope

The scope is the code execution environment, the global execution environment is the global scope, and the function execution environment is the private scope. They are all stack memory.

1.2 scope chain

When code is executed in an environment, a scope chain of variable objects (the chain formed by the scope) will be created. Since the search of variables is implemented along the scope chain, it is also calledScope chainMechanism for variable lookup.

  • The front end of the scope chain is always the variable object of the environment where the currently executed code is located
  • The next object in the scope chain comes from the external environment, while the next variable object comes from the next external environment until the global execution environment
  • The variable object of the global execution environment is always the last object in the scope chain

The internal environment can access all external environments through the scope chain, but the external environment cannot access any variables and functions of the internal environment.

1.3 internal principle

  • compile

    With var a = 2; Take JavaScript as an example to illustrate the internal compilation process of JavaScript, which mainly includes the following three steps:

    • Tokenizing

      The string composed of characters is decomposed into meaningful code blocks, which are called lexical units (tokens)

      var a = 2; It is decomposed into the following lexical units: VaR, a, =, 2;. These lexical units form an array of lexical unit streams

      [
        "var": "keyword",
        "a": "identifier",
        "=": "assignment",
        "2": "integer",
        ";": "eos" (end of statement)
      ]
    • Parsing

      The lexical unit stream array is transformed into a tree representing the program syntax structure composed of nested elements. This tree is called “abstract syntax tree” (AST)

      var a = 2; There is a top-level node called VariableDeclaration in the abstract syntax tree of, followed by a child node called identifier (its value is a) and a child node called assignmentexpression, and the node has a child node called numericliteral (its value is 2)

      {
        operation: "=",
        left: {
          keyword: "var",
          right: "a"
        }
        right: "2"
      }
    • code generation

      The process of converting ast into executable code is called code generation

      var a=2; The abstract syntax tree is transformed into a set of machine instructions to create a variable called a (including allocating memory, etc.) and store the value 2 in a

      In fact, the compilation process of JavaScript engine is much more complex, including a large number of optimization operations. The above three steps are the basic overview of the compilation process

      Any code fragment must be compiled before execution. In most cases, compilation occurs a few microseconds before code execution. The JavaScript compiler will first set var a = 2; This program is compiled, then ready to execute it, and it is usually executed immediately

  • implement

    In short, the compilation process is that the compiler decomposes the program into lexical units (tokens), then parses the lexical units into idiom tree (AST), and then turns the syntax tree into machine instructions waiting to be executed

    In fact, the code is compiled and executed. The following is still represented by var a = 2; As an example, the compilation and execution process are explained in depth

    • compile

      • The compiler looks for whether a variable named a already exists in the collection of the same scope. If yes, the compiler will ignore the declaration and continue to compile; Otherwise, it will require the scope to declare a new variable in the collection of the current scope and name it a
      • The compiler will var a = 2; This code fragment is compiled into machine instructions for execution

      According to the compiler’s compilation principle, repeated declarations in JavaScript are legal

      //Test appears in the scope for the first time, so declare a new variable and assign 20 to test
      var test = 20
      //Test already exists in the scope. Use it directly to replace the assignment of 20 with 30
      var test = 30
    • implement

      • When the engine runs, it will first query the scope and whether there is a variable called a in the current scope collection. If so, the engine will use this variable; If not, the engine continues to look for the variable
      • If the engine finally finds variable a, it assigns 2 to it. Otherwise, the engine will throw an exception
  • query

    In the first operation performed by the engine, the variable a is queried, which is called LHS query. In fact, engine queries are divided into two types: LHS queries and RHS queries

    From the literal meaning, LHS query is performed when the variable appears on the left side of the assignment operation, and RHS query is performed when it appears on the right side

    More precisely, RHS query is no different from simply finding the value of a variable, while LHS query is trying to find the container itself of the variable so that it can be assigned a value

    function foo(a) {
      console.log(a) // 2
    }
    foo(2)

    This code includes four queries in total, namely:

    1. Foo (…) makes RHS reference to foo

    2. The function passed parameter a = 2 and LHS referenced a

    3. Console.log (…) RHS references the console object and checks whether it has a log method

    4. Console.log (a) RHS references a and passes the obtained value to console.log (…)

  • nesting

    When a variable cannot be found in the current scope, the engine will continue to search in the outer nested scope until the variable is found or reaches the outermost scope (that is, the global scope)

    function foo(a) {
      console.log(a + b)
    }
    var b = 2
    foo(2) // 4

    Line RHS reference, not found; Then, the engine looks for B in the global scope. After finding it successfully, it makes RHS reference to it and assigns 2 to B

  • abnormal

    Why is it important to distinguish between LHS and RHS? Because when the variable has not been declared (the variable cannot be found in any scope), the behavior of the two queries is different

    • RHS

      • If the RHS query fails, the engine will throw a referenceerror exception
      //The variable could not be found while RHS querying B. That is, this is an "undeclared" variable
      function foo(a) {
        a = b
      }
      foo() // ReferenceError: b is not defined
      • If the RHS query finds a variable, but attempts to perform unreasonable operations on the value of the variable, such as making a function call to a value of non function type, or referencing a null or undefined attribute, the engine will throw another type of exception: typeerror
      function foo() {
        var b = 0
        b()
      }
      foo() // TypeError: b is not a function
    • LHS

      • When the engine executes LHS query, if the variable cannot be found, the global scope will create a variable with that name and return it to the engine
      function foo() {
        a = 1
      }
      foo()
      console.log(a) // 1
      • If the LHS query fails in strict mode, a global variable will not be created and returned, and the engine will throw a referenceerror exception similar to the RHS query failure
      function foo() {
        'use strict'
        a = 1
      }
      foo()
      console.log(a) // ReferenceError: a is not defined
  • principle

    function foo(a) {
      console.log(a)
    }
    foo(2)

    The above code fragment is used to illustrate the internal principle of scope, which is divided into the following steps:

    【1】 The engine needs to RHS reference the foo (…) function to find foo in the global scope. Successfully found and executed

    【2】 The engine needs to pass parameter a = 2 of foo function, make LHS reference for a, and find a in the scope of foo function. Successfully found and assigned 2 to a

    【3】 The engine needs to execute console.log (…), RHS reference the console object, and find the console object in the scope of foo function. Because console is a built-in object, it was found successfully

    【4】 The engine looks up the log (…) method in the console object and finds it successfully

    【5】 The engine needs to execute console.log (a), RHS reference a, find a in the scope of foo function, and successfully find and execute it

    【6】 Therefore, the engine transfers the value of a, that is, 2, to console. Log (…)

    【7】 Finally, the console outputs 2

2、 Understand lexical scope and dynamic scope

2.1 lexical scope

The first working stage of the compiler is called word segmentation, which is to decompose the string composed of characters into lexical units. This concept is the basis for understanding lexical scope

In short, the lexical scope is defined in the lexical stage. It is determined by where the variable and block scope are written when writing code. Therefore, the scope will remain unchanged when the lexical analyzer processes code

  • relationship

No matter where a function is called or how it is called, its lexical scope is only determined by the position where the function is declared

function foo(a) {
  var b = a * 2
  function bar(c) {
    console.log(a, b, c)
  }
  bar(b * 3)
}
foo(2) // 2 4 12

In this example, there are three nested scopes. To help understand, you can think of them as several bubbles contained step by step

Re learn JavaScript scope and closure

Scope bubbles are determined by where the corresponding scope block code is written, and they are included level by level

Bubble 1 contains the entire global scope with only one identifier: foo

Bubble 2 contains the scope created by foo with three identifiers: A, bar, and B

Bubble 3 contains the scope created by bar, with only one identifier: C

  • lookup

The structure of scope bubbles and the location relationship between them provide the engine with enough location information, which is used by the engine to find the location of the identifier

In the code snippet, the engine executes the console. Log (…) declaration and looks for references to variables a, B, and C. It starts with the innermost scope, that is, the scope of the bar (…) function. The engine cannot find a here, so it will go up to the scope of the nested foo (…). A is found here, so the engine uses this reference. The same is true for B. For C, the engine finds it in bar (…)

[note] lexical scope search will only find the first level identifier. If the code references foo.bar.baz, lexical scope search will only try to find the foo identifier. After finding this variable, the object attribute access rules will take over the access to bar and Baz attributes respectively

foo = {
  bar: {
    baz: 1
  }
}
console.log(foo.bar.baz) // 1
  • shelter

The scope search starts from the innermost scope where the runtime is located, and proceeds step by step outward or upward until the first matching identifier is met

An identifier with the same name can be defined in a multi-layer nested scope, which is called “masking effect”. The internal identifier “masks” the external identifier

var a = 0
function test() {
  var a = 1
  console.log(a) // 1
}
test()

Global variables are automatically the attributes of global objects, so they can be accessed indirectly through references to global object attributes rather than directly through the lexical names of global objects

var a = 0
function test() {
  var a = 1
  console.log(window.a) //0
}
test()

Through this technology, you can access global variables that are obscured by variables with the same name. However, if non global variables are masked, they cannot be accessed anyway

2.2 dynamic scope

JavaScript uses lexical scope. Its most important feature is that its definition process takes place in the writing stage of code

So why introduce dynamic scope? In fact, dynamic scope is a cousin of this, another important mechanism of JavaScript. Scope confusion is mostly due to the confusion between lexical scope and this mechanism

Dynamic scopes don’t care how and where functions and scopes are declared, only where they are called. In other words, the scope chain is based on the call stack, not the scope nesting in the code

var a = 2
function foo() {
  console.log(a)
}
function bar() {
  var a = 3
  foo()
}
bar()

【1】 If it is in the lexical scope, it is the current JavaScript environment. The variable a is first found in the foo () function, but it is not found. Then, search the global scope along the scope chain, find and assign a value of 2. So console output 2

【2】 If it is in dynamic scope, similarly, variable a is first looked up in foo (), but not found. Here, you will find the place where foo () function is called, that is, bar () function along the call stack, find it and assign it to 3. So console output 3

The difference between the two scopes. In short, lexical scopes are determined at definition time, while dynamic scopes are determined at run time

3、 Understand the execution context stack of JavaScript, and you can apply the stack information to quickly locate the problem

3.1 execution context

  • Global execution context: This is the default and most basic execution context. Code that is not in any function is in the global execution context. It does two things: 1. Create a global object, which is the window object in the browser. 2. Point the this pointer to the global object. Only one global execution context can exist in a program.
  • Function execution context: each time a function is called, a new execution context is created for the function. Each function has its own execution context, but it is created only when the function is called. Any number of function execution contexts can exist in a program. Whenever a new execution context is created, it will execute a series of steps in a specific order, and the specific process will be discussed later in this article.
  • Eval function execution context: the code running in Eval function also obtains its own execution context, but because JavaScript developers do not often use Eval function, it will not be discussed here.

3.2 execution stack

The execution stack, also known as the call stack in other programming languages, has a LIFO (last in first out) structure to store all execution contexts created during code execution.

When the JavaScript engine reads your script for the first time, it creates a global execution context and pushes it into the current execution stack. Whenever a function call occurs, the engine creates a new execution context for the function and pushes it to the top of the current execution stack.

The engine will run the function whose execution context is at the top of the execution stack. When this function is completed, its corresponding execution context will pop up from the execution stack, and the context control will be moved to the next execution context of the current execution stack.

Let’s understand this through the following code example:

let a = 'Hello World!';

function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}

function second() {
console.log('Inside second function');
}

first();
console.log('Inside Global Execution Context');

Re learn JavaScript scope and closure

When the above code is loaded in the browser, the JavaScript engine will create a global execution context and push it into the current execution stack. When called  first()  Function, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack.

When in  first()  Call in function  second()  Function, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack. When  second()  After the function is executed, its execution context pops up from the current execution stack, and the context control will be moved to the next execution context of the current execution stack, that is  first()  The execution context of the function.

When  first()  After the function is executed, its execution context pops up from the current execution stack, and the context control will be moved to the global execution context. Once all the code is executed, the JavaScript engine removes the global execution context from the execution stack.

3.3 how is the execution context created

So far, we have seen how the JavaScript engine manages the execution context. Now let’s understand how the JavaScript engine creates the execution context.

The execution context is created in two phases:  1) Creation phase; 2) Execution phase

3.4 creation phase

Before any JavaScript code is executed, the execution context is in the creation stage. Three things happened during the creation phase:

  • determine  this  The value of, also known as  This Binding 。
  • Lexical Environment  The component is created.
  • Variableenvironment  The component is created.

Therefore, the execution context can be conceptually expressed as follows:

ExecutionContext = {
  ThisBinding = <this value>,
  LexicalEnvironment = { ... },
  VariableEnvironment = { ... },
}

This Binding:

In the global execution context,  this  The value of points to the global object. In the browser,  this  The value of points to the window object.

In the context of function execution,  this  The value of depends on how the function is called. If it is called by an object reference, then  this  The value of is set to the object, otherwise  this  The value of is set to a global object or  undefined  (in strict mode). For example:

let person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear);  
  }
}

person.calcAge();
//'This' points to 'person' because 'calcage' is called by reference to the 'person' object.

let calculateAge = person.calcAge;
calculateAge();
//'This' points to the global window object because no object reference was given

3.4.1 Lexical Environment

The official ES6 document defines the lexical environment as:

Lexical environment is a canonical type, which defines the association between identifiers and specific variables and functions based on the lexical nesting structure of ECMAScript code. The lexical environment consists of an environment record and an external lexical environment that may be null.

In short, the lexical environment is a  Identifier variable mapping  Structure of. (here)  identifier   Represents the name of the variable / function,  variable  Is a reference to the actual object (including function type object) or the original value)

In the lexical environment, there are two components: (1)  Environment record (2) Reference to external environment

  1. Environmental records  Is the actual location where variables and function declarations are stored.
  2. Reference to external environment  This means that it can access its external lexical environment.

There are two types of lexical environments:

  • The global environment (in the global execution context) is a lexical environment without an external environment. The external environment reference of the global environment is  null 。 It has a global object (window object) and its associated methods and properties (such as array methods) as well as any user-defined global variables,  this  The value of points to this global object.
  • In the function environment, the variables defined by the user in the function are stored in  Environmental records  Yes. A reference to an external environment can be a global environment or an external function environment containing internal functions.

Note: for  Function environment  for,  Environmental records  It also includes a  arguments  Object that contains the mapping between the index and the parameters passed to the function, as well as the mapping of the parameters passed to the function  Length (quantity) 。 For example, the following functions  arguments  The objects are as follows:

function foo(a, b) {
var c = a + b;
}
foo(2, 3);

//Arguments object
Arguments: {0: 2, 1: 3, length: 2},

There are also two types of environmental records (as shown below):

  • Declarative environmental records  Store variables, functions, and parameters. A function environment contains declarative environment records.
  • Object environment record  Used to define the association of variables and functions that appear in the global execution context. The global environment contains object environment records.

In the abstract, the lexical environment looks like this in pseudo code:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      //The identifier is bound here 
      outer: <null>
    }
  }
}

FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      //The identifier is bound here 
      outer: <Global or outer function environment reference>
    }
  }
}

3.4.2 variable environment:

It is also a lexical environment  EnvironmentRecord  Included by  VariableStatements  The binding created in this execution context.

As mentioned above, the variable environment is also a lexical environment, so it has all the attributes of the lexical environment defined above.

In ES6,  LexicalEnvironment  Components and  VariableEnvironment  The difference between components is that the former is used to store function declarations and variables(  let  and  const ) Binding, which is only used to store variables(  var ) Binding.

Let’s combine some code examples to understand the above concepts:

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
  var g = 20;
  return e *f *g;
}

c = multiply(20, 30);

The execution context is as follows:

GlobalExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      //The identifier is bound here  
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }  
    outer: <null>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      //The identifier is bound here  
      c: undefined,
    }  
    outer: <null>
  }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      //The identifier is bound here  
      Arguments: {0: 20, 1: 30, length: 2},
    },  
    outer: <GlobalLexicalEnvironment>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      //The identifier is bound here  
      g: undefined
    },  
    outer: <GlobalLexicalEnvironment>
  }
}

Note: only when a function is encountered  multiply  The function execution context is created only when the function is called.

You may have noticed  let  and  const  The defined variable does not have any value associated with it, but  var  The defined variable is set to  undefined 。

This is because during the creation phase, the code is scanned and parsed variables and function declarations, where the function declarations are stored in the environment and variables are set to  undefined  (in)  var  Or remain uninitialized (in  let  and  const  In case of).

That’s why you can access it before you declare it  var  Defined variables (although  undefined ), But if you visit before the declaration  let  and  const  The defined variable will prompt the reason for the reference error.

This is what we call variable promotion.

3.5 execution phase

This is the simplest part of the whole article. At this stage, the allocation of all variables is completed, and finally the code is executed.

Note: during execution, if the JavaScript engine cannot find the actual location declared in the source code  let  The value of the variable, it will be assigned  undefined  Value.

3.6 clipping of error stack

Node.js supports this feature. It is implemented through error.capturestacktrace. Error.capturestacktrace receives an object as the first parameter and an optional function as the second parameter. Its function is to capture the current call stack and clip it. The captured call stack will be recorded on the stack attribute of the first parameter, and the reference point of clipping is the second parameter, that is, the previous calls of this function will be recorded on the call stack, and the subsequent calls will not.

Let’s illustrate with code. First, capture the current call stack and put it on myobj:

const myObj = {};
function c() {}
function b() {
  // write the current call stack to myObj.
  Error.captureStackTrace(myObj);
  c();
}
function a() {
  b();
}

//Call function a
a();

//Print myobj.stack
console.log(myObj.stack);

//The output will be like this
//    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
//    at a (repl:2:1)
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)

There is only a – > b in the above call stack, because we caught the call stack before B called C. Now make a few changes to the above code and see what happens:

const myObj = {};
function d() {
  // we store the current call stack on myObj, but remove part of B and B.
  Error.captureStackTrace(myObj, b);
}
function c() {
  d();
}
function b() {
  c();
}
function a() {
  b();
}

//Execute code
a();

//Print myobj.stack
console.log(myObj.stack);

//The output is as follows
//    at a (repl:2:1) <-- As you can see here we only get frames before b was called
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)
//    at emitOne (events.js:101:20)

In this code, because we passed in B when calling error.capturestacktrace, the call stack after B will be hidden.

Now you might ask, what’s the use of knowing this? You can try this technique if you want to hide the error stack (such as the internal implementation of a library) that has nothing to do with the user’s business.

3.7 error debugging

3.7.1 error object and error handling

When an error occurs during program operation, an error object is usually thrown. The error object can be used as the prototype inherited from the user-defined error object

The error.prototype object contains the following properties:
 
Constructor – the constructor that points to the instance

Message – error message

Name – wrong name (type)

The above are the standard attributes of error.prototype. In addition, different operating environments have their specific attributes, such as node, Firefox, chrome, edge, ie 10 +, opera and safari 6+

In such an environment, the error object has the stack attribute, which contains the stack trace of the error. The stack trace of an error instance contains all stack structures after the self constructor

3.7.2 how to view call stack

View only the call stack: console.trace

a()
function a() {
  b()
}
function b() {
  c()
}
function c() {
  let aa = 1
}
console.trace()

3.7.3 debugger break point form

4、 The principle of this and the values of several different use scenarios

4.1 method call as object

In JavaScript, a function is also an object, so a function can be used as an attribute of an object. At this time, the function is called the method of the object. When this calling method is used, this is naturally bound to the object

var test = {
  a:0,
  b:0,
  get:function(){
    return this.a;
  }
}

4.2 calling as a function

The function can also be called directly, and this is bound to the global object. In the browser, window is the global object. For example, the following example: when the function is called, this is bound to the global object,

Next, execute the assignment statement, which is equivalent to implicitly declaring a global variable, which is obviously not what the caller wants.

function makeNoSense(x) {
  this.x = x;
}

4.3 call as constructor

JavaScript supports object-oriented programming. Different from the mainstream object-oriented programming language, JavaScript does not have the concept of class, but uses the inheritance method based on prototype.

Accordingly, the constructor in JavaScript is also very special. If it is not called with new, it is the same as an ordinary function. As another convention, constructors begin with capital letters,

Remind the caller to call in the correct way. If the call is correct, this is bound to the newly created object.

function Point(x, y){
  this.x = x;
  this.y = y;
}

4.4 call in call or apply, bind

Let’s reiterate that in JavaScript, functions are also objects, objects have methods, and apply and call are the methods of function objects.

These two methods are extremely powerful. They allow switching the context of function execution, that is, the object bound by this.

Many JavaScript skills and class libraries use this method. Let’s take a concrete example:

function Point(x, y){
  this.x = x;
  this.y = y;
  this.moveTo = function(x, y){
    this.x = x;
    this.y = y;
  }
}

var p1 = new Point(0, 0);
var p2 = {x: 0, y: 0};
p1.moveTo(1, 1);
p1.moveTo.apply(p2, [10, 10])

5、 The implementation principle and function of closures can list several practical applications of closures in development

5.1 concept of closure

  • It refers to a function that has access to variables in the scope of another function. Generally, one function contains another function.

5.2 function of closure

  • Accessing the internal variables of the function and keeping the function in the environment will not be handled by the garbage collection mechanism

Because the variables declared inside the function are local and can only be accessed inside the function, but the variables outside the function are visible inside the function, which is the characteristic of scope chain.

Children can find variables from their parents, level by level, until they are found

Therefore, we can create another function inside the function, so that the variables of the outer function are visible to the internal function, and then we can access its variables.

function  bar(){
    //Variables declared by the outer function
    var value=1;

    function foo(){
        console.log(value);
    }
    return foo();
};
var bar2=bar;
//In fact, the bar () function is not disposed of by the garbage collection mechanism after execution
//This is the function of closures. When you call the bar () function, you will execute the foo function inside, and foo will access the variables in the outer layer
bar2();

Foo () contains the closure of the internal scope of bar (), so that the scope can survive forever and will not be disposed of by the garbage collection mechanism. This is the function of the closure for foo () to reference at any time.

5.3 advantages of closures

  • Facilitate the invocation of local variables declared in the context
  • The logic is tight. You can create another function in a function to avoid the problem of parameter transmission

5.4 disadvantages of closures

  • Because the use of closures can keep functions in memory without being destroyed after execution. If closures are used heavily, memory leakage will occur and memory consumption will be high

5.5 application of closure in practice

function addFn(a,b){
    return(function(){
        console.log(a+"+"+b);
    })
}
var test =addFn(a,b);
setTimeout(test,3000);

Generally, the first parameter of setTimeout is a function, but it cannot pass values. If you want to pass the value in, you can call a function to return the call of an internal function and pass the call of the internal function to setTimeout. The parameters required for the execution of the internal function are passed to it by the external function, and the external function can also be accessed in the setTimeout function.

6、 Understand the principle of stack overflow and memory leak and how to prevent it

6.1 memory leakage

  • If the requested memory is not cleaned or destroyed in time after execution, it will occupy free memory. If there are too many memory leaks, the subsequent programs will not apply for memory. Therefore, memory leakage will lead to internal memory overflow

6.2 stack overflow

  • The memory space has been applied for. There is not enough memory available

6.3 mark removal method

In some programming software, such as C language, malloc needs to be used to apply for memory space, and then free it. It needs to be cleared manually. JS has its own garbage collection mechanism. The commonly used garbage collection method is tag removal.

Mark clearing method: after a variable enters the execution environment, a mark is added to it: after entering the environment, the variables entering the environment will not be released, because they may be used as long as the execution flow enters the response environment. When a variable leaves the environment, it is marked “leave the environment”.

6.4 common causes of memory leakage

  • Memory leak caused by global variables
  • closure
  • No timer cleared

6.5 solutions

  • Reduce unnecessary global variables
  • Reduce the use of closures (because closures can lead to memory leaks)
  • Avoid dead circulation

7、 How to handle asynchronous operations of loops

7.1 using self executing functions

1. When a self executing function is used in a loop, the self executing function will not run until the end of the loop. For example, you define an array outside the self-executive function and add content to the array in the self-executive function. When you output outside the self-executive function, you will find that there is nothing in the array. This is because the self-executive function will not be executed until the loop runs.

2. When the self executing function is used in the loop, if AJAX is nested in the self executing function, the subscript i in the loop will not be passed into Ajax. You need to assign the subscript i to a variable outside Ajax and call this variable directly in Ajax.

example:

$.ajax({
    type: "GET",
    dataType: "json",
    url: "***",
    success: function(data) {
        //console.log(data);               
        for (var i = 0; i < data.length; i++) {
            (function(i, abbreviation) {
                $.ajax({
                    type: "GET",
                    url: "/api/faults?abbreviation=" + encodeURI(abbreviation),
                    dataType: "json",
                    success: function(result) {
                        //What to do after getting data
                    }
                })
            })(i, data[i].abbreviation);
        }
    }
});

7.2 using recursive functions

The so-called recursive function is to call this function in the function body. When using recursive functions, you must pay attention to that improper handling will enter an endless loop.

const asyncDeal = (i) = > {
    if (i < 3) {
        $.get('/api/changeParts/change_part_standard?part=' + data[i].change_part_name, function(res) {
            //What to do after getting data
            i++;
            asyncDeal(i);
        })
    } else {
        //What to do after asynchronous completion
    }
};
asyncDeal(0);

7.3 using async / await

  • Async / await features

Async / await is more semantic. Async is short for “asynchronous”. Async function is used to declare that a function is asynchronous; Await, which can be regarded as the abbreviation of async wait, is used to wait for the execution of an asynchronous method to complete;

Async / await is a solution to solve asynchronous problems with synchronous thinking (the code will not continue to execute until the results come out)

The traditional callback nesting can be replaced by the synchronous writing of multi-layer async functions

  • Async function syntax

The regular function is automatically converted to promise, and the return value is also a promise object

The callback function specified by the then method will not be executed until the asynchronous operation inside the async function is completed

Await can be used internally in asynchronous functions

  • Await syntax

Await is placed before the promise call. Await forces the subsequent code to wait until the promise object resolves. The value of resolve is obtained as the operation result of await expression

Await can only be used inside the async function. If it is used in an ordinary function, an error will be reported

const asyncFunc = function(i) {
    return new Promise(function(resolve) {
        $.get(url, function(res) {
            resolve(res);
        })
    });
}
const asyncDeal = async function() {
    for (let i = 0; i < data.length; i++) {
        let res = await asyncFunc(i);
        //What to do after getting data
    }
}
asyncDeal();

8、 Understand the practical problems solved by modularization, list several modularization schemes and understand the principles

8.1 commonjs specification (synchronous loading module)

Allow the module to synchronously load other modules to rely on through the require method, and then export the interfaces to be exposed through exports or module.exports.

Usage:

//Import
require("module");
require("../app.js");
//Export
exports.getStoreInfo = function() {};
module.exports = someValue;

advantage:

  • Simple and easy to use
  • The server-side module is easy to reuse

Disadvantages:

  • Synchronous loading is not suitable for use in the browser environment. Synchronous means blocking loading, and browser resources are loaded asynchronously
  • Multiple modules cannot be loaded in parallel without blocking

Why can’t the browser use synchronous loading and the server can?

  • Because the modules are placed on the server side, for the server side, when the module is loaded
  • For the browser side, because the modules are placed on the server side, the loading time also depends on factors such as the speed of the network. If you need to wait for a long time, the whole application will be blocked.
  • Therefore, modules on the browser side cannot use “common JS” and can only use “asynchronous loading” (AMD).

Refer to the commonjs module to represent the module system of node.js

8.2 amd (asynchronous loading module)

The module is loaded asynchronously, and the loading of the module does not affect the operation of the following statements. All statements that depend on modules are defined in a callback function. The callback function is not executed until loading is completed.

Usage example:

//Definition
define("module", ["dep1", "dep2"], function(d1, d2) {...});
//Load module
require(["module", "../app"], function(module, app) {...});

Load module require ([module], callback); The first parameter [module] is an array whose members are the modules to be loaded; The second parameter callback is the callback function after loading successfully.

advantage:

  • Suitable for loading modules asynchronously in browser environment
  • Multiple modules can be loaded in parallel

Disadvantages:

  • It increases the development cost, makes it difficult to read and write the code, and the semantics of module definition is not smooth
  • It does not conform to the general modular way of thinking and is a compromise implementation

Implementing amd specification represents require.js

Requirejs’ attitude towards modules is pre execution. Since requirejs is an AMD specification executed, all dependent modules are executed first; That is, requirejs executes the dependent modules in advance, which is equivalent to advancing the require

Requirejs execution process:

  • The require function checks the dependent module and obtains the actual path of the JS file according to the configuration file
  • According to the actual path of the JS file, insert the script node in the DOM and bind the onload event to get the notification of the completion of the module loading.
  • After the script is loaded, the callback function is called.

8.3 CMD specification (asynchronous loading module)

The CMD specification is very similar and simple to AMD, and maintains great compatibility with the modules specifications of commonjs and node.js; In the CMD specification, a module is a file.

The definition module uses the global function define, which receives the factory parameter. The factory can be a function, an object or a string;

Factory is a function with three parameters, function (require, exports, module):

  • Require is a method that accepts the module ID as a unique parameter to obtain the interface provided by other modules: require (ID)
  • Exports is an object used to provide module interfaces to the outside world
  • Module is an object that stores some properties and methods associated with the current module

example:

define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();
  //Rely on nearby writing, when to use and when to introduce
  var b = require('./b');
  b.doSomething();
});

advantage:

  • Dependency, delay execution
  • It can be easily run in node. JS

Disadvantages:

  • Depending on SPM packaging, the module loading logic is biased
  • Implement the representative library sea.js: seajs is lazy to execute a module. Seajs will only execute the module when it really needs to use (rely on) the module

8.4 difference between AMD and CMD

  • For dependent modules, amd executes in advance and CMD executes in delay. However, since 2.0, requirejs has also been changed to delay execution (different processing methods according to different writing methods). CMD advocates as lazy as possible
  • Amd advocates dependency preposition; CMD advocates relying on proximity, and only require when a module is used.
// AMD
Define (['. / a', '. / B'], function (a, b) {// dependencies must be written at the beginning  
   a.doSomething()    
   //Omit 100 lines here    
   b.doSomething()    
   ...
});
// CMD
define(function(require, exports, module) {
   var a = require('./a')   
   a.doSomething()   
   //Omit 100 lines here   
   var b = require('./b') 
   //Dependence can be written nearby   
   b.doSomething()
   // ... 
});

8.5 UMD

  • UMD is a blend of AMD and commonjs
  • Amd develops asynchronous loading module based on browser first principle.
  • Commonjs module is developed based on the principle of server first. It selects synchronous loading, and its modules do not need packaging.
  • UMD first determines whether there are modules (exports) supporting node.js. If there are, the node.js module mode is used; When judging whether AMD is supported (whether define exists), the module is loaded in AMD mode.
(function (window, factory) {
    if (typeof exports === 'object') {
    
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
    
        define(factory);
    } else {
    
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

8.6 ES6 modularization

  • At the level of language standard, ES6 realizes module functions, and the implementation is quite simple. It can completely replace commonjs and AMD specifications and become a general module solution for browsers and servers.
  • ES6 module design idea: try to be static, so that the dependencies of the module and the input and output variables can be determined at compile time (commonjs and AMD modules can only determine these things at run time).

Usage:

//Import
import "/app";
import React from “react”;
import { Component } from “react”;
//Export
export function multiply() {...};
export var year = 2018;
export default ...
...

advantage:

  • Easy static analysis
  • ECMAScript standard for the future

Disadvantages:

  • The native browser side has not yet implemented the standard
  • The new command word is only supported by the new version of node.js.

8.7 return to the question “the difference between require and import”

Require uses the commonjs specification, and import uses the ES6 module specification; Therefore, the essence of the difference between the two is the difference between the two norms;

CommonJS:

  • For the basic data type, it belongs to replication. It will be cached by the module; At the same time, the variables output by another module can be re assigned.
  • For complex data types, it belongs to shallow copy. Since the objects referenced by two modules point to the same memory space, modifying the value of this module will affect the other module.
  • When a module is loaded with the require command, the code of the entire module is run.
  • When using the require command to load the same module, the module will not be executed, but the value in the cache will be fetched. That is, no matter how many times the commonjs module is loaded, it will only run once when it is loaded for the first time. If it is loaded later, the result of the first run will be returned, unless the system cache is manually cleared.
  • During cyclic loading, it is executed during loading. That is, when the script code is required, it will be executed completely. Once a module is “cyclically loaded”, only the executed part will be output, and the unexecuted part will not be output.

ES6 module

  • The value in ES6 module belongs to dynamic read only reference.
  • For read-only, that is, it is not allowed to modify the value of the imported variable. The imported variable is read-only, whether it is a basic data type or a complex data type. When the module encounters the Import command, a read-only reference is generated. Wait until the script is actually executed, and then get the value in the loaded module according to the read-only reference.
  • For dynamic, the original value changes, and the value loaded by import also changes. Whether basic data type or complex data type.
  • During cyclic loading, the ES6 module is dynamically referenced. As long as there is a reference between two modules, the code can be executed.

Finally: require / exports is necessary and universal; As a matter of fact, the import / export you currently write is compiled into require / exports to execute.

Recommended Today

Swift advanced (XV) extension

The extension in swift is somewhat similar to the category in OC Extension can beenumeration、structural morphology、class、agreementAdd new features□ you can add methods, calculation attributes, subscripts, (convenient) initializers, nested types, protocols, etc What extensions can’t do:□ original functions cannot be overwritten□ you cannot add storage attributes or add attribute observers to existing attributes□ cannot add parent […]