directory

JS

  • Talk about lexical scope in JS
  • What is a closure
  • Js garbage Collection (GC)
  • Tell me about the design patterns you know

Es6

  • Differences between ES6 modules and CommonJS modules
  • Async function implementation principle

Node

  • Eventloops in The Browser and Node
  • Implement EventEmiter in a node
  • Implement a Node Util module’s Promisify method
  • How do I implement a custom stream

Performance optimization

  • Performance optimization dnS-prefetch, prefetch, preload, defer, and async
  • Talk about React performance optimization

Web/Browser

  • Talk about the browser rendering process
  • Tell me about http2.0

algorithm

  • Implement a Reduce method
  • Implement a promise.all method that requires error retention and concurrency of 3
  • Find the height of a binary tree without recursive functions
  • Add two large numbers in js
  • Implement an array random shuffling algorithm
  • Add a comma separator to the number

Talk about lexical scope in JS

There is only lexical scope in js, which means that the scope is determined at definition time rather than execution time. Such as:

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

Note: With and eval can modify the lexical scope

What is a closure

The definition of closures in Nodejs:

In JS, methods that implement external scope access to internal scope variables are called “closures.”

Js garbage Collection (GC)

V8’s garbage collection strategy is primarily based on a generational garbage collection mechanism. The memory is divided into the new generation and the old generation, using different algorithms.

Scavenge is used in the Cenozoic

Scavenge is an algorithm used by the new generation. It is a garbage collection algorithm realized by means of copying. It divides memory into from and to Spaces. With each GC, the living objects from space are copied to the to space. Then the two spatial roles are reversed (also known as inversion). This algorithm is to sacrifice space for time, so it is suitable for the new generation, because its object life cycle is short.

Older generations use Mark-Sweep and Mark-Compact

An object in the old generation lives for a long time and is not suitable for Scavenge. Mark-sweep means to Mark a Sweep. Scavenge copies only a living object, while Mark-Sweep cleans only a dead object. The algorithm is divided into two steps:

  1. Walk through all the objects in the heap and mark the living ones
  2. Clear unmarked objects

A problem with Mark-Sweep is that the memory space is discontinuous after the dead object is cleared. If a large object is allocated at this time, all the space fragmentation will not complete the allocation and the GC will be triggered early. V8 uses the Mark-Compact algorithm. Mark-copact means Mark up. It will move the living object to one end after the tag is completed, and then clean up the memory outside the boundary. Because of the sorting process, its speed is slower than mark-sweep, which is mainly used in Node.

Incremental Marking

To avoid any inconsistency between the Javascript application logic and what the garbage collector sees, the application logic stops during garbage collection. This behavior is called stop-the-world. This had a great impact on the old generations. Incremental Marking is called Incremental Marking, which is breaking it up into small “steps”, one at a time, and letting Javascript run for a while, alternating garbage collection with application logic. With Incremental Marking, the maximum gc pause time is reduced to about 1/6 of the original.

Memory limits for V8

  • 64-bit systems Max out at about 1.4g
  • 32-bit systems Max out at about 0.7G

Check the memory usage in node

➜ ~ node > process.memoryUsage() {RSS: 27054080, // heapTotal: 7684096, // heapUsed: 4850344, // Currently used heap memory external: 9978 // Memory out of the heap (not allocated through V8) > os.totalmem() // Total system memory 17179869184 > os.freemem() // Free system memory 3239858176Copy the code

Tell me about the design patterns you know

Publish subscribe model

In JS, the event model is equivalent to the traditional publish/subscribe model, which is implemented by implementing EventEmiter in a node

The strategy pattern

Definition: Define a set of algorithms, encapsulate them one by one, and make them interchangeable.

The policy mode implements form verification
const strategies = {
    isNoEmpty: function(value, errorMsg){
        if(value.trim() === ' ') {return errorMsg
        }
    },
    maxLength: function(value, errorMsg, len) {
        if(value.trim() > len) {
            return errorMsg
        }
    }
}

class Validator {
  constructor() { this.catch = []; } add(value, rule, errorMsg, ... others) { this.catch.push(function() {
      return strategies[rule].apply(this, [value, errorMsg, ...others]);
    });
  }
  start() {
    for (let i = 0, validatorFunc; (validatorFunc = this.catch[i++]); ) {
      let msg = validatorFunc();
      if (msg) {
        returnmsg; }}}} // Use const validatorFunc =function() {
    const validator = new Validator();
    validator.add(username, 'isNoEmpty'.'Username cannot be empty');
    validator.add(password, 'isNoEmpty'.'Password cannot be empty');
    const USERNAME_LEN = PASSWORD_LEN = 10;
    validator.add(username, 'maxLength'The user name cannot exceed${USERNAME_LEN}Word ` USERNAME_LEN); validator.add(password,'isNoEmpty'The password cannot be empty${PASSWORD_LEN}Word ` PASSWORD_LEN);let msg = validator.start();
    if(msg) {
        returnmsg; }}Copy the code

