preface

The most recent front-end interview has beenvolumeFly up, fromComputer principles, compilation principles, data structures, algorithms, design patterns, programming paradigmstoUnderstand and use compilation tools, formatting tools, Git, NPM, unit testing, Nginx, PM2, CI/CD.

This random selection of a part, knowledge can be in-depth excavation, deep bottomless kind.

Two days ago, I published the first article of JS basic series and got good feedback from students. ❤ ️ ❤ ️ ❤ ️

Let’s continue with the rest of the fun in this series and hopefully give you a little 🤏 help.

Warm tips: this article is suitable for the front-end entry of the students and recently in preparation to systematically review the JS foundation of friends. Mid-to-advanced front-end gurus who have been working for years can skip this article

Related articles:

  • Self-cultivation of a Junior Advanced Intermediate JavaScript Worker (PART 1)
  • Self-cultivation of a Junior Advanced Intermediate JavaScript Worker (Part 2)

Chapter 4 “Relearn JavaScript” execution mechanism

First, try and finally

Why is a try executed with a return in finally

1.1 Completion type

// return executes but does not return immediately. Finally is executed first
function kaimo() {
  try {
    return 0;
  } catch (err) {
    console.log(err);
  } finally {
    console.log("a"); }}console.log(kaimo()); // a 0
Copy the code
// Finally overrides the return in the try.
function kaimo() {
  try {
    return 0;
  } catch (err) {
    console.log(err);
  } finally {
    return 1; }}console.log(kaimo()); / / 1
Copy the code

Completion Record Completion Record describes the execution process of statements, such as exceptions and jumps. Represents the result of a statement execution. It has three fields.

[[type]] : Indicates the completion type, including break, continue, return, throw, and normal

[[value]] : indicates the return value of the statement, or empty if the statement does not have one

[[target]] : Represents the target of the statement, usually a JavaScript tag

JavaScript uses the Completion Record type to control the execution of the statement.

1.2 Common Statements

In JavaScript, statements without control are called plain statements. Categories can refer to the picture in the introduction.

1. These statements are executed from front to back (var and function declaration preprocessing is ignored) without any branching or repeated execution logic.

2. After the normal statement is executed, a Completion Record with [[type]] as normal will be obtained. The JavaScript engine will continue to execute the next statement when such a Completion Record is encountered.

3. Enter an expression in the Chrome console to get the result, but add var in front of it to make it undefined. The Chrome console displays the [[value]] of the Completion Record for the statement.

1.3 blocks

A statement block is a group of statements enclosed in braces. It is a compound structure of statements that can be nested.

If the [[type]] of the Completion Record of the statement inside the statement block is not normal, subsequent statement execution of the statement block will be interrupted.

1.3.1 A block of common statements inside

// In the comments for each line is a Completion Record
{
  var i = 1; // normal, empty, empty
  i++; // normal, 1, empty
  console.log(i); //normal, undefined, empty
} // normal, undefined, empty
Copy the code

If all types are normal in this block, the program will execute sequentially.

1.3.2 加入 return

// In the comments for each line is a Completion Record
{
  var i = 1; // normal, empty, empty
  return i; // return, 1, empty
  i++;
  console.log(i);
} // return, 1, empty
Copy the code

Non-normal completion types created in blocks can cut through complex statement nesting to exert control.

1.4 Control statements

Control statements take the if and switch keywords, which react to different types of Completion Records.

The control statement is divided into two parts:

Internal effects: if, switch, while/for, try. External impact, such as break, continue, return, and throw.

Penetration is to go to the upper level of the scope or control statement to find an execution environment that can consume a break or continue. Consumption is to execute the break or continue at this level

The combination of these two types of statements has the effect of controlling the order and logic of code execution.

1.5 Tagged statements

1, Any JavaScript statement can be tagged, simply preceded by a colon:

firstStatement: var i = 1;
Copy the code

2. Similar to comments, basically useless. The only time this works is when it is used in conjunction with the target in the completion record type to break out of a multi-level loop.

outer: while (true) {
  console.log("outer");
  inner: while (true) {
    console.log("inner1");
    break outer;
    console.log("inner2"); }}console.log("finished");
// outer inner1 finished
Copy the code

Macrotasks and microtasks

What are macro tasks and micro tasks

Macro tasks include: Script (overall code), setTimeout, setInterval, I/O, UI interaction events, postMessage, MessageChannel, setImmediate(node.js environment).

