高频前端面试手写题

321 阅读6分钟

1. 防抖

函数防抖是指再事件被出发n秒后再执行回调,如果再n秒内又被出发则重新计时。 一般使用再点击时间上面避免过多的请求

// 非立即执行版本
function debounce(callback, time) {
  let timer = null
  return function () {
    if (timer) {
      clearInterval(timer)
    }
    timer = setTimeout(() => {
      callback.call(this, arguments)
    }, time)
  }
}

// 立即执行版本
function debounce(callBack, time) {
  let timer = null
  return function () {
    if (timer) clearInterval(timer)
    let triggerNow = !timer
    timer = setTimeout(() => {
      timer = null
    }, time)
    triggerNow && callBack.apply(this, arguments)
  }
}

2. 节流

节流函数是指在规定的时间内只执行一次函数,如果在规定的时间内触发函数只会执行一次,并且是立即执行的节流可以使用在scroll函数的监听上,通过事件的节流来降低使用频率

function throttle(callback, time) {
  let curTime = 0;
  return function () {
    if (Data.now() - curTime >= time) {
      curTime = Date.now()
      callback.apply(this.arguments)
    }
  }
}

3. 数柯里化

 function curry(fn) {
  return function curryed(...args) {
    if (args.length < fn.length) {
      return (...args1) => {
        return curryed(...args, ...args1)
      }
    } else {
      return fn(...args)
    }
  }
}
fucntion test (x,y,z){
    return x+y+z
}
let addTest = curry(test)
addTest(1,2,3) // 6

4. 函数组合

function compose(...fns) {
  return function (args) {
    return fns.reverse().reduce((pre, cur) => {
      return cur(pre)
    }, args)
  }
}
//es6 写法 
const compose = (...fns) => args => fns.reverse().reduce((pre,cur)=> cur(pre),args)

function a(x) {
  return x + 1;
}
function b(x) {
  return x + 2;
}
function c(x) {
  return x + 3;
}
const addNum = compose(a, b, c);
addNum(0) // 6

5. call函数

  1. 判断调用是否是一个函数,即使我们是定义在函数的原型上,但是可能出现call等调用情况
  2. 判断传入的上下文是否存在,如果不存在的话设置为window
  3. 处理传入的参数,截取第一个参数后的所有参数
  4. 将函数作为上下文对象的一个属性
  5. 使用上下文调用这个函数 并保存这个结果
  6. 删除刚刚新增的属性
  7. 返回结果
Function.prototype.myCall = function (context) {
  if (this instanceof Function) throw new TypeError('error')  //1
  context = context || window //2
  let args = [...arguments].slice(1)  //3 
  context.fn = this     //4
  let result = context.fn(...args)    //5
  delete context.fn    //6
  return result    //7
}

6. apply函数

  1. 判断调用是否是一个函数,即使我们是定义在函数的原型上,但是可能出现call等调用情况
  2. 判断传入的上下文是否存在,如果不存在的话设置为window
  3. 处理传入的参数,因为apply函数第二个参数是一个数组,所有通过下标找到参数
  4. 将函数作为上下文对象的一个属性
  5. 使用上下文调用这个函数 判断参数是否存在 并保存这个结果
  6. 删除刚刚新增的属性
  7. 返回结果
Function.prototype.myApply = function (context) {
  if (typeof this !== 'function') throw new TypeError('Error');//1
  context = context || window//2
  let args = arguments[1], resutl = null //3
  context.fn = this //4
  let result = args ? context.fn(...args) : context.fn() // 5
  delete context.fn // 6
  return result // 7
}

7. bind函数

  1. 判断调用的是否是一个函数,即使我们定义在函数原型上面,但是会出现call调用的情况
  2. 获取剩余的参数
  3. 保存当前函数的引用
  4. 创建一个函数返回
  5. 函数内部使用apply来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的this给apply调用,其他情况都可以传入指定的上下文对象
Function.prototype.myBind = function (context) {
  if (!(this instanceof Function)) throw new TypeError('error')//1
  let args = [...arguments].slice(1) //2
  let fn = this //3
  const _this = this
  return function F() { // 4
    return fn.apply(this instanceof F ? this : context, [...args, ...arguments])//5
  }
}

8. new 操作符

在调用new的过程中会发生四件事情

  1. 首先创建一个新的对象
  2. 设置原型,将新对象的原型设置为函数的prototype对象(将this指向这个新对象)
  3. ,然后执行构造函数里面的代码(为这个对象添加穿过来的属性)
  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
function MyNew(...args) {
  // 获取要操作的构造函数 并且判断传入的是不是一个构造函数
  if (args[0] instanceof Function) {
    var constructor = args[0]
  }else {
    throw new TypeError('not a function')
  }
  // 基于这个构造函数创建一个新的对象(将this指向这个新的对象)
  let newObj = Object.create(constructor.prototype)
  // 执行这个构造函数
  let res = constructor.apply(newObj, args.splice(1))

  return (res instanceof Object || res instanceof Function) ? res : newObj
}

9. instanceof

intanceOf 运算符是用于判断构造函数的prototype属性是否出现在对象的原型链中的任何位置

  1. 首先获取类型的原型
  2. 然后获取对象的原型
  3. 然后一直判断对象的原型是否等于类型的原型,直到对象原型为null 因为原型链的最终也是null
 function myInstanceOf(left, right){
  let proto = Object.getPrototypeOf(left), // 获取对象的原型
    prototype = right.prototype; // 获取构造函数的 prototype 对象
  while (true) {
    if (!proto) return false
    if (proto === protoType) return true
    proto = Object.getPrototypeOf(proto)
  }
}

