总结下前端常见的手写题目

117 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

我们经常会在面试中遇到需要手写题目,今天刚好来总结下,查漏补缺,方便后面查阅。

1. 防抖函数

防抖函数是指在规定时间内,如果监听的事件多次触发,则不会执行对应的函数,直到最后一次间隔的时间超过规定时间才会执行。

const debounce = (fn, delay = 200) => {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn(...args)
    }, delay)
  }
}

2. 节流函数

节流函数是指在规定时间内,如果监听的事件多次触发,只会在规定时间内执行一次,以此类推,下一个规定时间也只会执行一次。

const throttle = (fn, delay = 200) => {
  let timer = null
  return function (...args) {
    if (timer) return
    timer = setTimeout(() => {
      fn(...args)
      clearTimeout(timer)
      timer = null
    }, delay)
  }
}

防抖函数和节流函数的区别是什么?

  • 二者都会返回一个新函数
  • 防抖函数判断有timer时会clear后继续执行
  • 节流函数判断如果有timer则return,如果没有timer则继续执行,执行后然后把timer清除。

3. 实现apply

apply,call方法是用来改变普通函数的this指向。但是apply只支持数组。

如果没有传context,默认是window。把函数绑定到context的属性(this),执行的时候this就指向了context。

Function.prototype._apply = function (context = window, args) {
  args = args ? args : []
  context.fn = this
  const res = context.fn(...args)
  delete context.fn
  return res
}

4. 实现 call

call跟apply不同的是,它的参数可以是任意类型,没有限制。所以要入参的时候通过扩展符把它变成数组,调用的时候再展开。

Function.prototype._call = function (context = window, ...args) {
  args = args ? args : []
  context.fn = this
  const res = context.fn(...args)
  delete context.fn
  return res
}

5. 实现bind

bind的使用情况跟apply,call不一样,它是返回一个新函数,而不是直接调用。

因为返回新函数,所以新函数就有可能会被当作构造函数,这里就要做判断。

判断如果是新函数的实例,则要调用new, new的对象是绑定的函数(const Fn = this,存起来).

否则就是普通的函数,调用call即可,也可以使用上面的_call方法。

参数也要注意合并。

Function.prototype._bind = function (context = window, ...args) {
  const Fn = this
  return function newFn (...newArgs) {
    let res = null
    // 构造函数
    if (this instanceof newFn) {
      res = new Fn(...args, ...newArgs)
    } else {
      res = Fn.call(context, ...args, ...newArgs)
    }
    return res
  }
}

6. 实现new

我们要了解new的过程中做了什么,才能实现new方法。

  1. new过程中会新建一个对象,然后构造函数的this会指向这个对象。
  2. 这个对象会继承构造函数prototype上的方法
  3. new过程执行构造函数返回的值,如果是对象,则返回该对象,否则返回新建的对象。

这里我们使用Object.create来模拟新建的对象,并继承构造函数的prototype。

然后调用apply,让构造函数的this指向新建的对象。

最后判断如果返回值是否为对象。

function NewFn (fn, ...args) {
  const obj = Object.create(fn.prototype)
  const res = fn.apply(obj, args)
  return res instanceof Object ? res : obj
}

7. 发布订阅模式

发布订阅模式,我们这里使用class实现, 使用对象存储,当然也可以使用 Map 存储,这样就不单单存字符串了。

我们实现了on方法,once方法,emit方法,off方法。

once方法和on方法的区别是,once只会执行一次。

所以实现它内部是调用on方法,执行后就调用off取消它,这样就能保证调用一次。

class MyEvent {
  constructor () {
    this.stack = {}
  }
  on (name, fn) {
    if (!this.stack[name]) this.stack[name] = []
    this.stack[name].push(fn)
  }
  once (name, fn) {
    const onceFn = () => {
      fn()
      this.off(name, onceFn)
    }
    this.on(name, onceFn)
  }
  emit (name) {
    const fns = this.stack[name] || []
    for (let i = 0; i < fns.length; i++) {
      fns[i]()
    }
  }
  off (name, fn) {
    let fns = this.stack[name] || []
    if (fn) {
      this.stack[name] = fns.filter(item => item !== fn)
    } else delete this.stack[name]
  }
}

8. 深拷贝

这个深拷贝,是很简单的深拷贝,通过遍历加递归实现,可以满足一些基本需求。

这里没有考虑循环利用(子属性是自己)的情况

function deepCopy (obj) {
  if (typeof obj !== 'object' || obj === null) return obj
  const res = Array.isArray(obj) ? [] : {}
  const type = Object.prototype.toString.call(obj)
  if(type === '[object Date]' || type === '[object RegExp]') return new obj.constructor(obj)
  for (const key in obj) {
    if (typeof obj === 'object') res[key] = deepCopy(obj[key])
    else res[key] = obj[key]
  }
  return res
}

总结

以上就是常见的手写题目,感谢你们的阅读。