Skip to content
霞露小伙 — HfWang
On this page

面试常见手写题

防抖节流深拷贝

防抖

javascript
export default function debounce(
  fn, // 需要防抖处理的函数
  wait = 1000, // 防抖时间
  immediate = false, // 是否需要立即执行
) {
  let timer = null
  let isImmediate = false

  return (...args) => {
    if (timer) clearTimeout(timer)

    if (immediate && isImmediate) {
      fn(...args)
      isImmediate = true
    }

    timer = setTimeout(() => {
      fn(...args)
      isImmediate = false
    }, wait)
  }
}

节流

javascript
export default function throttle(
  fn, // 需要节流处理的函数
  wait = 1000,
) {
  let preTime = 0

  return (...args) => {
    const currentTime = Date.now()
    const diffTime = currentTime - preTime

    if (diffTime - wait > 0) {
      fn(...args)
      preTime = currentTime
    }
  }
}

深拷贝

javascript
function deepClone(obj, map = 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 (map.has(obj)) return map.get(obj) //解决循环引用

  const cloneobj = new obj.constructor() //生成obj的对应类型
  map.set(obj, cloneobj)

  Reflect.ownKeys(obj).forEach((key) => {
    cloneobj[key] = deepClone(obj[key], map)
  })
  return cloneobj
}

promise

手写 promise

javascript
// MyPromise.js

const PROMISE_STATUS = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected',
}

function resolvePromise(thenPromise, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (thenPromise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // 判断x是不是 MyPromise 实例对象
  if (x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else {
    // 普通值
    resolve(x)
  }
}

export default class MyPromise {
  constructor(fn) {
    // fn 是一个执行器,进入会立即执行
    // 并传入resolve和reject方法
    try {
      fn(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  // 储存状态的变量,初始值是 pending
  status = PROMISE_STATUS.PENDING
  // 成功之后的值
  value = null
  // 失败之后的原因
  reason = null

  // 存储成功回调函数
  onFulfilledCallbacks = []
  // 存储失败回调函数
  onRejectedCallbacks = []

  // 更改成功后的状态
  resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status !== PROMISE_STATUS.PENDING) return

    // 状态修改为成功
    this.status = PROMISE_STATUS.FULFILLED
    // 保存成功之后的值
    this.value = value
    // resolve里面将所有成功的回调拿出来执行
    while (this.onFulfilledCallbacks.length) {
      // Array.shift() 取出数组第一个元素,然后()调用
      // shift不是纯函数,取出后,数组将失去该元素,直到数组为空
      this.onFulfilledCallbacks.shift()(value)
    }
  }

  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status !== PROMISE_STATUS.PENDING) return

    // 状态成功为失败
    this.status = PROMISE_STATUS.REJECTED
    // 保存失败后的原因
    this.reason = reason
    // resolve里面将所有失败的回调拿出来执行
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(reason)
    }
  }

  then(onFulfilled, onRejected) {
    // then 后面可能什么都没有传入,所以需要做下判断
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value

    const realOnRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (reason) => {
            throw reason
          }

    // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
    const thenPromise = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        // 创建一个微任务等待 promise2 完成初始化
        // https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = realOnFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      const rejectedMicrotask = () => {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = realOnRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      // 判断状态
      if (this.status === PROMISE_STATUS.FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === PROMISE_STATUS.REJECTED) {
        rejectedMicrotask()
      } else if (this.status === PROMISE_STATUS.PENDING) {
        // 等待,因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
        // 等到执行成功失败函数的时候再传递
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      }
    })

    return thenPromise
  }

  // resolve 静态方法
  static resolve(parameter) {
    // 如果传入 MyPromise 就直接返回
    if (parameter instanceof MyPromise) {
      return parameter
    }

    // 转成常规方式
    return new MyPromise((resolve) => {
      resolve(parameter)
    })
  }

  // reject 静态方法
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }

  static all() {}
  static race() {}
  static allSettled() {}
  static any() {}
}

手写 promise.all

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个 Promise 实例,  那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个 Promise 的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。

