[Front-end interview questions] 2023 front-end interview questions - coding chapter

There are always ups and downs in a person's life. It will not always rise like the rising sun, nor will it always be miserable. Repeated ups and downs are training for a person. Therefore, those who are floating above do not need to be proud; those who are sinking below do not need to be pessimistic. We must be frank and humble, optimistic and enterprising, and move forward. ——Konosuke Matsushita

Hello everyone, my name is Jiang Chen. In today's Internet environment, everyone must have felt it to some extent. In this impetuous society, only by constantly maintaining one's character can one perceive different gains and encourage each other.

A collection of the latest interview questions in 2023, so be prepared at all times.

This article was first published on WeChat public account: Wild Programmer Jiang Chen

Everyone is welcome to like, collect and follow

Article list

Implement a simple version of Promise

The following is a basic Promise implementation:

class MyPromise {
    
    
  constructor(executor) {
    
    
    this.status = 'pending';
    this.value = undefined;
    this.onResolveCallbacks = [];
    this.onRejectCallbacks = [];

    const resolve = (value) => {
    
    
      if (this.status === 'pending') {
    
    
        this.status = 'fulfilled';
        this.value = value;
        this.onResolveCallbacks.forEach((callback) => callback(this.value));
      }
    };

    const reject = (reason) => {
    
    
      if (this.status === 'pending') {
    
    
        this.status = 'rejected';
        this.value = reason;
        this.onRejectCallbacks.forEach((callback) => callback(this.value));
      }
    };

    try {
    
    
      executor(resolve, reject);
    } catch (error) {
    
    
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    
    
    if (this.status === 'fulfilled') {
    
    
      onFulfilled(this.value);
    } else if (this.status === 'rejected') {
    
    
      onRejected(this.value);
    } else {
    
    
      this.onResolveCallbacks.push(onFulfilled);
      this.onRejectCallbacks.push(onRejected);
    }
  }
}

// 示例用法
const promise = new MyPromise((resolve, reject) => {
    
    
  // 异步操作,比如请求数据
  setTimeout(() => {
    
    
    resolve('成功');
    // 或者 reject('失败');
  }, 1000);
});

promise.then(
  (value) => {
    
    
    console.log('成功:', value);
  },
  (reason) => {
    
    
    console.log('失败:', reason);
  }
);

This is a very basic Promise implementation, just for demonstration purposes. In actual applications, more details and error handling need to be considered. Modern JavaScript has Promises built into it, and there's usually no need to implement it manually.

Implement function throttling

Function throttling is a technique for controlling the frequency of function execution to ensure that the function is executed at most once within a certain time interval. The following is a simple JavaScript function throttling implementation:

function throttle(func, delay) {
    
    
  let timerId;
  let lastExecTime = 0;

  return function (...args) {
    
    
    const now = Date.now();
    if (now - lastExecTime >= delay) {
    
    
      func.apply(this, args);
      lastExecTime = now;
    } else {
    
    
      clearTimeout(timerId);
      timerId = setTimeout(() => {
    
    
        func.apply(this, args);
        lastExecTime = Date.now();
      }, delay);
    }
  };
}

The above throttlefunction accepts two parameters: functhe function to be throttled, delayand the time interval (in milliseconds) for execution.

Using this throttling function, you can wrap functions that need to be throttled to ensure that they are not executed frequently in a short period of time. For example:

// 原始函数,可能会频繁触发
function handleResize() {
    
    
  console.log('窗口大小改变了');
}

// 使用节流包装后的函数
const throttledResize = throttle(handleResize, 200); // 200毫秒的节流间隔

// 监听窗口大小改变事件,使用节流函数
window.addEventListener('resize', throttledResize);

Now, handleResizethe function will execute at most once every 200 milliseconds, no matter how often the window size changes. This helps reduce frequent function calls and improve performance.

Implement function anti-shake

Function anti-shake is a technology that controls the execution frequency of functions to ensure that the function is only executed once within a certain time interval. The following is a simple JavaScript function anti-shake implementation:

function debounce(func, delay) {
    
    
  let timerId;

  return function (...args) {
    
    
    clearTimeout(timerId);
    timerId = setTimeout(() => {
    
    
      func.apply(this, args);
    }, delay);
  };
}

The above debouncefunction accepts two parameters: functhe function to be anti-shake, delayand the waiting time interval (in milliseconds).

Using this debounce function, you can wrap functions that need to be debounced to ensure that they are only executed after a certain time interval. For example:

// 原始函数,可能会频繁触发
function handleInput(value) {
    
    
  console.log('输入值为:', value);
}

// 使用防抖包装后的函数
const debouncedInput = debounce(handleInput, 300); // 300毫秒的防抖间隔

// 监听输入事件,使用防抖函数
document.querySelector('input').addEventListener('input', (event) => {
    
    
  debouncedInput(event.target.value);
});

Functions will now handleInputexecute 300 milliseconds after the user stops typing, reducing frequent function calls and improving performance.

Implement the observer pattern

The Observer pattern is a design pattern in which a subject (the observer) maintains a list of observers and notifies the observers when the state changes. The following is a simple implementation of the observer pattern in JavaScript:

class Subject {
    
    
  constructor() {
    
    
    this.observers = [];
  }