Microtasks mainly include promise. then, MutationObserver, and Process.nexttick (node.js environment).

Asynchronous programming

How can asynchronous programming be implemented in JavaScript and EventLoop be described in detail

In JS, tasks are divided into macroTask and microtask. These two tasks maintain a queue respectively and are executed using a first-in, first-out strategy! All synchronized tasks are executed on macro tasks.

The specific operation steps are as follows:

  • Retrieves a task from the head of a macro task to execute;
  • Add microtasks to the queue if they are encountered during execution.
  • After the execution of the macro task, whether there are tasks in the queue of the micro task, if there are tasks, execute them one by one until the execution is completed;
  • GUI rendering;
  • Go back to Step 1 until the macro task completes;

The first four steps form a loop detection mechanism for events, known as eventloop.

Analyze asynchronous nesting

You can quickly analyze a complex asynchronous nested logic and master the analysis method

You can start by switching from complex asynchronous writing to simple writing. “Async”, “await”, the principle of which is the callback function.

Then the analysis is carried out according to the cyclic mechanism of events.

Use Promise to implement serial

5.1 an overview of the

The most common queue operation is array.prototype.reduce ()

let result = [1.2.5].reduce((accumulator, item) = > {
  return accumulator + item;
}, 0); // <-- Our initial value.

console.log(result); / / 8
Copy the code

The last value, 0, is the starting value, and each reduce return will be used as the first parameter of the next Reduce callback function until the queue loop is complete, so that cumulative calculation can be performed.

Try using reduce features for Promise:

function runPromiseByQueue(myPromise) {
  myPromise.reduce(
    (previousPromise, nextPromise) = > previousPromise.then(() = > nextPromise()),
    Promise.resolve()
  );
}
Copy the code

When the last Promise is executed (previousPromise.then), the next Promise is invoked and returned as a new Promise, and the next iteration continues the cycle.

const createPromise = (time, id) = > () = >
  new Promise(
    setTimeout(() = > {
      console.log("promise", id);
      solve();
    }, time)
  );

runPromiseByQueue([
  createPromise(3000.1),
  createPromise(2000.2),
  createPromise(1000.3),]);Copy the code

5.2 intensive reading

Reduce is executed synchronously, in a loop of events, but this is just a quick construction of the Promise execution queue in memory, expanded as follows:

new Promise((resolve, reject) = > {
  // Promise #1

  resolve();
})
  .then((result) = > {
    // Promise #2

    return result;
  })
  .then((result) = > {
    // Promise #3

    return result;
  }); // ... and so on!
Copy the code

The purpose of Reduce is to generate this queue in memory without having to write redundant queues in code!

5.3 An Easier way

With async/await support, the runPromiseByQueue function can be simplified:

async function runPromiseByQueue(myPromises) {
  for (let value of myPromises) {
    awaitvalue(); }}Copy the code

Thanks to async/await, the code looks so clean.

However, it should be noted that the difference between this approach and reduce approach is that the function using Reduce is a synchronous function as a whole. The Promise queue is constructed after completion of execution and then asynchronously executed in memory. A function that uses async/await is a function that transforms itself into an asynchronous function and waits for each Promise to complete.

Six, EventLoop

Differences between Node and browser EventLoop

6.1 Differences with the Browser Environment

In Node, the event loop behaves roughly the same as in the browser. The difference is that Node has its own model. The implementation of event loops in Node relies on the Libuv engine. As we know, Node selects Chrome V8 engine as the JS interpreter. V8 engine analyzes THE JS code and calls the corresponding Node API, which is driven by libuv engine to perform the corresponding tasks. Different events are placed in different queues waiting for the main thread to execute. So the event loop in Node actually exists in the Libuv engine.

6.2 Event cycle model

Here is a model of the event loop in the Libuv engine:

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ connections ─ ─ ─ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ├ ──┤ close callbacks ────── our r companyCopy the code

Note: Each square in the model represents a phase of the event cycle

This model is presented in an article on the Node website, from which I will explain the following. I will post the address at the end of the article, interested friends can personally and see the original text.

6.3 Description of each phase of the Event Cycle

From the above model, we can roughly analyze the sequence of events in node:

External input data –> Polling stage –> Check stage –> Close callback stage –> Timer detection stage –>I/O callback stage –> IDLE stage Prepare)–> Polling stage…

The names of the above stages are based on my personal understanding of the translation. In order to avoid mistakes and ambiguities, the following explanations will use English to represent these stages.