Command mode

Application scenario: Sometimes we want to send a request to some object, but we don’t know who the receiver of the request is, and we don’t know what the operation of the request is. In this case, we want to design the software in a loosely-coupled way, so that the sender and receiver of the request can decouple from each other.

The command mode implements animation
class MoveCommand {
  constructor(reciever, pos) {
    this.reciever = reciever;
    this.pos = pos;
    this.oldPos = null;
  }
  excute() {
    this.reciever.start("left", this.pos, 1000);
    this.reciever.getPos();
  }
  undo() {
    this.reciever.start("left", this.oldPos, 1000); }}Copy the code

Differences between ES6 modules and CommonJS modules

  1. CommonJS outputs a copy of the value, and the ES6 module outputs a reference to the value. That is, if the CommonJS reference changes the value of a variable in a module, the other referenced modules do not change, but the ES6 module does.

  2. CommonJS is a runtime load and THE ES6 module is a compile-time output interface. Webpack’s Tree Shaking is based on ES6 because ES6 determines dependencies at compile time. Because the default is to compile the ES6 module to CommonJS using Babel-preset -2015, you need to manually modify this preset to use Tree Shaking.

module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['babel-preset-es2015', {modules: false}]],}}}]}Copy the code

Async function implementation principle

The async function is implemented based on generator, so it involves the knowledge of generator. Before async functions were available, it was common to use the CO library to execute the generator, so with CO we can also simulate the implementation of async.

function Asyncfn() {
  return co(function* () {/ /... }); }function co(gen) {
  return new Promise((resolve, reject) => {
    const fn = gen();
    function next(data) {
      let { value, done } = fn.next(data);
      if (done) return resolve(value);
      Promise.resolve(value).then(res => {
        next(res);
      }, reject);
    }
    next();
  });
}
Copy the code

Eventloops in The Browser and Node

The browser

Figure: The browser is relatively simple, there are two event queues. When the main thread is idle, the Microtask Queue will be cleared. The callback functions in the Task Queue will be executed in turn, and the Microtask Queue will be cleared after each execution.

“The current execution stack” – > “micro – task” – > “task queue to take a callback” – > “micro – task” – >… (Constantly consuming task Queue) -> “Micro-task”

nodejs

There are some differences between the mechanisms in Node and browsers. The task queue in Node is divided into several phases, and the micro task is cleared after each phase (in the browser, after each task). The phases are as follows:

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─>│        timers         │<-- --, setTimeout execution(), setInterval()The callback │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘|             |<--To perform allNext Tick QueueAs well asMicroTask QueueThe callback │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ pending callbacks │<————— executed by the previous oneTickdelayedI/OCallback (inadequate, negligible) │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘|             |<--To perform allNext Tick QueueAs well asMicroTask QueueChrysene ───┴─── errateserritsyn ── │ idle.Prepare │<- - - internal call (negligible) │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘|             |<--To perform allNext Tick QueueAs well asMicroTask QueueThe callback|             |┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming:- (Perform almost all callbacks, except for close callbacks and setImmediate for timers scheduling()Scheduled callbacks will block at this stage at the appropriate time)
│  │         poll          │<─ ─ ─ ─ ─ ┤ connections.│ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data. etc.  │ 
│             |                   |               | 
|             |└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘|             |<--To perform allNext Tick QueueAs well asMicroTask QueueThe callback|Chrysene ───┴─── gp ── check │<-- -- setImmediate()The callback will be performed at this stage │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘|             |<--To perform allNext Tick QueueAs well asMicroTask QueueThe callback │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ┤ close callbacks │<-- -- the socket.on('close'. ...)└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

Here we focus on three phases: Timer, poll, and Check. The poll queue is relatively complex:

The polling phase has two important functions: 1. Calculates the time when I/O should be blocked and polled. 2. Then, the events in the polling queue are processed.

