【前端面试题】2023年前端面试真题之编码篇

人的一生,总是难免有浮沉。不会永远如旭日东升,也不会永远痛苦潦倒。反复地一浮一沉,对于一个人来说,正是磨练。因此,浮在上面的,不必骄傲;沉在底下的,更用不着悲观。必须以率直、谦虚的态度,乐观进取、向前迈进。——松下幸之助

大家好,我是江辰,在如今的互联网大环境下,想必大家都或多或少且有感受,浮躁的社会之下,只有不断的保持心性,才能感知不同的收获,互勉。

2023年最新的面试题集锦,时刻做好准备。

本文首发于微信公众号:野生程序猿江辰

欢迎大家点赞,收藏,关注

文章列表

实现简易版 Promise

以下是一个基本的 Promise 实现:

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);
  }
);

这是一个非常基本的 Promise 实现,仅用于演示目的。在实际应用中,要考虑更多的细节和错误处理。现代 JavaScript 已经内置了 Promise,通常不需要手动实现它。

实现函数节流

函数节流是一种控制函数执行频率的技术,确保函数在一定时间间隔内最多执行一次。以下是一个简单的 JavaScript 函数节流的实现:

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);
    }
  };
}

上述 throttle 函数接受两个参数:func 是要节流的函数,delay 是执行的时间间隔(以毫秒为单位)。

使用这个节流函数,您可以包装需要进行节流的函数,以确保它们不会在短时间内被频繁执行。例如:

扫描二维码关注公众号,回复: 17052910 查看本文章
// 原始函数,可能会频繁触发
function handleResize() {
    
    
  console.log('窗口大小改变了');
}

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

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

现在,handleResize 函数将在 200 毫秒内最多执行一次,无论窗口大小改变多频繁。这有助于减少频繁的函数调用,提高性能。

实现函数防抖

函数防抖是一种控制函数执行频率的技术,确保函数在一定时间间隔内只执行一次。以下是一个简单的 JavaScript 函数防抖的实现:

function debounce(func, delay) {
    
    
  let timerId;

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

上述 debounce 函数接受两个参数:func 是要防抖的函数,delay 是等待的时间间隔(以毫秒为单位)。

使用这个防抖函数,您可以包装需要进行防抖的函数,以确保它们只在一定时间间隔后被执行。例如:

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

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

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

现在,handleInput 函数将在用户停止输入 300 毫秒后执行,从而减少了频繁的函数调用,提高了性能。

实现观察者模式

观察者模式是一种设计模式,其中一个主题(被观察者)维护了一个观察者列表,并在状态变化时通知观察者。以下是一个简单的 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会收到更新

上述代码创建了一个简单的观察者模式实现,包括一个主题类 Subject 和一个观察者类 Observer。主题可以添加、移除观察者,并在状态变化时通知所有观察者。

在示例中,我们创建了一个主题 subject,并添加了两个观察者 observer1observer2。当主题状态发生变化时,它会通知所有观察者。

这只是一个基本的示例,实际应用中,您可能需要更复杂的实现以满足特定需求。

实现发布订阅模式

订阅者模式也被称为发布-订阅模式,它是一种设计模式,其中一个主题(发布者)维护了一个订阅者列表,并在事件发生时通知所有订阅者。以下是一个简单的 JavaScript 订阅者模式的实现:

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会收到通知

在上述代码中,我们创建了一个简单的订阅者模式实现,包括一个发布者类 Publisher 和一个订阅者类 Subscriber。发布者可以添加、移除订阅者,并在事件发生时通知所有订阅者。

示例中,我们创建了一个发布者 publisher,并添加了两个订阅者 subscriber1subscriber2。当发布者发布事件时,它会通知所有订阅者。

这只是一个基本的示例,实际应用中,您可以根据需要扩展订阅者模式以满足特定需求。

实现 new 关键字

要实现 JavaScript 中 new 操作符的基本功能,您可以编写一个函数,该函数接受构造函数和构造函数参数,并返回一个新的对象实例。以下是一个示例的实现:

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

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

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

然后,您可以使用 myNew 函数来模拟 new 操作符的行为。例如:

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 }