The general functions of these stages are as follows:

  • Timers: This phase performs callbacks in the timer queue such as setTimeout() and setInterval().
  • I/O Callbacks: This stage performs almost all callbacks. But not close events, timers, and setImmediate() callbacks.
  • Idle, prepare: This stage is for internal use only and can be ignored.
  • Poll: Waiting for new I/O events. Node blocks here in some special cases.
  • The check: setImmediate() callback is performed at this stage.
  • Close Callbacks: such as socket.on(‘close’,…) The callback to this close event.

Let’s elaborate on these stages in the order in which the code first entered the Libuv engine:

6.3.1 poll phase

After the V8 engine parses the JS code and passes it to the Libuv engine, the cycle first enters the poll phase. The execution logic of the poll phase is as follows: Check whether there are events in the poll queue, and perform callbacks in first-in, first-out order if there are tasks. When the queue is empty, it checks for setImmediate() callbacks, and if so, it enters the Check phase to execute them. If there are any expired timers, the callback of these expired timers will be placed in the timer queue in the order in which they are called. After that, the loop will enter the Timer phase and execute the callback in the queue. The order of the two is not fixed and is influenced by the environment in which the code is run. If both queues are empty, the loop stays in the poll phase until an I/O event returns, and the loop enters the I/O callback phase and immediately executes the callback for that event.

It is worth noting that the poll phase does not actually go on indefinitely while performing callbacks in the poll queue. There are two cases where the poll phase terminates the execution of the next callback in the poll queue: 1. All callbacks are complete. 2. The number of executions exceeded the node limit.

6.3.2 check phase

The Check phase is specifically used to perform the callback of the setImmediate() method, and the event loop enters this phase when the poll phase goes idle and there are callbacks in the setImmediate Queue.

6.3.3 close phase

When a socket connection or a handle is suddenly closed (for example by calling socket.destroy()), the close event is sent to this phase to execute the callback. Otherwise the event is sent using the process.nexttick () method.

6.3.4 timer phase

This phase performs first-in, first-out (FIFO) execution of all expired timers to the callback in the timer queue. A timer callback refers to a callback set by setTimeout or setInterval.

6.3.5 I/O Callback Phase

As mentioned above, this phase performs most of the callbacks to I/O events, including some for the operating system. For example, when a TCP connection generates an error, the system needs to perform a callback to get a report of the error.

6.4 Methods of Postponing task Execution

There are three commonly used in the node used to delay the task execution methods: process. NextTick, setTimeout (setInterval the same) and setImmediate

There are some very different differences among the three:

process.nextTick()

Although not mentioned, there is actually a special queue in Node called the nextTick Queue. While the callback execution in this queue is not represented as a phase, these events take precedence when each phase is completed and ready to move on to the next phase. Before the event loop is ready to move to the next phase, the nextTick Queue is checked to see if there are any tasks in it, and if so, the queue is emptied. Unlike performing tasks in a poll queue, this operation does not stop until the queue is clear. This means that using the process.nexttick () method incorrectly can cause Node to go into an infinite loop. Until memory leaks.

So is it appropriate to use this method? Here’s an example:

const server = net.createServer(() = > {}).listen(8080);

server.on("listening".() = > {});
Copy the code

In this example, when the listen method is called, it is immediately bound to the corresponding port unless the port is occupied. This means that the port can immediately fire the Listening event and execute its callback. However, on(‘listening) has not set the callback yet, so no callback can be executed. To avoid this, Node uses the process.nexttick () method in the Listen event to ensure that the event is fired after the callback function is bound.

SetTimeout () and setImmediate() Of the three methods, these two are the most easily confused. In fact, in some cases the two methods behave very similarly. In practice, however, the two approaches have very different meanings.

The setTimeout() method defines a callback and expects it to be executed the first time after the interval we specify. Note this “first time execution”, which means that the callback will not execute precisely after the time interval we expect due to the many influences of the operating system and the current executing task. It is inevitable that there will be delays and errors in the execution time. Node will execute the task as soon as it can execute the timer callback.

The setImmediate() method does not perform a callback until a certain stage, after the poll stage. Interestingly, the meaning of this name best matches the process.nexttick () method mentioned earlier. The Node developers are aware of the confusion and say they will not switch the names of the two methods – because there are so many NDOE applications that use the two methods, the benefits of switching the names are insignificant compared to the impact.

