Interview -- 基础手写题

Huy大约 5 分钟anonymousnote

数组去重方法汇总

  1. 利用 Set 去重
  2. 利用 for 循环遍历,includes (能识别 NAN)、indexOf(不能识别 NAN)
// 1. set 去重
function unique(arr) {
  // 或者 return Array.from(new Set(arr))
  return [...new Set(arr)]
}

// 2. for 循环遍历
function unique2(arr) {
  if (!Array.isArray(arr)) return
  const resArr = []
  for (const item of arr) {
    if (!resArr.includes(item)) {
      resArr.push(item)
    }
  }
  return resArr
}

对象相等判断

function deepEqual(obj1, obj2) {
  if (obj1 === obj2) return true
  if (
    typeof obj1 !== 'object' ||
    obj1 === null ||
    typeof obj2 !== 'object' ||
    obj2 === null
  ) {
    return false
  }

  const keys1 = Object.keys(obj1)
  const keys2 = Object.keys(obj2)

  if (keys1.length !== keys2.length) return false

  for (const key of keys1) {
    if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false
  }
}

手写 Array 的 filter、map、some、every 和 reduce 方法

手写 filter

Array.prototype.myFilter = function (callback) {
  const len = this.length
  const res = []
  for (let i = 0; i < len; i++) {
    const item = this[i] // 当前项
    if (callback(item)) {
      res.push(item)
    }
  }
  return res
}

const words = ['spray', 'elite', 'exuberant', 'destruction', 'present']
const result = words.myFilter((word) => word.length > 6)
console.log('🚀 ~ myFilter:', result)

手写 map

Array.prototype.myMap = function (callback) {
  const len = this.length
  const res = []
  for (let i = 0; i < len; i++) {
    const item = this[i]
    res.push(callback(item))
  }
  return res
}

const array = [1, 4, 9, 16]
const map1 = array.myMap((x) => x * 2)
console.log('🚀 ~ myMap:', map1)

手写 some

Array.prototype.mySome = function (callback) {
  const len = this.length
  for (let i = 0; i < len; i++) {
    const item = this[i]
    if (callback(item)) {
      return true
    }
  }
  return false
}

const array = [1, 2, 3, 4, 5]
const even = (element) => element % 2 === 0
console.log('🚀 ~ mySome:', array.mySome(even))

手写 every

Array.prototype.myEvery = function (callback) {
  const len = this.length
  for (let i = 0; i < len; i++) {
    const item = this[i]
    if (!callback(item)) {
      return false
    }
  }
  return true
}

const isBelowThreshold = (currentValue) => currentValue < 40
const array = [1, 30, 39, 29, 10, 13]

console.log('🚀 ~ myEvery:', array.every(isBelowThreshold))

手写 reduce

Array.prototype.myReduce = function (callback, pre) {
  const len = this.length
  for (let i = 0; i < len; i++) {
    if (pre !== undefined) {
      pre = callback(pre, this[i], i, this)
    } else {
      pre = callback(this[i], this[i + 1], i, this)
      i++ // 提前一项结束
    }
  }
  return pre
}

const array = [1, 2, 3, 4]

// 0 + 1 + 2 + 3 + 4
const initialValue = 0
const sumWithInitial = array.reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  initialValue
)
console.log('🚀 ~ myReduce:', sumWithInitial)

实际应用: runPromisesInSequence

const runPromisesInSequence = (array, initialValue) => {
  array.reduce((promiseChain, currentFunction) => {
    return promiseChain.then(currentFunction)
  }, Promise.resolve(initialValue))
}

实现一个简单的模板引擎

const tpl = template('<p>hey there {{ name }} {{ name }}</p>')
const res = tpl({ name: 'Neo' })
console.log('🚀 ~ template:', res)

function template(str) {
  return function (obj) {
    for (let key in obj) {
      const val = obj[key]
      const reg = new RegExp(`{{\\s+${key}\\s+}}`, 'g')
      str = str.replace(reg, val)
    }
    return str
  }
}

对象扁平化

/**
 * 对象扁平化
 * 说明:请实现 flatten(input) 函数,input 为一个 javascript 对象(Object 或者 Array),返回值为扁平化后的结果。
 * 示例:
 *   var input = {
 *     a: 1,
 *     b: [ 1, 2, { c: true }, [ 3 ] ],
 *     d: { e: 2, f: 3 },
 *     g: null,
 *   }
 *   var output = flatten(input);
 *   output如下
 *   {
 *     "a": 1,
 *     "b[0]": 1,
 *     "b[1]": 2,
 *     "b[2].c": true,
 *     "b[3][0]": 3,
 *     "d.e": 2,
 *     "d.f": 3,
 *     // "g": null,  值为null或者undefined,丢弃
 *  }
 */
