At the beginning

The content mainly for my own weak points, the end of the current is preparing for the interview, there are some special content will be written in detail, and some of the content is written is relatively small, but the inside of the guarantee content is dry, a lot of a lot of have detailed explanation, dry goods are behind ah, continue to read on… You pass by the little brother miss sisters, I hope to finish reading to help you. Ten thousand words long article ~~~ if you read all the content count me lose!!

Let’s go!!!

Some basic JS questions 🆒

Data types in Javascript (8 types)

  • Simple data types: Number, String, Boolean, undefined, NULL, Symbol, Bigint(new in ES2020)
  • Complex data type: Object
    • Object has several subtypes. Array, Function, RegExp, and Date are all Object types

The for loop setTimeout prints the output

If you don’t print 10 10s in the form of an immediate function or let, you don’t have to print 10 10s in the form of closures or block-level scopes

for (var i = 0; i < 10; i++) {
  (function (j) {
    setTimeout(() = > {
      console.log(j)
    }, 1000)
  })(i)
}
Copy the code

Pass a third argument to the timer. The timer can pass multiple arguments to the timer function. Here the outer I is passed to the timer callback function as an argument.

for(var i = 1; i <=5; i++){
  setTimeout(function timer(j){
    console.log(j)
  }, 0, i)
}
Copy the code

Give the block-level scope with let

for (let i = 0; i < 10; i++) {
  setTimeout(() = > {
    console.log(i)
  }, 1000)}Copy the code

Some cold stuff about setTimeout

  • Due to the mechanism of message queuing, it may not be possible to execute at the time you set

  • Settimeout if settimeout is nested, the system sets the minimum interval to 4ms

  • For inactive pages, the minimum settimeout interval is 1000ms

  • The maximum delayed execution time is 2147483647(32bit). If this value is exceeded, the timer is executed immediately

    setTimeout(() = > {
        console.log('This will be executed immediately.')},2147483648)
    Copy the code

Array flattening method

// Use the array.prototype. flat method in ES6
arr.flat(Infinity)
Copy the code
// How to use reduce
function arrFlat(arr) {
  return arr.reduce((pre, cur) = > {
    return pre.concat(Array.isArray(cur) ? arrFlat(cur) : cur)
  }, [])
}
Copy the code
// Use recursion plus loop
function arrFlat(arr) {
  let result = []
  arr.map((item, index) = > {
    if (Array.isArray(item)) {
      result = result.concat(arrFlat(item))
    } else {
      result.push(item)
    }
  })
  return result
}
Copy the code
// Change the array to a string, then undo toString()
// There is a flaw in this method, that is, only the elements of the array are Number or String
function arrFlat(arr) {
    return arr.toString().split(', ').map(item= > +item)
}
Copy the code

Array to heavy

Define deduplicated data

let arr = [1.1."1"."1".null.null.undefined.undefined./a/./a/.NaN.NaN, {}, {}, [], []]

Copy the code

Let’s look at a few ways to write without removing duplicate reference data types

/ / using the Set
let res = [...new Set(arr)]
console.log(res)
Copy the code

Although this method is very succinct, we can see that the reference datatypes are not successfully reduplicated, only the basic datatypes are removed

/ / using the filter
let res = arr.filter((item, index) = > {
  return arr.indexOf(item) === index
})
console.log(res)

Copy the code
/ / use the reduce
let res = arr.reduce((pre, cur) = > {
  return pre.includes(cur) ? pre : [...pre, cur]
}, [])
console.log(res)
Copy the code

Using these two methods, like the above method, does not remove the reference data type.

Let’s look again at how to remove duplicate values of reference types

The hasOwnProperty method of the object is used to determine whether the object contains the property. If it does, the property is filtered out. If it does not, the property is returned to the new array

let obj = {}
let res = arr.filter(item= > {
  if (obj.hasOwnProperty(typeof item + item)) {
    return false
  } else {
    obj[typeof item + item] = true
    return true}})console.log(res)
Copy the code

This time you can see that the reference data type was successfully removed.

In addition to the above methods, there are several similar methods for looping

Class arrays become arrays

Class arrays have the length attribute, but no methods on the array prototype. For example, DOM operations return an array of classes. So how do you turn an array of classes into an array

  • Array.from(document.querySelectorAll('div'))
  • Array.prototype.slice.call(document.querySelectorAll('div'))
  • [...document.querySelectorAll('div')]

Data type detection

typeof 1  	 	 // number
typeof '1'  	 // string
typeof undefined // undefined
typeof true      // boolean
typeof Symbol(a)// symbol
Copy the code

All of the above types are correctly detected, but reference data types are shown as object except functions, and are also object for typeof NULL. This is a historical bug that has not been fixed for fear of affecting some existing Web projects.

When checking for reference data types, instanceof is a good option, which queries based on the stereotype chain and returns true if the query result is in the stereotype chain.

Object. The prototype. ToString. Call (test data type best solution)

Call the toString() method on the Object prototype and change the this pointer with call. Return a string. Let’s look at the results returned by each of the eight data types

function checkType(param) {
  return Object.prototype.toString.call(param)
}

console.log(checkType(123)) //[object Number]
console.log(checkType("123")) //[object String]
console.log(checkType(true)) //[object Boolean]
console.log(checkType({ a: 123 })) //[object Object]
console.log(checkType(() = > {})) //[object Function]
console.log(Symbol(1)) //Symbol(1)
console.log(null) //null
console.log(undefined) //undefined
Copy the code

So let’s do that again

function checkType(param) {
  return Object.prototype.toString.call(param).slice(8, -1).toLowerCase()
}

console.log(checkType(1)) // number
Copy the code

Object. Is and ===

Object. Is fixes special cases where NaN is not equal to NaN on the basis of strict equality

