2024前端面试题库 - 手写题

931 阅读5分钟

本文将常见手写真题汇总,便于大家刷题使用。本文内容包括:

  • Promise系列
  • 数组系列
  • JS原理系列
  • 函数式编程系列
  • 等等

Promise系列

手写Promise

class MyPromise {
  constructor(executor) {
    this.value = undefined;
    this.reason = undefined;
    this.status = 'pending';

    const resolve = (value) => {
      if (this.status === 'pending') {
        this.value = value;
        this.status = 'resolved';
      }
    };

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

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

  then(onFulfilled, onRejected) {
    if (this.status === 'resolved') {
      onFulfilled(this.value);
    } else if (this.status === 'rejected') {
      onRejected(this.reason);
    }
  }

  catch(onRejected) {
    if (this.status === 'rejected') {
      onRejected(this.reason);
    }
  }
}

手写Promise.all

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completedCount = 0;
    
    // 没有任务
    if(!promises.length) {
        return resolve(results)
    }
    
    promises.forEach((promise, i) => {
        Promise.resolve(promise)
        .then(result => {
            completedCount ++;
            results[i] = result;
            if(completedCount === promises.length) {
               resolve(results);
            }
        })
        .catch(error => {
            reject(error)
        })
    });
  });
}

手写Promise.race

function promiseRace(promises) {
  return new Promise((resolve, reject) => {
      promises.forEach(p => {
          // 让每个任务都变成Promise任务。等他们任何一个执行完就会走到then和catch结束
          Promise.resolve(p).then(resolve).catch(reject);
      })
  });
}

手写defer

class Deferred {
  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    });
  }
}

手写Promise.allSettled

function promiseAllSettled(promises) {
  return Promise.all(
    promises.map((promise) =>
      promise
        .then((value) => ({
          status: "fulfilled",
          value: value,
        }))
        .catch((reason) => ({
          status: "rejected",
          reason: reason,
        }))
    )
  );
}

手写Promise.map

function promiseMap(promises) {
  return Promise.all(promises.map((promise) => promise.catch((error) => error)));
}

手写Promise.retry

function promiseRetry(asyncFn, maxRetries) {
  return new Promise((resolve, reject) => {
    let retries = 0;

    function attempt() {
      asyncFn()
        .then(resolve)
        .catch((error) => {
          retries++;
          if (retries <= maxRetries) {
            attempt();
          } else {
            reject(error);
          }
        });
    }

    attempt();
  });
}

手写异步串行任务

function series(tasks) {
  return tasks.reduce((promiseChain, currentTask) => {
    return promiseChain.then((result) => {
      return currentTask().then((currentResult) => {
        return result.concat(currentResult);
      });
    });
  }, Promise.resolve([]));
}

手写批量请求

function batchRequest(requests, maxConcurrency) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completedCount = 0;
    let currentIndex = 0;

    function makeRequest(index) {
      if (index >= requests.length) {
        if (completedCount === requests.length) {
          resolve(results);
        }
        return;
      }

      const currentRequest = requests[index];
      currentRequest()
        .then((result) => {
          results[index] = result;
        })
        .catch((error) => {
          results[index] = error;
        })
        .finally(() => {
          completedCount++;
          makeRequest(currentIndex++);
        });

      if (currentIndex - index < maxConcurrency) {
        makeRequest(currentIndex++);
      }
    }

    makeRequest(0);
  });
}

手写Promise.resolve

  static resolve(value: any) {
    if (value instanceof Promise2) return value
    return new Promise2((resolve: Function) => {
      resolve(value)
    })
  }

手写Promise.finally

  // 1. 调用当前 Promise 的 then 方法返回一个新的 Promise 对象(保证链式调用)
  // 2. 调用 Promise 中的 resolve 方法进行返回
  public finally(callback: Function) {
    return this.then(
      (value: any) => this.resolve(callback()).then(() => value),
      (reason: any) =>
        this.resolve(callback()).then(() => {
          throw reason;
        })
    );
  }

手写Promise.catch

  public catch(cb: Function) {
    this.then(undefined, cb)
  }

数组系列

