Recently, I have arranged the high frequency test questions on the front face and shared them with you to learn. If you have any questions, please correct me!

Note: 2021.5.21 has been updated to modify some errors in this article. Add a mind map, according to their own interview experience and the interview on the platform such as Niuker.com, the frequency of the interview questions will be roughly divided, can be targeted to review.

The following is a series of articles.

[1] “2021” high frequency front face test summary HTML article

[2] “2021” high-frequency front end of the CSS section

[3] 2021

[4] 2021

[5] “2021”

[6] “2021”

【7】 “2021” High-Frequency front end test questions summary

[8] “2021” high frequency front end test questions summary

[9] “2021” high frequency front end test question summary of computer network article

[10] “2021” high frequency front end test summary of browser principles

[11] the performance optimization of “2021” high frequency test paper summary

[12] “2021” high frequency front end of the handwritten code of the test summary

[13] “2021” high frequency front end test summary code output results

Sixth, this call/apply/bind

1. Understanding the this object

This is a property in the execution context that points to the object on which the method was last called. In real development, the reference to this can be determined by four invocation patterns.

  • The first is the function call pattern. When a function is called directly as a function that is not a property of an object, this refers to the global object.
  • The second is the method call pattern. If a function is called as a method on an object, this refers to that object.
  • The third is the constructor call pattern. If a function is called with new, a new object is created before the function is executed. This refers to the newly created object.
  • The fourth is the apply, Call, and bind invocation mode, where all three methods can display the point to this that specifies the calling function. The apply method takes two arguments: an object bound to this and an array of arguments. The call method receives arguments, the first being the object to which this is bound, and the rest of the arguments that are passed into the function execution. That is, when using the call() method, the arguments passed to the function must be listed one by one. The bind method passes an object and returns a new function that binds this to the passed object. The this point to this function does not change except when new is used.

Of the four, the constructor call pattern has the highest priority, followed by the Apply, call, and bind call pattern, then the method call pattern, and then the function call pattern.

2. What is the difference between call() and apply()?

They do exactly the same thing; the only difference is the form of the parameters passed in.

  • Apply takes two arguments. The first argument specifies the reference to this object in the function body. The second argument is a collection of objects with lower objects, which can be an array or a class array.
  • Call passes a variable number of arguments. As with apply, the first argument also refers to the “this” point in the function body. After the second argument, each argument is passed to the function in turn.

3. Implement the call, apply and bind functions

(1) Call function implementation steps:

  • Determine if the calling object is a function, even if it is defined on the prototype of the function, but it may be called by means such as call.
  • Determines if the incoming context object exists, and if it does not, sets it to window.
  • Processes the parameters passed in, intercepting all parameters after the first.
  • Take the function as a property of the context object.
  • Use the context object to call this method and save the returned result.
  • Delete the attributes you just added.
  • Return the result.
Function.prototype.myCall = function(context) {
  // Determine the calling object
  if (typeof this! = ="function") {
    console.error("type error");
  }
  // Get the parameter
  let args = [...arguments].slice(1),
    result = null;
  // Check whether the context was passed in. If not, set it to window
  context = context || window;
  // Sets the calling function as a method of an object
  context.fn = this;
  // Call the functionresult = context.fn(... args);// Delete the attribute
  delete context.fn;
  return result;
};
Copy the code

(2) Implement the apply function:

  • Determine if the calling object is a function, even if it is defined on the prototype of the function, but it may be called by means such as call.
  • Determines if the incoming context object exists, and if it does not, sets it to window.
  • Take the function as a property of the context object.
  • Determines whether the parameter value is passed in
  • Use the context object to call this method and save the returned result.
  • Delete the attributes you just added
  • Returns the result
Function.prototype.myApply = function(context) {
  // Determine whether the calling object is a function
  if (typeof this! = ="function") {
    throw new TypeError("Error");
  }
  let result = null;
  // Check whether the context exists, and if not, window
  context = context || window;
  // Sets functions as methods of objects
  context.fn = this;
  // Call the method
  if (arguments[1]) { result = context.fn(... arguments[1]);
  } else {
    result = context.fn();
  }
  // Delete the attribute
  delete context.fn;
  return result;
};
Copy the code

(3) Bind function

  • Determine if the calling object is a function, even if it is defined on the prototype of the function, but it may be called by means such as call.
  • Saves a reference to the current function and gets the values of the remaining parameters passed in.
  • Create a function return
  • If you use apply internally to bind a function call, you need to determine if the function is a constructor. In this case, you need to pass the current function’s this to the apply call, and in other cases, you need to pass the specified context object.