function is(x, y){
    if(x === y){
        // 1/+0 = +Infinity 1/-0 = -infinity these two are not equal
        // When x and y are both equal to 0, we evaluate x/0 and y/0
        returnx ! = =0|| y ! = =0 || x / 0 === y / 0}}Copy the code

The difference between == and === and implicit data type conversion

=== is strictly equal. The left and right sides must not only have the same value, but also have the same type. For example, ‘1’===1 is false because the left side is string and the right side is number.

== will return true as long as the value is equal, and implicit type conversion will occur when using ==. In JS, when the operator is in operation, if the two sides of the data are not unified, the CPU cannot calculate, then the compiler will automatically convert the data on both sides of the operator to the same data type and then calculate.

  • Converts to string: + string concatenators such as 1 + “1” = “11”

  • Into a number types: + +, – (from increasing the decrement operator) +, -, *, /, % more than take arithmetic operators (addition, subtraction, multiplication, and division) >, <, > =, < =, = =,! === =! == (relational operator)

    let i = "1"
    console.log(++i) / / 2
    Copy the code
  • Convert to Boolean:! Use Boolean to convert to true in all but the following eight cases. 0, -0, NaN, undefined, null, “” (empty string), false, document.all()

  • If one is Object and the other is String, Number, or Symbol, the Object is converted to a String and then compared

Example:

// String concatenation
console.log(1 + 'true')// + is a String concatenator, String(1) + 'true', printing '1true'

// The arithmetic operator
console.log(1 + true) // + is the arithmetic operator,true by Number(true)->1, printing 2
console.log(1 + undefined) // 1 + Number(undefined) -> 1 + NaN
console.log(1 + null) // 1 + Number(null) -> 1 + 0 prints 1

// Relational operators
// String Number("2")
// 2 > 5, print false
console.log("2" > 5)
// Both strings, call "2". CharCodeAt () -> 50
CharAtCode ()-> 53, print false
console.log("2" > "5") 

// Multiple strings are matched from left to right, also calling the charCodeAt method for comparison
// Compare "a".charcodeat () < "b".charcodeat (), print false
console.log("abc" > "b") 

// The first "a" on the left has the same Unicode encoding as the first "a" on the right
// Continue to compare the second character on both sides, "b" > "a", and print true
console.log("abc" > "aaa") 

// Ignore the above rules
console.log(NaN= =NaN) // NaN is false when compared to any data
console.log(undefined= =undefined) //true
console.log(undefined= = =undefined) //true
console.log(undefined= =null) //true
console.log(undefined= = =null) //false
Copy the code

For complex data types, such as objects and arrays

ValueOf () is used to get the original value. If the original value is not number, the toString() method is used to convert it to valueOf -> toString

// a conversion to a.valueof ().tostring () occurred, printing true
console.log([1.2] = ="1, 2,") 

// a conversion to a.valueof ().tostring () occurred, printing true
let a = {}
console.log(a == "[object Object]") 
Copy the code

Converting an object to a primitive type calls the built-in [ToPrimitive] function, for which the logic is as follows:

  • If you have SettingsSymbol.toPrimitive()Method is called first and returns data
  • callvalueOf(), if cast to the original type
  • calltoString(), if cast to the original type
  • If no primitive type is returned, an error is reported

Let’s look at two examples 👇

let obj = {
  value: 3.valueOf() {
    return 4
  },
  toString() {
    return 5},Symbol.toPrimitive]() {
    return 6}},console.log(obj + 1) / / print 7
Copy the code

Let if(a ==1 &&a == 2 &&a == 3) be true

let a = {
  value: 0.valueOf() {
    return ++a.value
  },
}
// Every time we call this object, we increment 0 by 1. After 3 calls, we get 3
console.log(a == 1 && a == 2 && a == 3) //true
Copy the code

If an array or object is compared to a number type, valueOf is used to get the original value. If the original value is not number, toString is called, and then the string type is converted to a number by number. Call order valueOf() -> toString() -> Number()

The toString() method of an empty array returns an empty string, and the toString() method of an empty object returns a string.

// Number([].valueof ().toString())) prints true
console.log([] == 0) 

// Logical non-operators take precedence over relational operators
// Empty array booleans get true, and inverts get false
//false = 0, print true
console.log(! [] = =0) 

{}.valueof ().toString() gets "[Object object]",Number(" [object object] ")->NaN
// Right:! {} get false,Number(false) -> 0
// If both sides are not equal, print false
console.log({} == ! {})// Left: [].valueof ().toString() gets an empty string
// Right:! [] are false
// Number("") = Number(false
/ / print true
console.log([] == ! [])// Because the reference data type is stored in the heap, the left and right sides belong to two separate Spaces
// They have different addresses, so they are not equal
// Print false in both cases
console.log([] == [])
console.log({} == {})
Copy the code

Record a related problem encountered

Print results of the following three

// Typof null returns object
console.log(typeof null)

// Look from right to left, first look at the right typeof NULL whole, after returning object
// Then treat the whole as typeof Object
// Prints a string because typeof NULL returns an Object string
console.log(typeof typeof null)

// Look from right to left, equivalent to typeof String
// The result is printed as string
console.log(typeof typeof typeof null)
Copy the code

Implement an Instanceof

function myInstanceof(left,right) {
    if(typeofleft ! = ='object' || left === null) return false
    // Get the prototype
    let proto = Object.getPrototypeOf(left)
    while(true) {// If the prototype is null, you have reached the top of the prototype chain
        if(proto === null) return false
        // If the left stereotype equals the right stereotype, the result is returned
        if(proto === right.prototype) return true
        // Otherwise, go ahead and get the prototype
        proto = Object.getPrototypeOf(proto)
    }
}
Copy the code

Implementation inheritance

