2022 mandatory front-end handwritten interview questions

Video Explanation of interview questions (efficient learning): Enter learning

2, Title

1. Anti shake throttling

This is also a classic topic. First of all, we should know what is anti shake and what is throttling.

  • Anti shake: the event will only be triggered once for the last time in a period of time.
  • Throttling: an event that is triggered at an interval of time.
    If you really don't understand, you can go to the big guy's Demo address Anti shake throttle DEMO
// Anti shake
    function debounce(fn) {
      let timeout = null; 
      return function () {
        // If the event is triggered again, clear the timer and re time
        clearTimeout(timeout);
        timeout = setTimeout(() => {
          fn.apply(this, arguments);
        }, 500);
      };
    }
    
    // throttle
    function throttle(fn) {
      let flag = null; // Save a tag through a closure
      return function () {
        if (flag) return; // When the timer is not executed, the tag is always null
        flag = setTimeout(() => {
          fn.apply(this, arguments);
           // Finally, set the tag to null (key) after the setTimeout is executed
           // Indicates that the next cycle can be executed.
          flag = null;
        }, 500);
      };
    }
    
Copy code

This question is mainly to check the understanding of anti shake and throttling. Don't remember it backwards!

2. A regular question

It is required to write area code + 8 digits, or area code + special number: 10010 / 110, regular verification separated by a dash in the middle. The area code starts with three digits.

For example, 010-12345678

let reg = /^\d{3}-(\d{8}|10010|110)/g
 Copy code

This is relatively simple. You can do it if you are familiar with the basic usage of regular.

3. How to realize the function of a tag without using a tag

// Through window Open and location The href method can actually be implemented. 
 // Corresponding to the blank and self attributes of a tag respectively
 Copy code

4. Do not use the loop API to delete the element at the specified position in the array (for example, delete the third bit). The more you write, the better

The meaning of this question is an API that cannot be cycled (such as for filter).

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Method 1: slice operation array will change the original array 
arr.splice(2, 1)


// Method 2: slice intercepts the selected element and returns a new array without changing the original array
arr.slice(0, 2).concat(arr.slice(3,))

// Method 3 delete the element in the array and then eliminate it
delete arr[2]
arr.join("").replace("empty", "").split("")
Copy code

5. Deep copy

The difference between deep copy and shallow copy is

  • Shallow copy: for complex data types, shallow copy only assigns the reference address to the new object. Changing the value of the new object will change the value of the original object.
  • Deep copy: for complex data types, the address references are new after copying. Changing the value of the new object after copying will not affect the value of the original object.

Therefore, the key point lies in the processing of complex data types. I have written two methods here. The second one has partial performance improvement over the first one

const isObj = (val) => typeof val === "object" && val !== null;

// Writing method 1
function deepClone(obj) {
    // Use instanceof to judge whether the variable you want to copy is an array (if it is not an array, it is an object).

    // 1. Prepare the variable (new address) you want to return.
    const newObj = obj instanceof Array ? [] : {}; // Core code.

    // 2. Make copies; Simple data types only need assignment. If complex data types are encountered, enter again for deep copy until the found data is simple data type.
    for (const key in obj) {
        const item = obj[key];
        newObj[key] = isObj(item) ? deepClone(item) : item;
    }

    // 3. Return copied variables.
    return newObj;
}




// Writing method 2 uses the new feature of es6. WeakMap has better weak reference performance and supports Symbol
function deepClone2(obj, wMap = new WeakMap()) {
  if (isObj(obj)) {
    // Determine whether it is an object or an array
    let target = Array.isArray(obj) ? [] : {};

    // If this exists, return directly
    if (wMap.has(obj)) {
      return wMap.get(obj);
    }

    wMap.set(obj, target);

    // Traversal object
    Reflect.ownKeys(obj).forEach((item) => {
      // After getting the data, judge whether it is complex data or simple data. If it is complex data type, continue to call recursively
      target[item] = isObj(obj[item]) ? deepClone2(obj[item], wMap) : obj[item];
    });

    return target;
  } else {
    return obj;
  }
}
Copy code

The main solution of this problem is recursion plus judgment of data type.

If it is a complex data type, call your copy method again recursively until it is a simple data type, and then you can assign a value directly

6. Handwritten call bind apply

call bind apply is used to modify this point

  • The difference between call and apply lies in the parameter passing
  • bind differs in that it returns a function at the end.
