JS engine working mechanism of browser & event loop


JS engine working mechanism of browser

When parsing HTML to build a DOM tree, the rendering thread will parse the script tag, and the execution right will be handed over to the JS thread (engine). At this time, the JS engine starts to work. How does it do it? In fact, it has been mentioned in the scope section, which is based on the Es3 specification. Here, we understand it according to the new specification of ES6

JS engine structure

Memory heap: store reference type data
Call stack: store basic data type, reference type address, store execution context (runtime)
interpreter: interpret, compile, execute the source code

JS engine (V8) execution code

  • 1. Convert the source code to abstract syntax tree and generate execution context
#Steps (no details)
Stage one: word segmentation, namely lexical analysis
Stage two: parsing, grammatical analysis
Stage 3: generation of AST
  • 2. Generate bytecode
The interpreter participates in the work and generates bytecode according to ast
Bytecode: code between bytecode and machine code
  • 3. Execution code
The interpreter interprets and executes bytecode line by line; an optimization here is for code that is executed multiple times (hot code),
The background compiler (JIT) will convert these bytecodes into machine code

Execution context

When the browser loads script, the JS engine starts to work;
In this case, the global context will be directly entered by default, and the global context will be pushed to the engine’sCall stack;
If the function is called in the code, Function (function context) is created and pressed into the call stack to become the context of the current execution environment.
When the function is executed, the execution context of the function pops up from the call stack and returns to the previous execution context

  • Execution context classification
Global: when the JS file is loaded into the browser, the global execution context is entered
     Global variables are in this execution context and can be accessed by code anywhere

Function: the context defined in a specific method. It enters the function context when the function is called
     Local variables are accessed in this function

Eval: defines the context in the eval method

Execution context of Es3

  • Preparation stage (pre analysis)
executionContext = {
  //Variable object
  'variableObject': {
    'arguments statement':{
      length: 0
    'function declaration': FN,
    'var declaration': undefined
  //Scope chain
  'scopeChain': [
    'own variableobject',...'executioncontext of all parents' 
  //This object
  'this': {}
//Initialize arguments parameter, variable is undefined, FN variable points to heap memory
  • Implementation phase
Assign values to the above arguments parameters, variables, etc., and run the code
  • Examples
var a = 2
function addAll(x,y) {
  var b = 1;
  console.log (add) // ƒ add (x, y) {return x + y}; undefined if there is no add function declaration
  function add(x,y) {
    return x + y
  var add = function(x,y) {
    return x + y
  console.log(add) // ƒ (x,y) { return x + y }
  var result = add(x, y)
  return a + b + result
addAll(100, 100)

Execution context of ES6

We can see that in Es3, JS will have the problems of variable promotion and implicit coverage, and the I of for loop is global. Therefore, block level scope and let and const keywords are introduced in the new specification of ES6 to avoid some previous defects. Since JS needs to be downward compatible, it still retains the feature of variable promotion

  • 1. Preparation phase (create execution context)
executionContext = {
  //Lexical Environment
  blockContext: {
    'let, const declaration':'xxx '
  //Variable environment
  VO: { 
    /*VaR and other statements*/ 
  //External references (same as scope chain)
  outer: [
    // ...
  // this
  this: {}
  • 2. Implementation phase
Variable search, assignment, execution
  • Examples
function fn() {
  var a = 1
  // console.log(b) // Cannot access 'b' before initialization
  let b
  //When a let and const statement is encountered, B will be promoted to the top of the current block level scope,
  //From the top to the declaration is a temporary dead zone, unable to operate B, an error will be reported
  //The error is reported directly in the context of lexical analysis
    let b = 3
    var c = 4
fn() // a,b,c,d => 11, 3, undefined, 4

The context of the example

Variable environment Lexical Environment
a = 1,c=4 Block 1: B = 3
Block 2: B = undefined

asynchronousJS of

From the above, we know that JS createsCall stackandExecution contextTo execute a section of JS code; because JS is designed for single thread (because JS is mainly used to implement many interactive related operations, such as DOM related operations, if multithreading will cause data synchronization problems), it can only execute a single task from top to bottomTime consuming tasksIt’s going to block. So what?
JS throughCallback functionTo deal with thisTime consuming (asynchronous)In fact, JS engine does not provide asynchronous support, which mainly depends on the running environment (browser or Node.js )

Event loop under Browser (different under node)

When faced with time-consuming tasks(Calling web APIs)The browser process actually maintains aTask (callback) queueThe callback function for storing web APIs tasks;
In addition, the JS engine will continuously monitor whether the call stack of its main thread is empty. When the execution call stack is empty, it will take out the callback function in the task queue and put it into the execution stack for calling. This mechanism isevent loop

Task (callback) queueclassification

The tasks actually stored in the above task (callback) queue becomeMacro taskThis kind of task will be delayed by callback function;
In browserMacro taskWhat are included

Render events: DOM parsing, layout, drawing... (requestanimation frame)
User interaction events: click, zoom, scroll... Click events, etc
JS execution event under script: JS execution
Network request, file read (I / O): Ajax request
Timer: setTimeout

In macro tasks, the granularity of time control is relatively wide, such as page rendering events, various IO completion events, JavaScript script execution events, user interaction events, etc., may be added to the message queue at any time. Moreover, adding events is operated by the system, and JavaScript code cannot accurately control the position of tasks to be added to the queue, There is no control over the position of the task in the message queue, so it is difficult to control when the task begins to execute

<!DOCTYPE html>
        <div id='demo'>
    <script type="text/javascript">
        function timerCallback2(){
        function timerCallback(){
  The callback functions triggered by the setTimeout function are macro tasks; during this time, other system tasks may be inserted
  It will affect the execution time of the second timer, so it is not accurate enough and has poor real-time performance

In order to solve this kind of problem, we introduce theMicro task;
A micro task is a function that needs to be executed asynchronously. The execution time is after the execution of the main function and before the end of the current macro task

When JavaScript executes a script, V8 will create a global execution context for it. At the same time, V8 engine will create a micro task queue internally. As the name implies, this micro task queue is used to store micro tasks, because during the current macro task execution process, sometimes multiple micro tasks will be generated. At this time, this micro task queue is needed to save these micro tasks. However, this micro task queue is for internal use of V8 engine, so you can’t directly access it through JavaScript.

  • In the browser, there are two ways to generate micro tasks
The first way is to use mutationobserver to monitor a DOM node,
Then modify the node through JavaScript, or add or delete some child nodes for this node,
When the DOM node changes, the micro task of recording DOM changes will be generated.

The second way is to use promise when calling Promise.resolve () or Promise.reject () 
Micro tasks are also generated
  • Summary
1. Micro task and macro task are bound, each macro task will create its own micro task queue when executing;

The duration of the current micro task will be affected;

3. In a macro task, if you create a macro task and a micro task for callback, the micro task will execute earlier than the macro task in any case.

Task execution sequence

For example, take a look at the execution order of macro task and micro task

setTimeout(function() {

new Promise(function(resolve) {
    for (var i = 0; i <1000; i++) {
        i === 1000 && resolve()
}).then(function() {


//1. First, execute the macro task under script, and when it encounters setTimeout, put it into the queue of macro task

//2. In case of promise, execute the new promise directly and print B

//3. When you encounter the then method, which is a micro task, put it in the queue of the micro task

//4. Encounter console.log ('d '), direct printing

//5. After the macro task of this round is executed, check the micro task, find the function in the then method, and print C

//6. This round of event loop has been completed.

//7. In the next cycle, execute the macro task first, and find a setTimeout in the macro task queue, and print a