Inheritance is implemented in ES5

    // implement inheritance
    function Parent() {
      this.name = "Adult"
      this.hairColor = "Black"
    }

    function Child() {
      Parent.call(this)
      this.name = "Child"
    }

    Child.prototype = Object.create(Parent.prototype)
	// Add the missing constructor back
    Child.prototype.constructor = Child

    let c1 = new Child()
    console.log(c1.name, c1.hairColor) // The child is black
    console.log(Object.getPrototypeOf(c1))
    console.log(c1.constructor) // The Child constructor

    let p1 = new Parent()
    console.log(p1.name, p1.hairColor) // Adult, black
    console.log(Object.getPrototypeOf(p1))
    console.log(p1.constructor) //Parent constructor
Copy the code

ES6 implements inheritance

// ES6 inheritance
class Parent {
  constructor() {
    this.name = "Adult"
    this.hairColor = "Black"}}class Child extends Parent {
  constructor() {
    super(a)// Invoke parent methods and properties
    this.name = "Child"}}let c = new Child()
console.log(c.name, c.hairColor) // The child is black

let p = new Parent()
console.log(p.name, p.hairColor) // Adult black
Copy the code

How to implement const in ES5 environment

The Object.defineProperty(Obj,prop,desc) API is needed here

function _const (key, value) {
	const desc = {
        value,
        writable:false
    }
    Object.defineProperty(window,key,desc)
}

_const('obj', {a:1}) / / define obj
obj = {} // Reassignment does not take effect
Copy the code

Write a Call

/ / write a call
let obj = {
  msg: "My name is Wang Dachu.",}function foo() {
  console.log(this.msg)
}

// foo.call(obj)
// Call works just like here, by mounting a function to an object and executing the function in the object
// obj.foo = foo
// obj.foo()

Function.prototype.myCall = function (thisArg, ... args) {
  const fn = Symbol("fn") // Declare a unique Symbol property to prevent fn from overwriting existing properties
  thisArg = thisArg || window // If this is not passed, the window object is bound by default
  thisArg[fn] = this //this points to the caller
  constresult = thisArg[fn](... args)// Execute the current function
  delete thisArg[fn]
  return result
}

foo.myCall(obj)
Copy the code

Handwritten apply

// Write apply by hand (args passes in an array). The principle is similar to call, except that the input parameters are different
Function.prototype.myApply = function (thisArg, args = []) {
  const fn = Symbol("fn")
  thisArg = thisArg || window
  thisArg[fn] = this
  // Even though apply() accepts an array, the arguments array is still expanded when the original function is called
  In contrast to native apply(), the function receives an expanded array of arguments
  constresult = thisArg[fn](... args)delete thisArg[fn]
  return result
}

foo.myApply(obj)
Copy the code

Write a bind

Function.prototype.myBind = function (thisArg, ... args) {
  let self = this // this here refers to thisArg(caller)
  let fnBound = function () {
    //this instanceof self ? This: thisArg determines whether it is a constructor or a normal function
    / / at the back of the args. Concat (Array) prototype. Slice. Call (the arguments)) is the use of function calls the curry is changed to obtain the incoming parameters
    self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))}// Inherits properties and methods from the stereotype
  fnBound.prototype = Object.create(self.prototype)
  // Returns the bound function
  return fnBound
}

// Through a normal function call
// foo.myBind(obj, 1, 2, 3)()

// call through the constructor
function fn(name, age) {
  this.test = "Test data"
}
fn.prototype.protoData = "Prototype data"
let fnBound = fn.myBind(obj, "Hammer king".18)
let newBind = new fnBound()
console.log(newBind.protoData) // "prototype data"

Copy the code

Another question about bind I saw earlier is included here

For foo.bind(A).bind(B).bind(C)

let obj = { a: 1 }
let obj2 = { a: 2 }
let obj3 = { a: 3 }
let obj4 = { a: 4 }

function foo() {
  console.log(this.a)
}

let boundFn = foo.bind(obj).bind(obj2).bind(obj3)
boundFn.call(obj4)  // Prints 1
boundFn.apply(obj4) // Prints 1
boundFn()  // Prints 1

Copy the code

From this we can see that bind is permanent and does not change its direction any further operations

The simulation implements a new operator

The simulation implements a new operator, passing in a constructor and arguments

function myNew(constructFn, ... args) {
  // Create a new object and inherit the constructor's Prototype property,
  // Attach obj to the prototype chain, equivalent to obj.__proto__ = constructfn. prototype
  let obj = Object.create(constructFn.prototype)

  // Execute the constructor to pass in the args argument, mainly for the assignment of this.name = name etc
  let res = constructFn.apply(obj, args)

  // Make sure the return value is an object
  return res instanceof Object ? res : obj
}

function Dog(name) {
  this.name = name

  this.woof = function () {
    console.log(Woof woof woof)}The constructor can return an object
  //return { a: 1 }
}

let dog = new Dog("O raccoon dog")
console.log(dog.name) / / o raccoon dog
dog.woof() / / auf

let dog2 = myNew(Dog, "Big dog")
console.log(dog2.name) / / big dogs
dog2.woof() / / auf
Copy the code

The throttle

Throttling can control the frequency of the event trigger, throttling just like small pipes, if without throttling, water will going out la la la la, but once the throttle valve, you can control the velocity of water, add throttling after the water from going into a tick tick, into our function is the ability to make it say in events trigger slow, For example, event triggering can only trigger once every second, which can improve performance.

function throttle(fn, wait) {
    let prev = new Date(a)return function() {
        let now = new Date(a)/* A new event is triggered when the difference between the next event and the initial event is greater than the wait time */
        if(now - prev > wait) {
            fn.apply(this.arguments)
            // Reset the initial trigger time
            prev = new Date()}}}Copy the code

Image stabilization

Anti shake is can limit the event can not be triggered many times within a certain period of time, for example, you crazy click the button, a fierce operation like a tiger, without anti shake it will follow you crazy up, crazy implementation of the trigger method. But once you add the anti-shake, no matter how many times you click, it will only execute on your last click. Anti – shake is often used in search box or scroll bar listening event processing, can improve performance.