js
Promise.MyAll = Function(promises) {
  let arr = []
  let count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, index) => {
      Promise.resolve(item).then(res => {
        arr[index] = res
        count++
        if (count === promise.length) {
          resolve(res)
        }
      }, reject)
    })
  })
}

手写 promise.race

Promise.race 从字面意思理解就是赛跑,以状态变化最快的那个 Promise 实例为准,最快的 Promise 成功 Promise.race 就成功,最快的 Promise 失败 Promise.race 就失败。

js
Promise.MyRace = Function(promises) {
  return new Promise((resolve, reject) => {
    // 这里不需要使用索引,只要能循环出每一项就行
    for (const item of promises) {
      Promise.resolve(item).then(resolve, reject)
    }
  })
}

手写 promise.any

Promise.any 与 Promise.all 可以看做是相反的。Promise.any 中只要有一个 Promise 实例成功就成功

js
Promise.MyAny = Function(promises) {
  let arr = []
  let count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, index) => {
      Promise.resolve(item).then(resolve, err => {
        all[index] = {
          status: 'reject',
          reason: err
        }
        count++
        if (count === promise.length) {
          reject(new Error('没有一个成功'))
        }
      })
    })
  })
}

手写 promise.allSettled

无论 promise 是成功还是失败,都返回出来

js
Promise.MyAllSettled = Function(promises) {
  let arr = new Array(promises.length)
  let count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, index) => {
      if (item && typeof item.then === 'function') {
        item.then(res => {
          arr[index]= { status: 'fulfilled', value: res }
          count++
          if (count === promises.length) {
            resolve(arr)
          }
        }).catch(err) {
          arr[index] = { status: 'reject', reason: err }
          count++
          if (count === promises.length) {
            resolve(arr)
          }
        }
      } else {
        arr[index] = { status: 'fulfilled', value: item }
        count++
        if (count === promises.length) {
          resolve(arr)
        }
      }
    })
  })
}

实现 promise 并行限制

js
class Schedule {
  constructor(max = 2) {
    this.maxNum = max
    this.queue = []
    this.runCounts = 0
  }

  add(promise) {
    this.queue.push(promise)
  }

  request() {
    if (this.queue.length === 0 || this.runCounts >= this.maxNum) return 

    this.runCount++
    this.queue.shift().then(() => {
      this.runCount--
      this.request()
    })
  }

  run() {
    for (let i = 0; i < this.maxNum; i++) {
      this.request()
    }
  }
}

// ............使用..............
import sleep from 'sleep' // 返回 promise 的延迟函数
const schedule = new Schedule(3)
const promises = Array(10).fill(Promise.resolve(() => sleep(1000)))
promises.forEach(item => {
  schedule.add(item)
})
schedule.run()

promise 超时报错

原理; 利用 promise.race, 只要有一个 promise resolve / reject 就结束运行的原理,我们手动创建一个 promise 定时器,在规定时间后报错,如果目标 promise 的执行时间超过预定时间就会执行返回我们定时器里的报错信息

js
const overTime = (fn, time = 1000) => {
  const outTime = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('请求超时'))
      }, time)
    })
  }

  return Promise.race([fn, outTime(time)])
}

async-await 实现 promise.all

js
async function asyncAll(promises) {
  try {
    const results = promises.map(async (item) => await item)
    let arr = []
    for (const item of results) {
      arr.push(item)
    }
    return arr
  } catch (err) {
    throw new Error(err)
  }
}

this 相关

手写 call

call(target, a, b, c, ...)

  • call
    • 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的
    • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
    • 处理传入的参数,截取第一个参数后的所有参数。
    • 将函数作为上下文对象的一个属性。
    • 使用上下文对象来调用这个方法,并保存返回结果
    • 删除刚才新增的属性。
    • 返回结果。
javascript
Function.prototype.myCall = function(ctx) {
  if (typeof this !== 'function') {
      throw New Error('类型异常')
  }
  // 获取参数
	let args = [...arguments].slice(1)
	let result = null
	// 判断是否传入 ctx, 没有传的话取 window
	ctx = ctx || window
	// 将调用函数设为对象的方法
	ctx.fn = this
  // 执行函数
	result = ctx.fn(...args)
	// 执行结束移除属性
	delete ctx.fn
	return result
}

