面试常见手撕题目

236 阅读14分钟
  1. bind call apply
bind
    Function.prototype.myBind = function(context,..args){
        const fn = this
        return function(...innerArgs){
            return fn.apply(context,[...args,...innerArgs])
        }
    }
 call
     Function.prototype.myCall = function(context , ...args){
         context = context || window
         context.fn = this
         const res = context.fn(...args)
         delete context.fn;
         return res
     }
  apply
      Function.prototype.myApply = function(context , argsArray){
          context = context || window
          context.fn = this;
          const res = context.fn(...argsArray)
          delete context.fn;
          return res
      }
  1. instanceof
function myInstanceof(obj,func){
    if(typeof obj !== 'object' || obj === null){
        return false
    }
    let proto = Object.getPrototypeOf(obj)
    while(proto !== null){
        if(proto === func.prototype){
            return true
        }
        proto = Object.getPrototypeOf(proto)
    }
    return false
}
  1. new
 function myNew (fn){
     let obj = {};
     obj._proto_ = fn.prototype;
     let result = fn.call(this, ...args)
     return typeof result === 'Object' ? result : obj;
 }
  1. 深拷贝
**递归实现深拷贝**
function deepCopy(obj,cache = new WeakMap()){
    if(typeof obj !=='object' || obj === null){
        return obj
    }
    //检查缓存,避免循环引用导致无限递归
    if(cache.has(obj)){
        return cache.get(obj)
    }
    let copy
    if(obj instanceof Date){
        copy = new Date(obj)
    }else if(obj instanceof RegExp){
        copy = new RegExp(obj.source,obj.flags);
    }else if(typeof obj === 'function'){
        copy = function () {
            return obj.apply(this,arguments)
        }
    }else if(Array.isArray(obj)){
        copy = []
        cache.set(obj,copy)
        for(let i = 0; i < obj.length;i++){
            copy[i] = deepCopy(obj[i],cache)
        }
    }else{
        copy = Object.create(Object.getPrototypeOf(obj))
        cache.set(obj,copy);
    }
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            copy[key] = deepCopy(obj[key],cache)
        }
    }
    return copy
}
这个深拷贝函数基于递归实现,在对象或数组中进行深度复制。它使用了一个缓存 `cache`(使用 `WeakMap`)来存储已经复制过的对象,避免处理循环引用时的无限递归问题。

在处理特殊类型时:

-   对于日期类型,通过创建一个新的 `Date` 对象来复制。
-   对于正则表达式类型,复制其源字符串和标志。
-   对于函数类型,创建一个新的函数,保留原函数的作用域和参数列表。
-   对于数组类型,创建一个新的空数组,并递归地复制每个元素。
-   对于对象类型,创建一个新对象,并递归地复制每个属性。

使用这个深拷贝函数可以处理包括日期、函数、正则表达式和循环引用的复制:

**非递归实现深拷贝**
可以使用迭代的方式来遍历对象和数组,将每个属性或元素复制到新的对象或数组中。下面是一个非递归实现深拷贝的基本思路:

1.  创建一个栈(或队列),将要拷贝的源对象(或数组)放入栈中。
1.  循环遍历栈,每次弹出一个对象(或数组),并创建一个对应的新对象(或数组)。
1.  遍历源对象(或数组)的属性或元素,将它们复制到新对象(或数组)中,如果属性或元素仍然是对象(或数组),将其入栈。
1.  重复步骤 2 和 3,直到栈为空。

function deepCopy2 (source) {
      const stack = [{ source, target: undefined }];
      const copy = new Map(); // 用于存放已拷贝过的对象,避免循环引用问题

      while (stack.length > 0) {
        const { source, target } = stack.pop();
        let newTarget = target;

        if (!newTarget) {
          if (Array.isArray(source)) {
            newTarget = [];
          } else if (typeof source === 'object' && source !== null) {
            newTarget = {};
          } else {
            newTarget = source;
          }
        }

        if (typeof source === 'object' && source !== null) {
          copy.set(source, newTarget); // 记录已拷贝的对象
          for (const key in source) {
            if (Object.hasOwnProperty.call(source, key)) {
              if (copy.has(source[key])) {
                newTarget[key] = copy.get(source[key]); // 处理循环引用
              } else {
                stack.push({ source: source[key], target: newTarget[key] });
              }
            }
          }
        }

        if (Array.isArray(source)) {
          newTarget.length = source.length;
        }
      }

      return copy.get(source);
    }

    // 示例用法
    const obj = {
      a: 1,
      b: [2, 3, { c: 4 }],
      d: { e: 5 }
    };

    const clonedObj = deepCopy(obj);
    console.log(clonedObj);
  1. 浅拷贝
function shallowCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  
  const copy = Array.isArray(obj) ? [] : {};
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = obj[key];
    }
  }
  
  return copy;
}

const originalObject = { name: "John", age: 30 };
const shallowCopyObject = shallowCopy(originalObject);
  1. promise promise.all promise.race promise.allSettled
**Promise:**
       function MyPromise (executor) {
      //初始状态为PENDING
      this.status = 'PENDING';
      //Promise的结果值
      this.value = undefined;
      //存储成功回调函数的数组
      this.onResolvedCallbacks = []
      //存储失败回调函数的数组
      this.onRejectedCallbacks = []

      //定义resolve函数,用于将promise状态改变为fulfilled
      const resolve = (value) => {
        //状态只能从PENDING 变为fulfilled
        if (this.status === "PENDING") {
          this.status = "fulfilled"
          this.value = value;
          //执行所有已注册的成功回调函数
          this.onResolvedCallbacks.forEach((callback) => callback(this.value))
        }

      };
      //定义reject函数,用于将promise状态改变为rejected
      const reject = (reason) => {
        if (this.status === 'PENDING') {
          this.status = 'rejected'
          this.value = reason
          this.onRejectedCallbacks.forEach((callback) => callback(this.value))
        }
      };
      //执行exeuctor函数,并传入resolve和reject函数作为参数
      try {
        executor(resolve, reject)
      } catch (err) {
        // 如果在执行executor函数时捕获到异常,将Promise状态改为rejected
        reject(error);
      }
    }
    // then方法用于注册回调函数,并返回一个新的promise对象
    MyPromise.prototype.then = function (onResolved, onRejected) {
      //创建一个新的promise对象
      const newPromise = new MyPromise((resolve, reject) => {
        const resolvedHandler = typeof onResolved === "function" ? onResolved : (value) => value;
        const rejectedHandler = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }
        //如果promise状态已经是fulfilled,则立即执行成功回调函数
        if (this.status === 'fulfilled') {
          setTimeout(() => {
            try {
              const result = resolveHandler(this.value);
              resolve(result)
            } catch (err) {
              reject(err)
            }
          }, 0)
        }
        //如果promise状态已经是rejectd,则立即执行失败回调
        if (this.status === "rejected") {
          setTimeout(() => {
            try {
              const result = rejectedHandler(this.value)
              resolve(result)
            } catch (error) {
              reject(error)
            }
          }, 0)
        }
        //如果promise的状态是pending,则将成功回调和失败回调函数存储起来
        if (this.status === "PENDING") {
          this.onResolvedCallbacks.push((value) => {
            setTimeout(() => {
              try {

                const result = resolvedHandler(value)
                // consoke, log(result)
                resolve(result)
              } catch (error) {
                reject(error)
              }

            }, 0)

          })
          this.onRejectedCallbacks.push((reason) => {
            setTimeout(() => {
              try {
                const result = rejectedHandler(reason)
                resolve(result)
              } catch (error) {
                reject(error)
              }
            }, 0)
          })
        }
      })
      return newPromise;
    }
    
    //实例用法
     const promise = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve("Hello, Promise!");
      }, 1000);
    });

    promise
      .then((value) => {
        console.log(value);
        return "New Value";
      })
      .then((newValue) => {
        console.log(newValue);
      });
      
      
**Promise.allSettled**
function allSettled(promises){
    return new Promise((resolve) => {
        const results = []
        let completedCount = 0
        function checkCompletion(){
            if(completedCount === promises.length){
                resolve(results)
            }
        }
        promises.forEach((promise,index) => {
            Promises.resolve(promise).then((value) =>{
                results[index] = {status: 'fulfilled' , value};
                completedCount++
                checkCompletion()
            }).catch((reason)=>{
                results[index] = {status: "rejected",reason};
                completedCount++
                checkCompletion()
            })
        })
    })
  
}
上述代码中,`allSettled` 函数接受一个由 Promise 对象组成的数组 `promises`,并返回一个新的 Promise 对象。