When the event loop enters the polling phase and there is no scheduled timer, one of two things can happen: 1. If the polling queue is not empty, the event loop loops through its callback queue and executes them synchronously until the queue is exhausted or a hard limit associated with the system is reached. 2. If the poll queue is empty, two more things happen: a, if the script has been set to setImmediate, the event loop ends the poll phase and proceeds to the Check phase to execute the plan scripts. B. If the script is not set to setImmediate(), the event loop adds the wait callback to the queue and executes immediately.

Once the polling queue is empty, the event loop checks for timers that have reached the time threshold. If one or more timers are ready, the event loop loops back to the timer phase to perform the callbacks for those timers.

For details, see The Node.js Event Loop, Timers, and process.nextTick().

Programmatically understand the differences between browser and Node

setTimeout(() => {
  console.log("timer1");
  Promise.resolve().then(function() {
    console.log("promise1");
  });
}, 0);

setTimeout(() => {
  console.log("timer2");
  Promise.resolve().then(function() {
    console.log("promise2");
  });
}, 0);
Copy the code

Timer1 -> promise1 -> timer2 -> pormise2 Timer1 -> timer2 -> promise1 -> promise2

Implement EventEmiter in a node

Simple implementation:

class EventsEmiter {
  constructor() {
    this.events = {};
  }
  on(type, fn) {
    const events = this.events;
    if(! events[type]) {
      events[type] = [fn];
    } else {
      events[type].push(fn);
    }
  }
  emit(type. res) { const events = this.events;if (events[type]) {
      events[type].forEach(fn => fn.apply(this, res));
    }
  }
  remove(type, fn) {
    const events = this.events;
    if (events[type]) {
      events[type] = events[type].filer(lisener => lisener ! == fn); }}}Copy the code

Implement a Node Util module’s Promisify method

let fs = require("fs");
let read = fs.readFile;

function promisify(fn) {
  return function(... args) {returnnew Promise((resolve, reject) => { fn(... args, (err, data) => {if(err) { reject(err); } resolve(data); }); }); }; } // Callback method //read("./test.json", (err, data) => {
//   if (err) {
//     console.error("err", err);
//   }
//   console.log("data", data.toString()); / /}); The usage / / promiselet readPromise = promisify(read);

readPromise("./test.json").then(res => {
  console.log("data", res.toString());
});

Copy the code

How do I implement a custom stream

Depending on the type of flow being created, the new flow class must implement one or more specific methods, as shown in the figure below:

Use cases class Methods to implement
Read-only stream Readable _read
Just write flow Writable _write._writev._final
Readable and writable streams Duplex _read._write._writev._final
Perform operations on the written data and then read the results Transform _transform._flush._final

Take a duplex stream as an example:

const { Duplex } = require('stream'); class Myduplex extends Duplex { constructor(arr, opt) { super(opt); This.arr = arr this.index = 0} // Implement readable stream part _read(size) {this.index++if(this.index === 3) {
        this.push(null) 
    } else{this.push(this.index.tostring ())}} // Implement writable stream _write(chunk, encoding, callback) { this.arr.push(chunk.toString()) callback() } }Copy the code

For more on this, see my post on Readable and writable Streams in Node and the NodeJS website

Performance optimization dnS-prefetch, prefetch, preload, defer, and async

dns-prefetch

Converting domain names to IP is a time-consuming process, and DNS-Prefetch lets you do it for you when your browser is idle. In particular, large websites use multiple domain names, which makes DNS prefetch even more necessary.

// from baidu homepage <link rel="dns-prefetch" href="//m.baidu.com">
Copy the code

prefetch

Prefetch is used to preload resources that may be used. It is a judgment of user behavior. The browser loads prefetch resources when it is idle.

<link rel="prefetch" href="http://www.example.com/">
Copy the code

preload

Unlike prefetch, a prefecth usually loads resources for a page that may be used later. Preload loads resources such as scripts, styles, fonts and pictures for the current page. So instead of loading at idle, preload has a higher priority and consumes the number of HTTP requests.

<link rel='preload' href='style.css' as="style" onload="console.log('style loaded')"
Copy the code

As the value including

  • “script”
  • “style”
  • “image”
  • “media”
  • The “document” onLoad method is a callback function after the resource has been loaded

Defer and async

//defer
<script defer src="script.js"></script>
//async
<script async src="script.js"></script>
Copy the code

Both Defer and async load resources asynchronously (in parallel). The difference is that async executes immediately after loading, while defer does not execute until all elements are parsed, that is, before the DOMContentLoaded event is triggered. Because the resource loaded by async is loaded for execution, it is not guaranteed to be in order, and defer executes the script in order.

