2025年之前端高频面试手写题(励志做到最全)

5 阅读12分钟

DFS与BFS遍历

DFS

  • 从一个起点开始,沿着一条路径尽可能深地搜索,直到无法继续前进(即到达叶子节点或遇到已访问过的节点),然后回溯到上一个分支点,继续探索其他路径,
  • 逆序入栈, 先进后出
        1 A
         / \
      2 B  5 C
       / \
    3 D  4 E
    
    入 A
    出 A
    入 C B
    出 B
    入 E D
    出 D
    出 E
    出 C
    A B D E C

递归出栈

  • DFS(A)
  • DFS(B)
  • DFS(D)
  • DFS(E)
  • DFS(C)

访问顺序A → B → D → E → C

  function DFS(node){//栈  先进先出
            let stack=[node]
            let ans=[]
            while(stack.length){
                let item=stack.pop()
                ans.push(item)
                let children = item.children
                for(let i=children.length-1;i>=0;i--){
                    stack.push(children[i])
                }
            }
            return ans
        }
        DFS(parent)

589. N 叉树的前序遍历

BFS

先进先出

        1 A
         / \
      2 B  5 C
       / \
    3 D  4 E
    
    入 A
    出 A
    入 B  C
    出 B  
    入 D E
    出 C
    出 D
    出 E
 function BFS(node){//队列  先进先出
            let queue=[node]
            let ans=[]
            while(queue.length){
                let item=queue.shift()
                ans.push(item)
                let children = item.children
                for(let i=0;i<children.length;i++){
                    queue.push(children[i])
                }
            }
            return ans
        }
        BFS(parent))
   <div class="parent">
        <div class="child1">
            <div class="child1-1"></div>
            <div class="child1-2"></div>
        </div>
        <div class="child2">
            <div class="child2-1"></div>
            <div class="child2-2"></div>
        </div>
        <div class="child3">
            <div class="child3-1"></div>
            <div class="child3-2"></div>
        </div>
    </div>

429. N 叉树的层序遍历

发布订阅

核心思想:  发布者和订阅者之间 完全解耦 (Decoupled)

发布订阅模式是一种 消息传递模式,其中消息的发送者(称为 发布者)不会直接将消息发送给特定的接收者(称为 订阅者)。相反,发布者将消息发布到 事件通道 (Event Bus) 中介上,订阅者则可以订阅它们感兴趣的事件通道

class EventEmitter {
  constructor() {
    this.event = {};
  }
  on(type, cb) {
    if (!this.event[type]) {
      this.event[type] = [cb];
    } else {
      this.event[type].push(cb);
    }
  }
  emit(type, ...args) {
    if (!this.event[type]) {
      return;
    } else {
      this.event[type].forEach((item) => item(...args));
    }
  }
  off(type, cb) {
    if (!this.event[type]) {
      return;
    } else {
      this.event[type] = this.event[type].filter((item) => item != cb);
    }
  }
}
let mitt = new EventEmitter();
let foo = () => console.log("111");
mitt.on("fn", foo);
mitt.on("fn", () => console.log("object"));
mitt.off("fn", foo);
mitt.emit("fn");

防抖与节流

防抖

在事件被触发后,这n秒内又被触发,则重新计时。

    let btn = document.querySelector("#btn");
      function foo() {
        console.log(this, "已提交");
      }
      let clear;//污染全局变量
      function debounce(fn, time) {
        if (clear) clearTimeout(clear);
        setTimeout(() => {
          fn();
        }, time);
      }
      btn.addEventListener("click", () => debounce(foo, 1000));
  • 闭包保存私有变量
  • this指向

防抖函数通过 setTimeout 来延迟函数的执行。由于 setTimeout 会在全局上下文中执行回调函数,this指向全局对象(在浏览器中是 window)。

事件 :在事件监听器中,this 指向事件源对象,即触发事件的 DOM 元素。

   function foo(arg) {
        console.log(this, "已提交", arg);
      }
      btn.addEventListener("click", debounce(send, 1000, '参数'));
      function debounce(fn, time, ...arg) {
        let timer = null;
        return function () {
          if (timer) clearTimeout(timer);
          timer = setTimeout(() => {
            fn.call(this, ...arg);
          }, time);
        };
      }

节流