这个 myNew 函数首先创建一个新对象 obj,然后将新对象的原型指向构造函数 constructor 的原型。接下来,它调用构造函数,并将新对象绑定到构造函数的上下文中。最后,它检查构造函数的返回值,如果是对象则返回该对象,否则返回新创建的对象。

这是一个简单的 new 操作符的模拟实现,实际上,new 还涉及到原型链等更复杂的特性,但这个示例可以演示基本的原理。

实现 DeepClone

深拷贝(deep clone)是一种在复制对象时,不仅复制对象本身,还递归复制对象内部所有嵌套的对象和属性的操作。以下是一个简单的 JavaScript 深拷贝的实现示例:

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;
}

这个 deepClone 函数可以深度复制包括对象、数组和嵌套结构在内的复杂数据类型。它使用了一个哈希表 hash 来防止循环引用,确保不会陷入无限递归。

示例用法:

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,说明是不同的对象

请注意,这只是一个简单的深拷贝实现示例,实际应用中可能需要更复杂的处理,以应对各种数据类型和情况。

实现函数 Curry

函数柯里化(Currying)是一种将接受多个参数的函数转换为一系列接受单个参数的函数的技术。以下是一个简单的 JavaScript 函数柯里化的实现示例:

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));
      };
    }
  };
}

这个 curry 函数接受一个函数 fn,然后返回一个柯里化后的函数。当柯里化后的函数被调用时,它将检查传入的参数数量是否足够执行原始函数 fn。如果参数足够,它会直接调用 fn;如果参数不够,它将返回一个新的函数,等待更多参数传入,并持续追加参数,直到参数足够。

示例用法:

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

在示例中,我们首先使用 curry 函数将 add 函数柯里化,然后可以通过多种方式调用 curriedAdd 来实现加法操作。

这只是一个简单的函数柯里化的实现示例,实际应用中,您可能需要更复杂的处理,以应对不同的函数和参数情况。

实现 Call

call 是 JavaScript 中用于调用函数的方法,它允许您指定函数内部的 this 值并传递参数。以下是一个简单的 JavaScript call 方法的模拟实现:

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;
};

这个模拟的 myCall 方法可以添加到 Function.prototype 上,以使所有函数都能够调用它。它接受一个上下文对象 context 和一系列参数 args

示例用法:

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

const person = {
    
     name: 'Alice' };

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

在示例中,我们通过 myCall 方法来调用 greet 函数,并指定 person 对象作为上下文对象,这使得 this 在函数内部指向了 person 对象。

请注意,这只是一个简单的 call 方法模拟实现,实际的 call 方法还可以处理更多参数和特殊情况。

实现数组拍平

在 JavaScript 中,您可以使用递归或循环来实现数组的拍平(Flatten)。以下是一些拍平数组的方法:

递归方法:

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]

使用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]

使用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]

上述方法都可以将嵌套的数组拍平成一个一维数组。选择哪种方法取决于您的项目需求和对兼容性的要求。如果您的环境支持ES6的Array.flat方法,那是最简单的方式。如果需要兼容旧的环境,可以使用递归或reduce方法。

封装 Hooks 定时器

要封装一个可以在多个组件中共享的自定义Hooks定时器,您可以创建一个名为useTimer的自定义Hooks。以下是一个示例:

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;

这个useTimer自定义Hooks接受两个参数:initialCount(初始计数值,默认为0)和interval(定时器间隔,默认为1000毫秒)。它返回一个表示定时器计数值的count状态变量。

您可以在多个组件中使用useTimer来创建定时器。以下是一个示例:

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;

在上述示例中,我们导入了自定义HooksuseTimer,然后在TimerComponent组件中使用它创建了一个定时器。每个使用useTimer的组件都会独立拥有自己的定时器,但它们可以共享相同的定时器逻辑。

您可以在需要的多个组件中使用useTimer来创建和管理定时器,以便在整个应用程序中实现共享的定时器功能。

猜你喜欢

转载自blog.csdn.net/weixin_42439919/article/details/133065773