1、【‘1’,‘2’,‘3’】.map(parseInt)
2、函数修改形参,能否影响实参 ?
3、手写 convert 函数,将数组转为树
代码演示
/**
* @description array to tree
* @author 双越老师
*/
interface IArrayItem {
id: number
name: string
parentId: number
}
interface ITreeNode {
id: number
name: string
children?: ITreeNode[]
}
function convert(arr: IArrayItem[]): ITreeNode | null {
// 用于 id 和 treeNode 的映射
const idToTreeNode: Map<number, ITreeNode> = new Map()
let root = null
arr.forEach(item => {
const { id, name, parentId } = item
// 定义 tree node 并加入 map
const treeNode: ITreeNode = { id, name }
idToTreeNode.set(id, treeNode)
// 找到 parentNode 并加入到它的 children
const parentNode = idToTreeNode.get(parentId)
if (parentNode) {
if (parentNode.children == null) parentNode.children = []
parentNode.children.push(treeNode)
}
// 找到根节点
if (parentId === 0) root = treeNode
})
return root
}
const arr = [
{ id: 1, name: '部门A', parentId: 0 }, // 0 代表顶级节点,无父节点
{ id: 2, name: '部门B', parentId: 1 },
{ id: 3, name: '部门C', parentId: 1 },
{ id: 4, name: '部门D', parentId: 2 },
{ id: 5, name: '部门E', parentId: 2 },
{ id: 6, name: '部门F', parentId: 3 },
]
const tree = convert(arr)
console.info(tree)
连环问:手写 convert 函数,将树转为tree
代码演示
/**
* @description tree to arr
* @author 双越老师
*/
interface IArrayItem {
id: number
name: string
parentId: number
}
interface ITreeNode {
id: number
name: string
children?: ITreeNode[]
}
function convert1(root: ITreeNode): IArrayItem[] {
// Map
const nodeToParent: Map<ITreeNode, ITreeNode> = new Map()
const arr: IArrayItem[] = []
// 广度优先遍历,queue
const queue: ITreeNode[] = []
queue.unshift(root) // 根节点 入队
while (queue.length > 0) {
const curNode = queue.pop() // 出队
if (curNode == null) break
const { id, name, children = [] } = curNode
// 创建数组 item 并 push
const parentNode = nodeToParent.get(curNode)
const parentId = parentNode?.id || 0
const item = { id, name, parentId }
arr.push(item)
// 子节点入队
children.forEach(child => {
// 映射 parent
nodeToParent.set(child, curNode)
// 入队
queue.unshift(child)
})
}
return arr
}
const obj = {
id: 1,
name: '部门A',
children: [
{
id: 2,
name: '部门B',
children: [
{ id: 4, name: '部门D' },
{ id: 5, name: '部门E' }
]
},
{
id: 3,
name: '部门C',
children: [
{ id: 6, name: '部门F' }
]
}
]
}
const arr1 = convert1(obj)
console.info(arr1)
4、 代码输出
5、promise-then 的 执行顺序
Promise.resolve().then(() => {
console.log(1)
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
}).then(() => {
console.log(5)
})
Promise.resolve().then(() => {
console.log(10)
}).then(() => {
console.log(20)
}).then(() => {
console.log(30)
}).then(() => {
console.log(40)
}).then(() => {
console.log(50)
})
Promise.resolve().then(() => {
console.log(100)
}).then(() => {
console.log(200)
}).then(() => {
console.log(300)
}).then(() => {
console.log(400)
}).then(() => {
console.log(500)
})
6、React state 经典面试题
连环问: react setState 是微任务还是宏任务 ?
7、读代码- 对象和属性的连续赋值 ?
8、读代码 - 对象属性类型问题 ?
9、new一个对象的过程是什么,手写代码表示
export function customNew<T>(constructor: Function, ...args: any[]): T {
// 1. 创建一个空对象,继承 constructor 的原型
const obj = Object.create(constructor.prototype)
// 2. 将 obj 作为 this ,执行 constructor ,传入参数
constructor.apply(obj, args)
// 3. 返回 obj
return obj
}
class Foo {
// 属性
name: string
city: string
n: number
constructor(name: string, n: number) {
this.name = name
this.city = '北京'
this.n = n
}
getName() {
return this.name
}
}
// const f = new Foo('双越', 100)
const f = customNew<Foo>(Foo, '双越', 100)
console.info(f)
console.info(f.getName())
10、深度优先遍历一个DOM树
/**
* 访问节点
* @param n node
*/
function visitNode(n: Node) {
if (n instanceof Comment) {
// 注释
console.info('Comment node ---', n.textContent)
}
if (n instanceof Text) {
// 文本
const t = n.textContent?.trim()
if (t) {
console.info('Text node ---', t)
}
}
if (n instanceof HTMLElement) {
// element
console.info('Element node ---', `<${n.tagName.toLowerCase()}>`)
}
}
/**
* 深度优先遍历
* @param root dom node
*/
function depthFirstTraverse1(root: Node) {
visitNode(root)
const childNodes = root.childNodes // .childNodes 和 .children 不一样
if (childNodes.length) {
childNodes.forEach(child => {
depthFirstTraverse1(child) // 递归
})
}
}
/**
* 深度优先遍历
* @param root dom node
*/
function depthFirstTraverse2(root: Node) {
const stack: Node[] = []
// 根节点压栈
stack.push(root)
while (stack.length > 0) {
const curNode = stack.pop() // 出栈
if (curNode == null) break
visitNode(curNode)
// 子节点压栈
const childNodes = curNode.childNodes
if (childNodes.length > 0) {
// reverse 反顺序压栈
Array.from(childNodes).reverse().forEach(child => stack.push(child))
}
}
}
11、广度优先遍历一个DOM树
/**
* 访问节点
* @param n node
*/
function visitNode(n: Node) {
if (n instanceof Comment) {
// 注释
console.info('Comment node ---', n.textContent)
}
if (n instanceof Text) {
// 文本
const t = n.textContent?.trim()
if (t) {
console.info('Text node ---', t)
}
}
if (n instanceof HTMLElement) {
// element
console.info('Element node ---', `<${n.tagName.toLowerCase()}>`)
}
}
/**
* 广度优先遍历
* @param root dom node
*/
function breadthFirstTraverse(root: Node) {
const queue: Node[] = [] // 数组 vs 链表
// 根节点入队列
queue.unshift(root)
while (queue.length > 0) {
const curNode = queue.pop()
if (curNode == null) break
visitNode(curNode)
// 子节点入队
const childNodes = curNode.childNodes
if (childNodes.length) {
childNodes.forEach(child => queue.unshift(child))
}
}
}
【连环问】深度优先遍历可以不用递归吗
12、instanceof原理是什么,请写代码表示
/**
* @description 手写 instanceof
*/
/**
* 自定义 instanceof
* @param instance instance
* @param origin class or function
*/
export function myInstanceof(instance: any, origin: any): boolean {
if (instance == null) return false // null undefined
const type = typeof instance
if (type !== 'object' && type !== 'function') {
// 值类型
return false
}
let tempInstance = instance // 为了防止修改 instance
while (tempInstance) {
if (tempInstance.__proto__ === origin.prototype) {
return true // 配上了
}
// 未匹配
tempInstance = tempInstance.__proto__ // 顺着原型链,往上找
}
return false
}
// // 功能测试
console.info(myInstanceof({}, Object)) // true
console.info(myInstanceof([], Object)) // true
console.info(myInstanceof([], Array)) // true
console.info(myInstanceof({}, Array)) // false
console.info(myInstanceof('abc', String)) // false
13、手写函数bind功能
/**
* @description 手写 bind
*/
// @ts-ignore
Function.prototype.customBind = function (context: any, ...bindArgs: any[]) {
// context 是 bind 传入的 this
// bindArgs 是 bind 传入的各个参数
const self = this // 当前的函数本身
return function (...args: any[]) {
// 拼接参数
const newArgs = bindArgs.concat(args)
return self.apply(context, newArgs)
}
}
// // 功能测试
// function fn(this: any, a: any, b: any, c: any) {
// console.info(this, a, b, c)
// }
// // @ts-ignore
// const fn1 = fn.customBind({x: 100}, 10)
// fn1(20, 30)
【连环问】手写函数call和apply功能
/**
* @description 自定义 call apply
*/
// @ts-ignore
Function.prototype.customCall = function (context: any, ...args: any[]) {
if (context == null) context = globalThis
if (typeof context !== 'object') context = new Object(context) // 值类型,变为对象
const fnKey = Symbol() // 不会出现属性名称的覆盖
context[fnKey] = this // this 就是当前的函数
const res = context[fnKey](...args) // 绑定了 this
delete context[fnKey] // 清理掉 fn ,防止污染
return res
}
// @ts-ignore
Function.prototype.customApply = function (context: any, args: any[] = []) {
if (context == null) context = globalThis
if (typeof context !== 'object') context = new Object(context) // 值类型,变为对象
const fnKey = Symbol() // 不会出现属性名称的覆盖
context[fnKey] = this // this 就是当前的函数
const res = context[fnKey](...args) // 绑定了 this
delete context[fnKey] // 清理掉 fn ,防止污染
return res
}
function fn(this: any, a: any, b: any, c: any) {
console.info(this, a, b, c)
}
// // @ts-ignore
// fn.customCall({x: 100}, 10, 20, 30)
// @ts-ignore
// fn.customApply({x: 200}, [100, 200, 300])
14、手写一个JS函数,实现数组扁平化Array Flatten
/**
* @description 数组扁平化
*/
/**
* 数组扁平化,使用 push
* @param arr arr
*/
export function flatten1(arr: any[]): any[] {
const res: any[] = []
arr.forEach(item => {
if (Array.isArray(item)) {
item.forEach(n => res.push(n))
} else {
res.push(item)
}
})
return res
}
const arr = [1, 2, 3, 4, 5, [6, 7, [8, 9]]]
console.log(flatten1(arr)) //[1,2,3,4,5,6,7,[8,9]]
/**
* 数组扁平化,使用 concat
* @param arr arr
*/
export function flatten2(arr: any[]): any[] {
let res: any[] = []
arr.forEach(item => {
res = res.concat(item)
})
return res
}
// // 功能测试
const arr2 = [1, 2, 3, 4, 5, [6, 7, [8, 9]]]
console.log(flatten1(arr2)) //[1,2,3,4,5,6,7,[8,9]]
【连环问】手写一个JS函数,实现数组深度扁平化
/**
* @description 数组深度扁平化
*/
/**
* 数组深度扁平化,使用 push
* @param arr arr
*/
export function flattenDeep1(arr: any[]): any[] {
const res: any[] = []
arr.forEach(item => {
if (Array.isArray(item)) {
const flatItem = flattenDeep1(item) // 递归
flatItem.forEach(n => res.push(n))
} else {
res.push(item)
}
})
return res
}
const arrs = [1, 2, 3, 4, [5, 6, [7, 8]]]
console.log(flatternDeep1(arrs)) // [1,2,3,4,5,6,7,8]
/**
* 数组深度扁平化,使用 concat
* @param arr arr
*/
export function flattenDeep2(arr: any[]): any[] {
let res: any[] = []
arr.forEach(item => {
if (Array.isArray(item)) {
const flatItem = flattenDeep2(item) // 递归
res = res.concat(flatItem)
} else {
res = res.concat(item)
}
})
return res
}
// // 功能测试
// const arr = [1, [2, [3, ['a', [true], 'b'], 4], 5], 6]
// console.info( flattenDeep2(arr) ) // [1, 2, 3, 'a', true, 'b', 4, 5, 6]
15、 手写curry函数,实现函数柯里化(常考)
/**
* @description curry add
*/
export function curry(fn: Function) {
const fnArgsLength = fn.length // 传入函数的参数长度
let args: any[] = []
// ts 中,独立的函数,this 需要声明类型
function calc(this: any, ...newArgs: any[]) {
// 积累参数
args = [
...args,
...newArgs
]
if (args.length < fnArgsLength) {
// 参数不够,返回函数
return calc
} else {
// 参数够了,返回执行结果
return fn.apply(this, args.slice(0, fnArgsLength))
}
}
return calc
}
// function add(a: number, b: number, c: number): number {
// return a + b + c
// }
// // add(10, 20, 30) // 60
// const curryAdd = curry(add)
// const res = curryAdd(10)(20)(30) // 60
// console.info(res)