私有变量保存上次函数执行时间

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效

      function throttle(fn, time = 1000, ...args) {
        let prevTime = 0;
        return function () {
          let curTime = Date.now();
          if (curTime - prevTime > time) {
            fn.call(this, ...args);
            prevTime = curTime;
          }
        };
      }
      window.addEventListener("scroll", throttle(fn, 2000, "参数"));
      function fn() {
        console.log("object", arguments);
      }

拷贝

简单类型:简单类型的数据存储在栈内存中。

复杂类型:变量的引用地址存储在栈内存中,实际数据存储在堆内存中

image.png

浅拷贝

扩展运算符 slice concat assign

let obj = {
  a: 1,
  b: {
    c: 2,
  },
};
let newObj = Object.assign({}, obj);
newObj.a=0
newObj.b.c=3
console.log(obj);

let arr=[1,2,[3,4]]
let newArr=arr.slice(0)
newArr[2][1]=5
console.log(arr);

let arr1=[1,2,[3,4]]
let newArr1=arr1.concat([])
newArr1[2][1]=6
console.log(arr1);


let arr2=[1,2,[3,4]]
let newArr2=[...arr2]
newArr2[2][1]=7
console.log(arr2);

JSON

undefinedfunction symbol会丢失

let simpleDeepClone = {
  a: 1,
  b: { name: "owllai" },
  c: null, // null
  d: undefined, //丢失
  e: function () {}, //丢失
  f: new Date(), // 变成时间字符串
  g: new RegExp(/\d/), //
  h: new Error(), // new RegExp 和 new Error 变成空对象
  i: NaN,
  j: Infinity, //NAN 和 Infinity 会变成null
  k: new Map(), //变成空对象
  l: new Set(), //变成空对象
  m: Symbol("1"),
};
console.log(JSON.parse(JSON.stringify(simpleDeepClone)));

递归

const obj = { a: 1, b: 2 };
console.log(obj.hasOwnProperty('a')); // true
console.log(Object.hasOwn(obj, 'a')); // true

let obj = {
  a: [1, 2, 3, 4],
  b: { a: 1, b: [1, 2, 3] },
};
function deepClone(obj, map = new Map()) {
  //实现深拷贝
  if (!(obj instanceof Object)) return obj;
  //判断是对象还是数组
  let newObj = Array.isArray(obj) ? [] : {};
  map.set(obj, newObj);
  for (let key in obj) {
    //in 会遍历到原型链上的东西
    if (Object.hasOwn(obj, key)) {
      if (map.has(obj[key])) {
        newObj[key] = map.get(obj[key]);
      } else {
        newObj[key] = deepClone(obj[key], map);
      }
    }
  }
  return newObj;
}
console.log(deepClone(obj));

消息通道

无法处理函数、Symbol

let foo={a:1,b:{c:2}}
//缺点:无法处理函数、Symbol 等
function deepClone(obj) {
  return new Promise((resolve) => {
    let { port1, port2 } = new MessageChannel();
    port1.postMessage(obj);
    port2.onmessage = (e) => resolve(e.data);
  });
}
deepClone(foo).then((res) => console.log(res));

structuredClone

JSON的替代品,解决JSON不可序列化部分JS属性

无法处理函数、Symbol

const original = {
  a: 1,
  b: { c: 2 },
  d: [3, 4],
  e: new Date(),
  f: new Map([["key", "value"]]),
};
const clone = structuredClone(original);
console.log(clone);

this

  • 默认绑定:当一个函数独立调用,this指向window
  • 隐式绑定: 当函数被某个对象拥有, this指向引用它的对象

apply

思路:隐式调用

  • 缺点:可能覆盖原属性(symbol)
  • 缺点:symbol会被打印出来(definProperty隐藏)

foo.apply(obj,[2,3])

//隐式绑定
Function.prototype._apply=function(newThis,arr){
    newThis.fn=this
    newThis.fn(...arr)
}
Function.prototype._apply = function (newThis, arr = []) {
  newThis = newThis || window;
  let fn = Symbol();
  newThis[fn] = this;
  Object.defineProperty(newThis, fn, {
    enumerable: false,
  });
  let ans = newThis[fn](...arr);
  delete newThis[fn];
  return ans;
};

let arr = [1];
function fn() {
  console.log(this);
}
fn._apply(arr);

call

