前端八股文之手写系列

面试中常常会遇到同样的问题,大多数面经都将其整理成“面试八股文”以供背诵,尤其是技术乱七八糟一大堆的前端,八股文现象尤为显著

所以面试中击败你的,不光是 985 博士和海龟等精英,还有很多“八股文选手”

本着打不过就加入的原则,本篇文章会持续更新前端八股文系列

# 快排

这里给出分治+递归的写法 代码比较短,方便记忆

const sort = (array) => {
  if (array.length < 2) {
    return array
  }

  const mid = array.splice(Math.floor(array.length / 2), 1)[0]
  const left = []
  const right = []

  array.forEach((item) => {
    if (item < mid) {
      left.push(item)
    } else {
      right.push(item)
    }
  })

  return [...sort(left), mid, ...sort(right)]
}

# 数组扁平化

建议背前 3 个,后面的可以理解下思路(如果面试官问到别的方法的话)

递归(推荐) 元素可以是任意类型

const flatArray = (array) => {
  const result = []
  array.forEach((item) => {
    if (Array.isArray(item)) {
      result.push(...flatArray(item))
    } else {
      result.push(item)
    }
  })
  return result
}

循环+concat(推荐) 元素可以是任意类型

const flatArray = (array) => {
  while (array.some((item) => Array.isArray(item))) {
    array = [].concat(...array)
  }
  return array
}

reduce(推荐) 元素可以是任意类型

const flatArray = (array) => {
  return []
    .concat(array)
    .reduce(
      (item, next) =>
        item.concat(Array.isArray(array) ? flatArray(next) : next),
      []
    )
}

toString+split(不推荐) 代码短但是数组元素只能是number或者同一种类型的基本类型

const flatArray = (array) => {
  return array
    .toString()
    .split(',')
    .map((item) => Number(item))
}

join+split(不推荐) 优缺点同上,join 也可以转字符串

const flatArray = (array) => {
  return array
    .join(',')
    .split(',')
    .map((item) => Number(item))
}

JSON+正则(不推荐) 优缺点同上,正则不太好记忆

const flatArray = (array) => {
  return JSON.stringify(array)
    .replace(/(\[|\])/g, '')
    .split(',')
    .map((item) => Number(item))
}

ES6 自带的 flat(不推荐) 千万别写(就是考你 flat 实现的,你直接调了现成的。。。) 但是要了解一下 ES6 这个新引入的函数

const flatArray = (array) => {
  while (array.some((item) => Array.isArray(item))) {
    array = array.flat()
  }
  return array
}

# call、apply、bind

# call

  • 判断nullundefined时指向全局对象
  • 对基本类型装箱:new Object(...)
Function.prototype._call = function(context) {
  if (context === undefined || context === null) {
    context = globalThis
  }

  context = new Object(context)

  context.fn = this
  const args = []

  Array.prototype.forEach.call(
    Array.prototype.slice.call(arguments, 1),
    (item) => args.push(item)
  )

  const result = context.fn(...args)

  delete context.fn
  return result
}

# apply

类似 call 的实现,传参方式不同

Function.prototype._apply = function(context, args) {
  if (context === undefined || context === null) {
    context = globalThis
  }

  context = new Object(context)
  context.fn = this

  const result = context.fn(...args)

  delete context.fn
  return context
}

# bind

简易版 存在问题:返回的函数不能构造器调用(new xxx())

Function.prototype._bind = function(...args) {
  const fn = this
  const that = args[0]
  const bindArgs = args.slice(1)

  return (...args) => {
    fn.apply(that, [...bindArgs, ...args])
  }
}

类似 MDN 的实现

Function.prototype._bind = function(context) {
  if (typeof this !== 'function') {
    throw new Error(
      'Function.prototype.bind - what is trying to be bound is not callable'
    )
  }

  const fn = this
  const args = Array.prototype.slice.call(arguments, 1)
  const noop = function() {}
  const result = function() {
    const bindArgs = Array.prototype.slice.call(arguments)
    return fn.apply(this instanceof noop ? this : context, args.bindArgs)
  }

  noop.prototype = fn.prototype
  result.prototype = new noop()
  return result
}

# 数组去重

有很多种思路,这里只列出几种代码较少的

利用 Set 利用 Set 的不可重复特性(ES6)

const unique = (array) => [...new Set(array)]

利用 Map 将元素设置为 Map 的键再检索(ES6)

const unique = (array) => {
  const map = new Map()
  return array.filter((item) => !map.has(item) && map.set(item, 1))
}

reduce

const unique = array =>
    array.reduce((item, next) =>
        item.includes(next) ? item : [...item, next], [])
}

ES5 对象 这个方法会将'1'1看做相同

const unique = (array) => {
  const map = {}
  return array.filter((item) => !map.hasOwnProperty(item) && (map[item] = 1))
}

# 防抖、节流

# 防抖

const debounce = (fn, time) => {
  let timer = null
  return function() {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, time)
  }
}

# 节流

const throttle = (fn, time) => {
  let flag = true
  return function() {
    if (!flag) {
      return
    }
    flag = false
    setTimeout(() => {
      fn.apply(this, arguments)
      flag = true
    }, time)
  }
}

# 函数柯里化

实现 add 函数,使得add(1,2,3)add(1)(2)(3)add(1,2)(3)都得到正确结果

function add() {
  const args = [...arguments]

  function fn() {
    args.push(...arguments)
    return fn
  }

  fn.toString = function() {
    return args.reduce((item, next) => item + next)
  }

  return fn
}

# 类数组转数组

类数组是指具有 length 属性,但不具有数组方法,索引都为非负整数的对象

Array.from

const trans = Array.from

Array.slice

const transform = (likeArray) => Array.prototype.slice.call(likeArray)

concat

const transform = (likeArray) => Array.prototype.concat.apply([], likeArray)

# 深/浅拷贝

# 深拷贝

利用 JSON 对函数、undefinedSymbol元素无效

const deepCopy = (obj) => JSON.parse(JSON.stringify(obj))

递归

const deepCopy = (obj) => {
  let objNew = Array.isArray(obj) ? [] : {}
  if (obj && typeof obj === 'object') {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] === 'object') {
          objNew[key] = deepCopy(obj[key])
        } else {
          objNew[key] = obj[key]
        }
      }
    }
  }
  return objNew
}

# 浅拷贝

循环判断

const shallowCopy = (obj) => {
  let newObj = obj instanceof Array ? [] : {}
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key]
    }
  }
  return newObj
}

Object.assign

const shallowCopy = (obj) => Object.assign({}, obj)

# 链式调用

class Person {
  constructor() {
    this._name = ''
    this._age = 0
  }

  name(name) {
    this._name = name
    return this
  }

  age(age) {
    this._age = age
    return this
  }
}

// 使用
let person = new Person().name('王昭君').age(21)