zhangdizhangdi

面试手写题

文章

数组与树

ts
interface NodeItem {
  id: number
  name: string
  parentId?: number
}

interface TreeItem extends NodeItem {
  children?: NodeItem[]
}

type List = NodeItem[]
type Tree = TreeItem[]

给定数组

ts
const array: List = [
  { id: 2, name: '部门B', parentId: 0 },
  { id: 3, name: '部门C', parentId: 1 },
  { id: 1, name: '部门A', parentId: 2 },
  { id: 4, name: '部门D', parentId: 1 },
  { id: 5, name: '部门E', parentId: 2 },
  { id: 6, name: '部门F', parentId: 3 },
  { id: 7, name: '部门G', parentId: 2 },
  { id: 8, name: '部门H', parentId: 4 },
]

数组转树

ts
function arrayToTree(list: List, pId: number): Tree {
  const len = list.length
  const loop = (pId: number) => {
    const res: Tree = []
    for (let i = 0; i < len; i++) {
      // console.log(pId, i) //72次
      const item: TreeItem = list[i]
      if (item.parentId === pId) {
        item.children = loop(item.id)
        res.push(item)
      }
    }
    return res
  }
  return loop(pId)
}

const tree = arrayToTree(array, 0)
console.log('数组转树', tree)

树转数组

ts
function treeToArray(tree: Tree): List {
  const result = []
  tree.forEach(item => {
    const loop = (data: TreeItem) => {
      result.push({
        id: data.id,
        name: data.name,
        parentId: data.parentId,
      })
      const child = data.children
      if (child) {
        for (let i = 0; i < child.length; i++) {
          loop(child[i])
        }
      }
    }
    loop(item)
  })
  return result
}

console.log('树转数组', treeToArray(tree))

新增节点

ts
function addNode(root: Tree, node: TreeItem) {
  let added = false

  const loop = (tree: Tree) => {
    const len = tree.length
    for (let i = 0; i < len; i++) {
      if (added) break

      const item = tree[i]
      // console.log('新增 for item', item)
      if (item.id === node.parentId) {
        item.children ? item.children.push(node) : (item.children = [node])
        added = true
      } else {
        if (item.children) {
          loop(item.children)
        }
      }
    }
  }

  loop(root)
  return root
}

console.log('新增节点', addNode(tree, { id: 9, name: '新增部门', parentId: 3 }))

删除节点

ts
function deleteNode(root: Tree, deleteTreeId: number) {
  let deleted = false

  const loop = (tree: Tree) => {
    const len = tree.length
    for (let i = 0; i < len; i++) {
      if (deleted) break

      const node = tree[i]
      // console.log('删除 for item', node)
      if (node.id === deleteTreeId) {
        tree.splice(i, 1)
        deleted = true
      } else {
        if (node.children) {
          loop(node.children)
        }
      }
    }
  }
  loop(root)

  return root
}
console.log('删除节点', deleteNode(tree, 9))

修改节点

ts
function updateNode(root: Tree, node: NodeItem) {
  let updated = false

  const loop = (tree: Tree) => {
    const len = tree.length
    for (let i = 0; i < len; i++) {
      if (updated) break

      const item = tree[i]
      // console.log('修改 for item', item)
      if (item.id === node.id) {
        tree[i] = node
        updated = true
      } else {
        if (item.children) {
          loop(item.children)
        }
      }
    }
  }

  loop(root)
  return root
}

console.log('修改节点', updateNode(tree, { id: 5, name: '修改部门' }))

查找节点

ts
function findNode(root: Tree, id: number) {
  let foundNode = null

  const loop = (tree: Tree) => {
    const len = tree.length
    for (let i = 0; i < len; i++) {
      if (foundNode) break

      const node = tree[i]
      if (node.id === id) {
        foundNode = node
      } else {
        if (node.children) {
          loop(node.children)
        }
      }
    }
  }
  loop(root)

  return foundNode
}
console.log('查找节点', findNode(tree, 4))

函数式编程

柯里化

js
function currying(fn) {
  const length = fn.length
  return function (...args) {
    return args.length >= length
      ? fn.apply(this, args)
      : currying(fn.bind(this, ...args))
  }
}

// Test
const fn = currying(function (a, b, c) {
  console.log([a, b, c])
})

fn('a', 'b', 'c') // ["a", "b", "c"]
fn('a', 'b')('c') // ["a", "b", "c"]
fn('a')('b')('c') // ["a", "b", "c"]
fn('a')('b', 'c') // ["a", "b", "c"]

累加函数

js
function sum(...args) {
  let params = args
  const _sum = (...newArgs) => {
    if (newArgs.length === 0) {
      return params.reduce((pre, cur) => pre + cur, 0)
    } else {
      params = [...params, ...newArgs]
      return _sum
    }
  }
  return _sum
}

// Test
console.log(sum(1, 2)(3)()) // 6
console.log(sum(1)(2)(3)()) // 6
console.log(sum(1, 2, 4)(4)()) // 11

once 函数

js
const once = fn => {
  let res,
    isFirst = true
  return function (...args) {
    if (!isFirst) return res
    res = fn.call(this, ...args)
    isFirst = false
    return res
  }
}

// Test
const f = x => x
const onceF = once(f)
//=> 3
onceF(3)
//=> 3
onceF(4)

vDom转成真实Dom

ts
type VNode =
  | {
      tag: string
      attrs?: object
      children?: VNode[]
    }
  | number
  | string

const vnode: VNode = {
  tag: 'DIV',
  attrs: { id: 'app' },
  children: [
    {
      tag: 'SPAN',
      children: [
        {
          tag: 'mark',
          attrs: { class: 'text-amber-500' },
          children: ['这里是添加的dom'],
        },
      ],
    },
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] },
        { tag: 'A', children: [] },
      ],
    },
  ],
}

function _render(vnode: VNode) {
  if (typeof vnode === 'number') {
    vnode = String(vnode)
  }
  //处理文本节点
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode)
  }

  const { tag, attrs, children } = vnode

  //普通dom
  const dom = document.createElement(tag)

  if (attrs) {
    for (let key in attrs) {
      dom.setAttribute(key, attrs[key])
    }
  }

  if (children.length > 0) {
    children.forEach(node => dom.appendChild(_render(node)))
  }

  return dom
}

function render(vnode: VNode, container: HTMLElement) {
  const dom = _render(vnode)
  console.log(dom)
  container.appendChild(dom)
}

onMounted(() => {
  render(vnode, document.getElementById('root'))
})