  addObserver(observer) {
    
    
    this.observers.push(observer);
  }

  removeObserver(observer) {
    
    
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify(data) {
    
    
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
    
    
  constructor(name) {
    
    
    this.name = name;
  }

  update(data) {
    
    
    console.log(`${
      
      this.name} 收到更新,数据为:`, data);
  }
}

// 示例用法
const subject = new Subject();

const observer1 = new Observer('观察者1');
const observer2 = new Observer('观察者2');

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify('新数据更新了'); // 观察者1 收到更新,数据为: 新数据更新了
                               // 观察者2 收到更新,数据为: 新数据更新了

subject.removeObserver(observer1);

subject.notify('又有新数据更新了'); // 只有观察者2会收到更新

The above code creates a simple implementation of the Observer pattern, including a subject class Subjectand an observer class Observer. Topics can add and remove observers and notify all observers when state changes.

In the example, we create a topic subjectand add two observers observer1and observer2. When the topic state changes, it notifies all observers.

This is just a basic example; in practice, you may need a more complex implementation to meet your specific needs.

Implement publish-subscribe model

Subscriber pattern, also known as publish-subscribe pattern, is a design pattern in which a topic (publisher) maintains a list of subscribers and notifies all subscribers when an event occurs. The following is a simple implementation of the JavaScript subscriber pattern:

class Publisher {
    
    
  constructor() {
    
    
    this.subscribers = [];
  }

  subscribe(subscriber) {
    
    
    this.subscribers.push(subscriber);
  }

  unsubscribe(subscriber) {
    
    
    this.subscribers = this.subscribers.filter(sub => sub !== subscriber);
  }

  publish(eventData) {
    
    
    this.subscribers.forEach(subscriber => subscriber.notify(eventData));
  }
}

class Subscriber {
    
    
  constructor(name) {
    
    
    this.name = name;
  }

  notify(eventData) {
    
    
    console.log(`${
      
      this.name} 收到通知,事件数据为:`, eventData);
  }
}

// 示例用法
const publisher = new Publisher();

const subscriber1 = new Subscriber('订阅者1');
const subscriber2 = new Subscriber('订阅者2');

publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);

publisher.publish('新事件发生了'); // 订阅者1 收到通知,事件数据为: 新事件发生了
                                // 订阅者2 收到通知,事件数据为: 新事件发生了

publisher.unsubscribe(subscriber1);

publisher.publish('又有新事件发生了'); // 只有订阅者2会收到通知

In the above code, we created a simple subscriber pattern implementation, including a publisher class Publisherand a subscriber class Subscriber. Publishers can add and remove subscribers and notify all subscribers when events occur.

In the example, we create a publisher publisherand add two subscribers subscriber1and subscriber2. When a publisher publishes an event, it notifies all subscribers.

This is just a basic example; in practice, you can extend the subscriber pattern as needed to meet your specific needs.

Implement new keyword

To implement newthe basic functionality of the operator in JavaScript, you write a function that accepts a constructor and constructor arguments and returns a new object instance. Here is an example implementation:

function myNew(constructor, ...args) {
    
    
  // 创建一个新对象,并将其原型指向构造函数的原型
  const obj = Object.create(constructor.prototype);

  // 调用构造函数,将新对象绑定到构造函数的上下文中
  const result = constructor.apply(obj, args);

  // 如果构造函数返回的是一个对象,则返回该对象;否则返回新创建的对象
  return typeof result === 'object' ? result : obj;
}

You can then use myNewfunctions to simulate newthe behavior of the operator. For example:

function Person(name, age) {
    
    
  this.name = name;
  this.age = age;
}

// 使用 myNew 模拟 new 操作符
const person1 = myNew(Person, 'Alice', 30);
const person2 = myNew(Person, 'Bob', 25);

console.log(person1); // 输出: Person { name: 'Alice', age: 30 }
console.log(person2); // 输出: Person { name: 'Bob', age: 25 }

This myNewfunction first creates a new object objand then points the new object's prototype to the constructor constructor's prototype. Next, it calls the constructor and binds the new object to the constructor's context. Finally, it checks the return value of the constructor and returns that object if it is an object, otherwise it returns the newly created object.

This is a simple newmock implementation of the operator. In reality, newmore complex features such as prototype chains are involved, but this example can demonstrate the basic principles.

Implement DeepClone

Deep clone is an operation that not only copies the object itself, but also recursively copies all nested objects and properties within the object. The following is a simple JavaScript deep copy implementation example:

function deepClone(obj, hash = new WeakMap()) {
    
    
  // 如果是基本数据类型或 null,则直接返回
  if (obj === null || typeof obj !== 'object') {
    
    
    return obj;
  }

  // 如果已经拷贝过这个对象,则直接返回之前的拷贝结果,防止循环引用
  if (hash.has(obj)) {
    
    
    return hash.get(obj);
  }

  // 根据对象的类型创建新的对象
  const clone = Array.isArray(obj) ? [] : {
    
    };

  // 将新对象添加到哈希表
  hash.set(obj, clone);

  // 递归拷贝对象的属性
  for (const key in obj) {
    
    
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
    
    
      clone[key] = deepClone(obj[key], hash);
    }
  }