数组扁平化flat

function flat(arr) { 
    return arr.reduce((result, item) => {
        return result.concat(Array.isArray(item) ? flat(item) : item);
    }, []); 
}

数组去重

function unique(arr) {
  // return array.filter((it, i) => array.indexOf(it) === i)
  const result = [];

  for (const item of arr) {
    if (!result.includes(item)) {// 可以换成indexOf filter等
      result.push(item);
    }
  }

  return result;
}


手写reduce

function reduce(arr, reducer, initialValue) {
  let accumulator = initialValue;
  let start = 0;
      
  if(initialValue == null) {// 没传初始值的处理
      accumulator = arr[0]; 
      start = 1;
  }

  for (let i = start; i < arr.length; i++) {
    accumulator = reducer(accumulator, arr[i], i, arr);
  }

  return accumulator;
}

手写数组转树

type Item = {
	[key: string]: any
}

const foo = (list: Item[]) => {
	const map: Record<string, Item> = {}
	const treeList: Item[] = []

	list.forEach(item => {
		if(!item.children) item.children = [];
		map[item.id] = item
	});

	list.forEach(item => {
		const parent = map[item.parentId];
		if(parent) {
			parent.children.push(item);
		} else {
			treeList.push(item);
		}
	});

	return treeList;
}

const arr = [
	{
		id: 2,
		name: '部门B',
		parentId: 0
	},
	{
		id: 3,
		name: '部门C',
		parentId: 1
	},
	{
		id: 1,
		name: '部门A',
		parentId: 2
	},
	{
		id: 4,
		name: '部门D',
		parentId: 1
	},
	{
		id: 5,
		name: '部门E',
		parentId: 2
	},
	{
		id: 6,
		name: '部门F',
		parentId: 3
	},
	{
		id: 7,
		name: '部门G',
		parentId: 2
	},
	{
		id: 8,
		name: '部门H',
		parentId: 4
	}
]

console.log(foo(arr))

export default foo

手写filter/map/every

// filter 方法实现
Array.prototype.myFilter = function(callback) {
  const filteredArray = [];

  // 遍历数组元素
  for (let i = 0; i < this.length; i++) {
    // 根据回调函数的返回值进行过滤
    if (callback(this[i], i, this)) {
      filteredArray.push(this[i]); // 符合条件的元素加入结果数组
    }
  }

  return filteredArray; // 返回过滤后的新数组
};

// map 方法实现
Array.prototype.myMap = function(callback) {
  const mappedArray = [];

  // 遍历数组元素
  for (let i = 0; i < this.length; i++) {
    // 对每个元素进行映射操作,并将结果加入新数组
    mappedArray.push(callback(this[i], i, this));
  }

  return mappedArray; // 返回映射后的新数组
};

// every 方法实现
Array.prototype.myEvery = function(callback) {
  // 遍历数组元素
  for (let i = 0; i < this.length; i++) {
    // 如果有任何元素不满足条件,则返回 false
    if (!callback(this[i], i, this)) {
      return false;
    }
  }

  return true; // 所有元素都满足条件,返回 true
};


函数式编程系列

手写柯里化

const curry = (fn, ...args) => {
  if (args.length < fn.length) {
    // 未接受完参数,拼上参数
    return (..._args) => curry(fn, ...args, ..._args)
  } else {
    // 接受完所有参数,直接执行
    return fn(...args)
  }
}

const add = (a, b, c) => {
    return a + b + c
}

const curried = curry(add)

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

手写compose

function compose(...functions) {
  return function(init) {
    let result = init;
    for (let i = functions.length - 1; i >= 0; i--) {
      result = functions[i](result);
    }
    return result;
  };
}

JS原理系列

手写EventEmitter

type Listener = (...args: any[]) => void;