SetTimeout () and setImmediate() perform similarly. Guess what the result of the following code is?

setTimeout(() = > {
  console.log("timeout");
}, 0);

setImmediate(() = > {
  console.log("immediate");
});
Copy the code

Actually, the answer is not necessarily. That’s right, even Node developers can’t tell exactly which comes first. This depends on the environment in which the code is running. Various complications in the runtime environment can cause the order of the two methods in the synchronous queue to be randomly determined. However, there is one situation in which you can accurately determine the order in which two method callbacks are executed, and that is in a callback to an I/O event. The following code is always in the same order:

const fs = require("fs");

fs.readFile(__filename, () = > {
  setTimeout(() = > {
    console.log("timeout");
  }, 0);
  setImmediate(() = > {
    console.log("immediate");
  });
});
Copy the code

The answer is always:

immediate

timeout

Because in the callback of an I/O event, the setImmediate method’s callback is always executed before the Timer’s callback.

7. Processing massive data

How to handle massive amounts of data while keeping the page running smoothly

If you want to present a large amount of data at the front end, the common strategy is paging. It is rare for the front-end to present millions of data, but it is still common to display thousands of slightly more complex data. As long as the memory is enough, javascript is certainly able to tolerate, and the js efficiency is not a problem to calculate thousands of data, but the DOM rendering browser cannot bear. A computer with a slightly twisted CPU is bound to be jammed.

Policy: Display three screens of data, remove the DOM for others.

7.1 strategy

The following is a simple sketch I drew. We put a string of Data into a Container. The height of the Data List must be much higher than the height of the Container. And render in turn, the whole process JS does not spend too much time, the overhead is mainly DOM rendering.

To solve this problem, we make the data display a portion of the contents of the Container’s visual area, as well as the cached contents of the upper and lower screens (one screen refers to the size of the Container’s height). If the Container is high, you can cache only half of the screen. The reason for caching is that while scrolling through the scrollbar, js will need time to piece together the string (or create a Node) before the browser can render it, so there will be a temporary blank space, which can be quite unpleasant.

7.2 the Demo

<title> Fast and smooth display of millions of data front end </title><style type="text/css">
#box {position: relative; height: 300px; width: 200px; border:1px solid #CCC; overflow: auto}
#box div { position: absolute; height: 20px; width: 100%; left: 0; overflow: hidden; font: 16px/20pxCourier; }</style>

<div id="box"></div>

<script type="text/javascript">
var total = 1e5
  , len = total
  , height = 300
  , delta = 20
  , num = height / delta
  , data = [];

for(var i = 0; i < total; i++){
    data.push({content: "item-" + i});
}

var box = document.getElementById("box");
box.onscroll = function(){
    var sTop = box.scrollTop||0
      , first = parseInt(sTop / delta, 10)
      , start = Math.max(first - num, 0)
      , end = Math.min(first + num, len - 1)
      , i = 0;

    for(var s = start; s <= end; s++){
        var child = box.children[s];
        if(! box.contains(child) && s ! = len -1){ insert(s); }}while(child = box.children[i++]){
        var index = child.getAttribute("data-index");
        if((index > end || index < start) && index ! = len -1){ box.removeChild(child); }}};function insert(i){
    var div = document.createElement("div");
    div.setAttribute("data-index", i);
    div.style.top = delta * i + "px";
    div.appendChild(document.createTextNode(data[i].content));
    box.appendChild(div);
}

box.onscroll();
insert(len - 1);
</script>
Copy the code

7.3 Algorithm Description

  • Compute the start and end nodes

    The number of Data that a Container can hold is num = height/delta. The index value of the first node on the top of the Container is

    var first = parseInt(Container.scrollTop / delta);
    Copy the code

    And since we have a screen above and a screen below, so

    var start = Math.max(first - num, 0);
    var end = Math.min(first + num, len - 1);
    Copy the code
  • Insert the node

    From start to end, the node is inserted into the Container once, and the last node is inserted into the DOM.

    // Insert the last node
    insert(len - 1);
    // Insert nodes from start to end
    for (var s = start; s <= end; s++) {
      var child = Container.children[s];
      // Skip this node if it already exists in the Container or if it is the last one
      if(! Container.contains(child) && s ! = len -1) { insert(s); }}Copy the code

    Here’s why we want to insert the last node:

    function insert(i){
    var div = document.createElement("div");
    div.setAttribute("data-index", i);
    div.style.top = delta \* i + "px";
    div.appendChild(document.createTextNode(data[i].content));
    Container.appendChild(div);
    }
    Copy the code

    As you can see, we have added a top attribute to all the inserted nodes. The top of the last node is the largest. Only by inserting this node into the DOM can the scrollbar be elongated, making it feel like a lot of data has been placed.

  • Remove nodes

    To reduce browser reflow, we can hide data three screens away. I just deleted it for convenience, and I need to insert it again later.

    while ((child = Container.children[i++])) {
      var index = child.getAttribute("data-index");
      // Do not delete the last node
      if((index > end || index < start) && index ! = len -1) { Container.removeChild(child); }}Copy the code

    Once the DOM is loaded, trigger container.onscroll () and the program will be ready.

