前端手写

64 阅读14分钟

1. 防抖

用于搜索框输入 多次连续点击结束后,继续等待x秒,再执行回调

    function debounce(func,wait){
        let timer = null
        return function(...args){
            if(timer) clearTimeOut(timer)
            timer = setTimeOut(()=>{
                func.apply(this,args) 
            },wait)
        }
    }

2. 节流

拖拽,x秒内只执行一次回调

    function throttle(func,wait){
        let timer = null
        return function(...args){
            if(!timer) {
                timer = setTimeOut(()=>{
                    func.apply(this,args) 
                },wait)
            }
        }
    }

3.instance of

沿着原型链找

    function instanceOf(obj,func){
        if(typeof obj !== 'object'){
            throw TypeError('not a object')
        }
        let proto = Object.getPrototypeOf(obj)
        while(proto!==null){
            if(proto===func.prototype) return true
            proto = Object.getPrototypeOf(proto)
        }
        return false
    }

4.深拷贝

function deepClone(obj,map = new WeakMap()){
  //判断是否复杂对象
  const isComplexObject = (obj) => typeof obj === 'object'&&obj!==null
  if (!isComplexObject(obj)) return obj
  //判断是否date。正则等特殊对象
  const type = Object.prototype.toString.call(obj)
  if(type === '[object Date]') return new Date(obj)
  if(type === '[object RegExp]') return new RegExp(obj)
  //weakMap存储
  if(map.has(obj)) return map.get(obj)

  //使用obj原型create一个新对象,同时传入obj的所有属性
  const desc = Object.getOwnPropertyDescriptors(obj)
  const cloneObj = Object.create(Object.getPrototypeOf(obj),desc)
  map.set(obj,cloneObj)
  for(let key of Reflect.ownKeys(obj)){
    cloneObj[key] = (isComplexObject(obj[key])&&typeof obj[key]!=='function')?deepClone(obj[key],map):obj[key]
  }

  //遍历obj的属性,如果是复杂属性则递归clone,否则直接复制
  return cloneObj
}

5.new

    function myNew(func,...args){
        let obj = Object.create(func.prototype)
        let res = func.apply(this,args)
        return typeof res === 'object'? res:obj
    }

6.call/apply/bind

    function call(context=window,...args){
        let func = this
        let key = Symbol()
        context[key]=func
        let res = context[key](...args)
        delete context[key]
        return res
    }
    
    function bind(context=window,...args){
        let func = this
        function boundFn(...innerArgs){
            return func.apply(this instanceof boundFn?this:context,args.concat(innerArgs))
        }
        boundFn.prototype = Object.create(func.prototype)
        return boundFn
    }

7.promise简版

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

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

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

    try {
      executor(resolve, reject)
    } catch (e) {
      throw new Error(e)
    }
  }

  then(onFulfilled, onRejected) {
    switch (this.status) {
      case 'resolved':
        onFulfilled && onFulfilled(this.value)
        break
      case 'rejected':
        onRejected && onRejected(this.reason)
        break
      default:
        // 仍是 pending 状态下不做处理(完整版实现会在这里存回调)
        break
    }
  }
}

8.generator实现async await

function generatorToAsync(gen) {
  return function (...args) {
    const iterator = gen.apply(this, args)
    return new Promise((resolve, reject) => {
      function go(key, arg) {
        let res
        try {
          res = iterator[key](arg)
        } catch (err) {
          return reject(err)
        }

        const { value, done } = res
        if (done) {
          return resolve(value)
        } else {
          return Promise.resolve(value).then(
            val => go('next', val),
            err => go('throw', err)
          )
        }
      }
      go('next', undefined)
    })
  }
}

9.数组flatten

function flatten(arr){
  let res = []
  for(let i=0;i<arr.length;i++){
    res = res.concat(Array.isArray(arr[i]?flatten(arr[i]):arr))
  }
}

function flatten(arr){
  return arr.reduce((pre,cur)=>{
    return pre.concat(Array.isArray(cur?flatten(arr[i]):arr))
  },[]
)
}

//es6 flat
arr.flat(Infinity)

while(arr.some(Array.isArray)){
  arr = [].concat(...arr)
}

10.发布订阅

class Emitter{
  constructor(){
    this.eventList = {}
  }

  on(event,fn){
    if(!this.eventList[event]){
      this.eventList[event] = []
    }
      this.eventList[event].push(fn)
  }