手写 apply

apply(target, [a, b, c, ... ]), 第二个参数只能一次绑定

  • apply
    • 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情
    • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
    • 将函数作为上下文对象的一个属性。
    • 判断参数值是否传入
    • 使用上下文对象来调用这个方法,并保存返回结果。
    • 删除刚才新增的属性
    • 返回结果
javascript
Function.prototype.myApply = Function(ctx) {
  if (typeof this !== 'function') {
    throw New Error('类型异常')
  }
  let result = null
  ctx = ctx || window
  ctx.fn = this

  if (arguments[1]) {
    result = ctx.fn(...arguments[1])
  } else {
    result = ctx.fn()
  }

  delete ctx.fn
  return result
}

手写 bind

bind(target, [a, b, c]) 第二个参数可以多次绑定

  • bind
    • 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的
    • 保存当前函数的引用,获取其余传入参数值。
    • 创建一个函数返回
    • 数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数
javascript
Function.prototype.myBind = Function(ctx) {
  if (typeof this !== 'function') {
    throw New Error('类型异常')
  }
  let args = [...arguments].slice(1)
  let fn = this

  return function fn() {
    return fn.apply(
      this instanceof Fn ? this : ctx
      args.concat(...arguments)
    )
  }
}

sleep

js
const sleep = (time = 1000) => {
  new Promise((resolve) => {
    setTimeout(resolve, time)
  })
}

sleep(2000).then(() => {
  // do something
})

实现简单的发布订阅

简单实现

javascript
class EventBus {
  // 事件调度中心
  map = {}

  // 订阅事件
  on = (name, fn) => {
    // 创建、获取数组容器
    this.map[name] = this.map[name] || []
    // 保存事件
    this.map[name].push(fn)
  }

  // 取消订阅
  off = (name, fn) => {
    const fnList = this.map[name] || []
    const index = fnList.indexof(fn)
    if (index < 0) return
    fnList.splice(index, 1)
  }

  // 发布订阅
  emit = (name, data) => {
    const fnList = this.map[name] || []
    fnList.forEach((fn) => fn.call(undefined, data))
  }
}

使用

javascript
// 使用
const e = new MyBus()
e.on('click', (name) => {
  console.log('hi ' + name)
})
e.on('click', (name) => {
  console.log('hello ' + name)
})
setTimeout(() => {
  e.emit('click', 'frank')
}, 3000)

实现简单的 keep-alive 组件

javascript
// 接收:字符串,正则,数组
const patternTypes: Array<Function> = [String, RegExp, Array]

// keep-alive 组件
export default {
  name: 'keep-alive',
  // 抽象组件,是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
  abstract: true,
  props: {
    // 匹配的组件,缓存
    include: patternTypes,
    // 不去匹配的组件,不缓存
    exclude: patternTypes,
    // 缓存组件的最大实例数量, 由于缓存的是组件实例(vnode),
    // 数量过多的时候,会占用过多的内存,可以用max指定上限
    max: [String, Number],
  },
  created() {
    // 用于初始化缓存虚拟DOM数组和vnode的key
    this.cache = Object.create(null)
    this.keys = []
  },
  mounted() {
    // prune 削减精简[v.]
    // 去监控include和exclude的改变,根据最新的include和exclude的内容,
    // 来实时削减缓存的组件的内容
    this.$watch('include', (val) => {
      pruneCache(this, (name) => matches(val, name))
    })
    this.$watch('exclude', (val) => {
      pruneCache(this, (name) => !matches(val, name))
    })
  },
  destroyed() {
    // 销毁缓存cache的组件实例
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },
}

new

调用 new 的过程

  • 首先创建了一个新的空对象
  • 设置原型,将对象的原型设置为函数的 prototype 对象。
  • 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  • 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
javascript
function MyNew(fn, ...args) {
  if (typeof fn !== 'function') {
    throw new Error('类型异常')
  }

  const obj = Object.create(fn.prototype)
  const val = fn.apply(obj, args)

  return typeof val === 'object' ? val : obj
}

实现 create

javascript
function create(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}

函数柯里化

