Start with language types

To know where your code is going next, understand interpreted and compiled languages.

Interpreted languages: This type of programming language runs code verbatim without the need for Compiled language to compile it into machine code and run it later as in Compiled language. This language requires an interpreter to dynamically interpret code into machine code at run time, or a subroutine that has been precompiled into machine code and then run.

Compiled language: a programming language implemented by a compiler. Instead of an interpreter running the code sentence by sentence, as in an interpreted language, it uses a compiler, which first compiles the code into machine code and then runs it. In theory, any programming language can be compiled or literal. The difference between them is only related to the application of the program.

So, JavaScript is a typical interpreted language, so to run JavaScript program must have a responsive execution environment, that is, to parse and execute JS code through JavaScript engine. The basic job of a JavaScript engine is to convert the JavaScript code written by a developer into efficient, optimized code that can be interpreted by a browser or even embedded in an application. Take the famous V8 engine.

The parsing process of JavaScript is divided into two phases: the precompile phase (preprocessing) and the execution phase. During precompilation, the JavaScript interpreter preprocesses the JavaScript code and converts it into bytecode. During execution, the JavaScript interpreter converts bytecode to binary code and executes it in sequence.

Precompilation period:

The compilation process of a normal compiled language can be divided into six steps: lexical analysis, syntax analysis, semantic analysis, source code optimization, code generation, and object code optimization. In JavaScript, once the syntax tree is obtained through lexical analysis and parsing, it is time to execute the code.

Lexing: In the lexing phase, the JavaScript interpreter first converts the code’s character stream into a token stream, for example:

a = (b -c)
Copy the code

Convert to token stream:

NAME "a"  
EQUALS  
OPEN_PARENTHESIS  
NAME "b"  
MINUS  
NAME "c"  
CLOSE_PARENTHESIS  
SEMICOLON 
Copy the code

The lexical analysis stage can be achieved as follows: 1. Remove annotations and generate documents; 2. Record error information; 3. Complete the pretreatment

Grammatical analysis:

The syntax analysis stage is to generate the syntax tree through the notations generated in the lexical analysis stage, that is, to store the information collected from the program in the data structure, which can be divided into two types: 1. Symbol table: record variables, functions and classes; 2. 2. Syntax tree: tree representation of program structure, which generates intermediate code. Such as:

 if(typeof a == "undefined" ) { 
    a = 0
 } else { 
    a = a
 } 
 alert(a)
Copy the code

The generated syntax tree is:

When the syntax tree cannot be constructed, a syntax error is reported and parsing of the entire code block ends. The lexical analysis and syntax analysis stages are staggered, and each lexical token is sent to the parser for analysis. There are rules for lexical and grammatical analysis, and ECMAScript262 defines a complete set of standards for JavaScript. Syntax analysis depends on this standard, of course, there are not in accordance with the standard to achieve, such as THE IE JS engine. This is why JavaScript has compatibility issues.

Designating a

In preparation for the compile phase, the code is built in memory into a syntax tree that the JavaScript engine interprets as it executes. During interpretation, the engine strictly follows the scope mechanism. The lexical scope adopted by JavaScript, which simply means that the scope of variables and functions is determined at the time of definition, depends on the source code structure.

var value = 1;
function foo() {
    console.log(value);
}
function bar() {
    var value = 2;
    foo();
}
bar();
Copy the code

As the engine interprets the execution of each function, it creates an execution environment in which it creates a call object that stores all local variables, parameters, nested functions, reference functions, and a list of parent levels in the current domain. The call object declaration cycle is the same as that of a function. When the function is called and no external reference is available, it is collected by the garbage collection mechanism.

At the same time, the interpreter strings together nested scopes through the scope chain, and uses the chain to look for variable values from the inside out to the global object, and returns “undefined” if none is found. The scope chain is composed of a series of variable objects of the current environment and the upper environment, which ensures the orderly access of the current execution environment to the variables and functions that meet the access permissions.

closure

During the process of performing environment creation, there is a special case — closures. It consists of two parts. The execution context (code A), and the function created within that execution context (code B). When B executes, the closure is generated if the value in the variable object in A is accessed. In most interpretations, including many well-known books, the closure generated here is referred to by the name of function B. In Chrome, closures are referred to by the name of the function that executes context A.