class EventEmitter {
  private events: Record<string, Listener[]> = {};
  public on(eventName: string, listener: Listener): void {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(listener);
  }
  public emit(eventName: string, ...args: any[]): void {
    const listeners = this.events[eventName];
    if (listeners) {
      listeners.forEach(listener => listener(...args));
    }
  }
  public off(eventName: string, listener: Listener): void {
    const listeners = this.events[eventName];
    if (listeners) {
      this.events[eventName] = listeners.filter(l => l !== listener);
    }
  }
  public once(eventName: string, listener: Listener): void {
    const wrapper = (...args: any[]) => {
      listener(...args);
      this.off(eventName, wrapper);
    };
    this.on(eventName, wrapper);
  }
}

export default EventEmitter

手写防抖函数debounce

function debounce(fn, delay) {
  let timer = null
  return function (...args) {
      clearTimeout(timer)
      timer = setTimeout(() => {
          fn.apply(this, args)
      }, delay)
  }
}

const Component = () => {
    const onClick = debounce(() => {
            console.log('防抖')
    }, 1000);

    return <button onClick={onClick}>测试</button>
}

export default Component;

手写节流函数throttle

function throttle(fn, delay) {
  let start = +Date.now()
  let timer = null
  return function(...args) {
      const now = +Date.now()
      if (now - start >= delay) {
          clearTimeout(timer)
          timer = null
          fn.apply(this, args)
          start = now
      } else if (!timer){
          timer = setTimeout(() => {
              fn.apply(this, args)
          }, delay)
      }
  }
}

// 计算时间的,所以第一次会执行
const throtte2 = (fn, wait) => {
    let pre = 0;
    return function() {
            const now = new Date().getTime();
            if(now - pre > wait) {
                    fn();
                    pre = now;
            }
    }
}

const Component = () => {
    const onClick = throtte2(() => {
            console.log('节流')
    }, 1000);

    return <button onClick={onClick}>测试</button>
}

export default Component;

手写instanceOf

function myInstanceOf(obj, constructor) {
  // 加上异常处理
  if(['object', 'function'].includes(typeof obj) || obj === null) return false;// 非有效对象\函数
  
  let proto = Object.getPrototypeOf(obj);

  while (proto !== null) {
    if (proto === constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }

  return false;
}

手写Object.create

function createObject(prototype) {
  function Temp() {} // 创建一个空的构造函数

  Temp.prototype = prototype; // 将原型对象赋值给构造函数的原型

  return new Temp(); // 使用构造函数创建一个新对象
}

setTimeout模拟setInterval

function simulatedSetInterval(callback, timeout) {
  let timer = null;
  function interval() {
    timer = setTimeout(() => {
        callback();
        interval()
    }, timeout);
  }

  interval();
  
  return () => clearTimout(timer);
}

setInterval模拟setTimeout

function simulatedSetTimeout(callback, delay) {
  let timer = 0;

  timer = setInterval(function() {
    clearInterval(timer);
    callback();
 
  }, delay);
  
  return () => clearInterval(timer);
}

对象扁平化

const flattern = (obj) => {
  const res = {};

  const dfs = (curr, path) => {
    if(typeof curr === 'object' && curr !== null) {
      const isArray = Array.isArray(curr);
      for(let key in curr) {
        const newPath = path ? isArray ? `${path}[${key}]` : `${path}.${key}` : key;
        dfs(curr[key], newPath);
      }
    } else {
      res[path] = curr
    }
  }
  dfs(obj);
  return res;
}

手写call

Function.prototype.myCall = function (context, ...args) {
  context = context || window;
  const fn = Symbol('fn');
  context[fn] = this;// 将myCall方法绑定
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

手写apply

Function.prototype.myApply = function (context, args) {
  context = context || window;
  const fn = Symbol('fn');
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

手写bind

Function.prototype.myBind = function (context, ...args) {
  const self = this;
  return function (...innerArgs) {
    return self.apply(context, args.concat(innerArgs));
  };
};

手写深拷贝

function deepClone(obj, clonedMap = new WeakMap()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  
  // 缓存避免循环引用
  if (clonedMap.has(obj)) {
    return clonedMap.get(obj);
  }
  
  // 正则表达式对象
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
    
  // 日期对象
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }

  let clone = Array.isArray(obj) ? [] : {};

  clonedMap.set(obj, clone);
  // 递归拷贝
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], clonedMap);
    }
  }

  return clone;
}