常见的前端手写功能,你全都会吗?

169 阅读6分钟

每次一到面试,就是在掘金上看各种面经,背八股。今年断断续续也一直在面试,很多时候都是栽在手撕环节。所以现在把我之前在面试过程中遇到的手撕题目整理分享给大家,希望可以帮到和我一样栽在手撕环节的童鞋们。不吸勿喷~

js手写

1、防抖

// 防抖:在n秒后执行该事件,若在n秒内被重复触发,则从新计算
const debounce = (fn,delay)=>{
  let timer = null;
  return function (...argument) {
    if(timer){
      clearTimeout(timer)
    }
    timer = setTimeout(()=>{
      fn.apply(this,argument)
    },delay)
  }
}

2、节流

// 节流:n秒内只运行一次,若在n秒内重复触发,只执行一次。
function throttle(fn,delay) {
  let last = 0  // 上次触发时间
  return function(...args){
    const now = Date.now();
    if(now - last > delay){
      last = now;
      fn.apply(this,args)
    }
  }
}

3、手写promsie

class MyPromise{
  constructor(exector){
    this.value = null;  // 成功的值
    this.status = "pending";  // 初始状态
    this.reason=null;  // 失败原因的值
    this.fulfilledCallBacks = [];  // 成功的回调函数数组
    this.rejectedCallBacks = [];   // 失败的回调函数数组
    let resolve=(value)=>{
      if(this.status==="pending"){
        this.value = value;
        this.status="fulfilled";
        this.fulfilledCallBacks.forEach(fn=>fn())
      }
    }
    let reject=(reason)=>{
      if(this.status==="pending"){
        this.reason = reason;
        this.status="rejected";
        this.rejectedCallBacks.forEach(fn=>fn())
      }
    }
    try {
      exector(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.status === 'fulfilled') {
        setTimeout(() => {
          const x = onFulfilled(this.value);
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        })
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          const x = onRejected(this.reason)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        })
      }
      if (this.status === 'pending') {
        this.fulfilledCallBacks.push(() => { // 将成功的回调函数放入成功数组
          setTimeout(() => {
            const x = onFulfilled(this.value)
            x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
          })
        })
        this.rejectedCallBacks.push(() => { // 将失败的回调函数放入失败数组
          setTimeout(() => {
            const x = onRejected(this.reason)
            x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
          })
        })
      }
    })
  }
}

// 测试
function p1() {
  return new MyPromise((resolve, reject) => {
    setTimeout(resolve, 1000, 1)
  })
}
function p2() {
  return new MyPromise((resolve, reject) => {
    setTimeout(resolve, 1000, 2)
  })
}
p1().then((res) => {
  console.log(1111)
  console.log(res) // 1
  return p2()
}).then((ret) => {
  console.log(ret) // 2
})

4、promise.all

// 所有的promise 都成功,则返回成功后的数组结果
// 所有的promise 有一个失败,则返回失败的结果
MyPromise.all = function (proimises){
  return new Promise((resolve,reject)=>{
    let result = [];
    let index = 0;
    for(let i=0;i<proimises.length;i++){
      Promise.resolve(proimises[i]).then(res=>{
        result[i] = res;
        index++;
        if(index===proimises.length){
          resolve(result)
        }
      }).catch(reject)
    }
  })
}

5、promise.race

// race 哪一个最快得到结果,就返回哪一个结果,无论成功或者失败
function race(promiseArr){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promiseArr.length;i++){
      Promise.resolve(promiseArr[i]).then((ret)=>resolve(ret)).catch((err)=>reject(err))
    }
  })
}

6、promise.allSettled

// 将每一个promise的结果,集合成数组返回
const allSettled = (promises)=>{
  return new Promise((resolve,reject)=>{
    const res = [];
    const count = 0;
    const addData = (status,value,index)=>{
      res[index]={
        status,
        value
      }
      count++;
      if(count===promises.length) return resolve(res);
    }
    promises.forEach((element,index) => {
      if(element instanceof Promise){
        element.then((res)=>addData('fulfilled',res,index),err=>{
          addData("rejected",err,index)
        })
      }else{
        addData("fulfilled",element,index)
      }
    });
  })
}