foo.call(obj,2,3)

 Function.prototype._call = function(obj, ...arr) {
   obj.fn = this
   obj.fn(...arr)   //隐式绑定
 }
Function.prototype._call=function(newThis,...arr){
    newThis=newThis||window
    let fn=Symbol()
    newThis[fn]=this
    Object.defineProperty(newThis, fn, { enumerable: false, });
    let ans=newThis[fn](...arr)
    delete newThis[fn]
    return ans
}

let arr = [1];
function fn() {
  console.log(this == arr);
}
fn._call(arr);

bind

  • 保存将调用的函数
  • apply改变将调用的函数的this
Function.prototype._bind = function (newThis, ...args) {
  const fn = this;
  return function (...args1) {
    return fn.apply(newThis, [...args, ...args1]);
  };
};

function fn(...args) {
  console.log(this, args);
}
let obj = {
  myname: "张三",
};
const bindFn = fn._bind(obj); 
bindFn(1, 2); 

数组 APi

手写Map

数组中每一项都执行函数

array.map(function(item, index, arr){},this)
let ins = {a: 1};
let array = [1, 2, 3];
array.map(function(item, index, arr) {
    console.log(item, index, arr); 
    console.log(this);
}, ins);
Array.prototype._map = function (callback, ins) {
    let res = [];
    for (let i = 0; i < this.length; i++) {
        res[i] = callback.call(ins , this[i], i, this)
    }
    return res;
};

手写forEach(无返回值)

array.forEach(function(item, index, arr), ins)
Array.prototype._forEach = function(callback, ins) {
    for(let i = 0; i < this.length; i++) {
        callback.call(ins, this[i], i, this);
    }
}

filter

函数处理完的值(满足条件),返回

Array.prototype._filter = function (callback, ins) {
    let res = [];
    for (let i = 0; i < this.length; i++) {
        if (callback.call(ins, this[i], i, this)) {
            res.push(this[i])
        }
    }
    return res;
};

let res = [1, 2, 3]._filter(function (item, index, arr) {
    console.log(item, index, arr);
    console.log(this);
    return item > 1;
}, { o: 100 });
console.log(res);

reduce

  • reducer 逐个遍历数组元素,每一步都将当前元素的值前一步的结果相操作
Array.prototype._reduce = function(callback, initialValue) {
    let index =  initialValue ? 0 : 1;
    let res = initialValue ? initialValue : this[0];
    for(let i = index; i < this.length; i++) {
        res = callback.call(null, res, this[i], i, this);
    }
    return res;
}

扁平

递归

let arr=[1,[23,[1,2,3]]]
function _flat(arr){
    let ans=[]
    for(let item of arr){
        if(Array.isArray(item)){
            ans=ans.concat(_flat(item))//concat不改变原数组
        }else{
            ans.push(item)
        }
    }
    return ans
}
console.log(_flat(arr));

flat

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

toString

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.toString().split(',').map(i=>Number(i));
}
console.log(flatten(arr)); //  [1, 2, 3, 4]

判断对象是否相同

===与Object.is()

比较两个对象内存指向是否相同

JSON序列化

JSON.stringify 方法将对象转换为字符串,比较字符串

const obj1 = { b: "hello",a: 1, c: true };
const obj2 = { a: 1, b: "hello", c: true };
const obj3 = { a: 1, b: "hello", c: true };

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
console.log(JSON.stringify(obj2) === JSON.stringify(obj3)); // true

手动实现

  1. 判断长度是否一致
  2. for in 遍历判断每一项
  3. 两个都是对象就递归判断
let obj={ a: 1, b: 2, c: { a: 1, b: 2 } }
let obj1= { b: 2, a: 1, c: { b: 2, a: 1 ,c:3 } }
function isEqual(obj, obj1) {
  const key1 = Object.keys(obj);
  const key2 = Object.keys(obj1);
  if (key1.length !== key2.length) return false;
  for (const k in obj) {
      if (obj[k] instanceof Object && obj1[k] instanceof Object) {
        if (!isEqual(obj[k], obj1[k])) return false;
      } else {
        if (obj[k] !== obj1[k]) return false;
      }
  }
  return true;
}
console.log(isEqual(obj,obj1))

对象去重

如何定义重复