Chapter 5: “Relearning JavaScript” syntax and apis

ECMAScript and JavaScript

Understand the relationship between ECMAScript and JavaScript

A common question is, what exactly is the relationship between ECMAScript and JavaScript?

To make this clear, we need to go back to history. In November 1996, Netscape, the creator of JavaScript, decided to submit JavaScript to the standardization organization ECMA, hoping that the language would become an international standard. The following year, ECMA released the first version of the standard document 262 (ECMA-262), which defined the browser scripting language and called it ECMAScript. This version became version 1.0.

The standard was written for the JavaScript language from the beginning, but it’s not called JavaScript for two reasons. Java is a trademark of Sun, and according to the license agreement, only Netscape can legally use the name JavaScript, and JavaScript itself has been registered as a trademark by Netscape. Second, I want to show that the creator of this language is ECMA, not Netscape, so as to ensure the openness and neutrality of this language.

Thus, the relationship between ECMAScript and JavaScript is that the former is a specification of the latter and the latter is an implementation of the former (other ECMAScript dialects also include JScript and ActionScript). In everyday situations, the two words are interchangeable.

Second, the ES6

Familiar with es5 and ES6 syntax

JavaScript tutorial

ECMAScript introduction to 6

Third, setInterval

SetInterval: Use settimeout to implement setInterval

3.1 setInterval Points to pay attention to

When using the setInterval method, each time you start the setInterval method, you need to make a judgment on the value returned by the setInterval method to determine whether it is null. If it is not, you need to stop the timer and set the value to null, and then restart the timer. If you do not make the judgment and assign a value, it may cause the timer to be called repeatedly. Code that executes simultaneously in the same amount of time increases as the running time of the code increases, causing functionality to fail to be implemented or even to become jammed and crash due to excessive resource usage. So every time you use the setInterval method, you need to make a judgment call.

let timer = setInterval(func, 1000);
// Use setInterval(func, 1000) again elsewhere
if(timer ! = =null) {
  clearInterval(timer);
  timer = null;
}
timer = setInterval(func, 1000);
Copy the code

3.2 Using setTimeout to Implement setInterval

setIntervalFunc = () = > {
  console.log(1); // Use recursion
  setTimeout(setIntervalFunc, 1000);
};
setInterval(a);Copy the code

Regular expressions

JavaScript provides regular expression API, you can use regular expressions (mailbox verification, URL parsing, deduplication, etc.) to solve common problems

The RegExp object

5. Error handling

JavaScript exception handling method, unified exception handling scheme

When a JavaScript engine executes JavaScript code, various exceptions can occur, such as syntax exceptions, missing functionality in the language, and exceptions due to abnormal output from the server or the user.

Javascript engines are single-threaded, so when an exception is encountered, the Javascript engine usually stops execution, blocks subsequent code and throws an exception message, so predictable exceptions should be caught and properly displayed to the user or developer.

5.1 the Error object

Throw and promise.reject () can throw an exception of type string, and can throw an exception of type Error object.

An exception of type Error contains not only an exception message, but also a trace stack so that you can easily find the number of lines of code that went wrong.

Therefore, it is recommended to throw an exception of the Error object type rather than a string type.

Create your own exception constructor

function MyError(message) {
  var instance = new Error(message);
  instance.name = "MyError";
  Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
  return instance;
}

MyError.prototype = Object.create(Error.prototype, {
  constructor: {
    value: MyError,
    enumerable: false.writable: true.configurable: true,}});if (Object.setPrototypeOf) {
  Object.setPrototypeOf(MyError, Error);
} else {
  MyError.__proto__ = Error;
}

export default MyError;
Copy the code

Throw custom exception types in your code and catch them

