跳到主要内容

笔试代码题

构造树形结构

前端使用树形结构的数据很常见,比如展示层级的Tree 树形控件.

;[
{
id: 1,
name: 'Item 1',
parentId: 8,
children: [
{
id: 2,
name: 'Item 1.1',
parentId: 1,
children: [],
},
],
},
]

但是有时候给我们的数据是这样的,需要我们将数据处理成树形结构那样.

;[
{ id: 6, name: 'Item 2.2', parentId: 1 },
{ id: 1, name: 'Item 1', parentId: 8 },
{ id: 2, name: 'Item 1.1', parentId: 1 },
]

这里使用了Map来记录每一项数据 id 与之对应的关系,默认给每一项加入了 children 属性,用于存储子节点.

遍历源数据拿到每一项的 id 与 parentId,根据 parentId 找到对应的父节点,然后将当前节点添加到父节点的 children 属性中.这里将没有对应父节点的节点添加到根节点上,可以根据具体的业务逻辑修改.

function buildTree(flatArray, idKey = 'id', parentIdKey = 'parentId') {
const itemMap = new Map()
const tree = []

flatArray.forEach((item) => {
const { [idKey]: id } = item
const node = { ...item, children: [] }
itemMap.set(id, node)
})

flatArray.forEach(({ [idKey]: id, [parentIdKey]: parentId }) => {
const currentNode = itemMap.get(id)
const parentNode = itemMap.get(parentId)

parentNode ? parentNode.children.push(currentNode) : tree.push(currentNode)
})

return tree
}

数字进行千分位格式化.

类似下面这样每三位对数字进行转换

  • 1234567 => 1,234,567
  • 1234567.891245 => 1,234,567.891,245

这个用正则很快就能解决掉,但是对正则不熟悉的有点麻烦.这里不使用正则来进行分割.

function formatPrice(price) {
const [integerPart, decimalPart] = price.toString().split('.')
const int = integerPart.split('').reduceRight((prev, next, index) => {
return (index % 3 ? next : next + ',') + prev
})
const dec = decimalPart?.split('').reduce((prev, next, index) => {
return (index % 3 ? prev : prev + ',') + next
})
return dec ? `${int}.${dec}` : int
}
console.log(formatPrice(1234567))
console.log(formatPrice(1234567.891245))
console.log(formatPrice(0.891245))
console.log(formatPrice(0.0))

这里分别对整数和小数进行格式化.利用了 reduce 方法以及它里面回调函数的 index 来判断是否需要添加逗号.

获取 url 参数

function getAllUrlParams(url, query) {
const queryString = url.split('?')[1] // 提取查询字符串
const params = new URLSearchParams(queryString)

// return params.getAll(query)
return params.get(query)
}

// 示例
const url =
'https://example.com/page?name=JohnDoe&age=25&country=US&name=JohnDoe1'
console.log(getAllUrlParams(url, 'name'))

lodash.get

function get(obj, path, defaultValue) {
if (!path) return obj
const pathKey = Array.isArray(path) ? path : _basePath(path)

let result = obj
for (const k of pathKey) {
result = result[k]
if (!result) return defaultValue
}

return result
}
// 将 'a[0].b.c' => ['a', '0', 'b', 'c'] 处理成数组形式方便处理
function _basePath(path) {
return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
}

const object = { a: [{ b: { c: 3 } }] }

console.log(get(object, 'a[0].b.c'))
// => 3

console.log(get(object, ['a', '0', 'b', 'c']))
// => 3

console.log(get(object, 'a.b.c', 'default'))
// => 'default'

es6 里面的可选链可以替代这个方法了

lodash.set

设置 object 对象中对应 path 属性路径上的值,如果 path 不存在,则创建。 缺少的索引属性会创建为数组,而缺少的属性会创建为对象。 使用_.setWith 定制 path 创建。

function set(obj, path, value) {
if (!path) return obj
const pathKey = Array.isArray(path) ? path : _basePath(path)

let cur = obj
for (const [i, v] of pathKey.entries()) {
if (i === pathKey.length - 1) {
cur[v] = value
return
}
if (typeof cur[v] === 'undefined') {
cur[v] = /[0-9]/.test(v) ? [] : {}
}
cur = cur[v]
}
}
// 将 'a[0].b.c' => ['a', '0', 'b', 'c'] 处理成数组形式方便处理
function _basePath(path) {
return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
}

const object = { a: [{ b: { c: 3 } }] }

set(object, 'a[0].b.c', 4)
console.log(object.a[0].b.c)
// => 4

set(object, ['x', '0', 'y', 'z'], 5)
console.log(object.x[0].y.z)
// => 5

console.log(/[0-9]/.test(1))

这个 set 和 get 方法,都需要对传入的参数进行处理成数组形式方便后面使用,同时需要一个额外的变量保存当前节点.

set 方法还需要对属性和索引不存在的情况额外判断赋值.