preface

This series is a supplement to my previous post on the front end of the high frequency interview, which tends to be in the middle and high end of the interview questions

The original

I believe that many of you are afraid of the most part of the interview is handwritten code. You may have heard the phrase “talk is cheap”, Show me the code is fine this article not only contains the front end of the classic handwritten source code interview questions also contains a lot of analysis and guidance in the hope of helping you better eat (welcome to comment not regularly updated supplementary questions library)

Pay attention to

All the hand-written source code implementations in this article are based on ES6 and do not want to use native implementations for the following reasons: on the one hand, there are too many native implementations on the Internet, on the other hand, we need to use MORE ES6 features for future programming to be more suitable for actual work

1 promise

Think first?

  1. What is a promise?

Asynchronous callback solution

  1. How do promises ensure that they execute asynchronously before the rest of the code is executed?

Use the THEN keyword and then accepts two arguments. The first argument (function) is executed after Promise Resolve. The second argument (function) is executed after Promise resolve

  1. Why is it possible to fire a function in a THEN after an asynchronous event has completed its callback?

Introduce event registration (register the events in the then code and then fire the event after the asynchronous execution has finished)

  1. How to guarantee a promise chain call in the form promise.then().then()

Each THEN returns a PROMISE object

  1. How do I know when an asynchronous event has completed or failed?

State representation required


The specific implementation is as follows