Talk about React performance optimization

shouldComponentUpdate

Example: Here is the handling of an internal mask component in antD-Design-Mobile’s Modal component

import * as React from "react";

exportinterface lazyRenderProps { style: {}; visible? : boolean; className? : string; }export default class LazyRender extends React.Component<lazyRenderProps, any> {
  shouldComponentUpdate(nextProps: lazyRenderProps) {
    return!!!!! nextProps.visible; }render() { const props: any = { ... this.props }; delete props.visible;return <div {...props} />;
  }
}

Copy the code

immutable

Only one visible property is compared, and it is of type string. If it is an object, it cannot be compared directly. In this case, the immutable library is better. Immutable advantage:

  • Better performance
  • More secure immutable disadvantage:
  • Large library (about 16K after compression)
  • API is not compatible with JS

Solution: Seamless – Immutable This library does not fully implement the Persistent Data Structure. Instead, it extends JS ‘Object and Array objects by using object.defineProperty. So the same Api is maintained, while the amount of code in the library is less, about 2k after compression

Key-based optimization

It has been stressed in the documentation that the key should be unique in the current scope and that the index of the current loop should not be used (especially in long lists). Reference reactjs.org/docs/reconc…

Talk about the browser rendering process

Browser main process: Browser process

  1. Responsible for downloading resources
  2. Create and destroy the renderer process
  3. Is responsible for rendering the bitmap generated by the renderer process to the page
  4. Interacting with users

Browser kernel: renderer process

Js engine thread

Consists of a main thread and multiple Web Worder threads. Web worker threads cannot manipulate the DOM

The GUI thread

To parse HTML to generate DOM tree, parse CSS to generate CSSOM, layout, paint. Reflux and redraw depend on this thread

Event thread

When the event is fired, the thread puts the event’s callback function in the callback queue and waits for the JS engine thread to process it

Timing triggered thread

SetTimeout and setInterval are timed by the thread. When the timer ends, the callback function is placed in the task queue

HTTP request thread

This thread is opened for every HTTP request, and a state change event is generated whenever a state change is detected. If the event is called back by the corresponding function, this function is put on the task queue

Task queue polling thread

Used to poll the listening task queue

process

  1. Get the HTML file
  2. Parse HTML from top to bottom
  3. Parallel requests for resources (CSS resources do not block HTML parsing, but they do block page rendering. Javascript resources organize HTML parsing.)
  4. Generate DOM tree and style rules
  5. Build the render tree
  6. The layout process (also called backflow) is performed to determine the exact coordinates of the elements on the screen
  7. Paint to the screen

The event

DOMContentLoaded

The DOMContentLoaded event is triggered when the original HTML document is fully loaded and parsed (the script executes and the stylesheet before the script to which it belongs is loaded and parsed)

onload

The onLoad event of the window is triggered when all resources are loaded

Reference flow chart:www.processon.com/view/5a6861…

Tell me about http2.0

Http2.0 is an update to the SPDY protocol. Compared with HTTP1.0, it has the following features:

  • Binary framing
  • The first compression
  • multiplexing
  • Request priority
  • Server push

For details, see HTTP—-HTTP2.0 new features

Implement a Reduce method

Note the boundary conditions: 1. If the array length is 0 and reduce does not pass in the initial argument, an error is thrown. 2. Reduce has a return value.

Array.prototype.myReduce = function(fn, initial) {
  if(this.length === 0 && ! initial) { throw new Error("no initial and array is empty");
  }
  let start = 1;
  let pre = this[0];
  if (initial) {
    start = 0;
    pre = initial;
  }
  for (let i = start; i < this.length; i++) {
    let current = this[i];
    pre = fn.call(this, pre, current, i);
  }
  return pre;
};
Copy the code

Implement a promise.all method that requires error retention and concurrency of 3

The standard ALL approach is to immediately set the promise to a failed state upon an error and trigger an error callback. A retention error is defined as: a promise encounters an error and saves it in the returned result.

function promiseall(promises) {
  return new Promise(resolve => {
    let result = [];
    let flag = 0;
    lettaskQueue = promises.slice(0, 3); // The task queue starts with a maximum concurrency of 3letothers = promises.slice(3); ForEach ((promise, I) => {singleTaskRun(promise, I); });leti = 3; // The new task starts at index 3function next() {
      if (others.length === 0) {
        return;
      }
      const newTask = others.shift();
      singleTaskRun(newTask, i++);
    }

    function singleTaskRun(promise, i) {
      promise
        .then(res => {
          check();
          result[i] = res;
          next();
        })
        .catch(err => {
          check();
          result[i] = err;
          next();
        });
    }
    function check() {
      flag++;
      if(flag === promises.length) { resolve(result); }}}); }Copy the code