function debounce(fn, wait = 50) {
    // Initialize a timer
    let timer
    return function() {
        // If the timer exists, clear it
        if(timer) {
            clearTimeout(timer)
        }
        / / reset the timer
        timer = setTimeout(() = > {
            // Bind the input parameter to the calling object
            fn.apply(this.arguments)
        }, wait)
    }
}
Copy the code

Deep copy and shallow copy

Shallow copy: As the name implies, the so-called shallow copy is to copy the attributes of an object at only one layer, excluding the reference type data in the object. When there are child objects, the child objects will affect each other. Modifying the copied child objects will also affect the original child objects

Deep copy: Deep copy copies an object and all its children. That is, properties in the children of the newly copied object do not affect the original object

Let’s define an object

let obj = {
  a: 1.b: 2.c: {
    d: 3.e: 4}}Copy the code

Implementing shallow copy

Use the Object. The assign ()

let obj2 = Object.assign({}, obj)
obj2.a = 111
obj2.c.e = 555
console.log(obj)
console.log(obj2)

Copy the code

Use the expansion operator

letobj2 = {... obj} obj2.a =111
obj2.c.e = 555
console.log(obj)
console.log(obj2)
Copy the code

Check the result and find that the data in the first layer object A does not affect each other, but the data in the child object C does

The same thing is true for shallow copies of arrays

Define an array

let arr = [1.2, { a: 3 }]
Copy the code

The use of Array. The prototype. Slice ()

let arr2 = arr.slice()
arr2[0] = 222
arr[2].a = 333
console.log(arr)
console.log(arr2)
Copy the code

Use the Array. The prototype. The concat ()

let arr2 = arr.concat()
arr2[0] = 222
arr[2].a = 333
console.log(arr)
console.log(arr2)
Copy the code

Using the expansion operator…

let arr2 = [...arr]
arr2[0] = 222
arr[2].a = 333
console.log(arr)
console.log(arr2)
Copy the code

They all end up printing the same thing

We can also write a shallow copy function that iterates through arrays and objects

Source: indicates the source input

Target: indicates the target output

function shallowCopy(source) {
    // the input parameter is an object
    let target = Array.isArray(source) ? [] : {}
    for(let key in source) {
        // Use hasOwnProperty to restrict loops to the object itself and not to iterate over properties on the prototype
        if(source.hasOwnProperty(key)) {
            target[key] = source[key]
        }
    }
    return target
}
Copy the code

Implementing deep copy

The simplest way to deep copy

let target = JSON.parse(JSON.stringify(source))

Copy the code

This method supports only object, array, String, number, true, false, and null. Other data types, such as functions, undefined, Date, and RegExp, are not supported. Data that it does not support is ignored.

Recursively

Shallow copy since it only copies the properties of one layer of objects, it is also possible to recursively call shallow copy when there are child objects

function deepCopy(source) {
  // The input parameter is an object
  let target = Array.isArray(source) ? [] : {}
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      // Here we do another level of judgment to see if there are any child attributes
      // We can also call the checkType function above, instead of writing two typeOf
      if (source[key] && typeofsource[key] ! = =null && typeof source[key] === "object")
      {
        target[key] = Array.isArray(source[key]) ? [] : {}
        // recursive call
        target[key] = deepCopy(source[key])
      } else {
        target[key] = source[key]
      }
    }
  }
  return target
}
Copy the code

Here deep copies of copies only simple data types, if is a process of complex data types are copied will be lost data, such as copy of the object contains regular expression or function, date, these will not be able to copy, if need to copy these requires us to go alone to judge each type, In addition, the recursive version will also have the problem of circular reference, such as obj. Obj = obj, so that the infinite loop will burst the stack, so we need to optimize

Let’s take a look at the optimized version

function deepCopy(source, cache = new Map(a)) {
  if (cache.has(source)) {
    // If there is already a value in the cache, return it directly
    return cache.get(source)
  }
  -> Function Array RegExp Date all belong to Object
  if (source instanceof Object) {
    let target
    if (source instanceof Array) {
      // Check the array
      target = []
    } else if (source instanceof Function) {
      // Check the function condition
      target = function () {
        return source.apply(this.arguments)}}else if (source instanceof RegExp) {
      // Determine the regular expression situation
      target = source
    } else if (source instanceof Date) {
      target = new Date(source)
    } else {
      // Common objects
      target = {}
    }
    // Cache the attributes and copied values
    cache.set(source, target)
    // Start making traversal recursive calls
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        target[key] = deepCopy(source[key], cache)
      }
    }
    return target
  } else {
    If it is not a complex data type, return it directly
    return source
  }
}
Copy the code

Let’s test it out here

let obj = {
  a: 1.b: undefined.c: null.d: Symbol(),
  e: new Date(),
  f: new RegExp("123"."ig"),
  g: function () {
    console.log("My name is Wang Dachu.")},h: [1.2.3].i: { a: 1.b: 2}},let obj2 = deepCopy(obj)
obj2.g = function () {
  console.log("My name is not Sledgehammer Wang.")
}
obj.g() // My name is Wang Dachu
obj2.g() // My name is not Wang Dachu
obj2.h[0] = 111
console.log(obj)
console.log(obj2)
Copy the code

We can see that this time our deep copy is more perfect, of course we can make further optimization, for example, the performance of the for in loop is lower, we can change to the while loop as traversal, we will not change here.

Event loop mechanism

In the browser environment

Macro tasks: including overall code script, setTimeout, setInterval, setImmediate, I/O operations, UI rendering, etc

Microtasks: Promise.then, MuationObserver

New Promise(resolve(console.log(‘1’))) is executed synchronously, and resolve is followed by.then.