// Es6 class implementation is used here
class Mypromise {
  constructor(fn) {
    // Indicates the state
    this.state = "pending";
 // Represents the successful function for then registration  this.successFun = [];  // Represents the failed function registered by then  this.failFun = [];   let resolve = val= > {  // Keep state changes immutable (resolve and reject only trigger one)  if (this.state ! = ="pending") return;   // Successful trigger timing changes state while executing the callback event registered in then  this.state = "success";  // In order to ensure that then events are registered first, the Promise specification simulates asynchron here  setTimeout((a)= > {  // Execute all registration functions in the current event  this.successFun.forEach(item= > item.call(this, val));  });  };   let reject = err= > {  if (this.state ! = ="pending") return;  // The failure trigger time changes the state while executing the callback event registered in then  this.state = "fail";  // In order to ensure that then events are registered first, the Promise specification simulates asynchron here  setTimeout((a)= > {  this.failFun.forEach(item= > item.call(this, err));  });  };  // Call the function  try {  fn(resolve, reject);  } catch (error) {  reject(error);  }  }   // The instance method then   then(resolveCallback, rejectCallback) {  // Check whether the callback is a function  resolveCallback =  typeofresolveCallback ! = ="function" ? v= > v : resolveCallback;  rejectCallback =  typeofrejectCallback ! = ="function"  ? err= > {  throw err;  }  : rejectCallback;  // To keep the chained call continuing to return the promise  return new Mypromise((resolve, reject) = > {  // Register the callback with the successFun Event collection  this.successFun.push(val= > {  try {  // Execute the callback function  let x = resolveCallback(val);  // (the hardest part)  // If the result of the callback is normal then resolve out to the next then chain call. If it is a PROMISE object (which represents asynchron again) then call the THEN method of x and pass resolve and reject in until the asynchron is inside x The resolve passed in automatically executes when the execution is complete (the state is complete), controlling the sequence of chained calls  x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);  } catch (error) {  reject(error);  }  });   this.failFun.push(val= > {  try {  // Execute the callback function  let x = rejectCallback(val);  x instanceof Mypromise ? x.then(resolve, reject) : reject(x);  } catch (error) {  reject(error);  }  });  });  }  // Static methods  static all(promiseArr) {  let result = [];  // Declare a counter that incremented each promise returned  let count = 0  return new Mypromise((resolve, reject) = > {  for (let i = 0; i < promiseArr.length; i++) {  promiseArr[i].then(  res= > {  // We can't push the array directly because we need to control the sequence.  result[i] = res  count++  // Promise will be resolved only after all promises are successfully executed  if (count === promiseArr.length) {  resolve(result);  }  },  err => {  reject(err);  }  );  }  });  }  // Static methods  static race(promiseArr) {  return new Mypromise((resolve, reject) = > {  for (let i = 0; i < promiseArr.length; i++) {  promiseArr[i].then(  res= > {  // The promise array can be returned if any promise state changes  resolve(res);  },  err => {  reject(err);  }  );  }  });  } }  / / use let promise1 = new Mypromise((resolve, reject) = > {  setTimeout((a)= > {  resolve(123);  }, 2000); }); let promise2 = new Mypromise((resolve, reject) = > {  setTimeout((a)= > {  resolve(1234);  }, 1000); });  // Mypromise.all([promise1,promise2]).then(res=>{ // console.log(res); // })  // Mypromise.race([promise1, promise2]).then(res => { // console.log(res); // });  promise1  .then(  res= > {  console.log(res); // Output 123 after two seconds  return new Mypromise((resolve, reject) = > {  setTimeout((a)= > {  resolve("success");  }, 1000);  });  },  err => {  console.log(err);  }  )  .then(  res= > {  console.log(res); // Output success after one second  },  err => {  console.log(err);  }  ); Copy the code

Extension: How to cancel a Promise

Think first?

How can I cancel asynchrony that has been initiated?

The promise.race () method can be used to compete for promises whose state changes first to return. This can be done by wrapping a false Promise and the Promise to be initiated


The specific implementation is as follows

function wrap(pro) {
  let obj = {};
  // Construct a new promise to compete with
  let p1 = new Promise((resolve, reject) = > {
    obj.resolve = resolve;
 obj.reject = reject;  });   obj.promise = Promise.race([p1, pro]);  return obj; }  let testPro = new Promise((resolve, reject) = > {  setTimeout((a)= > {  resolve(123);  }, 1000); });  let wrapPro = wrap(testPro); wrapPro.promise.then(res= > {  console.log(res); }); wrapPro.resolve("Intercepted."); Copy the code

2 Anti-shake throttling

Think first?

  1. Shake and throttling difference

Stabilization is N seconds function will be executed only once, if it is triggered again N seconds to recalculate the delay time (an extreme example If the window scroll event added stabilization 2 s to perform again If you keep rolling Never stop The callback function that will never be able to perform)

Throttling is defined as a unit of time in which a function execution can be triggered at most once (or a scrolling event in which a callback is executed 2 seconds if you keep scrolling)

  1. How to guarantee anti – shaking

The event is delayed and needs to be cleared if it fires again within a specified time

  1. How does throttling guarantee

If it is triggered once per unit of time, it is no longer valid and can be controlled by a flag


The specific implementation is as follows

/ / image stabilization
function debounce(fn, delay=300) {
  // The default is 300 milliseconds
  let timer;
  return function() {
 var args = arguments;  if (timer) {  clearTimeout(timer);  }  timer = setTimeout((a)= > {  fn.apply(this, args); // Change this to refer to the object referred to by calling debounce  }, delay);  }; }  window.addEventListener(  "scroll". debance((a)= > {  console.log(111);  }, 1000) );  / / throttling // Method 1: set a flag function throttle(fn, delay) {  let flag = true;  return (a)= > {  if(! flag)return;  flag = false;  timer = setTimeout((a)= > {  fn();  flag = true;  }, delay);  }; } // Method 2: Use a timestamp function throttle(fn, delay) {  let startTime = new Date(a); return (a)= > {  let endTime = new Date(a); if (endTime - startTime >= delay) {  fn();  startTime = endTime;  } else {  return;  }  }; } window.addEventListener(  "scroll". throttle((a)= > {  console.log(111);  }, 1000) ); Copy the code

Shake throttling is a bit of performance optimization. For more performance optimization extensions, click Performance Optimization

3 EventEmitter(Publish subscription model — Simple version)

Think first?

  1. What is the publish-subscribe model

The publis-subscribe pattern is a one-to-many dependency between objects. When an object’s state changes, all objects that depend on it are notified of the change

  1. How to achieve one-to-many

Since there must be a one-to-many event center to dispatch events subscribers can register events (on) to the event center and publishers can emit events (emit) to the event center subscribers can also unsubscribe (OFF) or only subscribe (once)


The specific implementation is as follows

// Write a subscription model EventEmitter
class EventEmitter {
  constructor() {
    this.events = {};
  }
 // Implement the subscription  on(type, callBack) {  if (!this.events) this.events = Object.create(null);   if (!this.events[type]) {  this.events[type] = [callBack];  } else {  this.events[type].push(callBack);  }  }  // Delete the subscription  off(type, callBack) {  if (!this.events[type]) return;  this.events[type] = this.events[type].filter(item= > {  returnitem ! == callBack; });  }  // Execute the subscription event only once  once(type, callBack) {  function fn() {  callBack();  this.off(type, fn);  }  this.on(type, fn);  }  // Triggers the event emit(type, ... rest) { this.events[type] && this.events[type].forEach(fn= > fn.apply(this, rest));  } } // Use the following const event = new EventEmitter();  const handle = (. rest) = > {  console.log(rest); };  event.on("click", handle);  event.emit("click".1.2.3.4);  event.off("click", handle);  event.emit("click".1.2);  event.once("dbClick", () = > { console.log(123456); }); event.emit("dbClick"); event.emit("dbClick"); Copy the code

4 Call, apply, bind

Think first?

  1. Call usage

The first argument can change the calling function’s this to point to the second and subsequent arguments to be arguments to the passed function

let obj = {
  a: 1
};
function fn(name, age) {
  console.log(this.a); / / 1
 console.log(name);  console.log(age); } fn.call(obj, "I am lihua"."18"); Copy the code
  1. How do I change the direction of this

According to the method call on the this property object then the this inside the method points to that object

let obj = {
  a: 1.  fn(name, age) {
    console.log(this.a); / / 1
    console.log(name);
 console.log(age);  } };  obj.fn("I am lihua"."18"); Copy the code
  1. How do I get the indefinite parameters that are passed in

Using the es6… Args Residual parameter Acquisition method (REST)


The specific implementation is as follows

Function.prototype.myCall = function(context, ... args) {
  if(! context || context ===null) {
    context = window;
  }
  // Create a unique key value as the internal method name of the context we construct
 let fn = Symbol(a); context[fn] = this; // This refers to the function that called the call  // Executing the function and returning the result is equivalent to calling the method itself as the context passed in  returncontext[fn](... args);};  // Apply is the same except that the second argument is the array passed in Function.prototype.myApply = function(context, args) {  if(! context || context ===null) {  context = window;  }  // Create a unique key value as the internal method name of the context we construct  let fn = Symbol(a); context[fn] = this;  // Execute the function and return the result  returncontext[fn](... args);};  // Test call and apply let obj = {  a: 1 }; function fn(name, age) {  console.log(this.a);  console.log(name);  console.log(age); } fn.myCall(obj, "I am lihua"."18"); fn.myApply(obj, ["I am lihua"."18"]); let newFn = fn.myBind(obj, "I am lihua"."18"); newFn();   // The bind implementation is a little more complicated because it takes into account more cases and involves parameter merging (like function currying)  Function.prototype.myBind = function (context, ... args) {  if(! context || context ===null) {  context = window;  }  // Create a unique key value as the internal method name of the context we construct  let fn = Symbol(a); context[fn] = this;  let _this = this  // The bind case is a little more complicated  const result = function (. innerArgs) {  // In the first case, if we use the new operator as a constructor, we don't bind the passed this, but instead point this to the instantiated object  // In this case, the new operator refers to the result instance object and result inherits from _this  // this.__proto__ === result.prototype //this instanceof result =>true  // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true  if (this instanceof _this === true) {  // This is pointing to an instance of result  this[fn] = _this  this[fn](... [...args, ...innerArgs])// The es6 method is used to make bind support parameter merging  delete this[fn]  } else {  // If it's just a normal function call, it's easy to simply change this to refer to the context passed in context[fn](... [...args, ...innerArgs]); delete context[fn]  }  };  // If you bind to a constructor, you need to inherit the constructor stereotype properties and methods  // The first way to implement inheritance is to construct an intermediate function to implement inheritance  // let noFun = function () { }  // noFun.prototype = this.prototype  // result.prototype = new noFun()  // The second way to implement inheritance is to use object.create  result.prototype = Object.create(this.prototype)  return result }; // Test  function Person(name, age) {  console.log(name); //' I am the name passed in by the parameter '  console.log(age); //' I'm the age passed in '  console.log(this); // The constructor this points to the instance object } // The method of the constructor prototype Person.prototype.say = function() {  console.log(123); } let obj = {  objName: 'I'm the name obj passed in.'. objAge: 'I'm the age from OBj.' } // Normal function function normalFun(name, age) {  console.log(name); //' I am the name passed in by the parameter '  console.log(age); //' I'm the age passed in '  console.log(this); // The normal function this refers to the first argument to bind, which is obj in our example  console.log(this.objName); //' I am the name passed by obj '  console.log(this.objAge); //' I'm the age passed in by obj ' }  // Test first as constructor call // let bindFun = person.mybind (obj, 'I am the name passed in as an argument ') // let a = new bindFun(' I'm the age passed in ') // a.say() //123  // Retest as a normal function call let bindFun = normalFun.myBind(obj, 'I'm the name that was passed in.')  bindFun('I'm the age passed in.') Copy the code

If you are not familiar with JS prototype chains and inheritance, click portal

5 The new operator

Think first?

  1. What is the use of new?

Create an instance object from the constructor. The constructor this points to the created instance function and can use the constructor prototype properties and methods

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
 console.log(this.age); }; let p1 = new Person("lihua".18); console.log(p1.name); p1.say(); Copy the code
  1. How to implement this pointing change?

call apply

  1. How to implement the use of constructor prototype properties and methods

Prototype chain prototype inheritance


The specific implementation is as follows

function myNew(fn, ... args) {
  // 1. Create an instance object
  let obj = {};
  // 2. The generated instance object inherits the constructor prototype

 // Method 1 rude change to complete inheritance  obj.__proto__ = fn.prototype;   // Method 2 is implemented using object.create  // obj=Object.create(fn.prototype)   // 3. Change constructor this to point to an instance object   letresult = fn.call(obj, ... args);  // 4. If the result of the constructor execution is an object or function, then return that object or function  if ((result && typeof result === "object") | |typeof result === "function") {  return result;  }  // Otherwise, return to boj directly  return obj; }  // Test function Person(name, age) {  this.name = name;  this.age = age; } Person.prototype.say = function() {  console.log(this.age); }; let p1 = myNew(Person, "lihua".18); console.log(p1.name); console.log(p1); p1.say(); Copy the code

For a deeper understanding of the prototype chain, learning suggestions look at the portal

6 instanceof

Think first?

  1. Instanceof principles?

Whether the right object’s prototype is on the left object’s prototype chain

  1. How is traversing the prototype chain of the left object the key point?

While (true) iterates all the way to the end of the prototype chain and null equality means there is no return false


The specific implementation is as follows

function myInstanceof(left, right) {
  let leftProp = left.__proto__;
  let rightProp = right.prototype;
  // The loop is executed until the return function
  while (true) {
 // We go to the top of the prototype chain  if (leftProp === null) {  return false;  }  if (leftProp === rightProp) {  return true;  } else {  // Iterate over the value __proto__ for comparison  leftProp = leftProp.__proto__;  }  } } // Test let a = []; console.log(myInstanceof(a, Array)); Copy the code

7 deep copy

Think first?

  1. What is a deep copy?

Js for reference types of data replication, deep copy won’t copy a reference type reference, but will refer to a copies of all types of value and form a new reference types, such reference disorder problem will happen, so we can repeatedly use the same data, and don’t have to worry about data will play a conflict between

  1. How can I copy all of them?

The assignment is performed through recursive traversal until the data type is not a reference type


The specific implementation is as follows

// Define a deep copy function that accepts the target parameter
function deepClone(target) {
    // Define a variable
    let result;
    // If the object you want to deep copy is an object
 if (typeof target === 'object') {  // If it is an array  if (Array.isArray(target)) {  result = []; // Assign result to an array and perform the traversal  for (let i in target) {  // Recursively clone each item in the array  result.push(deepClone(target[i]))  }  // Check if the current value is null; Assign the value to null directly  } else if(target===null) {  result = null;  // If the current value is a RegExp object, assign it directly  } else if(target.constructor===RegExp) { result = target;  }else {  // If it is not a normal object, it will loop directly for in and recursively assign all the values of the object  result = {};  for (let i in target) {  result[i] = deepClone(target[i]);  }  }  // If it is not an object, then it is a basic data type  } else {  result = target;  }  // Return the final result  return result; } Copy the code

Extension: Simple deep copy using JSON methods

let targetObj = JSON.parse(JSON.stringify(sourceObj))
Copy the code

But it has limitations

  • You can’t copy undefined, function, RegExp, etc

  • Discard constructor for objects; all constructors will point to objects

  • Object with a circular reference