function flatten(obj) {
  const res = {}
  const recur = (target, oldKey) => {
    for (const key in target) {
      let newKey = ''
      if (oldKey) {
        // 区分是否是初次递归
        if (Array.isArray(target)) {
          newKey = `${oldKey}[${key}]`
        } else {
          newKey = `${oldKey}.${key}`
        }
      } else {
        if (Array.isArray(target)) {
          newKey = `[${key}]`
        } else {
          newKey = key
        }
      }

      if (target[key] != null) {
        // 剔除非空结果
        if (typeof target[key] === 'object') {
          recur(target[key], newKey) // 递归扁平值
        } else {
          res[newKey] = target[key] // 添加结果
        }
      }
    }
  }

  recur(obj, '')

  // 返回扁平结果
  return res
}

const inputObj = {
  a: 1,
  b: [1, 2, { c: true }, [3]],
  d: { e: 2, f: 3 },
  g: null,
}
console.log('🚀 ~ flatten:', flatten(inputObj))

根据表达式计算字母数

/**
 * 根据表达式计算字母数
 * 说明:
 *   给定一个描述字母数量的表达式,计算表达式里的每个字母实际数量
 *   表达式格式:
 *     字母紧跟表示次数的数字,如 A2B3
 *     括号可将表达式局部分组后跟上数字,(A2)2B
 *     数字为1时可缺省,如 AB3。
 * 示例:
 *   countOfLetters('A2B3'); // { A: 2, B: 3 }
 *   countOfLetters('A(A3B)2'); // { A: 7, B: 2 }
 *   countOfLetters('C4(A(A3B)2)2'); // { A: 14, B: 4, C: 4 }
 */

function countOfLetters(str) {
  let i = 0
  const strLen = str.length
  const strMap = [new Map()] // 栈

  /** 获取当前字母后续的数字 */
  const getNum = () => {
    if (i === strLen || isNaN(Number(str[i]))) {
      return 1 // 到末尾了 || 不是数字,视作 1
    }
    let num = 0
    while (i < strLen && !isNaN(Number(str[i]))) {
      num = num * 10 + Number(str[i]) // 扫描数字
      i++
    }
    return num
  }

  while (i < strLen) {
    const curStr = str[i]
    if (curStr === '(') {
      i++
      strMap.unshift(new Map()) // 压栈
    } else if (curStr === ')') {
      i++
      const beforeMap = strMap.shift() // 退栈
      const newMap = strMap[0]
      const num = getNum() // 获取括号后的数字
      // 合并对象
      for (const [beforeStr, beforeNum] of beforeMap.entries()) {
        newMap.set(beforeStr, (newMap.get(beforeStr) || 0) + beforeNum * num)
      }
    } else {
      // 当前为字母
      const mapStr = curStr // 当前字母
      i++
      const curNum = getNum() // 获取字母数量
      const curMap = strMap[0]
      curMap.set(mapStr, (curMap.get(mapStr) || 0) + curNum) // 统计数量
    }
  }

  const resMap = strMap[0]
  return Object.fromEntries(resMap.entries())
}

// 测试
console.log(countOfLetters('A2B3')) // { A: 2, B: 3 }
console.log(countOfLetters('A(A3B)2')) // { A: 7, B: 2 }
console.log(countOfLetters('C4(A(A3B)2)2')) // { A: 14, B: 4, C: 4 }

数组转 tree 结构的数据

思路:

  1. 建立查找表:遍历原始数组,创建一个对象(查找表),其中每个节点的 id 是键,节点本身(包括一个空的 children 数组)是值。这一步允许我们快速查找每个节点。

  2. 构建树结构:再次遍历原始数组,根据每个节点的 parentId 将其添加到相应父节点的 children 数组中。如果节点没有 parentId,则将其视为根节点,直接添加到最终的树数组中。

function arrayToTree(array) {
  const tree = []
  const lookup = {}

  // 创建一个查找表,以便快速访问每个节点
  array.forEach((item) => {
    lookup[item.id] = { ...item, children: [] }
  })

  // 构建树结构
  array.forEach((item) => {
    if (item.parentId === null) {
      // 如果没有 parentId,说明是根节点
      tree.push(lookup[item.id])
    } else {
      // 否则,将当前节点添加到其父节点的 children 数组中
      if (lookup[item.parentId]) {
        lookup[item.parentId].children.push(lookup[item.id])
      }
    }
  })

  return tree
}

// 示例数据
const data = [
  { id: 1, parentId: null, name: '根节点1' },
  { id: 2, parentId: 1, name: '子节点1-1' },
  { id: 3, parentId: 1, name: '子节点1-2' },
  { id: 4, parentId: 2, name: '子节点1-1-1' },
  { id: 5, parentId: null, name: '根节点2' },
  { id: 6, parentId: 5, name: '子节点2-1' },
]

const tree = arrayToTree(data)
console.log(JSON.stringify(tree, null, 2))
Loading...