const arr = [
  { a: 1, b: 2 },
  { a: 1, b: 2 },
  { a: 1, b: 2, c: { a: 1, b: 2 } },
  { b: 2, a: 1, c: { b: 2, a: 1 } },
];
function isEqual(obj, obj1) {
  const key1 = Object.keys(obj);
  const key2 = Object.keys(obj1);
  if (key1.length !== key2.length) return false;
  for (const k in obj) {
      if (obj[k] instanceof Object && obj1[k] instanceof Object) {
        if (!isEqual(obj[k], obj1[k])) return false;
      } else {
        if (obj[k] !== obj1[k]) return false;
      }
  }
  return true;
}
function foo(arr) {
  return arr.reduce((pre, cur) => {
    let is = true;
    for (let item of pre) {
      if (isEqual(item, cur)) {
        is = false;
        break;
      }
    }
    if (is) {
      pre.push(cur);
    }
    return pre;
  }, []);
}
console.log(foo(arr));

new

  • 创建一个空对象 let obj = {}
  • 设置对象的原型链,将其指向构建函数的原型 obj.__proto__ = Fun.prototype
  • 改变构建函数this指向,将其绑定到新创建的空对象 Fun.apply(obj,args)
  • 最后返回对象
function _new(fn, ...args) {
  let obj = {};
  obj.__proto__ = fn.prototype;
  fn.apply(obj, args);
  return obj;
}

new.target

new.target
function foo() {
  if (new.target !== undefined) {
    console.log("被new调用");
  } else {
    console.log("不是被new调用");
  }
}
new foo()
foo()

create

  • 给一个新对象设置原型
function create(obj) {
    function F() {}
    F.prototype = obj
    return new F()
  }
let obj={a:1}
let obj1=create(obj)
console.log(obj1.a);//1

继承

原型链继承

function parent() {
  this.colors = ["red", "blue", "green"];
}
function child() {}
// 继承 parent
child.prototype = new parent();
let instance1 = new child();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new child();
console.log(instance2.colors); // "red,blue,green,black"

缺点

  • 父类的prototype会被改变(内存共享)

盗用构造函数

function parent(name) {
  this.name = name;
  this.age = 12;
}
parent.prototype = { a: 1 };
function child(name) {
  parent.call(this, name);
}
console.log(new child("yi")); 
console.log(new child('yi').a)//undefined

缺点

  • 不能访问父类原型上的方法

原型链+构造函数

function parent(name) {
  this.name = name;
  this.age = 12;
}
parent.prototype = { a: 1 };

function child(name) {
  parent.call(this, name);
}
child.prototype = new parent();
console.log(new child("yi"));
console.log(new child("yi").a);
  • 父类构造函数被调用两次

create

function object(obj) { 
 function F() {} 
 F.prototype = obj; 
 return new F(); 
}

盗用构造函数+create

  • 盗用构造函数(无父元素的prototype属性)
  • child.prototype=parent.prototype
function inheritPrototype(child, parent) {
  let prototype = Object.create(parent.prototype); // 创建对象
  child.prototype = prototype; // 赋值对象
}

function Parent(name) {
  this.name = name;
  this.friends = ["rose", "lily", "tom"];
}
Parent.prototype.sayName = function () {
  console.log(this.name);
};
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
  console.log(this.age);
};
let child1 = new Child("yhd", 23);
child1.sayAge(); // 23
child1.sayName(); // yhd
child1.friends.push("jack");
console.log(child1.friends); // ["rose", "lily", "tom", "jack"]

let child2 = new Child("yl", 22);
child2.sayAge(); // 22
child2.sayName(); // yl
console.log(child2.friends); // ["rose", "lily", "tom"]

extends(最优)

class Person {
  constructor(name) {
    this.name = name
  }
  // 原型方法
  // 即 Person.prototype.getName = function() { }
  // 下面可以简写为 getName() {...}
  getName = function () {
    console.log('Person:', this.name)
  }
}
class Gamer extends Person {
  constructor(name, age) {
    // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    super(name)
    this.age = age
  }
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

instanceof

检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

symbol

class A {
  static [Symbol.hasInstance]() {
    return true;
  }
}
console.log(null instanceof A); // true

instanceof

function myinstanceof(L, R) {
    while (L !== null) {
    //通过原型链进行判断
        if (L.__proto__ = R.prototype) {
            return true
        }
        L = L.__proto__
    }
    return false
}

var arr = {}
console.log(myinstanceof(arr, Object))//true

isPrototypeOf

function _instanceof(L,R){
    return R.prototype.isPrototypeOf(L)
}

iterator

内置iterator