  return clone;
}

This deepClonefunction can deep copy complex data types including objects, arrays, and nested structures. It uses a hash table hashto prevent circular references and ensure that it does not fall into infinite recursion.

Example usage:

const originalObj = {
    
    
  name: 'John',
  age: 30,
  address: {
    
    
    street: '123 Main St',
    city: 'New York'
  }
};

const clonedObj = deepClone(originalObj);

console.log(clonedObj); // 输出深拷贝后的对象
console.log(originalObj === clonedObj); // 输出 false,说明是不同的对象

Please note that this is just a simple deep copy implementation example, and actual applications may require more complex processing to deal with various data types and situations.

Implement function Curry

Function currying is a technique that converts a function that accepts multiple parameters into a series of functions that accepts a single parameter. The following is a simple implementation example of JavaScript function currying:

function curry(fn) {
    
    
  return function curried(...args) {
    
    
    if (args.length >= fn.length) {
    
    
      return fn.apply(this, args);
    } else {
    
    
      return function (...moreArgs) {
    
    
        return curried.apply(this, args.concat(moreArgs));
      };
    }
  };
}

This curryfunction takes a function fnand returns a curried function. When the curried function is called, it checks whether the number of arguments passed in is sufficient to execute the original function fn. If there are enough parameters, it will call it directly fn; if there are not enough parameters, it will return a new function, wait for more parameters to be passed in, and continue to append parameters until there are enough parameters.

Example usage:

function add(a, b, c) {
    
    
  return a + b + c;
}

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6

In the example, we first use currythe function to addcurry the function, which can then be called in a variety of ways curriedAddto implement the addition operation.

This is just a simple implementation example of function currying. In actual applications, you may need more complex processing to deal with different functions and parameters.

Implement Call

callis a method used in JavaScript to call a function, which allows you to specify thisvalues ​​inside the function and pass parameters. The following is a simple callmock implementation of a JavaScript method:

Function.prototype.myCall = function (context, ...args) {
    
    
  // 如果没有传递上下文对象,则使用全局对象(浏览器环境下为 window)
  context = context || globalThis;

  // 将当前函数作为上下文对象的一个属性
  const uniqueKey = Symbol('uniqueKey');
  context[uniqueKey] = this;

  // 调用函数,并传递参数
  const result = context[uniqueKey](...args);

  // 删除临时属性
  delete context[uniqueKey];

  return result;
};

This simulated myCallmethod can be added to Function.prototypeso that all functions can call it. It accepts a context object contextand a series of parameters args.

Example usage:

function greet(greeting) {
    
    
  console.log(`${
      
      greeting}, ${
      
      this.name}`);
}

const person = {
    
     name: 'Alice' };

// 使用 myCall 来调用 greet 函数,并指定上下文对象为 person
greet.myCall(person, 'Hello'); // 输出: Hello, Alice

In the example, we myCallcall greetthe function through the method and specify personthe object as the context object, which makes the object thispointed to inside the function person.

Please note that this is just a simple callmethod mock implementation, the actual callmethod can also handle many more parameters and special cases.

Implement array flattening

In JavaScript, you can flatten an array using recursion or looping. Here are some ways to flatten an array:

Recursive method:

function flattenArray(arr) {
    
    
  let result = [];

  for (let i = 0; i < arr.length; i++) {
    
    
    if (Array.isArray(arr[i])) {
    
    
      // 如果当前元素是数组,递归拍平
      result = result.concat(flattenArray(arr[i]));
    } else {
    
    
      // 如果不是数组,直接添加到结果数组中
      result.push(arr[i]);
    }
  }

  return result;
}

const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]