// call
    Function.prototype.MyCall = function (context) {
      if (typeof this !== "function") {
        throw new Error('type error')
      }
      if (context === null || context === undefined) {
        // this value specified as null and undefined will automatically point to the global object (window in the browser)
        context = window
      } else {
        // this whose value is the original value (number, string, Boolean) will point to the instance object of the original value
        context = Object(context)
      }

      // Use Symbol to determine unique
      const fnSym = Symbol()

      //this point of the simulated object
      context[fnSym] = this

      // Get parameters
      const args = [...arguments].slice(1)

      //Bind parameters and execute functions
      const result = context[fnSym](...args) 

      //Clear the defined this
      delete context[fnSym]

      // Return results 
      return result
    } 
    
    
    // call if you can understand, apply is actually a matter of changing parameters
    // apply
    Function.prototype.MyApply = function (context) {
      if (typeof this !== "function") {
        throw new Error('type error')
      }

      if (context === null || context === undefined) {
        // this value specified as null and undefined will automatically point to the global object (window in the browser)
        context = window
      } else {
        // this whose value is the original value (number, string, Boolean) will point to the instance object of the original value
        context = Object(context) 
      }


      // Use Symbol to determine unique
      const fnSym = Symbol()
      //this point of the simulated object
      context[fnSym] = this

      // Get parameters
      const args = [...arguments][1]

      //Bind the parameters and execute the function. Since the input of apply is an array, it needs to be deconstructed
      const result = arguments.length > 1 ? context[fnSym](...args) : context[fnSym]()

      //Clear the defined this
      delete context[fnSym]

      // Return a result. / / clear the defined this
      return result
    }
    
    
    
    // bind
    Function.prototype.MyBind = function (context) {
      if (typeof this !== "function") {
        throw new Error('type error')
      }

      if (context === null || context === undefined) {
        // this value specified as null and undefined will automatically point to the global object (window in the browser)
        context = window
      } else {
        // this whose value is the original value (number, string, Boolean) will point to the instance object of the original value
        context = Object(context) 
      }

      //this point of the simulated object
      const self = this

      // Get parameters
      const args = [...arguments].slice(1)
        
      // Finally, return a function and bind this. Consider using new to call, and bind can pass parameters
      return function Fn(...newFnArgs) {
        if (this instanceof Fn) {
            return new self(...args, ...newFnArgs)
        }
            return self.apply(context, [...args, ...newFnArgs])
        }
    }
Copy code

7. Handwriting implementation inheritance

Here, I only implement two methods: parasitic composite inheritance before ES6 and class inheritance after ES6.

/**
    * es6 Previous parasitic combinatorial inheritance 
    */
    {
      function Parent(name) {
        this.name = name
        this.arr = [1, 2, 3]
      }

      Parent.prototype.say = () => {
        console.log('Hi');
      }

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

      //  The core code is passed through object Create creates a new object, and the child and parent classes are isolated
      // Object.create: create a new object and use the existing object to provide the information of the newly created object__ proto__ 
      Child.prototype = Object.create(Parent.prototype)
      Child.prototype.constructor = Child
    }
    
    
    
    /**
    *   es6 Inherit using keyword class
    */
     {
      class Parent {
        constructor(name) {
          this.name = name
          this.arr = [1, 2, 3]
        }
      }
      class Child extends Parent {
        constructor(name, age) {
          super(name)
          this.age = age
        }
      }
    }
Copy code

To add a little knowledge, the Class inheritance of ES6 uses parasitic combinatorial inheritance when converting into ES5 code through Babel.

There are many methods of inheritance. Just remember the above two basic methods!

8. Handwritten new operator

First, we need to know what happens when we new an object.

In fact, it is to generate an object internally, then attach your properties to the object, and finally return the object.

function myNew(fn, ...args) {
  // Create a new object based on the prototype chain
  let newObj = Object.create(fn.prototype)

  // Add the attribute to the new object and get the result of obj function
  let res = fn.call(newObj, ...args)

  // If the execution result has a return value and is an object, return the execution result; otherwise, return the newly created object
  return res && typeof res === 'object' ? res : newObj;
}
Copy code

9. js execution mechanism says the result and why

This question examines the task execution process of js and the understanding of macro tasks and micro tasks

console.log("start");

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

(async function foo() {
  console.log("async 1");

  await asyncFunction();

  console.log("async2");

})().then(console.log("foo.then"));

