2024秋招面试手写题

230 阅读4分钟

一、控制并发数

 
//实现方式1:函数
function createLimitPromise(limitNum, promiseListRaw) {
  let resArr = [];
  let handling = 0;
  let resolvedNum = 0;
  let promiseList = [...promiseListRaw];
  let runTime = promiseListRaw.length;

  return new Promise((resolve) => {
    //并发执行limitNum 次
    for (let i = 1; i <= limitNum; i++) {
      run();
    }

    function run() {
      if (!promiseList.length) return;
      handling += 1;
      handle(promiseList.shift())
        .then((res) => {
          resArr.push(res);
        })
        .catch((e) => {
          //ignore
          console.log("catch error");
        })
        .finally(() => {
          handling -= 1;
          resolvedNum += 1;
          if (resolvedNum === runTime) {
            resolve(resArr);
          }
          run();
        });
    }
    function handle(requestFn) {
      return new Promise((resolve, reject) => {
        requestFn()
          .then((res) => resolve(res))
          .catch((e) => reject(e));
      });
    }
  });
}


//实现方式二:类
class Scheduler {
  constructor(limit) {
    this.queue = [];
    this.maxCounter = limit;
    this.runCounter = 0;
  }
  add(time, val) {
    const promiseEvent = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(val);
          resolve();
        }, time);
      });
    };
    this.queue.push(promiseEvent);
  }
  start() {
    for (let i = 0; i < this.maxCounter; i++) {
      this.request();
    }
  }
  request() {
    if (
      !this.queue ||
      !this.queue.length ||
      this.runCounter >= this.maxCounter
    ) {
      return;
    }
    this.runCounter++;
    this.queue
      .shift()()
      .then((res) => {
        this.runCounter--;
        this.request();
      });
  }
}

const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.start();


//实现方式三
function timeout(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

class SuperTask {
  constructor(MAX_NUM = 2) {
    this.tasks = [];
    this.count = 0;
    this.max_num = MAX_NUM;
  }
  add(task) {
    return new Promise((resolve, reject) => {
      this.tasks.push({ task, resolve, reject });
      this.run();
    });
  }
  run() {
    while (this.count < this.max_num && this.tasks.length > 0) {
      this.count++;
      const { task, resolve, reject } = this.tasks.shift();
      task()
        .then(resolve, reject)
        .finally(() => {
          this.count--;
          this.run();
        });
    }
  }
}
const superTask = new SuperTask();

function addTask(time, name) {
  superTask
    .add(() => timeout(time))
    .then(() => {
      console.log(`任务${name}完成`);
    });
}

addTask(10000, 1);
addTask(5000, 2);
addTask(3000, 3);
addTask(4000, 4);
addTask(5000, 5);

面试失败总结:createLimitPromise没有返回值,无法读取到resArr

二、发布订阅模式

class EventEmitter {
  constructor() {
    this.event = {};
  }
  on(type, cb) {
    if (this.event[type]) {
      this.event[type].push(cb);
    } else {
      this.event[type] = [cb];
    }
  }
  once(type, cb) {
    const fn = (...args) => {
      cb(...args);
      this.off(type, fn);
    };
    this.on(type, fn);
  }
  emit(type, ...args) {
    if (this.event[type]) {
      this.event[type].forEach((cb) => cb(...args));
    } else {
      return;
    }
  }
  off(type, cb) {
    if (this.event[type]) {
      this.event[type] = this.event[type].filter((item) => item != cb);
    } else {
      return;
    }
  }
}

// 运行示例
let ev = new EventEmitter();

const fun1 = (str) => {
  console.log(str);
};

ev.on("say", fun1);
ev.emit("say", "visa");
// ev.off("say", fun1);
ev.once("say1", fun1);
ev.emit("say1", "visa1");
ev.emit("say1", "visa1");
ev.emit("say", "visa");

总结:面试的时候没写出来。复盘后发现思路很简单。写一个类,勾造函数里定义一个事件对象(事件类型是键值,事值是事件数组)。on注册事件,就是在事件对象里添加事件,emit是把事件类型的每个事件都执行一遍。off是删除事件类型的某个事件,用数组filter,once是把事件用一个函数包裹住,并且在函数里调用off。

三、数组排平加排序和去重

const arrFlat = (arr) => {
  const res = [];
  const fn = (arr) => {
    for (let i = 0; i < arr.length; i++)
      if (Array.isArray(arr[i])) {
        fn(arr[i]);
      } else {
        res.push(arr[i]);
      }
  };
  //数组拍平
  fn(arr);
  //数组去重加升序
  const setData = Array.from(new Set(res)).sort((a, b) => a - b);
  return setData;
};

const arr = [1, 2, [3, 6, 1, 3], [7.5, 9, [1, 2, 3]]];
console.log(arrFlat(arr));

四、字符串类型转数

const str = `Language
  JavaScript
    TypeScript
    NodeJS
  HTML
Server
  DataBase
    MongoDB
System
  Linux
  Window`;
    const strSplit = str.split("\n");
    const strArr = [];
    const spaceNum = [];
    const json = {};
    strArr.push(json);
    spaceNum.push(-1);
    for (let i = 0; i < strSplit.length; i++) {
      const num = strSplit[i].lastIndexOf(" ") + 1;
      const str = strSplit[i].replace(/\s/g, "");
      const curOcj = { val: str };
      while (num <= spaceNum.slice(-1)) {
        strArr.pop();
        spaceNum.pop();
      }
      strArr.slice(-1)[0].children
        ? strArr.slice(-1)[0].children.push(curOcj)
        : (strArr.slice(-1)[0].children = [curOcj]);
      strArr.push(curOcj);
      spaceNum.push(num);
    }
    console.log(json);

五、数组转树和树转数组

    const list = [
          { id: 12, parent_id: 1, name: "朝阳区" },
          { id: 241, parent_id: 24, name: "田林街道" },
          { id: 31, parent_id: 3, name: "广州市" },
          { id: 13, parent_id: 1, name: "昌平区" },
          { id: 2421, parent_id: 242, name: "上海科技绿洲" },
          { id: 21, parent_id: 2, name: "静安区" },
          { id: 242, parent_id: 24, name: "漕河泾街道" },
          { id: 22, parent_id: 2, name: "黄浦区" },
          { id: 11, parent_id: 1, name: "顺义区" },
          { id: 2, parent_id: 0, name: "上海市" },
          { id: 24, parent_id: 2, name: "徐汇区" },
          { id: 1, parent_id: 0, name: "北京市" },
          { id: 2422, parent_id: 242, name: "漕河泾开发区" },
          { id: 32, parent_id: 3, name: "深圳市" },
          { id: 33, parent_id: 3, name: "东莞市" },
          { id: 3, parent_id: 0, name: "广东省" },
        ];
   
   
    //数组转树
    const arrToTree = (list) => {
      const res = [];
      const map = {};

      for (let item of list) {
        map[item.id] = item;
      }
      for (let obj of list) {
        if (map[obj.parent_id]) {
          (
            map[obj.parent_id].children || (map[obj.parent_id].children = [])
          ).push(obj);
        } else {
          res.push(obj);
        }
      }
      return res;
    };

    const tree = arrToTree(list);
    console.log(tree);
    
    
    //树转数组
    const treeToArr = (tree) => {
      const copyTree = [].concat(tree);
      const res = [];
      while (true) {
        const item = copyTree.shift();

        if (!item) return res;
        if (item.children) {
          copyTree.push(...item.children);

          delete item.child;
        }

        res.push(item);
      }
    };
    console.log(treeToArr(tree));

六、顺时针螺旋打印二维数组

  const rows = matrix.length,
    cols = matrix[0].length;

  let top = 0,
    left = 0,
    bottom = rows - 1,
    right = cols - 1;

  const res = [];

  while (left <= right && top <= bottom) {
    for (let col = left; col <= right; col++) {
      res.push(matrix[top][col]);
    }
    top++;
    for (let row = top; row <= bottom; row++) {
      res.push(matrix[row][right]);
    }
    right--;
    if (top <= bottom && left <= right) {
      for (let col = right; col >= left; col--) {
        res.push(matrix[bottom][col]);
      }
      bottom--;
      for (let row = bottom; row >= top; row--) {
        res.push(matrix[row][left]);
      }
      left++;
    }
  }
  return res;
};

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