  1. 字符串
  2. 数组
  3. Map
  4. Set
  5. arguments 对象
  6. NodeList 等 DOM 集合类型

iterator的应用

  1. for-of 循环
  2. 数组解构
  3. 扩展操作符
  4. Array.from()
  5. 创建 Set
  6. 创建 Map
  7. Promise.all()接收由 Promise组成的可迭代对象
  8. Promise.race()接收由 Promise组成的可迭代对象
  9. yield*操作符,在生成器中使用

使用

let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

对象解构

let obj = {
  name: "tom",
  age: 18,
  gender: "男",
  intro: function () {
    console.log("my name is " + this.name);
  },
};
obj[Symbol.iterator] = function () {
  return Object.values(this)[Symbol.iterator];
};
obj[Symbol.iterator] = function () {
  let index = 0;
  let values = Object.values(this); //转为数组
  return {
    next: function () {
      return {
        value: values[index++],
        done: index > values.length,
      };
    },
  };
};
let [name, age, gender] = obj;
console.log(name, objAge, gender);

Generator

函数遇到 yield 的时候会暂停,并把 yield 后面的表达式运行,并将其作为生成器value

image.png

手写async

function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url);
    }, 2000);
  });
}
function* getData() {
  const res1 = yield request("1");
  console.log("res1:", res1);

  const res2 = yield request(res1 + "2");
  console.log("res2:", res2);

  const res3 = yield request(res2 + "3");
  console.log("res3:", res3);
}

const generator = getData();
// generator.next().value.then((res1) => {
//   generator.next(res1).value.then((res2) => {
//     generator.next(res2).value.then((res3) => {
//       generator.next(res3);
//     });
//   });
// });
// 自动化执行生成器函数(了解)
function execGenFn(fn) {
  // 1.获取对应函数的generator
  const gen = fn();
  // 2.定义一个递归函数
  function exec(res) {
    // result -> { done: true/false, value: 值/undefined }
    const result = gen.next(res);
    if (result.done) return;
    result.value.then((res) => {
      exec(res);
    });
  }
  // 3.执行递归函数
  exec();
}
execGenFn(getData);

手写PromiseAPI

all

接受一个迭代器 ,返回一个Promise

当所有输入的 Promise 都被兑现时,返回所有兑现值的数组

输入的任何一个 Promise 被拒绝,则返回的 Promise 将被拒绝

Promise._all = function (promises) {
  let ans = [];
  return new Promise((resolve, reject) => {
    promises.forEach((item) => {
      Promise.resolve(item).then(
        (val) => {
          ans.push(val);
          if (ans.length == promises.length) {
            resolve(ans);
          }
        },
        (err) => {
          reject(err);
        }
      );
    });
  });
};

let p1 = Promise.resolve("1");
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2 延时一秒");
  }, 2000);
});
let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3 延时一秒");
  }, 1000);
});
Promise._all([p1, p2, p3])
  .then((res) => console.log(res))
  .catch((err) => console.error(err));

race

接受一个迭代器 ,返回一个Promise

返回的 promise 会随着第一个 promise 的敲定而敲定。

Promise._race = function (promiseArray) {
   return new Promise((resolve, reject) => {
     promiseArray.forEach(p => {
        Promise.resolve(p).then((res) => resolve(res), (err) => reject(err));
     })
   });
 }

let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("p3 延时一秒");
    }, 100);
  });

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2 延时一秒");
  }, 1000);
});
Promise._race([p3, p2]).then((res) => console.log(res));

any

接受一个迭代器 ,返回一个Promise

当所有输入的 Promise 都被拒绝时,返回所有拒绝值的数组

输入的任何一个 Promise 被接受,则返回的 Promise 将被接受

Promise._any = function (promises) {
  let ans = [];
  return new Promise((resolve, reject) => {
    for (let item of promises) {
      Promise.resolve(item).then(
        (res) => {
          resolve(res);
        },
        (err) => {
          ans.push(err);
          if (ans.length == Object.keys(promises).length) {
            reject(ans);
          }
        }
      );
    }
  });
};

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3 延时一秒");
  }, 100);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p2 延时一秒");
  }, 1000);
});
Promise._any([p3, p2])
  .then((res) => console.log(res))
  .catch((err) => {
    console.log(err);
  });