async function asyncFunction() {
  console.log("asyncFunction");

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

  new Promise((res) => {
    console.log("promise1");

    res("promise2");
  }).then(console.log);
}

console.log("end");
Copy code

Tips:

  1. The script tag is a macro task, so it is executed at the beginning
  2. async await the code after await will be put into the micro task queue

Start execution:

  • I first met console log("start"); Directly execute and print out start
  • Go down and put a setTimeout1 into the macro task queue
  • Execute the function foo immediately and print async 1
  • When await blocks the queue, execute the await function first
  • Execute asyncFunction function and print out asyncFunction
  • Encounter the second setTimeout2 and put it in the macro task queue
  • Execute new Promise immediately and print out promise 1
  • Execute the res ("promise 2") function call, which is promise then. Put into micro task queue
  • The asyncFunction function is executed, and the subsequent printing async2 will be placed in the micro task queue
  • Then print out the then method foo that executes the function immediately then
  • Finally, execute print end
  • The queue that starts executing the micro task prints out the first promise 2
  • Then print the second async2
  • After the micro task is executed, execute the macro task and print the first setTimeout1
  • Execute the second macro task to print setTimeout2
  • At this point, the function is completed

The painting is not good. Just understand the meaning 😭. See if your ideas and answers are consistent with this process

10. How to intercept the global Promise reject without setting the reject Processor

I didn't write this question. I first thought about trycatch, but this is not the overall situation.

After checking the data, it was found that the method above a window was used

// Using Try catch can only intercept the data in the try statement block
try {
  new Promise((resolve, reject) => {
    reject("WTF 123");
  });
} catch (e) {
  console.log("e", e);
  throw e;
}

// Use unhandledrejection to intercept global errors (this is right)
window.addEventListener("unhandledrejection", (event) => {
  event && event.preventDefault();
  console.log("event", event);
});
Copy code

11. Realize sleep by handwriting

I only realized this through one method, which I mentioned in the js execution process above. await means asynchronous blocking

Another method is the method I found on the Internet. It's a little tricky to achieve this by completely blocking the process

// The asynchronous method of promise and await is used to realize sleep
    {
      (async () => {
        console.log('start');
        await sleep(3000)
        console.log('end');

        function sleep(timer) {
          return new Promise(res => {
            setTimeout(() => {
              res()
            }, timer);
          })
        }
      })();
    }

    // The second method is to completely block the process to achieve sleep
    {
      (async () => {
        console.log('start');
        await sleep(3000)
        console.log('end');

        function sleep(delay) {
          let t = Date.now();
          while (Date.now() - t <= delay) {
            continue;
          }
        };
      })()
    }
Copy code

12. Implement add(1)(2) =3

This alone can be achieved through closures

I added a difficulty to this. How can I realize continuous call

// Answer to the question
   const add = (num1) => (num2)=> num2 + num1;
   
   
   // I made an enhanced version myself, which can call add(1)(2)(3)(4)(5) in an infinite chain
   function add(x) {
      // Storage and
      let sum = x;
       
      // Function calls are added, and the function itself is returned each time
      let tmp = function (y) {
        sum = sum + y;
        return tmp;
      };
      
      // The toString of the object must be a method in which the sum is returned
      tmp.toString = () => sum
      return tmp;
   }
   
   alert(add(1)(2)(3)(4)(5))
Copy code

The key to the implementation of infinite chain call is the toString method of the object: each object has a toString() method, which is automatically called when the object is represented as a text value or when an object is referenced in the expected string way.

That is, after I call it many times, their results will be stored in the sum variable in the add function. When I alert, add will automatically call the toString method to print the sum, that is, the final result

13. Completely independent data in two arrays

Is to find data that appears only once in two arrays

var a = [1, 2, 4], b = [1, 3, 8, 4]
const newArr = a.concat(b).filter((item, _, arr) => {
  return arr.indexOf(item) === arr.lastIndexOf(item)
})
Copy code

The final result is [2,3,8]. The principle is actually very simple: merge two arrays, and then find out whether the index appearing in the first array is consistent with the index appearing in the last array, so as to judge whether it is independent data.

14. Judge the complete square

It is to judge whether a number can be squared. For example, the square of 9 is 3, which is right. If you can't square it, it's wrong.

var fn = function (num) {
  return num ** 0.5 % 1 == 0
};
Copy code

The principle is to determine whether it is a positive integer after square

15. Execute the function, say the result and say why

function Foo() {
  getName = function () {
    console.log(1);
  };
  return this;
}