在内部实现中,我们创建了一个空数组 `results` 来存储每个传入 Promise 的状态和结果。使用 `completedCount` 变量来记录已完成的 Promise 数量。

然后,我们使用 `forEach` 方法遍历 `promises` 数组中的每个 Promise。对于每个 Promise,我们使用 `Promise.resolve` 包装它,以确保它始终是一个 Promise 对象。

对于每个包装后的 Promise,我们使用 `then` 方法添加一个成功处理程序,将结果存储在 `results` 数组中,并递增 `completedCount`。然后,我们检查是否所有 Promise 都已完成,如果是,则通过 `resolve` 将结果传递给返回的 Promise。

如果 Promise 失败,我们使用 `catch` 方法添加一个拒绝处理程序,将原因存储在 `results` 数组中,并递增 `completedCount`。然后,同样检查是否所有 Promise 都已完成,如果是,则通过 `resolve` 将结果传递给返回的 Promise。

这样,我们就实现了一个简单的 `Promise.allSettled` 函数,它返回一个 Promise 对象,该对象在所有输入的 Promise 对象都已解决(无论成功还是失败)后才会被解决,返回的结果是一个包含每个 Promise 结果的数组,每个结果对象包含 `status` 表示状态("fulfilled" 表示成功,"rejected" 表示失败)以及对应的 `value``reason`。

**实现Promise.retry**
三个参数版本:
function promiseRetry(promiseFn,maxAttempts,delay = 0){
    return new Promise((resolve ,reject) => {
        let attempts = 0;
        function attempt(){
            attempts++
            if(attempts > maxAttempts){
                reject(new Error('已经达到最大尝试次数'))
                return;
            }
            promiseFn().then(resolve).catch(() => {
                if(delay> 0){
                    setTimeout(attempt,delay)
                }else{
                    attempt()
                }
            })
        }
        attempt()
    })
}
//调用示例
 // 模拟一个可能失败的异步操作
    function someAsyncOperation () {
      return new Promise((resolve, reject) => {
        // 随机成功或失败
        if (Math.random() < 0.5) {
          resolve('Success');
        } else {
          reject(new Error('Failed'));
        }
      });
    }

    // 使用 promise.retry 进行重试
    promiseRetry(someAsyncOperation, 3, 1000)
      .then((result) => {
        console.log('Operation succeeded:', result);
      })
      .catch((error) => {
        console.log('Operation failed after maximum retry attempts:', error.message);
      });
以上代码定义了一个 `promiseRetry` 函数,它接受三个参数:`promiseFn`(要执行的函数,返回一个 Promise)、`maxAttempts`(最大尝试次数)和可选的 `delay`(每次尝试之间的延迟时间,默认为 0)。

在内部实现中,我们使用了递归调用的方式来重试执行 `promiseFn`,直到达到最大尝试次数或成功执行为止。

如果 `promiseFn` 在执行过程中抛出错误,则将根据延迟时间 `delay` 决定是否延迟一段时间后进行下一次尝试。如果 `delay` 大于 0,则使用 `setTimeout` 设置延迟;否则立即进行下一次尝试。

如果达到最大尝试次数 `maxAttempts` 仍然没有成功执行,将会返回一个被拒绝的 Promise,并带有一个包含 "Maximum retry attempts reached" 错误信息的错误对象。
 
只接受两个参数
function promiseRetry(promiseFn,maxAttempts){
    return new Promise((resolve,reject)=>{
        let attempts = 0
        function attempt(){
            attempts++
            if(attemps > maxAttempts){
                reject('已达到最大次数')
                return;
            }
            promiseFn().then(resolve).catch(attempt)
        
        }
        attempt()
    })
}
//调用示例
 function someAsyncOperation () {
      return new Promise((resolve, reject) => {
        // 随机成功或失败
        if (Math.random() < 0.5) {
          resolve('Success');
        } else {
          reject(new Error('Failed'));
        }
      });
    }

    // 使用 promise.retry 进行重试
    promiseRetry(someAsyncOperation, 3)
      .then((result) => {
        console.log('Operation succeeded:', result);
      })
      .catch((error) => {
        console.log('Operation failed after maximum retry attempts:', error.message);
      });