try {
  throw new MyError("some message");
} catch (e) {
  console.log(e.name + ":" + e.message);
}
Copy the code

5.2 Throw

throw expression;
Copy the code

The throw statement is used to throw a user-defined exception. Execution of the current function is stopped (statements after the throw will not execute), and control is passed to the first catch block in the call stack. If there is no catch block in the caller function, the program terminates.

try {
  console.log("before throw error");
  throw new Error("throw error");
  console.log("after throw error");
} catch (err) {
  console.log(err.message);
}

// before throw error
// throw error
Copy the code

5.3 Try/Catch

try {
try_statements
}
[catch (exception) {
catch_statements
}][finally {
  finally_statements
}]
Copy the code

Try /catch is used to catch exceptions. A try/catch statement contains a try block and at least one catch block or finally block. There are three forms of try declarations:

  • try… catch
  • try… finally
  • try… catch… finally

The try block contains statements or functions that may raise exceptions

A catch block contains the statement to be executed. When an exception is thrown in a try block, the catch block catches the exception and executes the code in the catch block. If no exception is thrown in a try block, the catch block will skip.

A finally block executes after a try block and a catch block. It always executes whether or not an exception is thrown or caught. When an exception is thrown ina finally block, it overwrites the exception ina try block.

try {
  try {
    throw new Error("can not find it1");
  } finally {
    throw new Error("can not find it2"); }}catch (err) {
  console.log(err.message);
}

// can not find it2
Copy the code

If a value is returned from a finally block, that value will be the return value of the entire try-catch-finally, regardless of whether there are any return statements in the try and catch. This includes exceptions thrown in the catch block.

function test() {
  try {
    throw new Error("can not find it1");
    return 1;
  } catch (err) {
    throw new Error("can not find it2");
    return 2;
  } finally {
    return 3; }}console.log(test()); / / 3
Copy the code

Try/Catch performance

A well-known anti-optimization pattern is to use try/catch.

Use of try/catch statements in V8 (and possibly other JS engines) functions cannot be optimized by the V8 compiler.

5.4 the window. The onerror

By defining an event listener on window.onerror, uncaught exceptions generated by other code in the program are often caught by listeners registered on window.onerror. And some information about the exception is caught at the same time.

window.onerror = function(message, source, lineno, colno, error) {};
Copy the code
  • Message: exception message (string)
  • Source: URL of the script where the exception occurred (string)
  • Lineno: Line number (number) where the exception occurred
  • Colno: column number (number) of the exception
  • Error: Error object (object)

Note: Safari and IE10 do not yet support a fifth argument in the window.onerror callback, which is an Error object with a trace stack

Try /catch cannot catch an exception in asynchronous code, but it will throw the exception globally and then window.onError can catch it.

try {
  setTimeout(() = > {
    throw new Error("some message");
  }, 0);
} catch (err) {
  console.log(err);
}
// Uncaught Error: some message
Copy the code
window.onerror = (msg, url, line, col, err) = > {
  console.log(err);
};
setTimeout(() = > {
  throw new Error("some message");
}, 0);
// Error: some message
Copy the code

In Chrome, window.onError can detect exceptions in script files referenced from other fields and mark those exceptions as Script error. If you don’t want to process script files imported from other fields, you can filter them out in your program with the Script Error flag. However, in Firefox, Safari, or Internet Explorer 11, cross-domain JS exceptions are not introduced, and even in Chrome, if you surround the offending code with a try/catch, Chrome no longer detects these cross-domain exceptions.

In Chrome, if you want to get full cross-domain exception information through window.onerror, these cross-domain resources must provide the appropriate cross-domain header information.