Foo.getName = function () {
  console.log(2);
}

Foo.prototype.getName = function () {
  console.log(3);
}

var getName = function () { 
  console.log(4);
}

function getName() {
  console.log(5)
}

Foo.getName();

getName();

Foo().getName()

getName();

new Foo.getName(); 

new Foo().getName()

new new Foo().getName()
Copy code

This question actually depends on your understanding of the relationship between scope

Execution result:

  • Execute Foo Getname(), executes the static method on the Foo function object. Print out 2
  • Executing getName() is the function of the executed getName variable. Print 4

    • Why is the executed variable getName here, not the function getName. This is due to the precompiling of js
    • js is precompiled before execution, and function promotion and variable promotion will be carried out
    • Therefore, both functions and variables are promoted, but the function declaration has the highest priority and will be promoted to the top of the current scope
    • When it is executed later, it will cause getName to be re assigned, and the function with the execution result of 4 will be assigned to the variable
  • Execute Foo () Getname(), call the getname method on the return value after Foo execution. After the Foo function is executed, the external getname function will be re assigned and this will be returned. That is to execute this getName. So I printed out 1
  • Execute getName(), because the function was re assigned in the previous step. So the result this time is the same as that last time, or is it 1
  • Execute new Foo getName(), this new is actually new, so the static method getName above Foo is 2. Of course, if you print this in this function, you will find that it points to a new object, that is, a new object from new

    • You can put foo Getname () as a whole, because here The priority of is higher than new
  • Execute new Foo() getName(), where the function executes new Foo() to return an object, and then calls the getName method on the object prototype, so the result is 3
  • Execute new foo() Getname(), which is the same as the result of the last time. There is no return value after the last function call, so it is meaningless when doing new. The final result is 3

16. The prototype calls the interview question, says the result and says why

function Foo() {
  Foo.a = function () {
    console.log(1);
  };
  this.a = function () {
    console.log(2);
  };
}

Foo.prototype.a = function () {
  console.log(4);
};

Function.prototype.a = function () {
  console.log(3);
};


Foo.a();

let obj = new Foo();
obj.a();
Foo.a();
Copy code

Execution result:

  • Execute Foo A (), Foo itself does not have the value of a at present, so it will pass__ proto__ Search, but

    So the output is 3
  • new instantiates the Foo generation object obj, and then calls obj A (), but a function is attached to this obj object inside the Foo function. So the result is 2. If the object is not assigned a internally, it will go to the prototype chain to find the a function and print 4
  • Execute Foo A (), in the previous step, Foo function is executed, and Foo itself is internally assigned with function a, so 1 will be printed this time

17. Array grouping is changed to subtraction

This question means that [5, [[4, 3], 2, 1]] becomes (5 - ((4 - 3) - 2 - 1)) and executes. And eval() cannot be used

Method 1: since we can't use eval, let's use new Function 🤭

Method 2: of course, method 1 is a little contrary to the meaning of the topic, so there is a second method

var newArr = [5, [[4, 3], 2, 1]]

    // 1. Trickery
    // Convert to string
    let newStringArr = `${JSON.stringify(newArr)}`
    // Cycle through parentheses and minus signs
    let fn = newStringArr.split("").map((el) => {
      switch (el) {
        case "[":
          return '('
        case "]":
          return ')'
        case ",":
          return '-'
        default:
          return el
      }
    }).join("")
    // Finally, you can call it through new Function!
    new Function("return " + fn)()
    
    
    // 2. Method 2 
    function run(arr) {
      return arr.reduce((pre, cur) => {
        let first = Array.isArray(pre) ? run(pre) : pre
        let last = Array.isArray(cur) ? run(cur) : cur
        return first - last
      })
    }
    run(nweArr)
Copy code
  • The principle of method 1 is very simple. It is transformed into a string, and the parentheses and minus signs are modified circularly for splicing. Finally, you can call it through new Function
  • Method 2 means to make a recursive call through reduce. If the left is not an array, the right can be subtracted, but if the right is an array, the right array must be subtracted first. It is also the priority of subtraction bracket operation

18. flat of handwriting array

const flat = function (arr, deep = 1) {
      // Declare a new array
      let result = []
      
      arr.forEach(item => {
        if (Array.isArray(item) && deep > 0) {
          // Hierarchical decline
          // Deep -- correction by the boss from the comment area: deep - 1
          // Use concat to link arrays  
          result = result.concat(flat(item, deep - 1))
        } else {
          result.push(item)
        }
      })
      return result
    }