const result = fn(matrix);
console.log(result); // 输出: [1, 2, 3, 6, 9, 8, 7, 4, 5]

七、单例模式

//1 构造函数写法
let singeDemo = null;
function SingleDemo1() {
  if (!singeDemo) {
    singeDemo = this;
  }
  return singeDemo;
}

SingleDemo1.prototype.show = function () {
  console.log("我是一个实例");
};

const a = new SingleDemo1();
const b = new SingleDemo1();
a.show();
console.log(a === b);

//2 静态方法写法
class SingleDemo2 {
  show() {
    console.log("我是一个实例");
  }
  static getInstance() {
    if (!SingleDemo2.instance) {
      SingleDemo2.instance = new SingleDemo2();
    }
    return SingleDemo2.instance;
  }
}
const a2 = SingleDemo2.getInstance();
const b2 = SingleDemo2.getInstance();
a2.show();
console.log(a2 === b2);

//1 闭包写法
class SingleDemo3 {
  show() {
    console.log("我是一个实例");
  }
}

SingleDemo3.getInstance = (function () {
  let instance = null;
  return function () {
    if (!instance) {
      instance = new SingleDemo3();
    }
    return instance;
  };
})();

const a3 = SingleDemo3.getInstance();
const b3 = SingleDemo3.getInstance();
a3.show();
console.log(a3 === b3);

八、二维数组去重

const arr = [
  [1, 2],
  [2, 1],
  [3, 4],
  [4, 6],
];

const obj = {};

const res = [];

for (let i = 0; i < arr.length; i++) {
  const key = arr[i].sort((a, b) => a - b).join(",");
  if (!obj[key]) {
    res.push(arr[i]);
    obj[key] = true;
  }
}

console.log(res);

九、对象数组去重

const arr = [
  { a: 1, b: 2 },
  { b: 2, a: 1 },
  { a: 1, b: 2, c: { a: 1, b: 2 } },
  { b: 2, a: 1, c: { b: 2, a: 1 } },
];

const res = [...arr];
const isObject = (val) => typeof val == "object" && typeof val != null;

function isEqual(val1, val2) {
  if (!isObject(val1) || !isObject(val2)) return Object.is(val1, val2);
  const key1 = Object.keys(val1);
  const key2 = Object.keys(val2);
  if (key1.length != key2.length) return false;
  for (let key of key1) {
    if (!val2[key]) return false;
    const res = isEqual(val1[key], val2[key]);
    if (!res) return false;
  }
  return true;
}

for (let i = 0; i < res.length; i++) {
  for (let j = i + 1; j < res.length; j++) {
    if (isEqual(res[i], res[j])) {
      res.splice(j, 1);
      j--;
    }
  }
}

console.log(res);