  off(event,fn){
    if(!this.eventList[event]){
      return
    }
      this.eventList[event] = this.eventList[event].filter(cb=>cb!==fn)
            if(!this.eventList[event].length){
        // 如果没有事件监听它了,就直接删除这个事件类型
        delete this.eventList[event];
      }
  }

  emit(event,...args){
      if(!this.eventList[event]){
          return
      }
    this.eventList[event].forEach(fn=>fn(...args))
  }
}

11.promise.all .allsetteled .race

// Promise.all
Promise.all = function(promises) {
  const res = []
  let count = 0
  const n = promises.length
  return new Promise((resolve, reject) => {
    if (n === 0) return resolve(res)
    for (let i = 0; i < n; i++) {
      Promise.resolve(promises[i])
        .then(value => {
          res[i] = value
          count++
          if (count === n) resolve(res)
        })
        .catch(reject)
    }
  })
}

// Promise.allSettled
Promise.allSettled = function(promises) {
  const res = []
  let count = 0
  const n = promises.length
  return new Promise(resolve => {
    if (n === 0) return resolve(res)
    for (let i = 0; i < n; i++) {
      Promise.resolve(promises[i])
        .then(value => res[i] = { status: 'fulfilled', value })
        .catch(reason => res[i] = { status: 'rejected', reason })
        .finally(() => {
          count++
          if (count === n) resolve(res)
        })
    }
  })
}

// Promise.race
Promise.race = function(promises) {
  const n = promises.length
  return new Promise((resolve, reject) => {
    if (n === 0) return
    for (let i = 0; i < n; i++) {
      Promise.resolve(promises[i])
        .then(resolve)
        .catch(reject)
    }
  })
}

12.array

Array.prototype.myFilter(cb,context=window){
  //filter根据cb返回true/false,过滤掉false的数组项
  const res = []
  const arr = this
  for(let i=0;i<this.length;i++){
    if(cb.apply(context,[this[i],i,this])) res.push(arr[i])
  }
  return res
}

Array.prototype.myMap(cb,context=window){
  const res = []
  for(let i=0;i<this.length;i++){
    res.push(cb.apply(context,[this[i],i,this]))
  }
  return res
}

Array.prototype.myReduce(cb,pre){
  //reduce的cb有pre,cur两个参数
  let res = pre?pre:this[0]
  let startIndex = pre?1:0
  for(let i=startIndex;i<this.length;i++){
    cb.call(null,res,this[i],i,this)
  }
  return res
}

13.大数相加

function add(a ,b) {
    let res = ''
    let carry = 0
    let m = num1.length - 1
    let n = num2.length - 1
    while (m >= 0 || n >= 0 || carry > 0) {
        let cur = (m >= 0 ? parseInt(num1[m]) : 0) + (n >= 0 ? parseInt(num2[n]) : 0) + carry
        carry = cur >= 10 ? 1 : 0
        res = cur % 10 + res
        m--
        n--
    }
    return res
}

 console.log(add('3782647863278468012934670', '23784678091370408971329048718239749083'))
 

14.撤销重做历史状态管理

class UndoRedoStack {
  constructor(initialState = null, maxHistory = 100) {
    this.past = [];
    this.present = initialState;
    this.future = [];
    this.maxHistory = maxHistory;
  }

  setPresent(state) {
    if (this.present !== null && this.present !== state) {
      this.past.push(this.present);
      if (this.past.length > this.maxHistory) this.past.shift();
    }
    this.present = state;
    this.future = [];
  }

  undo() {
    if (this.past.length === 0) return;
    this.future.push(this.present);
    this.present = this.past.pop();
  }

  redo() {
    if (this.future.length === 0) return;
    this.past.push(this.present);
    this.present = this.future.pop();
  }

  get canUndo() { return this.past.length > 0; }
  get canRedo() { return this.future.length > 0; }
}

15.倒计时

import React, { useState, useEffect } from "react";