Copy code
  • The principle is to generate a new array internally and traverse the original array
  • When there is an array in the original array and the level deep is greater than or equal to 1, recursion is performed. If this condition is not met, you can directly push the data to the new array
  • At the same time, recursion should first reduce the level, and then link the recursive array through concat
  • Finally, you can return this array

19. Convert array to tree

The top-level parent is - 1, and the other parents are the IDs of the upper level nodes

let arr = [
      { id: 0, name: '1', parent: -1, childNode: [] },
      { id: 1, name: '1', parent: 0, childNode: [] },
      { id: 99, name: '1-1', parent: 1, childNode: [] },
      { id: 111, name: '1-1-1', parent: 99, childNode: [] },
      { id: 66, name: '1-1-2', parent: 99, childNode: [] },
      { id: 1121, name: '1-1-2-1', parent: 112, childNode: [] },
      { id: 12, name: '1-2', parent: 1, childNode: [] },
      { id: 2, name: '2', parent: 0, childNode: [] },
      { id: 21, name: '2-1', parent: 2, childNode: [] },
      { id: 22, name: '2-2', parent: 2, childNode: [] },
      { id: 221, name: '2-2-1', parent: 22, childNode: [] },
      { id: 3, name: '3', parent: 0, childNode: [] },
      { id: 31, name: '3-1', parent: 3, childNode: [] },
      { id: 32, name: '3-2', parent: 3, childNode: [] }
    ]

    function arrToTree(arr, parentId) {
       // Judge whether it is a top-level node. If so, return. If not, judge whether it is the child node you are looking for
      const filterArr = arr.filter(item => {
        return parentId === undefined ? item.parent === -1 : item.parent === parentId
      })
       
      // Make a recursive call to add the child node to the child node of the parent node
      filterArr.map(item => {
        item.childNode = arrToTree(arr, item.id)
        return item
      })
       
      return filterArr
    }
    
    arrToTree(arr)
Copy code
  • This problem is also carried out by recursion. At the beginning, we will judge whether it is a top-level node
  • If yes, it returns directly. If not, it determines whether it is the child node to be added to the parent node
  • Then add nodes layer by layer
  • Finally, this object is returned

20. Merge arrays and sort to remove duplicates

I have two arrays. Merge them. Then, the logic of de duplication is that where the repetition times are more, I will leave it.

For example, in the following array, there are two numbers 5 on one side and three numbers 5 on the other half. Then I need to leave three numbers 5 and remove two numbers 5. Cycle back and forth, and the final results are sorted.

  • Array one: [1, 100, 0, 5, 1, 5]
  • Array two: [2, 5, 5, 5, 1, 3]
  • Final result: [0, 1, 1, 2, 3, 5, 5, 5, 100]
// Judge the most frequent occurrence
    function maxNum(item, arr) {
      let num = 0;
      arr.forEach(val => {
        item === val && num++
      })

      return num
    }

    function fn(arr1, arr2) {
      // Use the Map data type to record the number of times
      let obj = new Map();

      // Merge the array and find the maximum number of times, and store it in the Map data type with key value pairs
      [...arr1, ...arr2].forEach(item => {
        let hasNum = obj.get(item)
        let num = 1
        if (hasNum) {
          num = hasNum + 1
        }
        obj.set(item, num)
      })

      // Store the merged and de duplicated array
      let arr = []
      // Traverse the Map data type, and then directly push the most frequently to the new array
      for (const key of obj.keys()) {
        if (obj.get(key) > 1) {
          for (let index = 0; index < Math.max(maxNum(key, arr1), maxNum(key, arr2)); index++) {
            arr.push(key)
          }
        } else {
          arr.push(key)
        }
      }

    // Finally, sort
      return arr.sort((a, b) => a - b)
    }
Copy code
  • In fact, the idea of this problem is that I first combine the two arrays
  • It is stored in the Map data type in the form of key value pairs. The key is the data, and the value is the number of times the data appears
  • Generate a new array to store the merged array
  • Traverse the Map data type. If the number of occurrences of this data is greater than one, then find out who appears more frequently in the two arrays, and loop push the data with more occurrences into the new array. If the number of occurrences is equal to one, you can directly push it into the new array.
  • Finally, sort the array, and then return a new array.

Tags: Interview

Posted by miseleigh on Sun, 01 May 2022 17:34:15 +0300