Test code:

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("1");
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("2");
  }, 1500);
});
let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("3");
  }, 2000);
});
let p4 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("4");
  }, 2500);
});
let p_e = new Promise((resolve, reject) => {
  // throw new Error("Error");
  reject("Error");
});
let p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("5");
  }, 5000);
});

let all = promiseall([p_e, p1, p3, p2, p4, p5]);
all.then(
  data => {
    console.log("data", data); / / /'wrong'.'1'.'3'.'2'.'4'.'5']});Copy the code

Find the height of a binary tree without recursive functions

Let’s take a look at the implementation of recursion (depth-first traversal of a binary tree) :

function getBinaryTreeHeigth(node) {
  let maxDeep = 0;
  function next(n, deep) {
    deep++;
    if (n.l) {
      let newDeep = next(n.l, deep);
      if(newDeep > maxDeep) { maxDeep = newDeep; }}if (n.r) {
      let newDeep = next(n.r, deep);
      if(newDeep > maxDeep) { maxDeep = newDeep; }}return deep;
  }
  next(node, 0);
  return maxDeep;
}

function Node(v, l, r) {
  this.v = v;
  this.l = l;
  this.r = r;
}
Copy the code

Non-recursive implementation (breadth first traversal of a binary tree) :

function getBinaryTreeHeigth(node) {
  if(! node) {return 0;
  }
  const queue = [node];
  let deep = 0;
  while (queue.length) {
    deep++;
    for (let i = 0; i < queue.length; i++) {
      const cur = queue.pop();
      if (cur.l) {
        queue.unshift(cur.l);
      }
      if(cur.r) { queue.unshift(cur.r); }}}return deep;
}

function Node(v, l, r) {
  this.v = v;
  this.l = l;
  this.r = r;
}
Copy the code

Add two large numbers in js

Given two non-negative integers num1 and num2 represented as strings, return their sum, still represented as strings.

Input: num1 = ‘1234’, num2 = ‘987’

function bigIntAdd(str1, str2) {
  let result = [];
  let ary1 = str1.split("");
  let ary2 = str2.split("");
  let flag = false; // Whether to carrywhile (ary1.length || ary2.length) {
    let result_c = sigle_pos_add(ary1.pop(), ary2.pop());
    if (flag) {
      result_c = result_c + 1;
    }
    result.unshift(result_c % 10);

    if (result_c >= 10) {
      flag = true;
    } else {
      flag = false; }}if(flag) {
    result.unshift('1');
  }
  return result.join("");
}

function sigle_pos_add(str1_c, str2_c) {
  let l = (r = 0);
  if (str1_c) {
    l = Number(str1_c);
  }
  if (str2_c) {
    r = Number(str2_c);
  }
  return l + r;
}
Copy the code

Test code:

const str1 = "1234";
const str2 = "987654321";
const str3 = "4566786445677555";
const str4 = "987";

console.log(bigIntAdd(str1, str4))  //'2221'
console.log(bigIntAdd(str2, str3))  //'4566787433331876'
Copy the code

Implement an array random shuffling algorithm

function disOrder(ary) {
  for (let i = 0; i < ary.length; i++) {
    letrandomIndex = Math.floor(Math.random() * ary.length); swap(ary, i, randomIndex); }}function swap(ary, a, b) {
  let temp = ary[a];
  ary[a] = ary[b];
  ary[b] = temp;
}

let ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
disOrder(ary);

console.log(ary);

Copy the code

Add a comma to separate the numbers

Input: ‘”123456789.012″‘ Output: 123,456,789.012

Regular solution:

function parseNumber(num) {
  if(! num)return "";
  returnnum.replace(/(\d)(? =(\d{3})+(\.|$))/g,"The $1,");
}

Copy the code

Irregular:

function formatNumber(num) {
  if(! num)return "";
  let [int, float] = num.split(".");
  let intArr = int.split("");
  let result = [];
  let i = 0;
  while (intArr.length) {
    if(i ! == 0 && i % 3 === 0) { result.unshift(intArr.pop() +",");
    } else {
      result.unshift(intArr.pop());
    }
    i++;
  }

  return result.join("") + "." + (float ? float : "");
}
Copy the code