// 自定义 Hook 的名称必须以 use 开头
function useCountdown(initialTime) {
  // 使用 useState Hook 来创建一个时间和一个更新时间的函数
  const [time, setTime] = useState(initialTime);

  // 使用 useState Hook 来创建一个状态和一个更新状态的函数
  const [isRunning, setIsRunning] = useState(false);

  // 使用 useEffect Hook 来实现倒计时的逻辑
  useEffect(() => {
    // 如果状态为运行并且时间大于零
    if (isRunning && time > 0) {
      // 设置一个定时器,每隔一秒更新一次时间
      const timerId = setTimeout(() => {
        setTime(time - 1);
      }, 1000);
      // 返回一个清理函数,用于取消定时器
      return () => {
        clearTimeout(timerId);
      };
    }
    // 如果状态为停止或者时间等于零
    else {
      // 设置状态为停止
      setIsRunning(false);
    }
  }, [isRunning, time]); // 依赖于状态和时间

  // 定义一个开始倒计时的函数
  function start() {
    setIsRunning(true);
  }

  // 定义一个暂停倒计时的函数
  function pause() {
    setIsRunning(false);
  }

  // 定义一个重置倒计时的函数
  function reset() {
    setTime(initialTime);
    setIsRunning(false);
  }

  // 返回时间和控制函数
  return [time, start, pause, reset];
}

16.数组去重

//1 set
function uniq(arr){
  return [...new Set(arr)]
}
//2 filter
function uniq(arr){
  return arr.filter((cur,i,arr)=>{
    i===arr.indexOf(cur)
  })
}
//3 相邻元素
function uniq(arr){
  arr.sort((a, b) => a - b);
  let newArr = []
  for(let i=0;i<arr.length;i++){
    if(arr[i]===arr[i-1]) continue
    newArr.push(arr[i])
  }
  return newArr
}
//对象数组
function uniq(arr){
  const arr1 = arr.map(JSON.stringify)
  const arr2 = [...new Set(arr1)]
  return arr2.map(JSON.parse)
}
//高级版 对象数组去重
/**
 * 递归地对对象的属性进行字母顺序排序。
 * 
 * 该函数接受一个对象并返回一个新的对象,其属性按字母顺序排序。
 * 如果对象包含嵌套对象或数组,它们也会被递归地按字母顺序排序。
 * 
 * @param obj - 要排序的对象。
 * @returns 返回一个属性按字母顺序排序的新对象。如果输入不是对象,则直接返回输入。
 */
function sortObject(obj) {
    // 检查 obj 是否为对象且不为 null,如果不是,直接返回 obj
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    // 如果 obj 是数组,对每个元素递归调用 sortObject 并返回新数组
    if (Array.isArray(obj)) {
        return obj.map(sortObject);
    }
    // 创建一个新对象以存储排序后的属性
    const sortedObj = {};
    // 获取对象的键,按字母顺序排序,并对每个属性递归调用 sortObject,将结果存储在新对象中
    Object.keys(obj).sort().forEach(key => {
        sortedObj[key] = sortObject(obj[key]);
    });
    // 返回按字母顺序排序属性的对象
    return sortedObj;
}
let arr = [
    { id: { n: 1 }, name: '张三' },
    { name: '张三', id: { n: 1 } },
    { id: { n: 2 }, name: '李四' }
];

const arr1 = arr.map(item => JSON.stringify(sortObject(item)));
const arr2 = [...new Set(arr1)];
const result = arr2.map(item => JSON.parse(item));
console.log(result);

17.柯里化

//高阶函数,利用了闭包对函数做了一层封装
function curry(fn) {
  return function curried(...args){
    if(args.length>=fn.length){
      return fn(...args)
    }else{
      return function (...newArgs){
        return curried(...args,...newArgs)
      }
    }
  }
}

18.add

实现add(1)(2,3)(4)() 和add(1)(2,3)(4)

function add(...args){
  let sum = args.reduce((pre,cur)=>pre+cur)
  function fn(...newArgs){
    if(newArgs.length===0) return sum
    sum += newArgs.reduce((pre,cur)=>pre+cur)
    return fn
  }
  return fn
}

19.compose和pipe

组合多个函数,从右到左,比如:compose(f, g, h) 最终得到这个结果 (...args) => f(g(h(...args))).

// 从右到左:compose
function compose(fns) {
  if (fns.length === 0) return v => v
  if (fns.length === 1) return fns[0]
  return fns.reduce((a, b) => (...args) => a(b(...args)))
}

// 从左到右:pipe
function pipe(fns) {
  if (fns.length === 0) return v => v
  if (fns.length === 1) return fns[0]
  return fns.reduceRight((a, b) => (...args) => b(a(...args)))
}

20.settimeout和setInterval

