An overview of the

I have looked at the cycle of events many times to see the full picture, but until now I have only scratched the surface. The main purpose of writing this article is to study and record it again. This paper mainly introduces some interview questions, overview of event cycle, ASAP mechanism in Primise, nextTick, Mutation observer in VUE, etc.

Browser event loop

Since JS is a single-threaded language, at runtime all programs wait in an execution queue to be executed. Program is divided into synchronous task in js and asynchronous tasks, at run time synchronization task goes straight to the main thread of execution form an execution stack, when the stack of execution of the program, the system will go to read asynchronous task queue (event triggers thread runs a task queue, as long as there is the running asynchronous tasks as a result, is in the task queue put an event callback). Such as Ajax, setTimeout, click events, etc. The repetition of these processes is called the cycle of events. The concrete visible below:

Let’s look at a classic example

// Enter the main thread console.log('script start'); SetTimeout (function () {console.log('setTimeout'); }, 0); Then (function () {console.log('promise1'); }).then(function () { console.log('promise2'); }); // Enter the main thread console.log('script end'); // Enter the main thread console.log('script end'); // Output script end directlyCopy the code

The program starts to execute, if it is not asynchronous directly execute, if it is asynchronous code into the asynchronous queue hang, when the synchronization task is finished, start to execute the asynchronous task, when the asynchronous callback end return value, start to execute the return content. Script start script end promise1 promise2 setTimeout It’s odd why setTimeout comes after a promise. This is where macro tasks and microtasks come in.

Macro task & Micro task

MacroTask

In ECMAScript, macroTasks are also referred to as tasks

We can think of the code executed on each execution stack as a macro task (including getting an event callback from the event queue and putting it on the execution stack each time). Each macro task is executed from the beginning to the end, and nothing else is executed. Common macro tasks are:

  • The main block of code
  • setTimeout
  • setInterval
  • requestAnimationFrame
  • /O, UI interaction events
  • SetImmediate (Node. Js environment)

Micro tasks

Microtasks were a concept that was new to the browser rules when the ES6 Promise came along. In ECMAScript, microTasks are also referred to as Jobs. We already know that when the macro task finishes, rendering is performed and then the next macro task is executed, while a microtask can be understood as a task that is executed immediately after the current macro task executes. Common microtasks include:

  • Promise
  • MutaionObserver
  • Process. NextTick (Node. Js environment)
  • Object.observe

Asynchronous task running mechanism

In the event loop, each iteration is called a tick, and the key point of the tick is:

  1. Select the oldest task in the tick and execute it if there is one.
  2. Checks whether jobs exist, and if so, executes until the Jobs Queue is cleared
  3. Update the render
  4. Repeat the above steps for the main thread

The illustration below

Related examples and frequent meeting questions

Listen for div clicks

<div class="outer">
  <div class="inner"></div>
</div>
<script>
  let outer = document.querySelector('.outer');
  let inner = document.querySelector('.inner');


  new MutationObserver(function () {
    console.log('mutate');
  }).observe(outer, {
    attributes: true,
  });

  function onClick() {
    console.log('click');

    setTimeout(function () {
      console.log('timeout');
    }, 0);

    Promise.resolve().then(function () {
      console.log('promise');
    });

    outer.setAttribute('data-random', Math.random());
  }

  inner.addEventListener('click', onClick);
  outer.addEventListener('click', onClick);
</script>