allsettled

接受一个迭代器 ,返回一个Promise

当全部Promise执行完时,返回包括Promise状态与value

Promise._allsettled = function (promises) {
  let ans = [];
  return new Promise((resolve) => {
    for (let item of promises) {
      Promise.resolve(item)
        .then(
          (res) => {
            ans.push({ status: "resolve", res: res });
          },
          (err) => {
            ans.push({ status: "reject", res: err });
          }
        )
        .finally(() => {
          if (ans.length == Object.keys(promises).length) {
            resolve(ans);
          }
        });
    }
  });
};

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3 延时一秒");
  }, 100);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p2 延时一秒");
  }, 1000);
});
Promise._allsettled([p3, p2])
  .then((res) => console.log(res))
  .catch((err) => {
    console.log(err);
  });

Promise异步题

控制并发

浏览器最多只支持6个请求

  • 并发池
  • 请求地址池
  • 开始时用url地址请求将并发池填满
  • 执行最快的一个(Promise.race)
  • 每执行一个,删除并发池中完成的那个,从url地址池拿出一个放入并发池
const URLs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
class PromisePool {
  constructor(max, fn) {
    this.max = max; // 最大并发数
    this.fn = fn; // 自定义的请求函数
    this.pool = []; // 并发池
    this.urls = []; // 剩余的请求地址
  }
  start(urls) {
    this.urls = urls;
    //  循环把并发池塞满
    while (this.pool.length < this.max) {
      let url = this.urls.shift();
      this.setPool(url);
    }
    // 利用Promise.race 方法来获得并发池中某个任务完成的信号
    this.run(Promise.race(this.pool));
  }
  setPool(url) {
    if (!url) return;
    let task = this.fn(url);
    this.pool.push(task); // 将任务推入pool并发池中
    console.log(`${url}开始,当前的并发数:${this.pool.length}`);
    task.finally(() => {
      // 请求结束将该promise任务从并发池中移除
      this.pool.splice(this.pool.indexOf(task), 1);
      console.log(`${url}结束,当前的并发数:${this.pool.length}`);
    });
  }
  run(race) {
    race.then(() => {
      // 每当并发池中完成一个任务,就在塞入一个任务
      let url = this.urls.shift();
      this.setPool(url);
      this.run(Promise.race(this.pool));
    });
  }
}
// 模拟异步请求函
let fn = (url) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`任务${url}完成`);
    }, 1000);
  }).then((res)=>{
    console.log(res);
  })
};
// 并发数为3
const pool = new PromisePool(3, fn);
pool.start(URLs);

红绿灯实现

函数柯里化

核心思想是将一个接受多个参数的函数转化为一系列接受单一参数的函数

function add() {
  let arg = [...arguments];
  let fn = function () {
    return add.apply(null, arg.concat([...arguments]));
  };
  fn.toString = () => arg.reduce((a, b) => a + b);
  return fn;
}
console.log(+add(1)(2,3))

子树添加父ID

let arr1 = [
  {
    id: 1,
    children: [{ id: 11 }, { id: 12, children: [{ id: 121 }] }],
  },
];
function foo(arr) {
  arr.forEach((item) => {
    if (item.children) {
      foo(item.children);
      item.children.forEach((item1) => {
        item1.parent = item.id;
      });
    }
  });
  return arr;
}
foo(arr1);
console.log(arr1[0].children[1]);

数组转树

const arr = [
  { id: 1, parentId: null },
  { id: 2, parentId: 1 },
  { id: 3, parentId: 1 },
  { id: 4, parentId: 2 },
  { id: 5, parentId: 2 },
  { id: 6, parentId: 3 },
];
function createNode(id, arr) {
  // data中去查找根节点下有哪些子节点
  const childArr = arr.filter(({ parentId }) => parentId === id);
  // 重写节点
  return {
    id,
    children: childArr.map((item) => {
      return createNode(item.id, arr);
    }),
  };
}

排序

冒泡排序手写sort

快排

队排

乱序排

字符串

千位数分割

大写转驼峰

驼峰转大写

解析URL

路由原理

hash

history

Vue3响应式原理

defineProperty

proxy

setTimeOut实现setInterval

React调度器原理

zustand原理