//setTimeOut模拟setInterval
function mySetInterval(fn,wait){
  let timer = null
  function loop(){
    fn()
    timer = setTimeout(loop,wait)
  }
  timer = setTimeout(loop,wait)
  return {
    cancel:()=>clearTimeout(timer)
  }
}

//setInterval模拟setTimeOut
function mySetTimeOut(fn,wait){
  let timer = setInterval(()=>{
    fn();
    clearInterval(timer)
  },wait)
}

21.并发池

class Scheduler{
  constructor(limit){
    this.limit = limit
    this.queue = []
    this.count = 0
  }

  //当一个request完成后需要加入新的
  runNext(){
    if(!this.queue.length||this.count>=this.limit) return 
    this.count++
    this.queue.shift()().then(()=>{
      this.count--
      this.runNext()
    })
  }

  add(fn,wait){
    //队列中加入新的fn
    const task = ()=>{
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          fn()
          resolve()
        },wait)
      })
    }
    this.queue.push(task)
    this.runNext()
  }
}

22.计算一个对象的层数

function getDepth(obj){
  let res = 1
  function dfs(obj,level){
    if(typeof obj === 'object'){
      for(let key in obj){
        if(typeof obj[key] === 'object'){
          dfs(obj[key],level+1)
        }else{
          res = Math.max(res,level+1)
        }
      }
    }else{
      res = Math.max(res,level)
    }
  }
  dfs(obj,0)
  return res
}

23.对象的扁平化

const obj = {
  a: {
         b: 1,
         c: 2,
         d: {e: 5}
     },
  b: [1, 3, {a: 2, b: 3}],
  c: 3
 }
 
 flatten(obj) 
 // {
 //  'a.b': 1,
 //  'a.c': 2,
 //  'a.d.e': 5,
 //  'b[0]': 1,
 //  'b[1]': 3,
 //  'b[2].a': 2,
 //  'b[2].b': 3
 //   c: 3
 // }

function flattenObj(obj){
  let res = {}
  const isObject = (obj) => typeof obj ==='object' && obj !== null
  function dfs(cur,pre){
    if(isObject(cur)){
      if(Array.isArray(cur)){
        cur.forEach((item,index)=>{
          dfs(item,`${pre}[${index}]`)
        })
      }else{
        for(let key in cur){
          dfs(cur[key],`${pre}${pre?'.':''}${key}`)
        }
      }
    }else{
      res[pre] = cur
    }
  }
  dfs(obj,'')
  return res
}

24.实现(a == 1 && a == 2 && a == 3)为true

const a = {
  i:0,
  toString(){
    return ++this.i
  }
}

let i = 0
Object.defineProperty(window,'a',{
  get:()=>{
  return ++i
}})

//proxy
let value = 1
const a = new Proxy({},{
  get:function(target,name){
    return value++
  }
})


25.将DOM转化成树结构对象

function reverseDom(dom){
  const obj = {
    tag:dom.tagName,
    children:[],
    attrs:{}
  }
  for (let attr of dom.attributes) {
    obj.attrs[attr.name] = attr.value;
  }
  dom.childNodes.forEach((child)=>{
    obj.children.push(reverseDom(child))
  })
  return obj
}

26.将树结构转换为DOM

function treeToDom(vNode){
  if(typeof vNode==='number') return String(vNode)
  if(typeof vNode==='string') return vNode
  const dom = document.createElement(vNode.tag)
  //转换children。attr。
  if(vNode.attrs){
    for(let key in vNode.attrs){
    dom.setAttribute(key,vNode.attrs[key])
  }
  }
  vNode.children.forEach((child)=>{
    dom.appendChild(treeToDom(child))
  })
  return dom
}

27.判断一个对象有环引用

var obj = {
    a: {
        c: [
            1, 2
        ]
    },
    b: 1
}
obj.a.c.d = obj
console.log(cycleDetector(obj)) // true

function cycleDetector(obj){
  const set = new Set()
  function findCircle(obj){
    if(obj&&typeof obj === 'object'){
      if(set.has(obj)) return true
      set.add(obj)
      for(let key in obj){
        if(findCircle(obj[key])) return true
      }
    }
    return false
  }
  return findCircle(obj)
}

28.LazyMan

LazyMan(“Hank”)
Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)
Hi This is Hank!
Eat dinner~
Eat supper~

LazyMan(“Hank”).eat(“supper”).sleepFirst(5)
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper

class LazyMan{
  constructor(name){
    this.name = name
    this.queue = []
    const task = ()=>{
      console.log(`这个人叫做${name}!`)
      this.next()
    }
    this.queue.push(task)
    setTimeout(() => {
      this.next()
    }, 0);
  }
  next(){
    const task = this.queue.shift()
    task && task()
  }
  eat(meal){
    const task = ()=>{
      console.log(`吃了${meal}!`)
      this.next()
    }
    this.queue.push(task)
    return this
  }
  sleepWrapper(wait,flag){
    const task = ()=>setTimeout(()=>{
      console.log(`睡了${wait}秒`)
      this.next()
    },wait*1000)
    //flag为true就是first
    if(flag){
      this.queue.unshift(task)
    }else{
      this.queue.push(task)
    }
  }
  sleep(wait){
    this.sleepWrapper(wait,false)
    return this
  }
  sleepFirst(wait){    
    this.sleepWrapper(wait,true)
    return this
  }
}

29.Promise A+

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise{
  constructor(executor){
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = (value)=>{
      if(this.status!==PENDING) return
      this.status = FULFILLED
      this.value = value
      setTimeout(() => {
        this.onFulfilledCallbacks.forEach((fn)=>fn(value))
      });
    }

    const reject = (reason)=>{
      if(this.status!==PENDING) return
      this.status = REJECTED
      this.reason = reason
      setTimeout(() => {
        this.onRejectedCallbacks.forEach((fn)=>fn(reason))
      });
    }

    try{
      executor(resolve,reject)
    }catch(e){
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r; };
      const promise2 = new MyPromise((resolve,reject)=>{
        if(this.status === FULFILLED){
          setTimeout(() => {
            try{
              const x = onFulfilled(this.value)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          });
        }else if(this.status===REJECTED){
          setTimeout(() => {
            try{
              const x = onRejected(this.reason)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          });
        }else{
        this.onFulfilledCallbacks.push((value) => {
          try {
            const x = onFulfilled(value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
        this.onRejectedCallbacks.push((reason) => {
          try {
            const x = onRejected(reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
        }
      })
      return promise2
  }

  catch(onRejected){
    return this.then(null,onRejected)
  }

  finally(cb){
    return this.then(
      value => MyPromise.resolve(cb()).then(()=>value),
      reason => MyPromise.resolve(cb()).then(()=>{throw reason})
    )
  }

  static resolve(value){
    return new MyPromise(resolve=>resolve(value))
  }

  static reject(reason){
    return new MyPromise((_,reject)=>reject(reason))
  }

  static all(iterable){
    return new MyPromise((resolve,reject)=>{
      const res = []
      const arr = Array.from(iterable)
      if(arr.length===0) return resolve([])
      let count = 0
      arr.forEach((p,i)=>{
        MyPromise.resolve(p).then(
          val=>{
            res[i] = val
            count++
            if(count===arr.length) resolve(res)
          },
          reject
        )
      })
    })
  }

  static race(iterable){
    return new MyPromise((resolve,reject)=>{
      for(const p of iterable){
        MyPromise.resolve(p).then(resolve,reject)
      }
    })
  }

}

function resolvePromise(promise2,x,resolve,reject){
  if(promise2===x) {
    return reject(new TypeError('有环'))
  }
  if((x!==null)&&(typeof x === 'object'||typeof x === 'function')){
    let called = false;
    try{
      let then = x.then
      if(typeof then === 'function'){
        then.call(x,
          y=>{
            if(called) return 
            called = true
            resolvePromise(promise2,y,resolve,reject)
          },
           r => {
          if (called) return;
          called = true;
          reject(r);
        })
      }else{
        resolve(x)
      }
    }catch(e){
          if (called) return;
          called = true;
          reject(e);
    }
  }else{
    resolve(x)
  }
}

30.常用hooks:useState、useEffect、useMemo、useRef

let hooks = []
let index = 0
function useState(initialValue){
    let currentIndex = index
    hooks[currentIndex] = hooks[currentIndex]??initialValue
    const setState = (newValue)=>{
      hooks[currentIndex] = typeof newValue === 'function'? newValue(hooks[currentIndex]):newValue
      render()
    }
    index++
    return [hooks[currentIndex],setState]     
}

function useEffect(fn, deps) {
  const currentIndex = index
  const preDeps = hooks[currentIndex]
  let hasChanged = false

  if (deps === undefined) {
    // 无依赖 -> 每次执行
    hasChanged = true
  } else if (!preDeps) {
    // 首次渲染 -> 执行
    hasChanged = true
  } else {
    // 比较依赖数组
    hasChanged = deps.some((cur, i) => !Object.is(preDeps[i], cur))
  }

  if (hasChanged) {
    queueMicrotask(fn) // 模拟异步执行
    hooks[currentIndex] = deps
  }

  index++
}


function useRef(initialValue){
  const currentIndex = index
  if(!hooks[currentIndex]){
    hooks[currentIndex]={current:initialValue}
  }
  index++
  return hooks[currentIndex]
}

function useMemo(factory,deps){
  const currentIndex = index
  const pre = hooks[currentIndex]
  if(pre){
    const {value:preValue,deps:preDeps} = pre || {}
    if(deps&&preDeps){
      let hasChanged = false
      if(deps.some((cur,i)=>!Object.is(cur,preDeps[i]))){
        hasChanged = true
      }
      if(hasChanged){
        index++
        return preValue
      }
    }
  }

  hooks[currentIndex] = {
    deps,
    value:factory()
  }  
  index++
  return hooks[currentIndex].value
}

31.useRequest

function useRequest<T>(
    requestFn : (...args:any[])=>Promise<T>,
    options?:{
        manual?:boolean,
        deps?:any[]
    }
){
    const {manual = false,deps = []} = options||{}
    const [error,setError] = useState<unknown>(null)
    const [data,setData] = useState<T|null>(null)
    const [loading,setLoading] = useState(false)

    const run = useCallback(async(...args:any[])=>{
        setError(null)
        setLoading(true)
        try{
            const res = await requestFn(...args)
            setData(res)
            return res
        }catch(e){
            setError(e)
            throw e;
        }finally{
            setLoading(false)
        }
    },[requestFn])
    
    useEffect(()=>{
        if(!manual) run()
    },[manual,...deps])

    return {data,error,loading,run}
}

高阶版,包含轮询、取消、回调

export interface UseRequestOptions<TParams extends any[], TData> {
  manual?: boolean;
  deps?: any[];
  defaultParams?: TParams;
  onSuccess?: (data: TData) => void;
  onError?: (error: any) => void;
  pollingInterval?: number; // 轮询间隔
}

export function useRequest<TData = any, TParams extends any[] = any[]>(
  service: (...args: TParams) => Promise<TData>,
  options?: UseRequestOptions<TParams, TData>
) {
  const {
    manual = false,
    deps = [],
    defaultParams = [] as unknown as TParams,
    onSuccess,
    onError,
    pollingInterval,
  } = options || {};

  const [data, setData] = useState<TData | null>(null);
  const [error, setError] = useState<any>(null);
  const [loading, setLoading] = useState(false);
  const timerRef = useRef<number | null>(null);
  const abortController = useRef<AbortController | null>(null);

  const run = useCallback(async (...args: TParams) => {
    if (abortController.current) {
      abortController.current.abort(); // 取消前一次请求
    }
    abortController.current = new AbortController();

    setLoading(true);
    setError(null);
    try {
      const res = await service(...args);
      setData(res);
      onSuccess?.(res);
      return res;
    } catch (err: any) {
      if (err.name === 'AbortError') return;
      setError(err);
      onError?.(err);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [service]);

  // 自动执行
  useEffect(() => {
    if (!manual) {
      run(...defaultParams);
    }
  }, deps);

  // 轮询机制
  useEffect(() => {
    if (!pollingInterval) return;
    const loop = async () => {
      await run(...defaultParams);
      timerRef.current = window.setTimeout(loop, pollingInterval);
    };
    loop();
    return () => {
      if (timerRef.current) clearTimeout(timerRef.current);
    };
  }, [pollingInterval]);

  const refresh = useCallback(() => {
    if (data) run(...defaultParams);
  }, [data, run]);

  return {
    data,
    error,
    loading,
    run,
    refresh,
    cancel: () => abortController.current?.abort(),
  };
}

32.raf实现计时器

function RAFSetTimeOut(fn,delay=0){
  let start = performance.now()
  let timer;
  let stopped = false
  function tick(now){
    if(stopped) return
    if(now-start>=delay){
      fn()
    }else{
      timer = requestAnimationFrame(tick)
      //raf的回调的参数是一个 DOMHighResTimeStamp 参数,用于表示上一帧渲染的结束时间(基于 time origin 的毫秒数),接近于performance.now()
    }
  }
  timer = requestAnimationFrame(tick)
  //clear
  return () => {
    stopped = true;
    if (timer != null) cancelAnimationFrame(timer);
  };
}

function RAFSetInterval(fn,interval){
  let start = performance.now()
  let lastFired = start;
  let timer = null
  let stopped = false;
  function tick(now){
    if(stopped) return
    if(now-lastFired>=interval){
      start = now
      lastFired += interval
    }
    timer = requestAnimationFrame(tick)    
  }
  timer = requestAnimationFrame(tick)
  return ()=>{
    stopped = true
    if(timer) cancelAnimationFrame(timer)
  }
}

33.观察者模式:DOM 事件、MutationObserver

  //被观察者
  class Subject{
    constructor(name,state){
      this.name = name
      this.observers = []
    }

    addObserver(obs){
      this.observers.push(obs)
    }

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

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

  class Observer{
    constructor(name){
      this.name = name
    }
    update(data){
      console.log(`${this.name}${data}`)
    }
  }

  //可以添加一次性观察,异步执行回调功能

34.单例模式

//常见单例:windows。document。redux。全局 EventBus / PubSub
  class Singleton{
    constructor(){
      if(Singleton.instance){
        return Singleton.instance
      }
      this.name = name
      Singleton.instance = this
    }
  }
    //高阶函数把任意类封装为单例
function Singletonify(Class) {
  let instance;
  return function(...args) {
    if (!instance) {
      instance = new Class(...args);
    }
    return instance;
  };
}

35.ajax

function ajax(method,url,params,fn){
  const xhr = new XMLHttpRequest()
  //如果是get,拼接params
  method = method.toUpperCase()
  let pair = []
  for(let key in params){
    pair.push(key+'=' + params[key])
  }
  let getData = pair.join('&')
  if(method==='GET'){
    url = url+'?'+getData
  }
  xhr.open(method,url)
  //如果是post,需要加请求体
  let postData = null
  if(method==='POST'){
    xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
    postData = getData
  }
  xhr.send(postData)

  xhr.onreadystatechange = function (){
    if(this.readyState === 4){
      // 返回的应该是一个对象,这样客户端更好渲染
      fn(JSON.parse(xhr.responseText));
    }
  }
}

36.继承

//es5继承
  function Parent () {
    this.name = 'parent';
    this.play = [1, 2, 3];
  }
  function Child() {
    Parent.call(this);
    this.type = 'child';
  }
  Child.prototype = Object.create(Parent.prototype);
  Child.prototype.constructor = Child;
  
  const c = new Child()
  console.log(c.name)

//es6继承
class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
    
// class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)。

37.入参是tasks,timeout,retries。一次发任意多个请求,如果有失败就重新执行,成功则返回一个promise,直到所有的都成功;超时或者超出最大重试次数抛出异常

function runTasks(tasks, timeout = 3000, retries = 3) {
  // 超时包装器
  const withTimeout = (task, timeout) => {
    return Promise.race([
      task(),
      new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout))
    ]);
  };

  return new Promise(async (resolve, reject) => {
    let attempt = 0;
    let pendingTasks = [...tasks]; // 当前要执行的任务列表

    while (pendingTasks.length && attempt <= retries) {
      attempt++;
      console.log(`第 ${attempt} 次尝试执行 ${pendingTasks.length} 个任务`);

      // 执行所有任务
      const results = await Promise.allSettled(
        pendingTasks.map(task => withTimeout(task, timeout))
      );

      const failed = [];
      const successes = [];

      results.forEach((res, i) => {
        if (res.status === 'fulfilled') {
          successes.push(res.value);
        } else {
          failed.push(pendingTasks[i]); // 失败任务保留,稍后重试
        }
      });

      if (failed.length === 0) {
        return resolve(successes);
      }

      console.warn(`${failed.length} 个任务失败,准备重试...`);

      // 达到最大重试次数
      if (attempt >= retries) {
        return reject(new Error(`任务失败,已达到最大重试次数 ${retries}`));
      }

      pendingTasks = failed; // 仅重试失败任务
    }
  });
}

38.有一个任务队列,每项是一个函数,实现一个方法使得每项任务间隔时间超过150ms,且该任务队列一直在不停的添加任务

class TaskQueue {
  constructor(interval = 150) {
    this.queue = [];
    this.running = false;
    this.interval = interval;
  }

  add(task) {
    // task 是一个函数,可能返回 Promise
    this.queue.push(task);
    if (!this.running) this.run();
  }

  async run() {
    this.running = true;

    while (this.queue.length) {
      const task = this.queue.shift();
      try {
        await task(); // 任务可能是异步的
      } catch (e) {
        console.error('任务出错:', e);
      }
      await new Promise(res => setTimeout(res, this.interval));
    }

    this.running = false;
  }
}

39.洗牌

function shuffle(arr) {
  const res = arr.slice() // 不修改原数组
  for (let i = res.length - 1; i > 0; i--) {
    // 生成 [0, i] 之间的随机索引
    const j = Math.floor(Math.random() * (i + 1))
    // 交换位置
    ;[res[i], res[j]] = [res[j], res[i]]
  }
  return res
}

40.arrToTree

const arr = [
  { id: 1, name: '根节点', parentId: null },
  { id: 2, name: '子节点1', parentId: 1 },
  { id: 3, name: '子节点2', parentId: 1 },
  { id: 4, name: '子节点1-1', parentId: 2 },
  { id: 5, name: '子节点1-2', parentId: 2 },
]
function arrToTree(arr,treeRoot = null){
  const tree = []
  const map = new Map()
  arr.forEach((node)=>{
    map.set(node.id,{
      ...node,
      children:[]
    })
  })
  arr.forEach((item)=>{
    const node = map.get(item.id)
    if(node.parentId===treeRoot){
      tree.push(node)
    }else{
      const parent = map.get(node.parentId)
      if(parent){
        parent.children.push(node)
      }
    }
  })
  return tree
}

41. 快速排序

function sortArray(nums) {
    if(nums.length<=1) return nums;
    let pivot = Math.floor(Math.random() * nums.length)
    const left = []
    const right = []
    const middle = []
    for(let i=0;i<nums.length;i++){
        if(nums[i]>nums[pivot]){
            right.push(nums[i])
        }else if(nums[i]<nums[pivot]){
            left.push(nums[i])
        }else middle.push(nums[i])
    }
    return [...sortArray(left),...middle,...sortArray(right)]
};

42.冒泡排序

function sortArray(nums) {
    //冒泡
    const n = nums.length
    for (let i = 0; i < n - 1; i++) {
        for (let j = 0; j < n - 1 - i; j++) {
            if (nums[j] > nums[j + 1]) {
                [nums[j], nums[j + 1]] = [nums[j + 1], nums[j]]
            }
        }
    }
    return nums
};

42.插入排序

function sortArray(nums) {
    //插入,每次把元素放进已排序区间中的正确位置,适合基本有序
    const n = nums.length
    for (let i = 1; i < n; i++) {
        let key = nums[i]
        let j = i-1
        while(j>=0&&nums[j]>key){
            nums[j+1]=nums[j]
            j--
        }
        nums[j+1]=key
    }
    return nums
};

43.选择排序

function sortArray(nums) {
    //选择,每次找未排序区间的最小值
    const n = nums.length
    for (let i = 0; i < n - 1; i++) {
        let min = i
        for(let j=i+1;j<n;j++){
            if(nums[j]<nums[min]){
                min = j
            }
        }
        [nums[min],nums[i]]=[nums[i],nums[min]]
    }
    return nums
};

44 pick

    type MyPick<T,K extends keyof T> = {
        [P in K]:T[P]
    }

45 omit

    type MyOmit<T,K extends keyof T> = {
        [P in Exclude<keyof T,K>] : T[P]
    }

46 红绿灯

const traffic_light = (color, duration) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('traffic_light', color);
            resolve()
        }, duration)
    })
}
const main = () => {
    Promise.resolve()
        .then(() => traffic_light('green', 5000))
        .then(() => traffic_light('yellow', 1000))
        .then(() => traffic_light('red', 2000))
        .then(() => {
            main()
        })
}
main();

实现一个sleep函数

写一个点赞组件,按钮点赞和取消

怎么实现:用户右滑时,逐步展示操作步骤图(每张图代表一个步骤),左滑时退回上一步,类似一个可控的进度条式动画。

实现组件,一天有24小时,prop中传进来的值是想要剔除的时间段,比如[1,2]和[5,24],你会怎么设计这个组件

实现一个异步执行的wait

ts实现omit infer

实现一个第一个不会触发的useeffect

怎么实现一个方法使得localstorage 有过期时间