<style>
  .outer {
    height: 200px;
    width: 200px;
    background: lightgray;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .inner {
    height: 100px;
    width: 100px;
    background: gray;
  }
</style>
Copy the code
  1. What happens if we click on inner?
  2. Triggers the event oncLike output: click
  3. Settimeout The incoming macro task queue is suspended
  4. Promise entered the microteam and hung up
  5. The DOM listener listens for the event, and the entry to the microtask queue is suspended
  6. Primise out of stack input: promise
  7. Listener stack output: mutate
  8. The parent div fires the onclick event output: click
  9. Promise entered the microteam and hung up
  10. The DOM listener listens for the event, and the entry to the microtask queue is suspended
  11. Primise out of stack input: promise
  12. Listener stack output: mutate
  13. Setimeout Indicates the output of the stack

But in different browsers, the result may be different, because the kernel of the browser handles the asynchronous mechanism differently.

Here are a few examples I will not parse one, you analyze and judge by yourself example 1:

 async function one() {
    console.log('1')
    await two()
    console.log('1 end')
}

function two() {
    setTimeout(() => {
      console.log('2') 
    })
}

one()

setTimeout(() => {
  console.log('setTimeout')
})

new Promise((resolve, reject) => {
    console.log('promise')
    resolve()
}).then(function () {
    console.log('then')
})

console.log('end')
Copy the code

Running result:

Example 2:

async function async1() {
    console.log('async1 start');
    await async2();
    setTimeout(() => {
      console.log('setTimeout1')
    });
  }
  async function async2() {
    setTimeout(() => {
      console.log('setTimeout2')
    });
  }
  console.log('script start');
  setTimeout(() => {
    console.log('setTimeout3')
  });
  async1();
  new Promise((resolve) => {
    console.log('promise1');
    resolve();
  }).then(function () {
    console.log('promise2');
  });
  console.log('script end');
Copy the code

The answer:

I think after these two interview questions, the basic event loop mechanism should be clear, but the more core question is, how does js asynchronous mechanism work? The underlying principle of promise and how the asynchronous mechanism is implemented is it similar to setTimeout?

Promise principle and asynchronous mechanism

A Promise is an object that addresses async, and its behavior needs to meet the promiseA+ specification. The A + specification section describes the terms below

  • Henable is an object or function that defines a then method.

  • Value refers to any valid JavaScript value (including undefined, thenable, and promise);

  • An exception is a value thrown using a throw statement.

  • A reason is a reason for refusing a promise.

  • The state of the Promise

The current state of a Promise must be one of three: Pending, Fulfilled, or Rejected.

  1. Pending When a promise is in the Pending state, the following conditions must be met: It can be migrated to the execution state or rejected state

  2. In the Fulfilled state, the promise must meet the following conditions: it cannot be migrated to any other state and must have an immutable final value

  3. In the Rejected state, a promise must meet the following conditions: it cannot be migrated to any other state and it must have an immutable cause

Immutable here refers to identity (===), and does not imply deeper immutable (** ==).

Then method

A promise must provide a THEN method to access its current value, final value, and cause.

The THEN method of a PROMISE takes two arguments:

promise.then(onFulfilled, onRejected)
Copy the code

You can refer to the specification of Primise A +

Simple implementation (from the gold writer ‘ ‘ssh_ dream of dawn “implementation code)

function Promise(fn) { this.cbs = []; const resolve = (value) => { setTimeout(() => { this.data = value; this.cbs.forEach((cb) => cb(value)); }); } fn(resolve); } Promise.prototype.then = function (onResolved) { return new Promise((resolve) => { this.cbs.push(() => { const res = onResolved(this.data); if (res instanceof Promise) { res.then(resolve); } else { resolve(res); }}); }); };Copy the code

This is a more classic implementation of the chain call method, I also looked for a long time to understand.

Official core source code

'use strict'; var asap = require('asap/raw'); function noop() {} // States: // // 0 - pending // 1 - fulfilled with _value // 2 - rejected with _value // 3 - adopted the state of another promise, _value // // once the state is no longer pending (0) it is immutable // All `_` prefixed properties will be reduced to `_{random number}` // at build time to obfuscate them and discourage their use. // We don't use symbols or Object.defineProperty to fully hide them // because the performance isn't good enough. // to avoid using try/catch inside critical functions, we // extract them to here. var LAST_ERROR = null; var IS_ERROR = {}; function getThen(obj) { try { return obj.then; } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } function tryCallOne(fn, a) { try { return fn(a); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } function tryCallTwo(fn, a, b) { try { fn(a, b); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } module.exports = Promise; function Promise(fn) { if (typeof this ! == 'object') { throw new TypeError('Promises must be constructed via new'); } if (typeof fn ! == 'function') { throw new TypeError('Promise constructor's argument is not a function'); } this._deferredState = 0; this._state = 0; this._value = null; this._deferreds = null; if (fn === noop) return; doResolve(fn, this); } Promise._onHandle = null; Promise._onReject = null; Promise._noop = noop; Promise.prototype.then = function(onFulfilled, onRejected) { if (this.constructor ! == Promise) { return safeThen(this, onFulfilled, onRejected); } var res = new Promise(noop); handle(this, new Handler(onFulfilled, onRejected, res)); return res; }; function safeThen(self, onFulfilled, onRejected) { return new self.constructor(function (resolve, reject) { var res = new Promise(noop); res.then(resolve, reject); handle(self, new Handler(onFulfilled, onRejected, res)); }); } function handle(self, deferred) { while (self._state === 3) { self = self._value; } if (Promise._onHandle) { Promise._onHandle(self); } if (self._state === 0) { if (self._deferredState === 0) { self._deferredState = 1; self._deferreds = deferred; return; } if (self._deferredState === 1) { self._deferredState = 2; self._deferreds = [self._deferreds, deferred]; return; } self._deferreds.push(deferred); return; } handleResolved(self, deferred); } function handleResolved(self, deferred) { asap(function() { var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; if (cb === null) { if (self._state === 1) { resolve(deferred.promise, self._value); } else { reject(deferred.promise, self._value); } return; } var ret = tryCallOne(cb, self._value); if (ret === IS_ERROR) { reject(deferred.promise, LAST_ERROR); } else { resolve(deferred.promise, ret); }}); } function resolve(self, newValue) { // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) { return reject(  self, new TypeError('A promise cannot be resolved with itself.') ); } if ( newValue && (typeof newValue === 'object' || typeof newValue === 'function') ) { var then = getThen(newValue); if (then === IS_ERROR) { return reject(self, LAST_ERROR); } if ( then === self.then && newValue instanceof Promise ) { self._state = 3; self._value = newValue; finale(self); return; } else if (typeof then === 'function') { doResolve(then.bind(newValue), self); return; } } self._state = 1; self._value = newValue; finale(self); } function reject(self, newValue) { self._state = 2; self._value = newValue; if (Promise._onReject) { Promise._onReject(self, newValue); } finale(self); } function finale(self) { if (self._deferredState === 1) { handle(self, self._deferreds); self._deferreds = null; } if (self._deferredState === 2) { for (var i = 0; i < self._deferreds.length; i++) { handle(self, self._deferreds[i]); } self._deferreds = null; } } function Handler(onFulfilled, onRejected, promise){ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; this.onRejected = typeof onRejected === 'function' ? onRejected : null; this.promise = promise; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function doResolve(fn, promise) { var done = false; var res = tryCallTwo(fn, function (value) { if (done) return; done = true; resolve(promise, value); }, function (reason) { if (done) return; done = true; reject(promise, reason); }); if (! done && res === IS_ERROR) { done = true; reject(promise, LAST_ERROR); }}Copy the code

I am parsing the official code for you when there is time (recently I have been busy to find time to write the key points, and I will change it when I have time).

The asynchronous mechanism for Primise ASAP

Asapasap is short for as Soon as possible. In Node and browser environments, callback functions can be executed on high-priority tasks (before the next event loop), that is, tasks can be executed on a microtask queue. The core is: The asynchronous method is to achieve a stack of asynchronously executed tasks through setImmediate or process.nextTick, and the ASAP method is a further encapsulation of the rawAsap method, The cached domain and try/finally implementations allow the stack to resume execution even if a task throws an exception (call rawasap.requestFlush again). There are mainly two source files: ASap.js and raw.js. The key is in the raw.js code, so we only have to analyze the raw.js code. For analysis, see the comments.

"use strict"; var domain; // The domain module is executed on demand var hasSetImmediate = typeof setImmediate === "function"; Module. Exports = rawAsap; // exports = rawAsap; Function rawAsap(task) {if (! queue.length) { requestFlush(); flushing = true; } // Avoids a function call queue[queue.length] = task; Var queue = []; var flushing = false; Var index = 0; var index = 0; var capacity = 1024; Function flush() {while (index < queue.length) {function flush() {  var currentIndex = index; // Set the index of the next task before calling the task. This ensures that the exception task is skipped when the flush method is triggered again. If (index > capacity) {for (var scan = 0, newLength = queue.length-index; scan < newLength; scan++) { queue[scan] = queue[scan + index]; } queue.length -= index; index = 0; } } queue.length = 0; index = 0; flushing = false; } // Set this to a property of rawAsap to allow requestFlush to be triggered again in case of an exception. SetImmediate performs the flush method asynchronously through setImmediate. // The parentDomain is determined and the domain is set and restored only so that the current flush method is executed without binding any domain. If the setImmediate method doesn't exist, then use the process.nextTick method to mediate asynchronous execution. One drawback to using the process.nextTick method is that it can't handle recursion. Function requestFlush() {// ensure that the flushing is not bound to any domain var parentDomain = process.domain; if (parentDomain) { if (! Domain = require("domain"); // lazy-load execution domain module domain = require("domain"); } domain.active = process.domain = null; } if (flushing && hasSetImmediate) { setImmediate(flush); } else { process.nextTick(flush); } if (parentDomain) { domain.active = process.domain = parentDomain; }}Copy the code

Other content

Vue nextTick with event loop

In vue. js, the view changes are driven by data. Since the js execution is single-threaded, it may change the data several times during a single tick process, but vue. js is not foolish enough to drive the view changes every time it changes the data, it pushes all the data changes to a queue. The nextTick is then called internally once to update the view, so it takes the nextTick for the data to change to the DOM view. That’s why we need Vue.Nexttick. But in the process of using it, I found that:

  1. The interface can be rendered normally if the code does not involve asynchrony
  2. If the page is rendered with data that is executed in asynchron (the page is rendered but asynchron is not returned), nextTick cannot update the data
  3. The implementation part in nextTick uses the microtask part and the macro task part, resulting in different rendering timing.

Solution: In general, using setTimeout to turn a task into a macro task will do, but in some cases. For example, when the asynchronous response time is long, we need to use the listener to listen to the data variables, and if the data changes, we can do something about it.

DOM Mutation Observer

A MutationObserver is a built-in object that observes DOM elements and fires a callback when changes are detected. The MutationObserver is simple to use.

First, we create an observer with a callback function:

let observer = new MutationObserver(callback);
Copy the code

Then attach it to a DOM node:

observer.observe(node, config);
Copy the code

For example, here is a

that has the contentEditable property. This feature allows us to focus and edit elements.

<div contentEditable id="elem">Click and <b>edit</b>, please</div> <script> let observer = new MutationObserver(mutationRecords => { console.log(mutationRecords); // console.log(the changes) }); Observe.observe (elem, {childList: true, // Observe the direct child subtree: True, // and its lower descendants characterDataOldValue: true // passes the old data to the callback}); </script>Copy the code

If we run the above code in a browser, focus on the given

, and then change the text in
edit, console.log will display a change:
MutationRecords = [{type: "characterData", oldValue: "Edit ", target: <text node>, // Other properties are empty}];Copy the code

conclusion

Hahaha feel their level or limited ah, the whole document read down, more like a third party document. Although there are a lot of their own thinking, but very shallow. Hope that the future article excellence, as far as possible a little more of their own ideas. If there are improper places in the article welcome to point out the error oh, later will continue to maintain and update.

reference

NPM promise package juejin. Cn/post / 684490… Jakearchibald.com/2015/tasks-… Juejin. Cn/post / 684490… Zhuanlan.zhihu.com/p/87684858 github.com/kriskowal/a… www.ituring.com.cn/article/665… Juejin. Cn/post / 684490…