Function.prototype.myBind = function(context) {
  // Determine whether the calling object is a function
  if (typeof this! = ="function") {
    throw new TypeError("Error");
  }
  // Get the parameter
  var args = [...arguments].slice(1),
    fn = this;
  return function Fn() {
    // Pass in different binding values depending on how the call is made
    return fn.apply(
      this instanceof Fn ? this: context, args.concat(... arguments) ); }; };Copy the code

7. Asynchronous programming

1. How to implement asynchronous programming?

Asynchronous mechanisms in JavaScript can be divided into the following types:

  • One of the disadvantages of using callbacks is that when multiple callbacks are nested, the code coupling between the two levels of callbacks is too high, making the code less maintainable.
  • The way Promise is used to make nested callbacks appear as chained calls. Using this method, however, can sometimes result in chained calls to multiple then calls, which can cause the code to be semantically ambiguous.
  • In the form of a generator, it can transfer the execution weight of a function out during its execution and back outside of the function. When an asynchronous function is executed, the function execution authority is transferred out, and the function execution authority is transferred back when the asynchronous function is finished. Therefore, the way in which asynchronous operations are performed within the generator can be written in a synchronous order. The problem with this approach is when to transfer control back to the function, so you need to have a mechanism to automate the execution of the generator, such as a CO module.
  • Async is an automatic syntax sugar implemented by a generator and promise. It has an internal executor. When the function is executed inside an await statement, if the statement returns an await object, The function will wait until the promise object’s state changes to resolve before continuing. So asynchronous logic can be written in a synchronous order, and this function can be executed automatically.

2. The difference between setTimeout, Promise, Async/Await

SetTimeout (1)

console.log('script start')	//1. Print script start
setTimeout(function(){
    console.log('settimeout')	// 4. Print setTimeout
})	// 2. Call the setTimeout function and define the callback function to execute when it completes
console.log('script end')	//3. Print script start
// Output sequence: script start->script end-> setTimeout
Copy the code

(2) the Promise

The Promise itself is a synchronous instantexecution function. When a resolve or reject operation is executed in an executor, it executes then/catch first. When the main stack is complete, it calls the resolve/reject method. When you print P, you print the return result, a Promise instance.

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')})setTimeout(function(){
    console.log('settimeout')})console.log('script end')
// Output sequence: script start->promise1->promise1 end->script end->promise2-> setTimeout
Copy the code

When the JS main thread executes on a Promise object:

  • The callback to promise1.then() is a task
  • Promise1 is resolved or rejected: this task will be placed in the microtask queue for the current session of the event loop
  • Promise1 is pending: the task is placed in the Microtask queue at some (possibly next) turn in the future of the event loop
  • The callback to setTimeout is also a task and will be placed in the MacroTask Queue even if it is 0ms

(3) the async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')}async function async2(){
    console.log('async2')}console.log('script start');
async1();
console.log('script end')
// Output sequence: script start->async1 start->async2->script end->async1 end
Copy the code

The async function returns an await object. When the function executes, it returns an await object and waits until the async operation is complete before executing the following statement in the function body. Async: async: async: async: async: async

Such as:

async function func1() {
    return 1
}
console.log(func1())
Copy the code

The result of running func1 is essentially a Promise object. Therefore, you can also use THEN to process subsequent logic.

func1().then(res= > {
    console.log(res);  / / 30
})
Copy the code

Await means to wait. Async must wait until the async function has completed the execution of the await and has returned a result (a Promise object) before continuing with the following code. Await implements the effect of synchronization by returning a Promise object.

3. Understanding Promise

Promise is a solution to asynchronous programming, it is an object that can get messages for asynchronous operations, his appearance greatly improves the asynchronous programming dilemma, avoids the hell callback, it is more reasonable and more powerful than the traditional solution callback functions and events.

A Promise is simply a container that holds the result of an event (usually an asynchronous operation) that will end in the future. Syntactically, a Promise is an object from which to get messages for asynchronous operations. Promise provides a uniform API so that various asynchronous operations can be handled in the same way.

(1) An instance of a Promise has three states:

  • Pending (in progress)
  • Resolved
  • I have never Rejected it.

When a task is given to a promise, it is in the Pending state. When the task is completed, it is in the Resolved state and when it is not completed, it is Rejected.

(2) An instance of a Promise has two processes:

  • Fulfilled: fulfilled -> fulfilled: fulfilled
  • Pending -> rejected: rejected