__ in the browser environment: the sequence of __ events in the loop determines the order in which js code is executed. After entering the overall code (macro task), the first loop begins. Then perform all the microtasks. Then start from the macro task again, find one of the task queues to complete, and then execute all the microtasks. Then the macro task is put into the macro task queue. When the macro task queue is finished, the micro task queue is checked. When the micro task queue is empty, the next macro task is executed and the cycle repeats. Macro task -> micro task -> macro task -> micro task loop.

Let’s look at a couple more problems just to make sense of it

console.log(1);

setTimeout(() = > {
  console.log(2);
  new Promise((resolve) = > {
    console.log(3);
    resolve();
  }).then(() = > {
    console.log(4);
  });
});

new Promise((resolve) = > {
  console.log(5);
  resolve();
}).then(() = > {
  console.log(6);
});

setTimeout(() = > {
  console.log(7);
  new Promise((resolve) = > {
    console.log(8);
    resolve();
  }).then(() = > {
    console.log(9);
  });
});

console.log(10)
Copy the code

The first cycle: Look at the code from the top — > Print 1 (sync code), the first setTimeout enters the macro task queue for execution, then executes to the first new Promise, the contents of which are executed synchronously, print 5, then resolve, The code in.then is placed on the microtask queue to wait for execution, and the second setTimeout is placed on the macro task queue. Finally, print 10.

Print 1 -> 5 -> 10 after executing the script macro task. And then look at what happens in the queue

Macro task queue Microtask queue
setTimeout1 then1
setTimeout2

We find a microtask in the microtask queue and execute it.

Then1 prints 6, so 1 -> 5 -> 10 -> 6 is printed after the first loop

Second loop: execute setTimeout1 in the macro task queue, first execute the synchronization code inside, print 2 and 3, then enter the microtask queue

Macro task queue Microtask queue
setTimeout2 then2

Then it goes to the task in the microtask queue and prints 4. The second loop ends with 2 -> 3 -> 4

SetTimeout2 is executed in the macro task queue, and the synchronization code inside is printed 7 and 8. Then the microtask queue is entered

Macro task queue Microtask queue
then3

Then it goes to the task in the microtask queue and prints 9. The third loop ends with 7 -> 8 -> 9

The loop ends when both the macro and microtask queues are empty, and the final printing order is:

1 -> 5 -> 10 -> 6 -> 2 -> 3 -> 7 -> 8 -> 9

The results were the same in both the browser environment (Chrome 86) and Node V12.18.0

One final note: if you encounter async/await, you can say await as promise.then. Then we will consolidate the knowledge points

console.log('start')

async function async1() {
  await async2()
  console.log('async1')}async function async2() {
  console.log('async2')
}
async1()

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

new Promise(resolve= > {
  console.log('promise')
  resolve()
})
  .then(() = > {
    console.log('then1')
  })
  .then(() = > {
    console.log('then2')})console.log('end')
Copy the code

The contents of the async1 function in the above code can be viewed as async2() executing immediately, and then the contents of the. Then queue into the microtask

async function async1() {
  Promise.resolve(async2()).then(() = > {
     console.log('async1')})}Copy the code

Start -> async2 -> PROMISE -> end -> async1 -> then1-> then2-> setTimeout

Node environment (V12.18.0)

The event loop mechanism in the Node environment is a little different than in the browser

The node event cycle is divided into the following phases: Priority idle observer > I/O Observer > Check observer.

  • Idle Observer: process.nexttick
  • I/O Observer (polling) : Generic I/O callbacks, such as network, file, database I/O, etc
  • Check the mediate: setImmediate

In fact, in addition to the above three stages, there are several other stages, the details can be seen here, but we are mainly introduced to the above three stages

Let’s look at a bit of code comparing Process. nextTick to setImmediate

process.nextTick(() = > {
  console.log('1');
})

setImmediate(() = > {
  console.log('2');
  process.nextTick(() = > {
    console.log('3');
  })
})

process.nextTick(() = > {
  console.log('4');
})

setImmediate(() = > {
  console.log('5');
})

process.nextTick(() = > {
  console.log('6');
})
Copy the code

Unlike the macro and microtask queues in the browser environment, each observer queue in Node completes its own task before starting the next phase of execution.

Each phase has a FIFO queue to perform the callback. While each phase is special, typically, when an event loop enters a given phase, it will perform any operation specific to that phase and then execute the callbacks in that phase’s queue until the queue is exhausted or the maximum number of callbacks has been executed. When the queue is exhausted or the callback limit is reached, the event loop moves to the next phase, and so on.

We think of each observer as a queue, so let’s look at the queue order

Idle queue Check the queue
process1 setImemediate1
process2 setImemediate2
process3

Here the three processs are enqueued, and then the three processes are executed in the order 1 -> 4 -> 6.

The two setimemediates in the Check queue are then executed. NextTick is used to call process.nextTick callback, and then the second setImemediate is called, printing 5, so the printing order is 2 -> 3 -> 5

The final print order is 1 -> 4 -> 6 -> 2 -> 3 -> 5.

Process. nextTick is always executed before setImemediate. Why

Process.nexttick () is not technically part of the event loop. Instead, it will process the nextTickQueue(nextTick queue) after the current operation completes, regardless of the current phase of the event loop. Any time process.nexttick () is called in a given phase, all callbacks passed to process.nexttick () are resolved before the event loop continues.

So you can see why process.nextTick in setImemediate1 is executed before setImemediate2.

The second issue here concerns setTimeout and setImemediate

  • SetImmediate () is designed to execute the script once the current polling phase is complete.
  • SetTimeout () runs the script after the minimum threshold (ms).

If you run these two things in the main module, their execution order is uncertain (depending on the speed of the calculator), as in the following code

setImmediate(() = > {
  setImmediate(() = > {
    console.log('1')
    setImmediate(() = > {
      console.log('2')
    })
  })

  setImmediate(() = > {
    console.log('3')
    setImmediate(() = > {
      console.log('4')})})})setTimeout(() = > {
  console.log('timeout')},0);