7、实现一个带并发的异步调度器Schedule,保证同时运行的任务最多只能有两个

class Scheduler{
  constructor(){
    this.waitTasks = []; // 代执行的任务列队
    this.excutingTasks = [];  // 正在执行的任务列队
    this.maxExcutingNum = 2;  // 允许同时运行的任务数量
  } 
  add(promiseMarker){
    if(this.excutingTasks.length < this.maxExcutingNum){
      this.run(promiseMarker)
    }else{
      this.waitTasks.push(promiseMarker)
    }
  }
  run(promiseMaker){
    const len = this.excutingTasks.push(promiseMaker);
    const index = len - 1;
    promiseMaker().then(()=>{
      this.excutingTasks.splice(index,1);
      if(this.waitTasks.length>0){
        this.run(this.waitTasks.shift())
      }
    })
  }
}

const scheduler = new Scheduler();
const timeout = (time)=> new Promise((resolve)=>setTimeout(resolve,time))
const addTask = (time,order)=>{
  scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// output 2,1,3,4

8、 通过给定一个url地址字符串获取host,honame,主域等信息

// 这里主要借助的是location的一些方法,通过给定的地址创建一个a链接,并且把地址复制给a链接的地址,这样a链接就可以通过location方法获取相应的地址数据,然后返回成对象的形式
const getLocation = (url)=>{
  if(url){
    let aDom = document.createElement("a");
    aDom.href = url;
    let j = {
      hostname: aDom.hostname,
        host: aDom.host,
        origin: aDom.origin,
        protocol: aDom.protocol,
        pathname: aDom.pathname,
        hash: aDom.hash,
        domain:aDom.domain,
        port:aDom.port,
        search:aDom.search,
    }
    return j
  }
}
console.log(getLocation('https://www.baidu.com:8080/windows/location/page.html?vid=1&id=xjq#tip'))

9、手写new运算符

// new 运算符其实就是在内部生成了一个对象,在将你的属性添加到这个对象上,然后返回这个对象。
function myNew(fn,...args){
  // 基于原型创建了一个对象
  let object = Object.create(null);
  // 添加属性到新对象上,并获取obj函数的结果
  let res = fn.apply(object,...args)
  // 判断返回值,如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回创建的新对象
  return res && typeof res==="object" ? res : object;
}

10、手写instanceof

// instanceof原理:在查找的过程中会遍历左边变量的原型链,直到找到右边变量的prototype,查找失败,返回false
let myInstanceof = (target,origin)=>{
    while(target){
        if(target.__proto__ === origin.prototype){
            return true
        }
        target = target.__proto__
    }
    return false
}

11、手写数组的flat

function flat(arr){
  return arr.reduce((pre,cur)=>{
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  },[])
}
console.log(flat([1,2,[3,4,[5]]]))
function flat(arr,depth=1){
  let result = [];
  arr.forEach(element => {
    if(Array.isArray(element) && depth>0){
      result = result.concat(flat(element,depth-1))
    }else{
      result.push(element)
    }
  });
  return result;
}

console.log(flat([1,2,[3,4,[5,[7]]]],1))

12、实现数组chunk

// slice 提取字符串中的某一部分,并返回一个新的字符串,且不会改变原字符串
// slice (beginIndex,endIndex)  从beginIndex 开始提取,从endIndex处结束,若省略endIndex,slize会一直提取到字符串的末尾
function chunk(arr,size){
  let arr2=[];
  for(let i=0;i<arr.length;i+=size){
    console.log(i)
    arr2.push(arr.slice(i,i+size))
  }
  return arr2;
}
console.log(chunk2([0, 1, 2, 3, 4, 5], 1))

13、对象拍平

function isObject(val){
    return typeof val==="object" && val!==null;
}
function flatten(obj){
    if(!isObject(obj){
        return 
    }
    let res = {};
    const dfs = (cur,prefix)=>{
        if(isObject(cur)){
            if(Array.isArray(cur)){
                cur.forEach((item,index)=>{
                    dfs(item,`${prefix}[${index}]`)
                })
            }else{
                for(let k in cur){
                    dfs(cur[k],`${prefix}${prefix ? "." : ""}${k}`)
                }
            }
        } else{
            res[prefix] = cur
        }  
    }
    dfs(obj,"");
    return res
}
console.log(flatten({
  a: {
         b: 1,
         c: 2,
         d: {e: 5}
     },
  b: [1, 3, {a: 2, b: 3}],
  c: 3
 }))

14、统计对象有多少层?

function getLevel2(obj){
  if(obj===null){
    return 0;
  }
  let result=0;
  function fn(params,level=0){
    if(typeof params === "object" && params!==null){
      Object.values(params).forEach(item=>{
        if(typeof item==="object" && item!==null){
          fn(item,level+1)
        }else{
          result = level + 1 > result ? level+1:result
        }
      })
    }else{
      result = result > level ? result : level
    }
  }
  fn(obj)
  return result
}
console.log(getLeval({a:1,b:{ c:1,d:{e:1,g:{h:7},f:[ 1,1 ]}}}))

15、实现深拷贝

深拷贝:开辟一个新栈,两个完全相同的对象,对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的拷贝方式:
    1、lodash的_.cloneDeep()
    2JSON.stringify()

注:JSON.stringify()实现深拷贝有什么问题?
    1、当obj中有时间对象时,使用JSON.stringifyJSON.parse后,时间对象会变成字符串。
    2、当obj中有函数,undefined,使用JSON.stringify会将函数或者undefined丢失。
    3NaN和infinity格式的数值以及null都会被处理成null4、obj中正则会被处理成空对象{}
    5、obj中的原型属性会被忽略
function deepClone(obj,hash=new WeakMap()){
  if(obj===null) return obj;
  if(obj instanceof Date) return new Date(obj);
  if(obj instanceof RegExp) return new RegExp(obj)
  //可能是对象或者普通的值,如果是函数的话就不需要深拷贝
  if(typeof obj!=="object") return obj;
  //如果是对象的话,进行深拷贝
  if(hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的constructor指向的是当前类的本身
  hash.set(obj,cloneObj)
  for(let key in obj){
    // 实现一个递归
    if(obj.hasOwnProperty(key)){
      cloneObj[key]=deepClone(obj[key],hash)
    }
  }
  return cloneObj
}

16、curried(1)(2)(3)(4)(5)() 求和

function curry(fn){
    let parmas = []
    return function sum(...args){
        if(args.length){//判断是否有参数
            parmas = [...parmas,...args]
            return sum
        }
        return fn(parmas)
    }
}
function add(arr){
   return arr.reduce((acc,item)=>{
        return acc+item
    })
}
let curried = curry(add)
console.log(curried(1)(2)(3)(4)(5)())

17、大数相加

function bigNumber(str1,str2){
  const arr1 = str1.split("").reverse();
  const arr2 = str2.split("").reverse();
  const length = Math.max(arr1.length,arr2.length);
  let flag = 0 ; 
  let res = [];
  for( i=0;i<length;i++){
    const nums1 = Number(arr1[i] || 0);
    const nums2 = Number(arr2[i] || 0);
    let sum = nums1 + nums2 + flag;
    if(sum>=10){
      sum = sum % 10;
      flag = 1;
    }else{
      flag = 0;
    }
    res.push(sum);
    if(i === length-1 && flag){
      res.push(flag)
    }
  }
  return res.reverse().join("")
}

console.log(bigNumber("1234","557"))

18、循环打印红绿灯

function light(func,delay){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve(func()) 
    },delay)
  })
}
async function index2(){
  while(1){
      await light(yellow,1000);
      await light(red,2000);
      await light(green,3000)
  }
}
function yellow(){
 console.log("我是黄灯")
}
function red(){
 console.log("我是红灯")
}
function green(){
 console.log("我是绿灯")
}
index2()