function A() {
    var a = 20;
    var b = 30;
    function B() {
        return a + b;
    }
    return B;
}
var B = A();
B();
Copy the code

First, there is the execution context A, in which function B is defined and executed by returning B externally. When B executes, the internal variables A, B are accessed. So this is where the closure comes in. JavaScript has an automatic garbage collection mechanism, and one of the important behaviors of garbage collection is that when a value loses its reference in memory, the garbage collection mechanism finds it based on a special algorithm and collects it, freeing memory. Normally, when A completes execution, the life cycle ends and the execution context of A function loses reference. The memory it occupies is quickly released by the garbage collector. However, the presence of B functions will prevent this process, making B functions resident in memory.

Single threaded && event loop

The single thread of JavaScript, relative to its purpose. As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes that node, which thread should the browser use? So, to avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change. In order to take advantage of the computing power of multi-core CPUS, HTML5 proposes the WebWorker standard, which allows JavaScript scripts to create multiple threads, but the child threads are completely controlled by the main thread and cannot manipulate DOM. So, this new standard doesn’t change the single-threaded nature of JavaScript. A single thread makes it easy to run code because you don’t have to deal with complex scenarios — such as deadlocks — that occur in a multi-threaded environment. But running on a single thread is also very limiting. Because of JavaScript, there is only one call stack, so what happens when a piece of code runs slowly?

Since it is single-threaded, only certain code can be executed at any given time, blocking other code. Browsers, on the other hand, are Event driven, and much of the behavior in browsers is asynchronous, creating events and putting them into execution queues. A JavaScript engine is a single thread that processes its task queue. When asynchronous events occur, such as mouse click, a timer firing, or an XMLHttpRequest completing callback (mouse click, timer firing, XMLHttpRequest completing callback), Put them on an execution queue and wait for the current code to complete before pulling events out of the execution queue in sequence. The Event Loop does only one thing, listening on the Call Stack and Callback Queue. When the Call Stack in the Call Stack is empty, the Event Loop places the first Event (the Callback function) in the Callback Queue in the Call Stack and executes it, repeating the process.

That is, JS has only one call stack. The call stack is a data structure that keeps track of where we are in the program. If we run a function, it puts it at the top of the stack. When you return from this function, it pops the function off the top of the stack, and that’s what the call stack does. The task queue in the stack is divided into macro-task and micro-task, which are called task and Jobs respectively in the latest standard.

  • macro-taskSome of these include: Script, setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • micro-taskNextTick, Promise, Object.observe(deprecated), MutationObserver(new html5 feature).
  • SetTimeout /Promise etc we call task sources. What enters the task queue is the specific execution task they specify.
setTimeout(function() {
    console.log('xxxx'); // This code is the task to queue}) //setTimeout as a task dispenser, this function executes immediately, and the task to be dispensed, its first argument, is deferred executionCopy the code
  • Tasks from different task sources enter different task queues. Where setTimeout and setInterval are homologous.
  • The order of the event loop determines the order in which the JavaScript code is executed. It starts the first loop with script(the whole code). The global context then enters the function call stack. Until the call stack is empty (only global), then all micro-tasks are executed. When all executable micro-tasks have been executed. The loop starts again with macro-Tasks, finds one task queue to complete, and then executes all micro-tasks, and so on.
  • The execution of each of these tasks, whether macro-task or micro-task, is done with the help of a function call stack.
The garbage collection

There are many different garbage collection mechanisms, but a brief description of the tag clearing algorithm is used to determine whether an object can be found in order to determine whether it is needed. It consists of the following steps.

  1. The garbage collector generates a root list. Roots are typically global variables that store references in code. In JavaScript, the Window object is a global variable that can be used as the root.
  2. All roots are checked and marked as active (not garbage), and all child variables are recursively checked. Anything that might arrive from the root element is not considered garbage.
  3. Any memory that is not marked as active is considered garbage. The garbage collector can free up memory and return it to the operating system. This algorithm can effectively avoid the cyclic dependency problem.