10. 深拷贝

function deepClone(obj, cache = new WeakMap()) {
  if (typeof obj !== 'object') return obj // 普通类型,直接返回
  if (obj === null) return obj
  if (cache.get(obj)) return cache.get(obj) // 防止循环引用,程序进入死循环
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)

  // 找到所属原型上的constructor,所属原型上的constructor指向当前对象的构造函数
  let cloneObj = new obj.constructor()
  cache.set(obj, cloneObj) // 缓存拷贝的对象,用于处理循环引用的情况
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], cache) // 递归拷贝
    }
  }
  return cloneObj
}

11. 冒泡排序

一次比较如果前面一个数大于后面一个数进行替换以此内推

function bubbl(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
      }
    }
  }
  return arr
}

12. 插入排序

  1. 有2个牌组 old是传入的数组,new是old中的第一项
  2. 每次从old中拿到一项A 和new中从后往前进行比对,new中的当前项为B ,如果发先A>B 找到B的位置插入到B的后面,如果没有找到的话 则在数组的第一项添加一个内容
  3. 返回new
function insert(arr) {
  let newArr = [arr[0]]
  for (let i = 1; i < arr.length; i++) {
    for (let j = newArr.length - 1; j >= 0; j--) {
      if (arr[i] > newArr[j]) {
        newArr.splice(j + 1, 0, arr[i])
        break
      }
      if (j === 0) {
        newArr.unshift(arr[i])
      }
    }
  }
  return newArr
}

13. 快速排序

  1. 获取数组中中间的一项 midI,midV 。
  2. 再把当前数组中的minV的哪项去除
  3. 依次比较比midV大的放在arrRIght,反之 放在右边
  4. 递归这个函数
  5. 结束递归的条件 当数组的长度小于等于1的时候,则不需要再进行比对
function quick(arr) {
  if (arr.length <= 1) return arr
  let midIndex = Math.floor(arr.length / 2),
    midValue = arr.splice(midIndex, 1),
    arrLeft = [],
    arrRight = []
  for (let i = 0; i < arr.length; i++) {
    arr[i] > midValue ? arrRight.push(arr[i]) : arrLeft.push(arr[i])
  }
  return quick(arrLeft).concat(midValue, quick(arrRight))
}

14. 手写Promise

const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'

function resolvePromise(promise, res, resolve, reject) {
  if (promise === res) {
    reject('Chaining cycle detected for promise #<Promise>')
  }
  if (res instanceof MyPromise) {
    res.then(value => resolve(value), reason => reject(reason))
  } else {
    resolve(res)
  }
}
class MyPromise {
  constructor(exector) {
    try {
      exector(this.resolve, this.rejcet)
    } catch (e) {
      this.rejcet(e)
    }
  }
  value = undefined
  reason = undefined
  status = PENDING
  // 成功回调函数数组
  successCallBack = []
  // 失败的回调函数
  failCallBack = []
  resolve = (value) => {
    if (this.status == PENDING) {
      this.status = RESOLVED
      this.value = value
      this.successCallBack.forEach(item => { item(value) })
    }
  }
  rejcet = (reason) => {
    if (this.status == PENDING) {
      this.status = REJECTED
      this.reason = reason
      this.failCallBack.forEach(item => { item(reason) })
    }
  }
  then(successCallBack, failCallBack) {
    successCallBack = successCallBack ? successCallBack : value => value
    failCallBack = failCallBack ? failCallBack : reason => { throw reason }
    const promise = new MyPromise((resolve, reject) => {
      if (this.status == RESOLVED) {
        setTimeout(() => {
          try {
            let res = successCallBack(this.value)
            resolvePromise(promise, res, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status == REJECTED) {
        setTimeout(() => {
          try {
            let res = failCallBack(this.reason)
            resolvePromise(promise, res, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        this.successCallBack.push(() => {
          setTimeout(() => {
            try {
              let res = successCallBack(this.value)
              resolvePromise(promise, res, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        this.failCallBack.push(() => {
          setTimeout(() => {
            try {
              let res = failCallBack(this.reason)
              resolvePromise(promise, res, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise
  }
  catch(failCallBack) {
    return this.then(undefined, failCallBack)
  }
  static all(arr) {
    return new MyPromise((resolve, reject) => {
      let results = []
      let count = 0
      function addData(index, item) {
        count++
        results[index] = item
        if (count == results.length) {
          resolve(results)
        }
      }
      arr.forEach((item, index) => {
        if (item instanceof MyPromise) {
          item.then(value => { addData(index, value) }, reason => reject(reason))
        } else {
          addData(index, item)
        }
      })
    })
  }

  finally(callBack) {
    return this.then(value => {
      return MyPromise.resolve(callBack()).then(() => value)
    }, reaosn => {
      return MyPromise.resolve(callBack()).then(() => { throw reaosn })
    })
  }
  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise((resolve => resolve(value)))
  }
  static reject = (reason) => {
    if (reason instanceof MyPromise) return reason
    return new MyPromise((resolve, reject) => reject(reason))
  }
}