How to use reduce:

function flattenArray(arr) {
    
    
  return arr.reduce(function (flat, toFlatten) {
    
    
    return flat.concat(Array.isArray(toFlatten) ? flattenArray(toFlatten) : toFlatten);
  }, []);
}

const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]

Using ES6 Array.flat:

const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = nestedArray.flat(Infinity);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]

All of the above methods can flatten nested arrays into a one-dimensional array. Which method you choose depends on your project needs and compatibility requirements. If your environment supports the ES6 Array.flatapproach, that's the easiest way. If you need to be compatible with older environments, you can use recursion or reducemethods.

Encapsulating Hooks timer

To encapsulate a custom Hooks timer that can be shared among multiple components, you can create a useTimercustom Hook called. Here is an example:

import {
    
     useState, useEffect } from 'react';

function useTimer(initialCount = 0, interval = 1000) {
    
    
  const [count, setCount] = useState(initialCount);

  useEffect(() => {
    
    
    const timer = setInterval(() => {
    
    
      setCount((prevCount) => prevCount + 1);
    }, interval);

    // 在组件卸载时清除定时器
    return () => {
    
    
      clearInterval(timer);
    };
  }, [interval]);

  return count;
}

export default useTimer;

This useTimercustom Hooks accepts two parameters: initialCount(initial count value, default is 0) and interval(timer interval, default is 1000 milliseconds). It returns a status variable representing the timer count value count.

You can use it in multiple components useTimerto create timers. Here is an example:

import React from 'react';
import useTimer from './useTimer'; // 导入自定义Hooks

function TimerComponent() {
    
    
  const count = useTimer(); // 使用自定义Hooks创建定时器

  return (
    <div>
      <h1>定时器示例</h1>
      <p>计数:{
    
    count}</p>
    </div>
  );
}

export default TimerComponent;

In the above example, we imported custom Hooks useTimerand then TimerComponentused it in the component to create a timer. Each used useTimercomponent will have its own timer independently, but they can share the same timer logic.

You can create and manage timers by using them in as many components useTimeras you need to implement shared timer functionality throughout your application.

Guess you like

Origin blog.csdn.net/weixin_42439919/article/details/133065773