Note: You can never change the state once you change from the progress state to another state.

Features of Promise:

  • The state of an object is unaffected. The Promise object represents an asynchronous operation with three states,pending(In progress),fulfilled(succeeded),rejected(Failed). Only the result of an asynchronous operation can determine the current state, and no other operation can change it, which is where the name promise comes from –“commitment“;
  • Once the state changes it doesn’t change again, it can happen any time. The state of a PROMISE object can change in only two ways: frompendingintofulfilled, frompendingintorejected. This is calledresolved(has been finalized). If you add a callback to the Promise object if the change has already occurred, you get the same result immediately. This is completely different from events, which have the characteristic that if you miss it, you can’t listen again.

Promise’s shortcomings:

  • You cannot cancel a Promise, it is executed as soon as it is created, and you cannot cancel it in mid-stream.
  • If the callback function is not set, the errors thrown by the Promise internally are not reflected externally.
  • When you are in the Pending state, you have no way of knowing where you are (just started or about to finish).

Conclusion: Promise objects are a solution to asynchronous programming that was first proposed by the community. Promise is a constructor that takes a function as an argument and returns a Promise instance. A Promise instance has three states: Pending, Resolved, and Rejected, which represent ongoing, successful, and failed, respectively. The instance state can only change from pending to resolved or Rejected, and once the state changes, it becomes frozen and cannot be changed again.

The state change is done with the resolve() and reject() functions, which can be called after the asynchronous operation has finished to change the state of the Promise instance. Its prototype defines a THEN method that registers callbacks for the two state changes. This callback function is a microtask and is executed at the end of this round of the event loop.

Note: When a Promise is constructed, the code inside the constructor executes immediately

4. I Promise

(1) Create a Promise object

A Promise object represents an asynchronous operation and has three states: pending, fulfilled, and rejected.

The Promise constructor takes a function as an argument, which is resolve and Reject.

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else{ reject(error); }});Copy the code

New Promise() is normally used to create a Promise object, but promise.resolve and promise.reject can also be used:

  • Promise.resolve

The return value of promise.resolve (value) is also a Promise object. A.then call can be made to the returned value as follows:

Promise.resolve(11).then(function(value){
  console.log(value); // Print out 11
});
Copy the code

The resolve(11) code will make the Promise object enter the resolve state and pass the argument 11 to the onFulfilled function specified in the then.

Promise objects can be created using the new Promise form or promise.resolve (value) form.

  • Promise.reject

Promise.reject is also a shortcut to new Promise, which also creates a Promise object. The code is as follows:

Promise.reject(new Error"I was wrong, please forgive me!!" ));Copy the code

Is a simple form of the code new Promise:

new Promise(function(resolve,reject){
   reject(new Error("I was wrong!));
});
Copy the code

Use the resolve and reject methods:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks"); }}); };// Method call
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});
Copy the code

If true, then call the resolve() method on the Promise object and pass the arguments to the first function in the then function. So “Hello world” is printed, and if it’s false, the reject() method on the promise object is called, which goes into the second function of the then, which prints No thanks;

(2) Promise method

There are five common methods for promises: then(), catch(), all(), race(), finally. Let’s take a look at these methods.

  1. then()

The resolve function is called when the Promise executes something that meets the success criteria, and the reject function is called on failure. Once the Promise is created, how do you call it?

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
Copy the code

The THEN method can take two callback functions as arguments. The first callback is called when the Promise object’s state becomes Resolved and the second when the Promise object’s state becomes rejected. The second parameter can be omitted. The then method returns a new Promise instance (not the original Promise instance). So you can write chained, where the THEN method is followed by another THEN method.

When writing sequential asynchronous events that need to be serial, you can write:

let promise = new Promise((resolve,reject) = >{
    ajax('first').success(function(res){
        resolve(res);
    })
})
promise.then(res= >{
    return new Promise((resovle,reject) = >{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res= >{
    return new Promise((resovle,reject) = >{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res= >{})Copy the code

How do you write when the events to be written have no order or relationship? This can be solved using the all method.

2. catch()

In addition to the THEN method, a Promise object has a catch method that corresponds to the second argument of the THEN method, pointing to the reject callback. If an error occurs during the resolve callback, an exception is thrown. Instead of stopping, the catch method enters the catch method.

p.then((data) = > {
     console.log('resolved',data);
},(err) = > {
     console.log('rejected',err); }); p.then((data) = > {
    console.log('resolved',data);
}).catch((err) = > {
    console.log('rejected',err);
});
Copy the code

3. all()

The all method accomplishes parallel tasks by receiving an array, each item of which is a Promise object. When all the promises in the array are in the resolved state, the all method becomes in the resolved state, and if one of the promises is in the Rejected state, the all method becomes in the resolved state.

javascript
let promise1 = new Promise((resolve,reject) = >{
	setTimeout(() = >{
       resolve(1);
	},2000)});let promise2 = new Promise((resolve,reject) = >{
	setTimeout(() = >{
       resolve(2);
	},1000)});let promise3 = new Promise((resolve,reject) = >{
	setTimeout(() = >{
       resolve(3);
	},3000)});Promise.all([promise1,promise2,promise3]).then(res= >{
    console.log(res);
    // the result is: [1,2,3]
})
Copy the code

The result of the call to the all method succeeds when the arguments to the callback function are also an array that holds the values of each promise object’s resolve execution, in order.

(4) race ()

The race method, like all, takes an array of promises, but unlike all, returns the value of the Promise object directly after the first completed event has been executed. If the first PROMISE object becomes resolved, it becomes resolved; If the first promise becomes rejected, then the state becomes rejected.

let promise1 = new Promise((resolve,reject) = >{
	setTimeout(() = >{
       reject(1);
	},2000)});let promise2 = new Promise((resolve,reject) = >{
	setTimeout(() = >{
       resolve(2);
	},1000)});let promise3 = new Promise((resolve,reject) = >{
	setTimeout(() = >{
       resolve(3);
	},3000)});Promise.race([promise1,promise2,promise3]).then(res= >{
	console.log(res);
	// Result: 2
},rej= >{
    console.log(rej)};
)
Copy the code

So what does the RACE approach actually do? When you have to do something for a long time, you can use this method to solve the problem:

Promise.race([promise1,timeOutPromise(5000)]).then(res= >{})
Copy the code

5. finally()

The finally method is used to specify the action that will be performed regardless of the final state of the Promise object. This approach was introduced as a standard by ES2018.

promise
.then(result= >{...}). The catch (error= >{...}). Finally,() = > {···});
Copy the code

In the above code, the finally callback is executed after the then or catch callback, regardless of the final state of the promise.

Here is an example of a server that uses a Promise to process requests and then uses the finally method to shut down the server.

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);
Copy the code

The finally callback does not accept any arguments, which means there is no way to know whether the Promise state will be fulfilled or rejected. This indicates that the operations in the finally method should be state-independent and not depend on the result of the Promise. Finally is essentially a special case of the then method:

promise
.finally(() = > {
  / / statements
});
/ / is equivalent to
promise
.then(
  result= > {
    / / statements
    return result;
  },
  error= > {
    / / statements
    throwerror; });Copy the code

In the above code, if the finally method is not used, the same statement needs to be written once for both success and failure cases. With the finally method, you only need to write once.

5. What problem did Promise solve

I often encounter such A requirement in my work. For example, after I send A request A using Ajax, I get the data successfully, and then I need to send the data to the request B. Then you need to write code like this:

let fs = require('fs')
fs.readFile('./a.txt'.'utf8'.function(err,data){
  fs.readFile(data,'utf8'.function(err,data){
    fs.readFile(data,'utf8'.function(err,data){
      console.log(data)
    })
  })
})
Copy the code

The above code has the following disadvantages:

  • The latter request relies on the success of the previous request to pass the data down, resulting in nested Ajax requests and less intuitive code.
  • If the first and second requests do not need to pass parameters, then the second request also needs to be successful before the next step, in which case the code will also need to be written as above, making the code less intuitive.

After the Promise appears, the code looks like this:

let fs = require('fs')
function read(url){
  return new Promise((resolve,reject) = >{
    fs.readFile(url,'utf8'.function(error,data){
      error && reject(error)
      resolve(data)
    })
  })
}
read('./a.txt').then(data= >{
  return read(data) 
}).then(data= >{
  return read(data)  
}).then(data= >{
  console.log(data)
})
Copy the code

This makes the code look a lot cleaner and solves the hell callback problem.

6. Promise. All and Promise

(1) Promise.all Promise.all can wrap multiple Promise instances into a new Promise instance. Success returns an array of results, while failure returns the first reject state.

Promise.all passes an array, returns an array, and is mapped. Promise objects return values in the array in order, but note that the order in which they are executed is not in order, unless the iterable is empty.

Note that the array of successful results obtained by Promise.all is in the same order as the array received by promise. all, so that when you encounter scenarios where multiple requests are sent and data is fetched and used in the order of the request, promise. all can be used to solve the problem.

(2) the Promise. Race

Race ([p1, P2, p3]) returns a result that is faster in the promise.race ([P1, P2, p3]), regardless of whether the result itself is a success or a failure. When you have to do something for a long time, you can use this method to solve the problem:

Promise.race([promise1,timeOutPromise(5000)]).then(res= >{})
Copy the code

7. Understanding of async/await

Async /await is the syntax-sugar of the Generator. All the effects it can achieve can be implemented with the THEN chain. It was developed to optimize the THEN chain. Literally, async is short for “asynchronous” and await is waiting, so it makes sense that async is used to declare that a function is asynchronous and await is used to wait for an asynchronous method to complete. Of course, it is syntaxically mandatory that await functions only appear in asNYC functions. Let’s first look at what async functions return:

async function testAsy(){
   return 'hello world';
}
let result = testAsy(); 
console.log(result)
Copy the code

So the async function returns a Promise object. The async function (which contains function statements, function expressions, and Lambda expressions) returns a Promise object. If you return a direct object in a function, async wraps the direct object into a Promise object via promise.resolve ().

The async function returns a Promise object, so in the case that the outermost layer cannot retrieve the return value with await, of course the Promise object should be handled in the original way: then() chain, like this:

async function testAsy(){
   return 'hello world'
}
let result = testAsy() 
console.log(result)
result.then(v= >{
    console.log(v)   // hello world
})
Copy the code

What if async does not return a value? It’s easy to imagine that it will return promise.resolve (undefined).

Think of the Promise feature — there is no wait, so if async is executed without an await, it will execute immediately, return a Promise object, and never block subsequent statements. This is no different from a normal function that returns a Promise object.

Note: Promise.resolve(x) can be seen as a shorthand for new Promise(resolve => resolve(x)) and can be used to quickwrap literal objects or other objects to wrap them into a Promise instance.

I am awaiting the await.

I am awaiting the patent. In general, we think of await as waiting for an Async function to complete. However, the syntax says that await is an expression that evaluates to a Promise object or some other value (in other words, no special qualification).

Since async functions return a Promise object, await can be used to wait for the return value of an async function — this is also saying that await is waiting for the async function, but it is actually waiting for a return value. Note that await is not just used to wait for Promise objects; it can wait for the result of any expression, so it can actually be followed by ordinary function calls or direct quantities. So the following example works perfectly:

function getSomething() {
    return "something";
}
async function testAsync() {
    return Promise.resolve("hello async");
}
async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}
test();
Copy the code

The result of an await expression depends on what it is waiting for.

  • If it is not waiting for a Promise object, the result of the operation of the await expression is what it is waiting for.
  • If it waits for a Promise object and the await is busy, it blocks the code waiting for the Promise object to resolve and then gets the value of resolve as the result of the operation of the await expression.

Here’s an example:

function testAsy(x){
   return new Promise(resolve= >{setTimeout(() = > {
       resolve(x);
     }, 3000)})}async function testAwt(){    
  let result =  await testAsy('hello world');
  console.log(result);    // 3 seconds later, hello world
  console.log('cuger')   // After 3 seconds, cug appears
}
testAwt();
console.log('cug')  // Output cug immediately
Copy the code

This is why await must be used in async functions. Async function calls do not block; all of their internal blocking is wrapped in a Promise object and executed asynchronously. Await pauses the current async execution, so ‘cug’ is printed first, hello world’ and ‘cuger’ are 3 seconds later simultaneously.

9. Advantages of async/await

A single Promise chain does not discover the advantage of async/await, but it does if you have to deal with a THEN chain consisting of multiple Promises (it’s interesting that promises use then chains to solve the problem of multiple layers of callbacks, Now optimize it further with async/await.

Suppose a business is completed in several steps, each of which is asynchronous and depends on the results of the previous step. Still use setTimeout to simulate asynchronous operations:

/** * Pass n, indicating the time (milliseconds) for the function to execute * the result of execution is n + 200, which will be used in the next step */
function takeLongTime(n) {
    return new Promise(resolve= > {
        setTimeout(() = > resolve(n + 200), n);
    });
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
Copy the code

Now use the Promise method to implement these three steps:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2= > step2(time2))
        .then(time3= > step3(time3))
        .then(result= > {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
/ / doIt: 1507.251 ms
Copy the code

The output result is the parameter 700 + 200 = 900 of step3(). The doIt() sequence takes 300 + 500 + 700 = 1500 milliseconds, which is the same as the result of console.time()/ console.timeend () calculation.

If implemented with async/await, it would look like this:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();
Copy the code

The result is the same as the previous Promise implementation, but the code doesn’t look much cleaner, almost the same as the synchronized code

10. The advantages of async/await over Promise

  • The code is more synchronous to read, and promises get rid of callback hell, but then chaining calls also imposes additional reading burden
  • Promises are cumbersome to pass intermediate values, while async/await is almost synchronous and elegant
  • Error handling is friendly, async/await can use mature try/catch, and Promise error catching is very redundant
  • Debugging-friendly, Promise debugging is poor. You can’t set breakpoints in an arrow function that returns an expression because there is no code block. If you use the debugger’s step-over feature in a.then block, the debugger doesn’t go into subsequent.then blocks. Because the debugger can only track every step of the synchronized code.

11. How does async/await catch exceptions

async function fn(){
    try{
        let a = await Promise.reject('error')}catch(error){
        console.log(error)
    }
}
Copy the code

12. What’s the difference between concurrency and parallelism?

  • Concurrency is A macro concept. I have task A and task B, and I complete them by switching between tasks over A period of time. This is called concurrency.
  • Parallelism is A micro concept. If there are two cores in the CPU, I can do tasks A and B at the same time. Multitasking is called parallelization.

What is a callback function? What are the drawbacks of callback functions? How to solve the callback hell problem?

The following code is an example of a callback function:

ajax(url, () = > {
    // Processing logic
})
Copy the code

The Achilles heel of Callback functions is that they are easy to write Callback hell. Assuming multiple requests have dependencies, it might have the following code:

ajax(url, () = > {
    // Processing logic
    ajax(url1, () = > {
        // Processing logic
        ajax(url2, () = > {
            // Processing logic})})})Copy the code

The above code may seem unreadable and unmaintainable, but you can also write the functions separately:

function firstAjax() {
  ajax(url1, () = > {
    // Processing logic
    secondAjax()
  })
}
function secondAjax() {
  ajax(url2, () = > {
    // Processing logic
  })
}
ajax(url, () = > {
  // Processing logic
  firstAjax()
})
Copy the code

The above code looks good to read, but it doesn’t solve the underlying problem. The fundamental problem with callback hell is this:

  1. Nested functions are coupled, and if you make a change, the whole thing changes
  2. With many nested functions, it is difficult to handle errors

Of course, callbacks have several other disadvantages, such as the inability to catch an error using a try or catch, and the inability to return directly.

14. What are the characteristics of setTimeout, setInterval and requestAnimationFrame?

Asynchronous programming, of course, is not the timer, the common timer functions are setTimeout, setInterval, requestAnimationFrame. The most common one is setTimeout. Many people think that setTimeout is the time delay, so it should be the time after execution.

This is wrong, because JS is executed in a single thread, and if the previous code affects performance, the setTimeout will not be executed as scheduled. Of course, setTimeout can be corrected in code to make the timer relatively accurate:

let period = 60 * 1000 * 60 * 2
let startTime = new Date().getTime()
let count = 0
let end = new Date().getTime() + period
let interval = 1000
let currentInterval = interval
function loop() {
  count++
  // Elapsed time for code execution
  let offset = new Date().getTime() - (startTime + count * interval);
  let diff = end - new Date().getTime()
  let h = Math.floor(diff / (60 * 1000 * 60))
  let hdiff = diff % (60 * 1000 * 60)
  let m = Math.floor(hdiff / (60 * 1000))
  let mdiff = hdiff % (60 * 1000)
  let s = mdiff / (1000)
  let sCeil = Math.ceil(s)
  let sFloor = Math.floor(s)
  // Get the time for the next loop
  currentInterval = interval - offset 
  console.log(':'+h, ':'+m, '毫秒:'+s, 'Round up second:'+sCeil, Code execution time:+offset, 'Next cycle interval'+currentInterval) 
  setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)
Copy the code

Now let’s look at setInterval. This function actually does the same thing as setTimeout, except it executes a callback every once in a while.

In general, setInterval is not recommended. First, like setTimeout, there is no guarantee that the task will be executed at the expected time. Second, it has the problem of performing accumulation, as shown in the pseudocode below

function demo() {
  setInterval(function(){
    console.log(2)},1000)
  sleep(2000)
}
demo()
Copy the code

In the browser environment, if there is a time-consuming operation during the execution of the timer, multiple callbacks will be executed at the same time after the time-consuming operation has finished, which may cause performance problems.

If you need a loop timer, you can do it in requestAnimationFrame:

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () = > {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}
let a = 0
setInterval(timer= > {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)
Copy the code

First, requestAnimationFrame has its own function throttling function, which can be executed only once in 16.6 milliseconds (without losing frames), and the delay effect of this function is accurate. There are no other timer errors. Of course, you can also use this function to implement setTimeout.

Object oriented

1. How can objects be created?

Objects are usually created directly as literals, but this method of creating a large number of similar objects can result in a lot of duplicated code. But unlike ordinary object-oriented languages, JS had no concept of classes before ES6. However, functions can be used to simulate, resulting in reusable object creation methods, the common ones are as follows:

(1) The first is the factory pattern. The main working principle of the factory pattern is to use functions to encapsulate the details of creating objects, so as to achieve the purpose of reuse by calling functions. But it has a big problem with creating objects that cannot be associated with a type. It simply encapsulates reusable code without establishing a relationship between the object and the type.

(2) The second is the constructor pattern. Every function in js can be a constructor, as long as a function is called with new, then it can be called a constructor. The constructor first creates an object, then points the object’s prototype to the constructor’s prototype property, then points this in the execution context to the object, and finally executes the entire function. If the return value is not an object, the new object is returned. Because the value of this points to the newly created object, you can assign a value to the object using this. The advantage of the constructor pattern over the factory pattern is that the object created is linked to the constructor, so you can identify the type of the object through the prototype. Constructor exists a disadvantage is that caused unnecessary function object is created, because in js function is an object, so if the object attribute contains the function, so every time a new function object, wasted unnecessary memory space, because the function is all instances can be generic.

(3) The third mode is the prototype mode, because each function has a Prototype property. This property is an object that contains properties and methods shared by all instances created by the constructor. So you can use prototype objects to add common properties and methods and reuse your code. This approach solves the reuse problem of function objects compared to the constructor pattern. However, there are some problems with this pattern. One is that there is no way to initialize values by passing in parameters. The other is that if there is a value of a reference type such as Array, then all instances will share an object, and any change made by one instance to the value of the reference type will affect all instances.

(4) The fourth pattern is the combination of the constructor pattern and the prototype pattern, which is the most common way to create custom types. Because there are problems with using the constructor pattern and the prototype pattern separately, you can use the two patterns in combination, using the constructor to initialize object properties and using the prototype object to reuse function methods. This approach does a good job of addressing the disadvantages of using the two patterns alone, but one disadvantage is that it does not encapsulate the code well enough because it uses two different patterns.

(5) The fifth mode is the dynamic prototype mode, which moves the creation process of the prototype method assignment to the inside of the constructor. By judging whether the attribute exists, the effect of assigning a value to the prototype object can be realized only once when the function is called for the first time. This approach nicely encapsulates the blend mode above.

(6) The sixth pattern is the parasitic constructor pattern, which is implemented in the same way as the factory pattern. My understanding of this pattern is that it is mainly based on an existing type and extends the instantiated object when instantiated. This extends the object without modifying the original constructor. One drawback is that, like the factory pattern, object recognition is not possible.

2. What are the methods of object inheritance?

(1) The first method is to implement inheritance in the way of prototype chain. However, the disadvantage of this method is that data containing reference types will be shared by all instance objects, which is easy to cause confusion of modification. Also, you cannot pass parameters to a supertype when creating a subtype.

(2) The second method is to use the borrowed constructor method. This method is implemented by calling the supertype constructor in the subtype function. This method solves the disadvantages of not passing parameters to the supertype, but it has the problem of function method reuse. And the method subtypes defined by the supertype prototype are not accessible.

(3) The third approach is composite inheritance, which is a way of combining prototype chains and borrowed constructors. Inheritance of properties of types is achieved by borrowing constructors, and inheritance of methods is achieved by setting archetypes of subtypes to instances of supertypes. This approach solves the problem of using the two patterns separately, but since we prototyped the subtype using an instance of the supertype, we called the constructor of the superclass twice, resulting in a lot of unnecessary attributes in the subtype stereotype.

(4) The fourth way is the original type inheritance. The main idea of the original type inheritance is to create a new object based on the existing object. The principle of implementation is to pass an object into the function, and then return an object based on this object as the prototype. The idea of inheritance is not to create a new type, but to achieve a simple inheritance of an Object. The object.create () method defined in ES5 is the implementation of the original type inheritance. The drawbacks are the same as the prototype chain approach.

(5) The fifth way is parasitic inheritance. The idea of parasitic inheritance is to create a function that encapsulates the inheritance process by passing in an object, then making a copy of the object, then extending the object, and finally returning the object. This extension process can be understood as inheritance. The advantage of this inheritance is that it is implemented for a simple object, if the object is not of a custom type. The disadvantage is that there is no way to reuse functions.

(6) The sixth approach is parasitic composite inheritance, which has the disadvantage of using an instance of a supertype as a prototype for a subtype, resulting in the addition of unnecessary stereotype attributes. Parasitic combination inheritance uses a copy of the stereotype of the supertype as the stereotype of the subtype, thus avoiding the creation of unnecessary attributes.

Garbage collection and memory leaks

1. Garbage collection mechanism of the browser

(1) Concept of garbage collection

Garbage collection: When JavaScript code runs, memory space needs to be allocated to store variables and values. When variables do not participate in the run, the system needs to reclaim the occupied memory space, this is garbage collection.

Recycling mechanism:

  • Javascript has an automatic garbage collection mechanism that periodically frees the memory occupied by variables and objects that are no longer in use. The principle is to find variables that are no longer in use and then free the memory occupied by them.
  • There are two kinds of variables in JavaScript: local variables and global variables. The life cycle of global variables will continue to require page uninstallation; Local variables are declared in a function, and their life cycle starts from the execution of the function until the end of the function. During the process, local variables store their values in the heap or stack. When the function is finished, the local variables are no longer used, and their occupied space is freed.
  • However, when a local variable is used by an external function, one of the cases is a closure. After the function finishes, the variable outside the function still points to the local variable inside the function. The local variable is still in use, so it is not recycled.

(2) The way of garbage collection

There are two methods of garbage collection commonly used by browsers: tag cleanup and reference counting. 1) Mark clearance

  • Tag cleanup is a common garbage collection method in browsers. When a variable enters the execution environment, it is marked as “entering the environment.” Variables marked as “entering the environment” cannot be collected because they are in use. When a variable leaves the environment, it is marked as “out of the environment”, and variables marked as “out of the environment” are freed from memory.
  • When the garbage collector runs, it marks all variables stored in memory. It then removes the markup of variables in the environment and those referenced by variables in the environment. Variables tagged after that are considered to be ready to be deleted because they are no longer accessible to variables in the environment. At last. The garbage collector does the cleanup, destroying the tagged values and reclaiming the memory space they occupy.

2) Reference counting

  • Another garbage collection mechanism, which is used less often, is reference counting. The reference count keeps track of how many times each value is referenced. When a variable is declared and a reference type is assigned to it, the number of references to that value is 1. Conversely, if the variable containing the reference to this value takes another value, the number of references to that value is reduced by one. When the number of references goes to zero, the variable has no value, so the memory occupied by the variable will be freed the next time it is run during the payback period.
  • This method can causeA circular referenceFor example: obj1andobj2By referring to each other through properties, both objects are referenced 2 times. When loop counting is used, because after the function completes, both objects leave the scope and the function finishes,obj1andobj2They will continue to exist, so their number of references will never be zero, causing circular references.
function fun() {
    let obj1 = {};
    let obj2 = {};
    obj1.a = obj2; // Obj1 references obj2
    obj2.a = obj1; // Obj2 references obj1
}
Copy the code

In this case, manually free the memory occupied by variables:

obj1.a =  null
 obj2.a =  null
Copy the code

(3) Reduce garbage collection

Although browsers can do garbage collection automatically, when the code is more complex, garbage collection can be costly, so keep it to a minimum.

  • Optimizing arrays: The easiest way to empty an array is to assign it a value of [], but at the same time a new empty object is created. You can empty the array by setting the length of the array to zero.
  • rightobjectOptimize:Objects are reused as much as possible. For objects that are no longer used, they are set to NULL and recycled as soon as possible.
  • Function optimization: Function expressions in a loop, if reusable, should be placed outside the function.

2. What can cause memory leaks

Memory leaks can occur in four ways:

  • Unexpected global variable: a global variable is accidentally created by using an undeclared variable, leaving it in memory unrecyclable.
  • Forgotten timer or callback: setInterval timer is set and you forget to cancel it. If the loop function has a reference to an external variable, that variable is left in memory and cannot be reclaimed.
  • Detached from the DOM: Getting a reference to a DOM element that is later deleted and cannot be reclaimed because the reference to the element remains.
  • Closures: Improper use of closures so that some variables are left in memory.