上述代码中,`promiseRetry` 函数仅接收两个参数:`promiseFn`(要执行的函数,返回一个 Promise)和 `maxAttempts`(最大尝试次数)。

在内部实现中,我们仍然使用递归调用的方式来重试执行 `promiseFn`,直到达到最大尝试次数或成功执行为止。

如果 `promiseFn` 在执行过程中抛出错误,则会立即进行下一次尝试,而无需延迟。

如果达到最大尝试次数 `maxAttempts` 仍然没有成功执行,将会返回一个被拒绝的 Promise,并带有一个包含 "Maximum retry attempts reached" 错误信息的错误对象。

promise.all()
接受一个包含多个 Promise 的可迭代对象(如数组),并返回一个新的 Promise,该 Promise 在所有输入 Promise 都解决(resolve)时才解决,如果有一个 Promise 被拒绝(reject),则整个 Promise.all 也会被拒绝。
function myPromiseAll(promises){
    return new Promise((resolve,reject) => {
        const results = [];
        let promisesLen = promises.length
        if(promisesLen === 0){
            resolve(results)
            return;
        }
        promises.forEach((promise,index) => {
            promise.then((res) =>{
                results[index] = res
                promisesLen--
                if(promisesLen === 0){
                    resolve(results)
                }
            }).catch((err) => {
                reject(err)
            })
        })
        
    })
}
Promise.race
接受一个包含多个 Promise 的可迭代对象,返回一个新的 Promise,该 Promise 会在第一个输入 Promise 解决或拒绝时解决或拒绝。
function myPromiseRace(promises){
    return new Promise((resolve,reject) => {
        promises.forEach((promise) =>{
            promise.then((result) =>{
                resolve(result)
            }).catch((err) =>{
                reject(err)
            })
        })
    })
}
Promise.any
接受一个包含多个 Promise 的可迭代对象,返回一个新的 Promise,该 Promise 会在第一个被解决的输入 Promise 解决时解决,如果所有输入 Promise 都被拒绝,则 Promise.any 也会被拒绝
function myPromiseAny(promises){
    return new Promise((resolve,reject) =>{
        let errors = []
        promises.forEach((promise)=>{
            promise.then((res)=> {
                resolve(res)
            }).catch((err) => {
                errors.push(err)
                if(errors.length === promises.length){
                    reject(new Error(errors , '所有promises都被拒绝'))
                }
            })
        })
    })
}
promise.allSettled
接受一个包含多个 Promise 的可迭代对象,返回一个新的 Promise,该 Promise 在所有输入 Promise 都解决或拒绝后解决,返回一个包含每个 Promise 结果的对象数组。
function myPromiseAllSettled(promises){
    return new Promise((resolve) => {
        const results = []
        let promisesLen = promsies.length
        if(promisesLen === 0){
            resolve(results)
            return;
        }
        promises.forEach((promise,index ) => {
            promise.then((res) => {
                results[index] = {status: "fulfilled",value: result}
            }).catch((err) =>{
                results[index] = {status:"rejected",reason: err}
            }).finally(() =>{
                promisesLen--
                if(promisesLen === 0){
                    resolve(results)
                }
            })
        })
    })
}

  1. promise并发池
Promise并发池的作用是当有大量Promise需要执行时,可以确保同时执行的Promise数量不超过设置的最大并发数,并且可以在某个Promise状态改变后自动执行新的Promise,即维持并发数始终为最大值(除去开始和结束阶段)。
两种实现思路:
Promise递归解法思路:

1.  每层递归都往 run 里加 请求
1.  递归过程中,如果 run 达到临界,使用 Promise.race 来触发 递归。
1.  请求的 回调为从 run 中删除 自身
1.  递归终点是 所有的 request 都已经加完,返回 resolve。此时,就像盗梦空间一样,这个resolve 会不断的被返回。(此刻,请求并没有结束)。最外层会接受这个resolve,使用Promise.all 等待所有请求结束,执行回调函数。
function limitPromise(requesets = [],max = 1,callback = () =>{}){
    let run = []
    let next = 0
    let doit = () =>{
        if(next === requests.length){ //递归终止条件
            return Promise.resolve()
        }
        let thisRequest = requests[next++]
        run.push(thisRequest.then(requestRes =>{//将请求推入到run数组
            console.log(requestRes)
            //删除自己
            run.splice(run.indexOf(thisRequest),1)
        }))
        let res = null
        if(run.length === max){
            res = Promise.race(run)//需要等待一个请求到达
            
        }else{
            res = Promise.resolve()//不需要等待
        }
        return res.then(() => doit())
    }
    doit().then(()=> {
        Promise.all(run).then(() => callback())
    })
}
let request = (delay, id) => { return new Promise((resolve) => { setTimeout(resolve, delay, id) }) }
let test_requests = [ request(6000, 1), request(3000, 2), request(4000, 3), request(6000, 4), request(1000, 5), request(2000, 6), ]
limitPromise(test_requests, 3, () => console.log("requeset end"))
  
升级版 使用asyncawait
递归算法都有迭代写法。实际上这个题目,使用迭代更好理解。 这里使用await语法来实现 run 满的等待。 await promise 会暂停函数执行,直到 promise 成功。 这里的逻辑非常简单,就是便利请求数组,把它们加到run 中。但是,由于max的存在,需要在 run.length === max 时,使用await等待,race 到达。同样,添加完后,需要等待 run 里所有请求结束,然后执行回调函数。