Copy the code

If we execute it several times, we can see two different outputs:

timeout -> 1 -> 3 -> 2 -> 4

1 -> 3 -> timeout -> 2 -> 4

But why that is, we’ll explore that. Go to Github and search for the node source code. If you go to lib/internal/timer.js, you can see this code (line 164).

If the node setTimeout is not set, or is less than 1, or greater than TIMEOUT_MAX (2^31-1), it will be forced to set to 1ms. In other words, setTimeout(XXX,0) is the same as setTimeout(XXX,1).

So we can conclude that setTimeout is always executed before setImemediate!! If the setTimeout callback is not executed within 1ms, setImemediate will be executed first

setImmediate(() = > {
  setImmediate(() = > {
    console.log('1')
    setImmediate(() = > {
      console.log('2')
    })
  })

  setImmediate(() = > {
    console.log('3')
    setImmediate(() = > {
      console.log('4')})})})setTimeout(() = > {
  console.log('timeout')},0);

for (let i = 0; i < 10000; i++) { }
Copy the code

We add a for loop after setTimeout to verify that setTimeout is executed before setImemediate

SetTimeout -> 1 -> 3 -> 2 -> 4. SetTimeout executes the first callback to print timeout, and then setImemediate’s callback execution is blocked by the for loop. When the for loop ends, The callback in setImemediate just starts printing, so we can verify the result.

1 -> 3 -> timeout -> 2 -> 4

Execute this code on my computer (CPU is generation 10 I7) to change the time from 0 to 5

setImmediate(() = > {
  setImmediate(() = > {
    console.log('1')
    setImmediate(() = > {
      console.log('2')
    })
  })

  setImmediate(() = > {
    console.log('3')
    setImmediate(() = > {
      console.log('4')})})})setTimeout(() = > {
  console.log('timeout')},5);
Copy the code

1 -> 3 -> timeout -> 2 -> 4 = 1 -> 2 -> 4 = 1 -> 3 -> timeout -> 2 -> 4 = 0

(If changing a 0 to a 5 on your computer doesn’t work, try making the 5 larger to see if the CPU speed is affecting the execution order.)

Let’s take a look at one more piece of code to reinforce this knowledge

console.log('start');

async function async1() {
  await async2()
  console.log('async1')}async function async2() {
  console.log('async2')
}

async1()

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

setImmediate(() = > {
  console.log('immediate');
})

process.nextTick(() = > {
  console.log('nextTick');
})

new Promise(resolve= > {
  console.log('promise');
  resolve()
})
  .then(() = > {
    console.log('then');
  })

console.log('end');
Copy the code

The final print is: start -> async2 -> Promise -> end -> nextTick -> asynC1 -> THEN -> timeout -> immediate

One more note: Process. nextTick monopolizes a queue, and the process.nextTick queue takes precedence over the promise. then microtask queue.

 process.nextTick(() = > console.log(1)); 
 Promise.resolve().then(() = > console.log(2)); 
 process.nextTick(() = > console.log(3)); 
 Promise.resolve().then(() = > console.log(4)); 
 // Prints 1 -> 3 -> 2 -> 4
Copy the code

conclusion

In the browser environment, there are macro tasks and micro tasks

The main macro tasks are: Script main code, setTimeout, setInterval, and setImmediate

The main microtasks are promise. then and MuationObserver

First execute macro task queue -> micro task queue -> loop

In the Node environment, first execute the macro task queue -> process.nextTick queue -> microtask queue -> setTimeout -> setImemediate

Some programming topics included ⌨

Implement a sleep function

Consider from the perspective of Promise, async/await, generator, etc

// Method 1:
function sleep(time) {
    return new Promise(resolve= > {
        setTimeout(resolve, time)
    })
}

sleep(1000).then(() = > {
    console.log('Execute here after 1 second')})// Method 2:
Call the sleep method wrapped above with async await in the function
async foo() {
    await sleep(1000)
    console.log('This is going to print in a second.')
}
foo()

// Method 3:
/ / the generator
function* generatorSleep(time){
  yield new Promise(reslove= > {
      setTimeout(reslove, time)
  })
}

generatorSleep(1000).next().value.then(() = >{
  console.log('Generator implementation')})// Method 4:
// Call cb->callback via the callback function
function sleepCb(cb, time) {
    if(typeofcb ! = ='function') return
    setTimeout(cb, time)
}

function foo() {
    console.log('Print this callback in 1 second')
}
sleepCb(foo, 1000)
Copy the code

Implement a repeat function

You pass in a method, and you execute it every once in a while, n times

Const repeatFunc = repeat(console.log, 4, 2000); //repeatFunc("helloworld")

function repeat(fn, n, interval) {
  return (. args) = > {
    let timer
    let counter = 0
    timer = setInterval(() = > {
      counter++
      fn.apply(this, args)
      if (counter === n) {
        clearInterval(timer) } }, interval); }}const repeatFn = repeat(console.log, 4.2000)
repeatFn('helloworld')
Copy the code

Implement a function to convert underline naming to camel naming

function formatHump(str) {
  if (typeofstr ! = ="string") return false
  // Split STR into arrays
  str = str.split("_") // ["get", "element", "by", "id"]
  if (str.length > 1) {
    // The for loop starts at 1, because the first character of the array string does not need to be capitalized
    // Uppercase the first letter of each string in the array
    for (let i = 1; i < str.length; i++) {
      str[i] = str[i][0].toUpperCase() + str[i].substr(1)}// Concatenate the array back to the string
    return str.join("")}}console.log(formatHump("get_element_by_id"))  //getElementById
Copy the code

Implement an asynchronous Scheduler Scheduler with concurrency limits that runs up to two tasks simultaneously

// Asynchronous scheduler
class Scheduler {
  constructor(maxNum) {
    // Queue of tasks waiting to be executed
    this.taskList = []
    // Number of current tasks
    this.count = 0
    // Maximum number of tasks
    this.maxNum = maxNum
  }
    