5.5 Exceptions in promises

  • The Promise throws an exception

    new Promise((resolve, reject) = > {
      reject();
    });
    Promise.resolve().then((resolve, reject) = > {
      reject();
    });
    Promise.reject();
    throw expression;
    Copy the code
  • Catch exceptions in promises

    promiseObj.then(undefined.(err) = > {
      catch_statements;
    });
    promiseObj.catch((exception) = > {
      catch_statements;
    });
    Copy the code

    In JavaScript functions, only return/yield/throw interrupts the function’s execution, and nothing else prevents it from running to completion.

    A return before resolve/reject prevents further action.

    Without return:

    Promise.resolve()
      .then(() = > {
        console.log("before excute reject");
        reject(new Error("throw error"));
        console.log("after excute reject");
      })
      .catch((err) = > {
        console.log(err.message);
      });
    
    // before excute reject
    // throw error
    // after excute reject
    Copy the code

    Use return:

    Promise.resolve()
      .then(() = > {
        console.log("before excute reject");
        return reject(new Error("throw error"));
        console.log("after excute reject");
      })
      .catch((err) = > {
        console.log(err.message);
      });
    
    // before excute reject
    // throw error
    Copy the code
  • Throw or Reject

    What either a try/catch or a promise can catch is a “synchronous” exception

    Reject is a callback, while throw is just a synchronous statement that cannot be caught in the current context if thrown in another asynchronous context.

    So use reject in the Promise to throw an exception. Otherwise the catch may not catch.

    Promise.resolve()
      .then(() = > {
        setTimeout(() = > {
          throw new Error("throw error");
        }, 0);
      })
      .catch((err) = > {
        console.log(err);
      });
    
    // Uncaught Error: throw error
    Copy the code
    Promise.resolve()
      .then(() = > {
        return new Promise((resolve, reject) = > {
          setTimeout(() = > {
            reject(new Error("throw error"));
          }, 0);
        });
      })
      .catch((err) = > {
        console.log(err);
      });
    
    // Error: throw error
    Copy the code

5.6 the window onunhandledrejection

Window. Onunhandledrejection with window. Onerror similar, in a JavaScript Promise be reject but didn’t catch to catch this reject trigger. And some information about the exception is caught at the same time.

window.onunhandledrejection = (event) = > {
  console.log(event.reason);
};
Copy the code

The event event is an instance of the PromiseRejectionEvent, which has two properties:

  • Event. promise: JavaScript promise rejected
  • Event. reason: A value or Object that indicates why a promise is rejected, as in promise.reject ().

5.7 the window rejectionhandled

Because a Promise can defer calling a catch method, if catch is not called when reject is thrown but is called again later, the rejectionHandled event is raised.

window.onrejectionhandled = (event) = > {
  console.log("rejection handled");
};

let p = Promise.reject(new Error("throw error"));

setTimeout(() = > {
  p.catch((e) = > {
    console.log(e);
  });
}, 1000);

// Uncaught (in promise) Error: throw error
// Output after 1 second
// Error: throw error
// rejection handled
Copy the code

5.8 Unified Exception Handling

Exceptions thrown in code are either presented to the user or to the developer.

For exceptions displayed to users, alert or toast are generally used. Exceptions shown to developers are generally printed to the console.

Exceptions thrown can be captured in a single function or code block and presented in different ways according to different exception types.

Exception types that need to be clicked to confirm:

ensureError.js

function EnsureError(message = "Default Message") {
  this.name = "EnsureError";
  this.message = message;
  this.stack = new Error().stack;
}
EnsureError.prototype = Object.create(Error.prototype);
EnsureError.prototype.constructor = EnsureError;

export default EnsureError;
Copy the code

Types of exceptions that pop up:

toastError.js

function ToastError(message = "Default Message") {
  this.name = "ToastError";
  this.message = message;
  this.stack = new Error().stack;
}
ToastError.prototype = Object.create(Error.prototype);
ToastError.prototype.constructor = ToastError;

export default ToastError;
Copy the code

Type of exception that prompts developers:

devError.js

function DevError(message = "Default Message") {
  this.name = "ToastError";
  this.message = message;
  this.stack = new Error().stack;
}
DevError.prototype = Object.create(Error.prototype);
DevError.prototype.constructor = DevError;

export default DevError;
Copy the code

Exception handler:

When you throw a normal exception, you can bring a list of problems on StackOverflow so developers can find the cause.

errorHandler.js

import EnsureError from "./ensureError.js";
import ToastError from "./toastError.js";
import DevError from "./devError.js";
import EnsurePopup from "./ensurePopup.js";
import ToastPopup from "./toastPopup.js";

function errorHandler(err) {
  if (err instanceof EnsureError) {
    EnsurePopup(err.message);
  } else if (err instanceof ToastError) {
    ToastPopup(err.message);
  } else if (err instanceof DevError) {
    DevError(err.message);
  } else {
    error.message += `https://stackoverflow.com/questions?q=The ${encodeURI(
      error.message
    )}`;
    console.error(err.message); }}window.onerror = (msg, url, line, col, err) = > {
  errorHandler(err);
};

window.onunhandledrejection = (event) = > {
  errorHandler(event.reason);
};

export default errorHandler;
Copy the code