async function limitAsync(requests = [],max = 1,callback =() => {}){
    let run = [],i = 0
    for(const requests of requests){
        run.push(request.then((requesetRes) =>{ //依次推入到run
            console.log(requestRes)
            run.splice(fun.indexOf(request),1)
        })
        if(run.length === max){
            console.log('wait)
            await Promise.race(run)
        }
        
    }
    Promise.all(run).then(()=> callback())
}
limitPromise(test_requests, 3, () => console.log("requeset end"))
  

  1. 圣杯布局
  2. JS阻塞方式实现异步任务队列
有个需求,需要实现一个异步任务队列,并依次处理队列中的所有任务,具体如下:

1.  随机时间增加异步任务到队列中
1.  队列中的任务按照先进先出的规则依次执行
1.  任务为异步请求,等一个执行完了再执行下一个
<body>
    <button onclick="clickNe()">点击</button>
</body>
//异步请求队列
const queue = []
//用于模拟不同的返回值
let index = 0
//标志是否正在处理队列中的请求
let running = false
//使用setTimeout模拟异步请求
function request(index){
    return new Promsie(function(resolve) {
        setTimeout(()=> {
            resolve(index)
        }, 1000)
    })
}
//连续点击触发异步请求,加入任务队列
function clickMe(){
    addQueue(() => request(index++))
}
//当队列中任务数大于0,开始处理队列中的人物
function addQueue(item){
    queue.push(item)
    if(queue.length >  0 && !running){
        running = true
        process()
    }
}
function process(){
    const item = queue.shift()
    if(item){
        item().then(res =>{
            console.log('已经处理事件' + res)
            process()
        })
    }else{
        running = false
    }
    
}
  1. 轮播图
  2. 发布订阅
   class EventEmitter{
       constructor(){
           this.event = {}
       }
       on(type,cb){
           if(!this.event[type]){
               this.event[type] = [cb]  
           }else{
               this.event[type].push(cb)
           }
       }
       once(type,cb){
           let fn = () => {
               cb()
               this.off(type,fn)
           }
           this.on(type,fn)
       
       }
       emit(type,...args){
           if(!this.event[type]){
               return
           }else{
               this.event[type].forEach(cb =>{
                   cb(...args)
               })
           }
       }
       off(type,cb){
           if(!this.event[type]){
               return
           }else{
               this.event[type] = this.event[type].filter(item => item !== cb)
           }
       }
   }
  1. 超时重传
function fun(){
    let n = Math.random()
    return new Promise((resolve,reject) => {
        settimeout(() =>{
            if(n > 0.7){
                resolve(n)
            }else{
                reject(n)
            }
        },500)
    })
}
function retry(fun,times){
    new Promsie(async (resolve,reject) => {
        while(times--){
            try{
                const res = await fun()
                resolve(res)
                console.log('成功',res)
                break
            
            } catch(err){
                console.log('失败一次',err)
                if(!times){
                    reject(err)
                }
            }
        }
    
    }).catch(() => {
        console.log(失败了)
    })
}
  1. promise红绿灯
function green(){
    console.log('green')
}
function yellow(){
    console.log('yellow')
}
function red(){
    console.log('red')
}
const light = function(timer , cb){
    return new Promise((resolve,reject) =>{
        settimeout(() => {
            cb()
            resolve()
        } , timer)
    })
}
const step = function(){
    Promise.resolve().then(()=>{
        return light(300,yellow)
    }).then(() =>{
        return light(300,green)
    }).then(() => {
        return light(300,red)
    }).then(() =>{
        return step
    })
}

11.拖拽元素 12.实现reduce,flat

reduce
Array.prototype.myReduce = function(callback,initValue){
    if(this.length === 0 && initValue === undefined){
        throw new Error("error")
    } 
    let acc = initValue !== undefined ? initValue : this[0]
    for(let i = initValue !== undefiend ? 0 : 1;i <this.length;i++){
        acc = callback(acc . this[i] , i , this)
    }
    return acc
}

flat
不指定层级
Array.prototype.myFlat1 = function() {
    const result = []
    this.forEach(item => {
        if(Array.isArray(item)){
            result.push(...item.myFlat())
        }else{
            result.push(item)
        }
    })
    return result
}
指定层级
Array.prototype.myFlat2 = function(depth = 1){
    if(depth === 0){
        return this.slice()
    }
    const result = []
    this.forEach(item =>{
        if(Array.isArray(item)){
            result.push(...item.myFlat2(depth - 1))
        }else{
            result.push(item)
        }
    })
    return result
}

13.排序算法

 冒泡排序
 function bubbleSort(arr){
     const n = arr.length;
     for(let i = 0; i < n - 1;i++){
         for(let j = 0; j <n - i - 1;j++){
             if(arr[j] > arr[j+1){
                 [arr[j] , arr[j+1]] = [arr[j + 1], arr[j]]
             }
         }
     }
     return arr
 }
 // 时间复杂度:最好情况 O(n),平均情况 O(n^2),最坏情况 O(n^2) // 空间复杂度:O(1)
 插入排序
 function insertSort(arr){
     const n = arr.length
     for(let i = 1;i < n;i++){
         let current =  arr[i]
         let j = i - 1
         while(j >= 0 && arr[j] < current){
             arr[j+ 1]= arr[j]
             j--
         }
         arr[j + 1] = current
     }
 }
 时间复杂度:最好情况 O(n),平均情况 O(n^2),最坏情况 O(n^2) // 空间复杂度:O(1)
 
 快速排序
 function quickSort(arr){
     if(arr.length <= 1){
         return arr
     }
     const first = arr[0]
     const left = [] , right = []
     for(let i = 1; i < arr.length;i++){
         if(arr[i] < first){
             left.push(arr[i])
         }else{
             right.push(arr[i])
         }
     }
     return [...quickSort(left),first,...quickSort(right)]
 }
 / 时间复杂度:最好情况 O(n log n),平均情况 O(n log n),最坏情况 O(n^2) // 空间复杂度:O(log n)(递归调用栈的空间)
 
 归并排序
 function sort(left,right){
     let result = []
     let leftIndex = 0, rightIndex = 0;
     while(leftIndex < left.length && rightIndex < right.length){
         if(left[leftIndex] < right[rightIndex]){
             res.push(left[leftIndex])
             leftIndex++
         }else{
             result.push(right[rightIndex])
             rightIndex++
         }
     }
     return result.concat(left.slice(leftIndex),right.slice(rightIndex))
 }
 function mereSort(arr){
     if(arr.length <= 1){
         return arr;
     }
     const mid = Math.floor(arr.length/ 2)
     const left = arr.slice(0 , middle)
     const right = arr.slice(middle);
     return merge(mergeSort(left) , mergeSort(right))
 }

14.实现eventBus

class EventBus{
    constructor(){
        this.event = Object.create(null)
    }
    on(name,fn){
        //一个事件可能有多个监听者
        if(!this.event[name]){
            this.event[name] = []
        }
        this.event[name].push(fn)
    }
    //触发事件
    emit(name,...args){
        //给回调函数传参
        this.event[name] && this.event[name].forEach(fn=> {
            fn(...args)
        })
    };
    //只被触发一次的事件
    once(name,fn){
        //在这里同时完成了对该事件的注册,对该事件的触发,并在最后取消该事件
        const cb = (...args) => {
            //触发
            fn(...args)
            //取消
            this.off(name,fn)
        }
        //监听
        this.on(name,cb)
    }
    //取消事件
    off(name,offcb){
        if(this.event[name]){
            let index = this.event[name].findIndex((fn) => {
                return offcb === fn
            })
            this.event[name].splice(index,1)
            if(!this.event[name].length){
                delete this.event[name];
            }
        }
    }
}

15.实现vue-router

function Router(options){
    //定义routes对象,用于存储路由配置
    this.routes = options.routes || [];
    //初始化currentRoute变量,用于存储当前路由信息
    this.currentRoute = {};
    //监听浏览器历史记录的变化,以更新currentRoute
    window.addEventListener('popstate',() => {
        this.currentRoute ={
            path: window.location.pathname,
            params:{}
        };
        this.matchRoute()
    })
    //定义matchRoute方法,用于匹配当前路径与路由配置,并执行相应的回调函数
    Router.prototype.matchRoute = function() {
        const route = this.routes.find(route =>route.path === this.currentRoute.path)
        if(route){
            route.callback(this.currentRoute)
            
        }else{
            console.log('目标路由不存在')
        }
    }
    //定义push方法,用于切换路由并更新currentRoute
    Router.prototype.push = function(path){
        this.currentRoute = {
            path:path,
            params:{}
            
        };
        window.history.pushState({},"",path);
        this.matchRoute()
    }
}

16.手撕vuex

function Store(options){
    //定义state对象,用于存储状态
    this.state = options.state || {}
    //定义getters对象,用于定义获取计算属性的函数
    this.getters = {}
    const computed = options.computed || {}
    for(const key in computed){
        if(computed.hasOwnProperty(key)){
            this.getters[key] = computed[key].bind(null,this.state);
        }
    }
    //定义mutations对象,用于定义修改状态的方法
    this.mutations = options.mutations || {}
    //定义actions对象,用于定义执行异步操作的方法
    this.actions = options.actions || {}
    //定义commit方法,用于触发mutations中的方法来修改状态
    Store.prototype.commit = function(type,payload){
        if(this.mutations[type]){
            this.mutations[type](this.state,payload)
        }else{
            console.log('不存在')
        }
    }
    //定义dispatch方法,用于触发actions重点方法来执行异步操作
    Store.prototype.dispatch = function(type,payload){
    if(this.actions[type]){
        return this.actions[type](this,payload)
    }else{
        console.log('报错')
        return Promise.reject(new Error('err'))
    
    }
    
    }
    
}
// 示例用法: const store = new Store({ state: { count: 0, }, computed: { doubleCount: function (state) { return state.count * 2; }, }, mutations: { increment: function (state, payload) { state.count += payload; }, }, actions: { asyncIncrement: function (store, payload) { return new Promise((resolve, reject) => { setTimeout(() => { store.commit('increment', payload); resolve(); }, 1000); }); }, }, }); console.log(store.state.count); // 输出: 0 console.log(store.getters.doubleCount); // 输出: 0 store.commit('increment', 2); console.log(store.state.count); // 输出: 2 store.dispatch('asyncIncrement', 3).then(() => { console.log(store.state.count); // 输出: 5 });

17.settimeout实现setInterval

function newSetInterval(callback,interval){
    const wrapper = () => {
        callback()
        setTimeout(wrapper,interval)
    };
    setTimeout(wrapper,interval)
}
let count = 0
const intervalTime = 1000
newSetInterval(() => {
    count++
    console.log(`Count: ${count}`)
    if(count >= 5){
        console.log("newSetInterval finished")
    }

},intervalTime)
在这个示例中,`customSetInterval` 函数接受一个回调函数和一个时间间隔作为参数。它通过递归调用自身和 `setTimeout` 来实现定时循环。在每次回调函数执行后,它会再次设置一个 `setTimeout` 来触发下一次回调函数执行。

需要注意的是,与 `setInterval` 不同,`customSetInterval` 的延迟时间是在每次回调函数执行完成后才会开始计算。这可能导致一些微小的时间偏移,但在大多数情况下,它是可以接受的。

18.实现分片上传

//假设有一个上传函数uploadChunk
async function uploadLargrFile(file){
    const chunkSize = 1 * 1024 * 1024;//每个分片1mb
    const totalChunks = MAth.ceil(file.size / chunkSize)
    for(let i = 0;i < totalChunks;i++){
        const start = i * chunkSize
        consy end = Math.min(start + chunkSize,file.size)
        const chunk = file.slice(start,end)
        await uploadChunk(chunk)
    }
    console.log('上传成功')
}

19.防抖和节流

//防抖
function debounce(func,delay){
    let timeoutId;
    return function(...args){
        clearTimeout(timeoutId)
        timeoutId = setTimeout(()=>{
            func.apply(this,args)
        } , delay)
    }
}
//节流
function throttle(func ,interval){
    let lastTime = 0;
    return function(...args){
        const currentTime = Date.now();
        if(currentTime - lastTime >= interval){
            func.apply(this,args)
            lastTime = currentTime
        }
    }
}

function throttle(func, interval) {
    let timer = false; 
    return function (...args) { 
        if (!timer) { 
                timer = setTimeout(() => { func.apply(this, args); timer = false; }, interval); 
        }
    }; 
}

20.手写一个getUserInfo函数,向后端发起请求,要求多次调用getUserInfo只返回第一次请求的结果,如果在第一次请求过程中再次调用,等待第一次请求的结果,即避免请求重新发送

let userInfoPromise = null
function getUseInfo() {
    if(!userInfoPromise){
        //第一次调用,发起请求,并将promise存储起来
        UserInfoPromise= new Promise((resolve,reject) =>{
            //在这里发起向后端的请求,获取用户信息
            //假设请求返回的数据是user
            setTimeout(() =>{
                const user = {id : 1 , name: 'john'}
                resolve(user)
            } , 1000)//模拟请求延迟
        })
    }
    return userInfoPromise;
}
// 示例用法 getUserInfo().then((user) => { console.log('User info:', user); });
// 在第一次请求还没完成时再次调用
getUserInfo().then((user) => { 
    console.log('User info (waiting for the first request):', user);
});
在这个示例中,我们使用一个 `userInfoPromise` 变量来存储第一次请求的 Promise,以便后续的调用都能返回同一个 Promise。当第一次调用 `getUserInfo` 时,会发起请求并存储 Promise。后续的调用会直接返回存储的 Promise,等待第一次请求的结果。

21.数组去重的两种方式

set实现
const array = [1, 2, 2, 3, 4, 4, 5]; 
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 4, 5]
循环遍历实现
function unique(array) { 
    const result = []; 
    for (let i = 0; i < array.length; i++) { 
    if (result.indexOf(array[i]) === -1) { 
        result.push(array[i]); } } return result; 
    } 
    const array = [1, 2, 2, 3, 4, 4, 5]; 
    const uniqueArray = unique(array); 
    console.log(uniqueArray); // [1, 2, 3, 4, 5]

22.Map与Object

//Map转换为Object
const map = new Map(); 
map.set('key1', 'value1'); 
map.set('key2', 'value2'); 
const obj = Object.fromEntries(map);
console.log(obj) //{ key1: 'value1', key2: 'value2' }
或者循环遍历
const map = new Map()
map.set('key1','value1')
map.set('key2', 'value2')
const obj = {}
for(const [key,value] of map){
    obj[key] = value
}
console.log(obj) //{ key1: 'value1', key2: 'value2' }

//Object转换为Map
const obj = {key1: 'value1' , key2: 'value2'}
const map = new Map(Object.entries(obj))
console.log(map)
// Map { 'key1' => 'value1', 'key2' => 'value2' }
需要注意的是,MapObject 在数据结构和用法上有一些区别,转换时要考虑键值对的顺序、键的类型等因素。如果 Map 中存在复杂的数据结构,转换为 Object 可能会丢失一些信息。同样地,Object 转换为 Map 时,只能将 Object 中的字符串键转换为 Map 的键,其他类型的键会被忽略。