  async add(promiseCreator) {
     // When the current number of tasks exceeds the maximum number, the tasks are added to the queue for execution
    if (this.count >= this.maxNum) {
      await new Promise(resolve= > {
        this.taskList.push(resolve)
      })
    }
    this.count++
    const result = await promiseCreator()
    this.count--
    // When other tasks are completed, the queue is dequeued and executed
    if (this.taskList.length > 0) {
      this.taskList.shift()()
    }
    return result
  }
}

const timeout = time= > {
  return new Promise(resolve= > {
    setTimeout(resolve, time)
  })
}

const scheduler = new Scheduler(2)
const addTask = (time, value) = > {
  scheduler.add(() = > {
    return timeout(time).then(() = > {
      console.log(value)
    })
  })
}

addTask(1000."1")
addTask(500."2")
addTask(300."3")
addTask(400."4")

2 -> 3 ->1 -> 4
// Start tasks 1 and 2 are queued
// At 500ms, 2 completes, output 2, and task 3 enters the queue
// At 800ms, 3 completes, output 3, and task 4 enters the queue
// At 1000ms, 1 is complete and 1 is output
// at 1200ms, 4 is complete, output 4
Copy the code

More topics will be added in the future… (The liver doesn’t move)

Other explanations 🥧

Processes and threads

Multithreading can process tasks in parallel, but threads cannot exist alone and must be started and managed by processes. So the process is the father, the thread is the son, and one father can have many sons. Here is a picture of a thread. The outer box is a process and the inner one is a thread.

A process is a running instance of a program. When starting a program, the operating system creates a piece of memory for the program, which is used to store the code and data in the running and a main thread to execute tasks. This is a process.

Here are a few features:

  • An error in any thread of the process causes the entire process to crash

  • Threads can share data in a process

  • After a process is shut down, the operating system reclaims the memory occupied by the process

  • The contents of the processes are isolated from each other

The current Multi-process architecture of Google Chrome

The latest Google Browser includes: 1 main Browser process, 1 GPU process, 1 NetWork process, multiple rendering processes and multiple plug-in processes.

  • Browser process: responsible for page display, user interaction, sub-process management, storage and other functions
  • Render process: The core task is to turn HTML, CSS, and Js into web pages that can be interacted with by the user. The typography process Blink and Js’s V8 engine run in this process. By default, Chrome creates a new rendering process for each newly opened TAB (also affected by the same site, explained next). Each render process runs under a security sandbox,
  • GPU process: Achieves 3D CSS effect and draws UI interface of web page
  • Network process: responsible for loading network resources on a page
  • Plug-in process: responsible for running plug-ins, each plug-in corresponds to a thread. The main purpose of opening a separate thread is to prevent plugin crashes and affect the web page

What happens from entering a URL to displaying the page

Enter the URL address

  • After the user enters the address, the browser determines whether the input information is a search or a web address. If it is a search content, the default search engine synthesizes a new URL. If the entered URL meets the rules, the browser adds a valid URL based on the URL protocol. For example, if www.baidu.com is entered, www.baidu.com is added

Press enter

  • After the user enters content and presses Enter, the loading state is displayed in the navigation bar of the browser, and the content of the previous page is also displayed on the page. This is because the new page has no response data

Build request

  • The browser builds the request header and the request line information GET /index.html HTTP1.1The web process that sends the URL request to the browser via interprocess communication (IPC)

Find the cache

  • Before the network process receives the URL and initiates a network request, it searches the browser to see if there is a file to request. If there is a cached copy locally, it intercepts the request, returns the local resource copy, and directly ends the request and returns 200. If there is no local cache, the network request process is entered

The IP address and port number are available

  • The network process requests the DNS to return the IP address and port number corresponding to the domain name. If the DNS data caching service has cached the current domain name information, the DNS directly returns the cache information. Otherwise, the network process initiates a request to obtain the IP address and port number resolved based on the domain name. If there is no port number, the default port number is used. HTTP uses port 80 and HTTPS uses port 443. If the request is HTTPS, you need to establish a TLS connection.

The queuing mechanism of Chrome

  • A maximum of six TCP connections can be set up under a domain name. If more than six requests occur under a domain name, the remaining requests are queued until the ongoing requests are complete. If the number of current requests is less than six, TCP connections are directly set up

The initiating

  • The REQUEST is initiated after a TCP three-way handshake, and the HTTP request is sent down with a TCP header that includes the source port number, destination port number, and verification data integrity number
  • The network layer adds an IP header to the packet, including the source IP address and destination IP address, and continues the transmission to the lower layer
  • The underlying layer is transmitted to the destination server over the physical network

The destination server parses the request

  • After receiving the packet, the host network layer of the destination server parses the IP header, identifies the data part, and then unpackets the packet and sends it up to the transport layer
  • The transport layer obtains the data, parses it out of the TCP header, identifies the port, unpackets the data, and forwards the data to the application layer
  • Application layer HTTP parses the request header and request body. If a redirection is required, HTTP directly returns the HTTP response status code 301 (permanent redirection) or 302 (temporary redirection). At the same time, a redirect address is attached to the Location field in the request header, and the browser redirects the request. If it is not a redirect, the server uses theIf-None-MatchIf not, 304 is returned, telling the browser that the previous cache is still available. Otherwise, a new data 200 status code is returned, which the server can set in the response headerCache-control: max-age =2000(unit: seconds)To let the browser set the data cache time.
  • The data finally passes againApplication Layer - > Transport Layer - > Network Layer - > Bottom Layer - > Network Layer - > Transport Layer - > Application LayerTo return to the browser’s network process
  • After the data transfer is complete, TCP waves four times to disconnect the connection. If the browser or server adds theConnection:Keep-AliveThe field preserves the connection between the browser and the server, saving the time for re-establishing the connection.