javascript
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args)
}

实现 add(1)(2)(3)(4)() = 10

  • add(1)(2)(3)(4)() = 10
  • add(1, 2, 3)(4)() = 10
javascript
function add(...args) {
  let allArgs = [...args]

  function fn(...newArgs) {
    allArgs = [...allArgs, ...newArgs]
    return fn
  }

  fn.toString = function () {
    if (!allArgs.length) {
      return
    }
    return allArgs.reduce((sum, cur) => sum + cur)
  }
  return fn
}

提取 url 参数

javascript
const getUrlParams = (url) => {
  let reg = /([^?&=]+)=([^?&=]+)/g
  let obj = {}
  url.replace(reg, () => {
    obj[arguments[1]] = arguments[2]
  })
  return obj
}

斐波拉契数列

javascript
// 正常写法
const Fibonacci = (n) => {
  if (n <= 1) return 1
  return Fibonacci(n - 1) + Fibonacci(n - 2)
}

// 优化 - 尾递归
const Fibonacci = (n, sum1 = 1, sum2 = 1) => {
  if (n <= 1) return sum2
  return Fibonacci(n - 1, sum2, sum1 + sum2)
}

settimeout 模拟实现 setinterval

js
function myInterval(fn, wait = 1000) {
  let timer = null

  const pollFn = () => {
    fn()
    timer = setTimeout(pollFn, wait)
  }

  pollFn()

  return {
    cancel: () => clearTimeout(timer),
  }
}

数组扁平化

js
// 方法1
function myFlat(arr) {
  if (!arr.length) return
  return arr.reduce((pre, cur) => (Array.isArray(cur) ? [...pre, ...myFlat(cur)] : [...pre, cur]), [])
}

// 方法2
function myFlat(arr) {
  if (!arr.length) return
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}

手写 instanceof

js
function MyInstanceOf(Left, Right) {
  const L = Left.__proto__
  const R = Right.prototype

  while (true) {
    if (L === null) return false
    if (L === R) return true
    L = L.__proto__
  }
}

手写全等函数

对比两个对象里面的内容是否全等, 使用 JSON.stringify 转化后 undefined 的值会无法判断,例如

js
let a = { b: 1, c: undefined };
let b = { b: 1 }
console.log(JSON.stringify(a) === JSON.stringify(b)) 

// 打印结果为 true,但实际上 b 比 a 少了一个属性,应该是 false
js
const isObjAndNotNull = (x) => typeof x == "object" && x != null
const deepEqual = (x, y) => {
  // 指向同一内存时
  if (x === y) {
    return true;
  } else if (isObjAndNotNull(a) && isObjAndNotNull(b)) {
    // x,y 都是对象且不是 null

    if (Object.keys(x).length !== Object.keys(y).length) {
      return false;
    }

    for (let prop in x) {
      if (y.hasOwnProperty(prop)) {  
        if (!deepEqual(x[prop], y[prop])) return false;
      } else {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
}

排序

冒泡排序 O(n^2)

js
function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length
  // 外层循环用于控制从头到尾的比较+交换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于完成每一轮遍历过程中的重复比较+交换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素前面的数比后面的大
      if (arr[j] > arr[j + 1]) {
        // 交换两者
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
  }
  // 返回数组
  return arr
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

快速排序 nlogn ~ n^2 之间

js
function quickSort(arr) {
  if (arr.length < 2) {
    return arr
  }
  const cur = arr[arr.length - 1]
  const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1)
  const right = arr.filter((v) => v > cur)
  return [...quickSort(left), cur, ...quickSort(right)]
}
// console.log(quickSort([3, 6, 2, 4, 1]));

2 分查找排序 log2(n)

js
function search(arr, target, start, end) {
  let targetIndex = -1

  let mid = Math.floor((start + end) / 2)

  if (arr[mid] === target) {
    targetIndex = mid
    return targetIndex
  }

  if (start >= end) {
    return targetIndex
  }

  if (arr[mid] < target) {
    return search(arr, target, mid + 1, end)
  } else {
    return search(arr, target, start, mid - 1)
  }
}

本站中引用到的其他资料,如有侵权,请联系本人删除