The browser parses the response data

  • The browser network process gets the packet according to the response headerContent-TypeField determines the response data type and, if it is a byte stream type, passes the request to the download manager. If it is text/ HTML, notify the browser process that the document has been obtained for rendering.
  • Browser process received notice, to determine whether A page B from the page A opens and judge whether A and B is the same site (root domain name and agreement is belong to the same site), if meet the conditions for page B, and A Shared the same rendering process, if after open more page conforms to the same site rules, is A reuse page rendering process. If it is not the same site, create a separate renderer.
  • After receiving the confirmation message, the browser updates the browser page status, including the security status, URL address, forward and backward historical status, and updates the page.
  • The rendering process starts rendering the page, the HTML is parsed to generate the DOM tree, and the CSS styleSheets are converted to browser-readable styleSheets to calculate the STYLE of the DOM nodes
  • Create a layout tree to calculate element layout information
  • Layer the layout tree to generate a layered tree
  • Generate a drawing list for each layer and submit it to the compositing thread (which belongs to the renderer process), which divides the layer into blocks and converts the blocks into bitmaps in the rasterized thread pool
  • The composite thread sends the DrawQuad command to the browser process, which generates a page and displays it.

How is rubbish recycled

Memory space is divided into stacks and heaps. The data in the stack is destroyed whenever the execution context of a function is completed, such as the showName function

showName() {
    let name = King's Sledgehammer
    console.log(name)
}
Copy the code

When this function is executed, the JS engine creates its execution context and pushes it onto the call stack. When this function is finished, it is removed from the stack and memory is destroyed.

Destroying memory in the heap requires the help of the garbage collector. Let’s look at another function

function bar() {
    let obj = {name: King's Sledgehammer}  //obj is a reference to the memory in the heap that holds this object
}
Copy the code

When the bar function is executed, its execution context is pushed, and an object is created in the function. In this case, the variable obj is a reference type variable that points to a memory address in the heap where {name: ‘hammer ‘} is stored. The bar function destroys the obj variable when the execution context is removed from the stack, but the object obj is a reference to an address in the heap memory and remains in the heap undestroyed. Let’s take a look at how the V8 engine destroys garbage data in the heap.

Cenozoic and old generation

V8 will divide the heap into new generation and old generation areas. Handled by two different garbage collectors in the V8 engine.

  • New generation: Storage timeshortThe object. Generally, only 1 to 8 MB capacity is supported. From the V8 engineSecondary garbage collectorTo deal with
  • Old generation: storage survival timelongThe object. Support capacity than the New generationMuch more. From the V8 engineMain garbage collectorTo deal with
Garbage collector workflow
  • Mark space for live objects (still in use) and inactive objects (that can be reclaimed)
  • When the tag is complete, the memory occupied by the inactive objects is reclaimed
  • When objects are frequently reclaimed, discontinuous space (memory fragmentation) will appear in memory, and insufficient memory will occur when large contiguous memory needs to be allocated next time.(The primary garbage collector generates memory fragmentation, the secondary garbage collector does not)
Secondary garbage collector

Small objects are usually grouped into new areas. Although there is not much space in the new area, garbage collection is frequent. The Insane are adapted by the Scavenge algorithm, which is used to split the exploiture half the insane, half the insane. Newly added objects are stored in the object area and garbage collected when the object area is nearly full. Garbage removal process

  • Garbage tag
  • Copying live objects into the free area and arranging them in an orderly manner is equivalent to memory defragmentation
  • After the replication is completed, the identity of the object area and the idle area are exchanged. The original object area becomes the idle area, and the idle area becomes the object area. This role swap can be repeated indefinitely
  • If the surviving objects are garbage collected twice, they are moved to the old area (this process is calledTarget promotion strategy)
Main garbage collector

In addition to the new generation of promoted objects, some large objects will be directly assigned to the old area. The old area object has two characteristics: occupy large space and live long time.

The main garbage collector uses a mark-sweep algorithm for garbage collection: the tag starts with a set of root elements, traverses recursively, and the elements that can be reached are live objects. The elements that can’t be reached are garbage data, marked without external references. If an object has no variables that reference it, it is garbage collected.

Multiple marking of a memory address will produce a large number of discrete memory fragments, which need to be dealt with by another mark-defragmentation algorithm. The marking process is the same as that of mark-clearing algorithm. After marking, all living objects are moved to one end of the memory block, forming a continuous memory address.

The pause

Js runs on the main thread. Once the garbage collection algorithm is executed, the main thread will be blocked. After the garbage collection is finished, the JS script will resume running. If there is too much memory in the heap, it may take more than 1s to perform a full garbage collection, causing a page to stall. Total pauses occur in older generations, where objects are large and garbage collection takes a long time. To reduce this stalling, V8 uses the incremental tagging algorithm, which alternates garbage collection tagging and JS logic until the tagging phase is complete. This algorithm divides a large garbage collection into many small tasks and executes them interspersively, so that the page does not cause stalling.

The final summary

It took me five days to write so much content on and off. Every night after class, I went back to my dormitory to write. It was really difficult for me to write and temporarily my liver did not move. If there is any content is not correct, you are welcome to point out, I will correct in time! (Humble to learn from you)

Write the content above also refer to a lot of articles, all kinds of baidu check ah, give myself to feel weak knowledge to collect down, finally wrote the word long, the inside of the content is basic line written, code word is not easy, I hope you pass by the handsome boy and beauty who can finish a praise, Ball Ball you everybody!

Update:

The computer network section has done some summary, welcome to read

Network from transport layer to application layer

Refer to the article

Native JS soul asks

Front-end base pickup 90 questions

This time master deep copy thoroughly

Learn the Event Loop once

The node’s official website

Browser work and principle practice

Of course, in addition to these articles also include a variety of Baidu other articles, but also referred to other people’s interview questions, the final summary.

Done~