手写 Promise

  1. function IPromise(executor) {
  2. this.status = 'pending' // 当前状态
  3. this.value = null // resolve后的值
  4. this.reason = null // reject后的值
  5. this.onFullfilledArray = [] // fullfilled状态数组,用于存放fullfilled状态的回调函数
  6. this.onRejectedArray = [] // rejected状态数组,用于存放rejected状态的回调函数
  7. const resolve = value => {
  8. if (value instanceof IPromise) {
  9. return value.then(resolve, reject)
  10. }
  11. // 模拟微任务
  12. setTimeout(() => {
  13. if (this.status === 'pending') {
  14. this.value = value
  15. this.status = 'fullfilled'
  16. // 遍历执行
  17. this.onFullfilledArray.forEach(func => {
  18. func(value)
  19. })
  20. }
  21. })
  22. }
  23. const reject = reason => {
  24. setTimeout(() => {
  25. if (this.status === 'pending') {
  26. this.reason = reason
  27. this.status = 'rejected'
  28. this.onRejectedArray.forEach(func => {
  29. func(reason)
  30. })
  31. }
  32. })
  33. }
  34. try {
  35. executor(resolve, reject)
  36. } catch (e) {
  37. reject(e)
  38. }
  39. }
  40. // 由于变量result既可以是普通值,也可以是promise实例,所以用resolvePromise统一管理
  41. const resolvePromise = (promise2, result, resolve, reject) => {
  42. // result 和 promise2 相等会出现死循环
  43. if (result === promise2) {
  44. reject(new TypeError('error due to circular reference'))
  45. }
  46. let consumed = false // 是否已经执行过
  47. let thenable
  48. if (result instanceof IPromise) {
  49. if (result.status === 'pending') {
  50. result.then(function (data) {
  51. resolvePromise(promise2, data, resolve, reject)
  52. }, reject)
  53. } else {
  54. result.then(resolve, reject)
  55. }
  56. return
  57. }
  58. let isComplexResult = target =>
  59. typeof target === 'function' ||
  60. (typeof target === 'object' && target !== null)
  61. // 如果返回的是疑似 Promise类型
  62. if (isComplexResult(result)) {
  63. try {
  64. thenable = result.then
  65. // 判断返回值是否是promise类型
  66. if (typeof thenable === 'function') {
  67. thenable.call(
  68. result,
  69. function (data) {
  70. if (consumed) {
  71. return
  72. }
  73. consumed = true
  74. return resolvePromise(promise2, data, resolve, reject)
  75. },
  76. function (error) {
  77. if (consumed) {
  78. return
  79. }
  80. consumed = true
  81. reject(error)
  82. }
  83. )
  84. } else {
  85. resolve(result)
  86. }
  87. } catch (e) {
  88. if (consumed) return
  89. consumed = true
  90. return reject(e)
  91. }
  92. } else {
  93. resolve(result)
  94. }
  95. }
  96. IPromise.prototype.then = function (onFullfilled, onRejected) {
  97. // promise穿透
  98. onFullfilled =
  99. typeof onFullfilled === 'function' ? onFullfilled : data => data
  100. onRejected =
  101. typeof onRejected === 'function'
  102. ? onRejected
  103. : err => {
  104. throw error
  105. }
  106. let promise2 // 作为then方法的返回值
  107. if (this.status === 'fullfilled') {
  108. return (promise2 = new IPromise((resolve, reject) => {
  109. setTimeout(() => {
  110. try {
  111. let result = onFullfilled(this.value)
  112. resolvePromise(promise2, result, resolve, reject)
  113. } catch (e) {
  114. reject(e)
  115. }
  116. })
  117. }))
  118. }
  119. if (this.status === 'rejected') {
  120. return (promise2 = new IPromise((resolve, reject) => {
  121. setTimeout(() => {
  122. try {
  123. let result = onRejected(this.reason)
  124. resolvePromise(promise2, result, resolve, reject)
  125. } catch (e) {
  126. reject(e)
  127. }
  128. })
  129. }))
  130. }
  131. if (this.status === 'pending') {
  132. return (promise2 = new IPromise((resolve, reject) => {
  133. setTimeout(() => {
  134. this.onFullfilledArray.push(value => {
  135. try {
  136. let result = onFullfilled(value)
  137. resolvePromise(promise2, result, resolve, reject)
  138. } catch (e) {
  139. reject(e)
  140. }
  141. })
  142. this.onRejectedArray.push(reason => {
  143. try {
  144. let result = onRejected(reason)
  145. resolvePromise(promise2, result, resolve, reject)
  146. } catch (e) {
  147. reject(e)
  148. }
  149. })
  150. })
  151. }))
  152. }
  153. }
  154. const p = new IPromise((resolve, reject) => {
  155. setTimeout(() => {
  156. resolve('ha0ran')
  157. }, 2000)
  158. })
  159. p.then(null).then(data => console.log(data))

手写 Promise.all

该方法接收一个 promise 的 iterable 类型,并且只返回一个 Promise 实例,那个输入的所有 promise 的 resolve 回调的结果是一个数组。
这个 Promise 实例的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。
它的 reject 回调执行是在任意一个输入的 promise 的 reject 执行或者
如果传入的参数不包含任何 promise,则返回一个异步完成的 Promise
其他情况下返回一个处理中的 Promise。返回的数组的值会按照参数内的 promise 顺序排列。

  1. /**
  2. * 输入为 iterable 的参数,可以是 Array、Map、Set、String,可能也得包括模块的Iterator
  3. * 若输入的可迭代数据里不是 Promise,则也需要原样输出
  4. * 返回一个 Promise实例,可以调用 then 和 catch 方法
  5. * 输出在then里面体现为保持原顺序的数组
  6. * 输出在 catch 里面体现为最早的reject返回值
  7. * 空 iterator,resolve 返回空数组
  8. */
  9. function all(promises) {
  10. // your code here
  11. if (
  12. promises == null ||
  13. (Array.isArray(promises) && promises.length === 0)
  14. ) {
  15. return Promise.resolve([])
  16. }
  17. return new Promise((resolve, reject) => {
  18. let unSettledPromiseCount = promises.length
  19. const result = Array(unSettledPromiseCount)
  20. promises.forEach((promise, index) => {
  21. Promise.resolve(promise).then(
  22. res => {
  23. // - result.push(res);
  24. // +
  25. result[index] = res
  26. unSettledPromiseCount--
  27. if (unSettledPromiseCount === 0) {
  28. resolve(result)
  29. }
  30. },
  31. err => {
  32. reject(err)
  33. }
  34. )
  35. })
  36. })
  37. }

Promise.allSettled

字节实习一面
与 Promise.all 类似,会返回一个对象数组,但是该方法会返回所有的 promise 结果,不论 promise 的状态是 fullfilled 还是 rejected。

  1. function allSettled(promises) {
  2. // 如果传入一个空对象,返回一个已经完成的Promise
  3. if (promises.length === 0) {
  4. return Promise.resolve([])
  5. }
  6. return new Promise((resolve, reject) => {
  7. let unSettledPromiseCount = promises.length
  8. const result = Array(unSettledPromiseCount)
  9. promises.forEach((promise, index) => {
  10. Promise.resolve(promise).then(
  11. value => {
  12. result[index] = {
  13. status: 'fulfilled',
  14. value,
  15. }
  16. unSettledPromiseCount--
  17. // 如果全部都 resolve 掉了,就返回结果
  18. if (unSettledPromiseCount === 0) {
  19. resolve(result)
  20. }
  21. },
  22. reason => {
  23. result[index] = {
  24. status: 'rejected',
  25. reason,
  26. }
  27. unSettledPromiseCount--
  28. if (unSettledPromiseCount === 0) {
  29. resolve(result)
  30. }
  31. }
  32. )
  33. })
  34. })
  35. }

手写 Promise.race

  1. function PromiseRace(args) {
  2. let iteratorIndex = 0
  3. return new Promise((resolve, reject) => {
  4. for (const item of args) {
  5. iteratorIndex++
  6. Promise.resolve(item).then(resolve, reject)
  7. }
  8. if (iteratorIndex === 0) {
  9. resolve([])
  10. }
  11. })
  12. }

Promise.any

Promise.then

Promise.resolve

Promise.reject

Promise.catch

Promise.finally

猿辅导二面

  1. Promise.prototype.finally = function (cb) {
  2. this.then(val => {
  3. return Promise.resolve(cb()).then(() => val)
  4. }, err => {
  5. return Promise.resolve(cb()).then(() => {
  6. throw err
  7. })
  8. })
  9. }

手写 Reduce

  1. Array.prototype.ireduce = function (func, initialValue) {
  2. let arr = this
  3. let base = initialValue === 'undefined' ? arr[0] : initialValue
  4. let startPoint = initialValue === 'undefined' ? 1 : 0
  5. arr.slice(startPoint).forEach((value, index) => {
  6. base = func(base, value, index + startPoint, arr) // pre, cur, index, arr
  7. })
  8. return base
  9. }

手写 funPromiseInSequence(按顺序运行 promise)

  1. const runPromiseInSequence = (array, value) => {
  2. return array.reduce(
  3. (promiseChain, currentFunc) => promiseChain.then(currentFunc),
  4. Promise.resolve(value)
  5. )
  6. }

数组去重

  1. // 可去重 NaN & 引用类型
  2. function unique(arr) {
  3. let newObj = {};
  4. let newArr = [];
  5. arr.forEach((item) => {
  6. if (typeof item !== 'object') {
  7. // NaN是唯一一个不等于任何自身的类型
  8. if (item !== item) {
  9. if (!newObj[item.toString()]) {
  10. newArr.push(item);
  11. newObj[item.toString()] = true;
  12. }
  13. } else {
  14. if (newArr.indexOf(item) === -1) {
  15. newArr.push(item);
  16. }
  17. }
  18. } else {
  19. let str = JSON.stringify(item);
  20. if (!newObj[str]) {
  21. newArr.push(item);
  22. newObj[str] = true;
  23. }
  24. }
  25. });
  26. return newArr;
  27. }

手写 pipe

  1. const pipe =
  2. (...funcs) =>
  3. input =>
  4. funcs.reduce((pre, cur) => cur(pre), input)

手写 compose

  1. function compose(...args) {
  2. if (args.length === 0) {
  3. return arg => arg
  4. }
  5. if (args.length === 1) {
  6. return args[0]
  7. }
  8. return args.reduce(
  9. (a, b) =>
  10. (...arg) =>
  11. a(b(...arg))
  12. )
  13. }

写法四:迭代法

  1. var compose = function (funcs) {
  2. var len = funcs.length
  3. var index = length
  4. while (index--) {
  5. if (typeof funcs[index] !== 'function') {
  6. throw new TypeError('expected a function')
  7. }
  8. }
  9. return function (...args) {
  10. var index = 0
  11. var result = length ? funcs.reverse()[index].apply(this, args) : args[0]
  12. while (++index < length) {
  13. result = funcs[index].call(this, result)
  14. }
  15. return result
  16. }
  17. }

考察 this

手写 bind

  1. Function.prototype.bind = function (context) {
  2. const me = this
  3. const args = Array.prototype.slice.call(arguments, 1)
  4. let F = function () {}
  5. F.prototype = this.prototype
  6. let bound = function () {
  7. const innerArgs = Array.prototype.slice.call(arguments)
  8. const finalArgs = args.concat(innerArgs)
  9. return me.apply(this instanceof F ? this : context || this, finalArgs)
  10. }
  11. // 因为直接使用 bound.prototype = this.protoype 有个缺点
  12. // 修改 bound.prototype 的时候,也会修改 this.prototype
  13. // 这里就相当于使用了 Object.create
  14. bound.prototype = new F()
  15. return bound
  16. }

手写 apply

  1. Function.prototype.apply = function (context = window, argsArray = []) {
  2. context = new Object(context)
  3. const targetFnKey = 'targetFnKey' + Date.now()
  4. context[targetFnKey] = this
  5. const result = context[targetFnKey](...argsArray)
  6. delete context[targetFnKey]
  7. return result
  8. }

手写 call

差不多同上

  1. Function.prototype.call = function (context = window, ...args) {
  2. context = new Object(context)
  3. const targetKey = 'targetKey' + Date.now()
  4. context[targetKey] = this
  5. let result = context[targetKey](...args)
  6. delete context[targetKey]
  7. return result
  8. }

手写 new

  1. function newFun(...args) {
  2. // 获得构造函数
  3. const constructor = args.shift()
  4. // 创建一个空对象,且使这个空对象继承构造函数的prototype属性
  5. // obj.__proto__ = constructor.prototype;
  6. const obj = Object.create(constructor.prototype)
  7. // 执行构造函数
  8. const result = constructor.apply(obj, args)
  9. return typeof result === 'object' && result !== null ? result : obj
  10. }

链式调用

  1. Number.prototype.add = function(num) {
  2. let n = +num
  3. return Number(this + n)
  4. }
  5. // case
  6. let n = 1
  7. n.add(2).add(3) // -> 6

考察原型链

手写 instanceof

  1. function myInstanceof(left, right) {
  2. let proto = Object.getPrototypeOf(left), // 获取对象的原型
  3. prototype = right.prototype // 获取构造函数的 prototype 对象
  4. // 判断构造函数的 prototype 对象是否在对象的原型链上
  5. while (true) {
  6. if (!proto) return false
  7. if (proto === prototype) return true
  8. proto = Object.getPrototypeOf(proto)
  9. }
  10. }

手写 Object.create

创建一个对象,使用现有对象作为新创建对象的原型(prototype),新对象继承了原对象

  1. function create(proto) {
  2. let F = function () {}
  3. F.prototype = proto
  4. return new F()
  5. }

寄生式组合继承

  1. function Person() {}
  2. function Child() {
  3. Person.call(this)
  4. }
  5. let prototype = Object.create(Person.prototype) // 创建对象,创建父类原型的一个副本
  6. prototype.constructor = Child // 增强对象,解决重写原型导致默认 constructor 丢失的问题
  7. Child.prototype = prototype // 赋值对象,将新创建的对象赋值给子类的原型

考察闭包

柯里化

  1. function curry(fn, ...args) {
  2. if (args.length >= fn.length) {
  3. return fn.apply(null, args)
  4. }
  5. return (...args2) => curry(fn, ...args, ...args2)
  6. }

性能相关

手写 防抖

在一段时间结束之后才执行
应用场景:

  1. 搜索框:用户最后一次输入完成之后,才发送请求
  2. 手机号、邮箱等的验证
  3. 窗口大小的 resize
  1. function debounce(func, wait, immediate) {
  2. let timeout = null
  3. return function debounced () {
  4. const ctx = this, args = arguments
  5. if (timeout) {
  6. clearTimeout(timeout)
  7. }
  8. if (immediate) {
  9. // 刚进来的时候callnow = !timeout = true
  10. let callnow = !timeout
  11. timeout = setTimeout(function () {
  12. timeout = null // 到时间后 timeout = null, callnow 就为 true,就可以执行了
  13. }, wait)
  14. if (callnow) {
  15. func.apply(context, args)
  16. }
  17. } else {
  18. timeout = setTimeout(function (){
  19. func.apply(context, args)
  20. }, wait)
  21. }
  22. }
  23. }

手写节流

在一段时间之内连续触发,只执行一次
第一次立即执行
在一段时间间隔内只执行一次
在时间的间隔的末尾也要执行一次(也就是最后一次的操作要触发)
应用:

  1. 搜索:联想搜索
  2. 滚动加载
  3. 高频点击,表单重复提交
  1. // 立即执行
  2. var throttle = function (cb, wait, isImmediate = true) {
  3. let execTime = +new Date()
  4. let timeId = null
  5. // ! 为什么要返回一个函数
  6. return function () {
  7. const context = this
  8. const args = arguments
  9. if (isImmediate) {
  10. cb.apply(context, args)
  11. execTime = +new Date()
  12. isImmediate = false
  13. } else {
  14. // * 达到wait时间
  15. const currentTime = +new Date()
  16. if (currentTime - execTime >= wait) {
  17. cb.apply(context, args)
  18. execTime = +new Date()
  19. } else {
  20. // 实现执行最后一次动作
  21. // ! 还剩多少时间执行
  22. const timeWait = wait - (+new Date() - execTime)
  23. timeId && clearTimeout(timeId)
  24. timeId = setTimeout(() => {
  25. cb.apply(context, args)
  26. execTime = +new Date()
  27. timer = null
  28. }, timeWait)
  29. }
  30. }
  31. }
  32. }

useDebounce

  1. function useDebounce<T>(value: T, delay?: number): T {
  2. const [debouncedValue, setDebouncedValue] = useState<T>(value)
  3. useEffect(() => {
  4. const timer = setTimeout(() => {
  5. setDebouncedValue(value)
  6. }, delay || 500)
  7. return () => {
  8. clearTimeout(timer)
  9. }
  10. }, [value, delay])
  11. return debouncedValue
  12. }

useThrottle

  1. function useThrottle <T>(value: T, ms: number = 200) {
  2. const [state, setState] = useState<T>(value)
  3. const timeout = useRef<ReturnType<typeof setTimeout>>()
  4. const nextValue = useRef(null) as any
  5. const hasNextValue = useRef(0) as any
  6. useEffect(() => {
  7. if (!timeout.current) {
  8. setState(value)
  9. const timeoutCallback = () => {
  10. if (hasNextValue.current) {
  11. hasNextValue.current = false
  12. setState(nextValue.current)
  13. timeout.current = setTimeout(timeoutCallback, ms)
  14. } else {
  15. timeout.current = undefined
  16. }
  17. }
  18. timeout.current = setTimeout(timeoutCallback, ms)
  19. } else {
  20. nextValue.current = value
  21. hasNextValue.current = true
  22. }
  23. return () => {
  24. timeout.current && clearTimeout(timeout.current)
  25. }
  26. }, [value, ms])
  27. return state
  28. }

限制并发数

猿辅导二面

  1. function sendRequest(urls, max, callback) {
  2. let limit = max,
  3. n = urls.length,
  4. res = [],
  5. cnt = 0
  6. const tasks = urls.map(
  7. (url, index) => () =>
  8. fetch(url)
  9. .then(data => {
  10. res[index] = data
  11. })
  12. .catch(err => {
  13. res[index] = err
  14. })
  15. .finally(() => {
  16. if (++cnt === n) {
  17. return callback(res)
  18. }
  19. limit++
  20. doTask()
  21. })
  22. )
  23. doTask()
  24. function doTask() {
  25. while (limit > 0 && tasks.length) {
  26. limit--
  27. const task = tasks.shift()
  28. task()
  29. }
  30. }
  31. }

手写带最大请求数的并发请求

猿辅导二面

  1. function bingfa(requestArr, limitNum) {
  2. const n = requestArr.length
  3. const maxNum = n >= limitNum ? limitNum : n
  4. return new Promise((resolve, reject) => {
  5. const res = Array(n)
  6. let runnedCount = 0
  7. const recursive = (requestArr, index) => {
  8. Promise.resolve(requestArr[index]()).then((val) => {
  9. // 第一次执行then的时机是微任务队列里最快的promise完成
  10. res[index] = val // 执行结果按顺序记录
  11. // 表示requestArr中的请求任务已经全部添加完毕
  12. // 并且此时微任务队列里的任务一定是小于等于maxNum
  13. // 没有后续添加的任务了,所以可以直接正常执行微任务队列里的任务
  14. // 它们会自行按照快慢执行
  15. if (runnedCount >= n) {
  16. return resolve(res)
  17. }
  18. // 有一个进到这里(执行了then),说明它执行完毕,需要再往任务队列里添加一个
  19. // 补齐manNum个
  20. recursive(requestArr, runnedCount++)
  21. })
  22. }
  23. // 经过下面的循环之后,微任务队列里会有maxNum个promise的回调
  24. // runnedCount的值等于maxNum
  25. for (let i = 0; i < maxNum; i++) {
  26. recursive(requestArr, i)
  27. runnedCount++
  28. }
  29. })
  30. }

手写一个带并发限制的异步调度器 Scheduler

  1. class Scheduler {
  2. constructor(limitCount) {
  3. this.promises = []
  4. this.limit = limitCount
  5. this.runnedCount = 0
  6. }
  7. add(promiseCreator) {
  8. return new Promise((resolve, reject) => {
  9. promiseCreator.resolve = resolve
  10. promiseCreator.reject = reject
  11. this.promises.push(promiseCreator)
  12. this.run()
  13. })
  14. }
  15. run() {
  16. if (this.runnedCount < this.limit && this.promises.length) {
  17. this.runnedCount++
  18. const promise = this.promises.shift()
  19. promise().then(res => {
  20. promise.resolve(res)
  21. }).catch(err => {
  22. promise.reject(err)
  23. }).finally(() => {
  24. this.runnedCount--
  25. this.run()
  26. })
  27. }
  28. }
  29. }
  30. const scheduler = new Scheduler(2)
  31. const timeout = (wait) => {
  32. return new Promise((resolve, reject) => {
  33. setTimeout(resolve, wait)
  34. })
  35. }
  36. const addTask = (time, content) => {
  37. scheduler.add(() => timeout(time)).then(() => console.log(content))
  38. }
  39. addTask(1000, '1')
  40. addTask(500, '2')
  41. addTask(300, '3')
  42. addTask(400, '4')

手写观察者模式

  1. // 被观察者对象;
  2. class Subject {
  3. constructor() {
  4. this.observerList = []
  5. }
  6. addObserver(observer) {
  7. this.observerList.push(observer)
  8. }
  9. removeObserver(observer) {
  10. this.observerList = this.observerList.filter(
  11. o => o.name !== observer.name
  12. )
  13. }
  14. notifyObserver(message) {
  15. this.observerList.forEach(observer => observer.notified(message))
  16. }
  17. }
  18. // 观察者;
  19. class Observer {
  20. constructor(name, subject) {
  21. this.name = name
  22. if (subject) {
  23. subject.addObserver(this)
  24. }
  25. }
  26. notified(message) {
  27. console.log(this.name, message)
  28. }
  29. }

手写 EventEmitrer 发布订阅模式

  1. class EventEmitter {
  2. constructor() {
  3. this.listeners = Object.create(null)
  4. }
  5. /**
  6. * 注册事件 监听者
  7. * @param {string} eventName
  8. * @param {Function} listener
  9. */
  10. on(eventName, listener) {
  11. if (!this.listeners[eventName]) {
  12. this.listeners[eventName] = []
  13. }
  14. this.listeners[eventName].push(listener)
  15. }
  16. // 取消订阅
  17. off(eventName, listener) {
  18. if (!this.hasBind(eventName)) {
  19. console.warn(`this event ${eventName} don't exist`)
  20. return
  21. }
  22. // remove all listener
  23. if (!listener) {
  24. delete this.listeners[eventName]
  25. return
  26. }
  27. this.listeners[eventName] = this.listeners[eventName].filter(
  28. callback => callback !== listener
  29. )
  30. }
  31. // 只订阅一次
  32. once(eventName, listener) {
  33. function one() {
  34. listener.apply(this, arguments)
  35. this.off(eventName)
  36. }
  37. this.on(eventName, one) // 这样就可以用emit触发此函数
  38. }
  39. emit(eventName, ...args) {
  40. if (!this.hasBind(eventName)) {
  41. console.warn(`this event ${eventName} don't exist`)
  42. return
  43. }
  44. this.listeners[eventName].forEach(listener =>
  45. listener.call(this, ...args)
  46. )
  47. }
  48. hasBind(eventName) {
  49. return this.listeners[eventName] && this.listeners[eventName].length
  50. }
  51. }
  52. const baseEvent = new EventEmitter()
  53. function cb(value) {
  54. console.log(`hello ${value}`)
  55. }
  56. baseEvent.on('click', cb)
  57. baseEvent.emit('click', 2022)
  58. baseEvent.emit('click', 2023)

手写数组方法

Splice

  1. /**
  2. * 计算真实的start
  3. * @param {number} start 开始的下标
  4. * @param {number} len 数组的长度
  5. */
  6. function computeSpliceStartIndex(start, len) {
  7. if (start < 0) {
  8. start += len
  9. return start < 0 ? 0 : start
  10. }
  11. return start > len - 1 ? len - 1 : start
  12. }
  13. /**
  14. * 计算真实的 deleteCount
  15. * @param {number} startIndex 开始删除的下标
  16. * @param {number} deleteCount 要删除的个数
  17. * @param {number} len 数组长度
  18. */
  19. function computeSpliceDeleteCount(startIndex, deleteCount, len) {
  20. if (deleteCount > len - startIndex) {
  21. deleteCount = len - startIndex
  22. }
  23. if (deleteCount < 0) deleteCount = 0
  24. return deleteCount
  25. }
  26. /**
  27. * 记录删除元素
  28. * @param {*} startIndex 开始删除的下标
  29. * @param {*} delCount 要删除的个数
  30. * @param {*} array 目标数组
  31. * @param {array} deletedElements 记录删除的元素
  32. */
  33. function recordDeleteElements(startIndex, delCount, array, deletedElements) {
  34. for (let i = 0; i < delCount; i++) {
  35. deletedElements[i] = array[startIndex + i]
  36. }
  37. }
  38. /**
  39. * 移动元素,使可以插入新元素
  40. * @param {*} startIndex
  41. * @param {*} delCount
  42. * @param {*} addCount 要添加的元素个数
  43. * @param {*} array
  44. */
  45. function moveElement(startIndex, delCount, addCount, array) {
  46. let over = addCount - delCount
  47. // 后移
  48. if (over > 0) {
  49. for (let i = array.length - 1; i >= startIndex + delCount; i--) {
  50. array[i + over] = array[i]
  51. }
  52. }
  53. // 左移
  54. else if (over < 0) {
  55. for (let i = startIndex + delCount + over; i <= array.length - 1; i++) {
  56. if (i + Math.abs(over) > array.length - 1) {
  57. delete array[i]
  58. continue
  59. }
  60. array[i] = array[i + Math.abs(over)]
  61. }
  62. }
  63. }
  64. Array.prototype._splice = function (start, deleteCount, ...rest) {
  65. let array = Object(this)
  66. let len = array.length
  67. let addCount = rest.length
  68. let startIndex = computeSpliceStartIndex(start, len)
  69. let delCount = computeSpliceDeleteCount(startIndex, deleteCount, len)
  70. let deletedElements = new Array(delCount)
  71. recordDeleteElements(startIndex, delCount, array, deletedElements)
  72. if (delCount !== addCount && Object.isSealed(array)) {
  73. throw new TypeError('the array is sealed')
  74. }
  75. if (delCount > 0 && addCount > 0 && Object.isFrozen(array)) {
  76. throw new TypeError('the array is frozen')
  77. }
  78. moveElement(startIndex, delCount, addCount, array)
  79. let i = startIndex
  80. let argumentsIndex = 2
  81. while (rest.length > argumentsIndex) {
  82. array[i++] = rest[argumentsIndex++]
  83. }
  84. array.length = len - delCount + addCount
  85. return array
  86. }
  87. console.log([1, 2, 3, 4]._splice(1, 2))

Map

  1. Array.prototype.myMap = function (callback, thisArg) {
  2. // 确保调用者是个数组
  3. if (Object.prototype.toString.call(this) !== '[object Array]') {
  4. throw new TypeError('调用者必须是数组')
  5. }
  6. // 确保回调函数是个函数
  7. if (typeof callback !== 'function') {
  8. throw new TypeError(callback + ' is not a function')
  9. }
  10. let res = []
  11. for (let i = 0; i < this.length; i++) {
  12. res[i] = callback.call(this[i], i, this, thisArg)
  13. }
  14. return res
  15. }
  16. let arr = [1, 2, 3]
  17. arr.map((ele, index) => {
  18. console.log(`${ele}, ${index}`)
  19. })

filter

  1. Array.prototype.myFilter = function (callback, thisArg) {
  2. // 确保调用着是个数组
  3. if (Object.prototype.toString.call(this) !== '[object Array]') {
  4. throw new TypeError('调用者必须是数组');
  5. }
  6. // 确保回调函数是个函数
  7. if (typeof callback !== 'function') {
  8. throw new TypeError(callback + ' is not a function');
  9. }
  10. let res = [];
  11. for (let i = 0; i < this.length; i++) {
  12. // this[i]代表原数组当前元素
  13. // i 代表 下标
  14. // this 代表原数组
  15. if (callback.call(this[i], i this, thisArg)) {
  16. res.push(this[i]);
  17. }
  18. }
  19. return res;
  20. };

some

  1. Array.prototype.isome = function (callback, thisArg) {
  2. if (typeof callback !== 'function') {
  3. throw new Error(callback + ' is not a function')
  4. }
  5. if (!Array.isArray(this)) {
  6. throw new Error('调用者必须是数组')
  7. }
  8. for (let i = 0; i < this.length; i++) {
  9. if (callback(this[i], i, this, thisArg)) {
  10. return true
  11. }
  12. }
  13. }

实现一个精确的定时器

  1. var start = +new Date(),
  2. time = 0
  3. function instance() {
  4. time += 1000
  5. var diff = +new Date() - start - time
  6. console.log(diff)
  7. window.setTimeout(instance, 1000 - diff)
  8. }
  9. window.setTimeout(instance, 1000)

使用 setTimeout 模拟 setInterval

  1. let count = 0
  2. let timerId = null
  3. timerId = setTimeout(function run() {
  4. console.log('run -> ', count)
  5. if (count >= 3) {
  6. clearTimeout(timerId)
  7. return
  8. }
  9. count += 1
  10. timerId = setTimeout(run, 1000)
  11. }, 1000)

轮播图

  • 首先用一个 div包起来,设定整个轮播图的位置,大小等
  • 然后里面第一个ul里面放图片,第二个ul里面放下面的小点使用绝对定位
  • 然后就是左右两个箭头按钮,绝对定位
  • js 代码部分,监听 window.onload 方法
    • 获取整个盒子,获取图片所有的li标签,获取小点所有的li,获取左右箭头
    • 定义两个变量,一个是 index 记录当前位置,一个是 timerId 用来清除 定时器
    • 定义左滑方法,index —,如果减到比 0 小,则 index 变为最后一个,并展示最后一张图片
    • 右滑方法,index++,如果加到比图片个数还多,那么 index = 0,然后展示第一张图片
    • 鼠标经过,移除定时器,鼠标移除,添加定时器
    • 在外面添加定时器
    • run 方法,用来图片自己向右滑 index++,和右滑方法类似
    • 展示图片的方法,排他原则,先设置所有不展示,然后把特定 index 的设置为显示,并将 index 设为这个特定的 index
  1. <style>
  2. * {
  3. list-style: none;
  4. }
  5. .wrap {
  6. width: 590px;
  7. height: 400px;
  8. margin: 150px auto;
  9. position: relative;
  10. cursor: pointer;
  11. }
  12. .pic li {
  13. display: none;
  14. position: absolute;
  15. top: 0;
  16. left: 0;
  17. z-index: 1;
  18. }
  19. img {
  20. width: 590px;
  21. height: 400px;
  22. }
  23. .num {
  24. position: absolute;
  25. z-index: 2;
  26. bottom: 20px;
  27. left: 50%;
  28. transform: translateX(-50%);
  29. }
  30. .num li {
  31. float: left;
  32. width: 8px;
  33. height: 8px;
  34. margin: 5px;
  35. border-radius: 50%;
  36. border: 1px solid #fff;
  37. line-height: 20px;
  38. background: transparent;
  39. text-align: center;
  40. }
  41. .num li.active {
  42. background: #fefefe;
  43. }
  44. .arrow {
  45. z-index: 3;
  46. height: 40px;
  47. width: 30px;
  48. position: absolute;
  49. top: 45%;
  50. line-height: 40px;
  51. background: rgba(0, 0, 0, 0.3);
  52. text-align: center;
  53. display: none;
  54. }
  55. .wrap:hover .arrow {
  56. display: block;
  57. }
  58. .arrow:hover {
  59. background: rgba(0, 0, 0, 0.7);
  60. }
  61. #left {
  62. left: 0;
  63. }
  64. #right {
  65. right: 0;
  66. }
  67. </style>
  68. <div class="wrap">
  69. <ul class="pic">
  70. <li style="display: none;"><img src="./img/1.jpg" alt="" /></li>
  71. <li style="display: none;"><img src="./img/2.jpg" alt="" /></li>
  72. <li style="display: block;">
  73. <img src="./img/3.jpg" alt="" />
  74. </li>
  75. <li style="display: none;"><img src="./img/4.jpg" alt="" /></li>
  76. <li style="display: none;"><img src="./img/5.png" alt="" /></li>
  77. <li style="display: none;"><img src="./img/6.png" alt="" /></li>
  78. </ul>
  79. <ul class="num">
  80. <li class="active"></li>
  81. <li class=""></li>
  82. <li class=""></li>
  83. <li class=""></li>
  84. <li class=""></li>
  85. <li class=""></li>
  86. </ul>
  87. <div class="arrow" id="left">&lt;</div>
  88. <div class="arrow" id="right">></div>
  89. </div>
  90. <script>
  91. window.onload = function () {
  92. const wrap = document.querySelector('.wrap')
  93. const pic = document.querySelector('.pic').getElementsByTagName('li')
  94. const num = document.querySelector('.num').getElementsByTagName('li')
  95. const onLeft = document.querySelector('#left')
  96. const onRight = document.querySelector('#right')
  97. let index = 0
  98. let timerId = null
  99. const change = currentIndex => {
  100. //将所有都设置为不可见
  101. pic.forEach((item, index) => {
  102. item.style.display = 'none'
  103. num[index].className = ''
  104. })
  105. pic[currentIndex].style.display = 'block'
  106. num[currentIndex].className = 'active'
  107. index = currentIndex
  108. }
  109. onLeft.addEventListener('click', () => {
  110. index--
  111. if (index < 0) {
  112. index = pic.length - 1
  113. }
  114. change(index)
  115. })
  116. onRight.addEventListener('click', () => {
  117. index++
  118. if (index > pic.length - 1) {
  119. index = 0
  120. }
  121. change(index)
  122. })
  123. wrap.addEventListener('mouseover', () => {
  124. clearTimeout(timerId)
  125. })
  126. wrap.addEventListener('mouseout', () => {
  127. timerId = setTimeout(function fn() {
  128. run()
  129. timerId = setTimeout(fn, 2000)
  130. }, 2000)
  131. })
  132. timerId = setTimeout(function fn() {
  133. run()
  134. timerId = setTimeout(fn, 2000)
  135. }, 2000)
  136. const run = () => {
  137. index++
  138. if (index > pic.length - 1) {
  139. index = 0
  140. }
  141. change(index)
  142. }
  143. num.forEach((item, index) => {
  144. item.index = index
  145. item.addEventListener('mouseover', function () {
  146. change(this.index)
  147. })
  148. })
  149. }
  150. </script>

usePrevious

  1. function usePrevious(state) {
  2. const ref = useRef()
  3. useEffect(() => {
  4. ref.current = state
  5. }, [state])
  6. return ref.current
  7. }

useRef 不仅仅用来管理 DOM ref,还可以存放任何变量。可以很好的解决闭包带来的不便性。当 useRef 的内容改变时,不会通知,更改 .current 属性不会导致重新呈现,因为它只是一个引用。

useFetch

  1. function useFetch(url, options) {
  2. const cache = useRef({})
  3. const cancelRequest = useRef(false)
  4. const initialState = {
  5. data: undefined,
  6. error: undefined,
  7. }
  8. const fetchReducer = (state, action) => {
  9. switch (action.type) {
  10. case 'loading':
  11. return { ...initialState }
  12. case 'fetched':
  13. return { ...initialState, data: action.payload }
  14. case 'error':
  15. return { ...initialState, error: action.payload }
  16. default:
  17. return state
  18. }
  19. const [state, dispatch] = useReducer(fetchReducer, initialState)
  20. useEffect(() => {
  21. if (!url) {
  22. return
  23. }
  24. const fetchData = async () => {
  25. dispatch({ type: 'loading' })
  26. if (cache.current[url]) {
  27. dispatch({ type: 'fetched', payload: cache.current[url] })
  28. return
  29. }
  30. try {
  31. const response = await fetch(url, options)
  32. if (!response.ok) {
  33. throw new Error(response.statusText)
  34. }
  35. const data = await response.json()
  36. cache.current[url] = data
  37. if (cancelRequest.current) return
  38. dispatch({ type: 'fetched', payload: data })
  39. } catch (error) {
  40. if (cancelRequest.current) return
  41. dispatch({ type: 'error', payload: error })
  42. }
  43. }
  44. void fetchData()
  45. return () => {
  46. cancelRequest.current = true
  47. }
  48. }, [url])
  49. }
  50. return state
  51. }

手写深拷贝

深拷贝和浅拷贝的区别

  1. 浅拷贝会创建一个新的对象,这个对象有着原有对象 属性值的一份拷贝。如果属性值是基础数据类型,拷贝的就是基础数据类型的值;如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。改变浅拷贝的第一层是不会影响原始对象的
  2. 深拷贝是将一个对象从内存中完整地拷贝一份出来,从堆内存中开辟一个新的区域存放对象,且修改新对象不会影响原始对象。

基础版

  1. function deepClone(obj) {
  2. if (obj === null || typeof obj !== 'object') {
  3. return
  4. }
  5. let newObj = Array.isArray(obj) ? [] : {}
  6. for (let key in obj) {
  7. if (obj.hasOwnProperty(key)) {
  8. newObj[key] =
  9. typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
  10. }
  11. }
  12. return newObj
  13. }

解决循环引用问题

  1. function deepClone(obj, map = new Map()) {
  2. if (obj === null || typeof obj !== 'object') {
  3. return
  4. }
  5. let newObj = Array.isArray(obj) ? [] : {}
  6. if (map.has(obj)) {
  7. return map.get(obj)
  8. }
  9. map.set(obj, newObj)
  10. for (let key in obj) {
  11. if (obj.hasOwnProperty(key)) {
  12. newObj[key] =
  13. typeof obj[key] === 'object'
  14. ? deepClone(obj[key], map)
  15. : obj[key]
  16. }
  17. }
  18. return newObj
  19. }

使用 class 或原型链实现计算器,要求能够链式调用

  1. class MyCalculator {
  2. constructor(value) {
  3. this.value = value
  4. }
  5. add(num) {
  6. this.value += num
  7. return this
  8. }
  9. minus(num) {
  10. this.value -= num
  11. return this
  12. }
  13. multi(num) {
  14. this.value *= num
  15. return this
  16. }
  17. div(num) {
  18. this.value /= num
  19. return this
  20. }
  21. pow(num) {
  22. this.value = this.value ** num
  23. return this
  24. }
  25. }

进制转换

  1. function translate(num) {
  2. let remStack = []
  3. let digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  4. let rem
  5. let res = ''
  6. while (num > 0) {
  7. rem = Math.floor(num % 2)
  8. remStack.push(rem)
  9. rem = Math.floor(num / 2)
  10. }
  11. while (remStack.length) {
  12. res += digits[remStack.pop()]
  13. }
  14. return res
  15. }

向 URL 中插入 query 参数

  1. function resolve(url) {
  2. const params = qs.stringify({ a: 1, b: 2 })
  3. const urlObj = new URL(url)
  4. urlObj.search += urlObj.search ? '&' + params : '?' + params
  5. return urlObj.href
  6. }

or

  1. function buildUrl(url, params) {
  2. const urlObj = new URL(url)
  3. const paramsObj = qs.parse(urlObj.search.slice(1))
  4. Object.assign(paramsObj, params)
  5. urlObj.search = qs.stringify(paramsObj)
  6. return urlObj.href
  7. }

实现 PascalCase 到 camelCase 之间的转换

  1. // 实现CamelCase转换为PascalCase
  2. function camelCase(str) {
  3. return str.replace(/^(\w)/, function (l) {
  4. return l.toUpperCase()
  5. })
  6. }
  7. // 实现PascalCase转换为CamelCase
  8. function camelCase(str) {
  9. return str.replace(/^(\w)/, function (l) {
  10. return l.toLowerCase()
  11. })
  12. }

实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间

  1. //实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间
  2. function repeat(fn, times, interval) {
  3. let i = 0
  4. let timer = setInterval(function () {
  5. fn()
  6. i++
  7. if (i === times) {
  8. clearInterval(timer)
  9. }
  10. }, interval)
  11. }
  12. let fn = () => console.log('hello')
  13. repeat(fn, 3, 1000)

parseURLParams

  1. function parseURLParams(url) {
  2. let queryParams = {};
  3. let reg = /([^?=&]+)=([^?=&]+)/g;
  4. url.replace(reg, function () {
  5. queryParams[arguments[1]] = arguments[2];
  6. });
  7. return queryParams;
  8. }
  9. // result
  10. {
  11. source: 'gtx',
  12. sl: 'auto',
  13. tl: 'en',
  14. text: '%E7%A9%BA%E6%A0%BC',
  15. op: 'translate'
  16. }

URLSearchParams

  1. class MyURLSearchParams {
  2. /**
  3. * @params {string} init
  4. */
  5. constructor(init) {
  6. this.val = ''
  7. if (init.indexOf('http') > -1) {
  8. this.val = init
  9. } else {
  10. this.val = init.slice(init.indexOf('?') + 1)
  11. }
  12. this.arr = this.val.split('&')
  13. // console.log(this.val);
  14. }
  15. /**
  16. * @params {string} name
  17. * @params {any} value
  18. */
  19. append(name, value) {
  20. let tmp = name + '=' + value
  21. this.arr.push(tmp)
  22. return '?' + this.arr.join('&')
  23. }
  24. /**
  25. * @params {string} name
  26. */
  27. delete(name) {
  28. let res = []
  29. for (let i = 0; i < this.arr.length; i++) {
  30. if (this.arr[i].indexOf(name) != 0) {
  31. res.push(this.arr[i])
  32. }
  33. }
  34. this.arr = res
  35. return '?' + this.arr.join('&')
  36. }
  37. /**
  38. * @returns {Iterator}
  39. */
  40. *entries() {
  41. let res = []
  42. for (let i = 0; i < this.arr.length; i++) {
  43. let tmp = this.arr[i].split('=')
  44. yield [tmp[0], tmp[1]]
  45. }
  46. }
  47. /**
  48. * @param {(value, key) => void} callback
  49. */
  50. forEach(callback) {
  51. for (let i = 0; i < this.arr.length; i++) {
  52. let tmp = this.arr[i].split('=')
  53. let obj = {}
  54. callback(tmp[1], tmp[0])
  55. }
  56. }
  57. /**
  58. * @param {string} name
  59. * returns the first value of the name
  60. */
  61. get(name) {
  62. let res = null
  63. this.arr.filter(value => {
  64. if (res == null && value.indexOf(name) == 0) {
  65. let tmp = value.split('=')
  66. res = tmp && tmp[1]
  67. }
  68. })
  69. return res
  70. }
  71. /**
  72. * @param {string} name
  73. * @return {string[]}
  74. * returns the value list of the name
  75. */
  76. getAll(name) {
  77. let res = []
  78. this.arr.filter(value => {
  79. if (value.indexOf(name) == 0) {
  80. let tmp = value.split('=')
  81. res.push(tmp[1])
  82. }
  83. })
  84. return res
  85. }
  86. /**
  87. * @params {string} name
  88. * @return {boolean}
  89. */
  90. has(name) {
  91. let res = false
  92. this.arr.filter(value => {
  93. if (res == false && value.indexOf(name) == 0) {
  94. let tmp = value.split('=')
  95. res = tmp.length > 0 ? true : false
  96. }
  97. })
  98. return res
  99. }
  100. /**
  101. * @return {Iterator}
  102. */
  103. keys() {
  104. let res = []
  105. for (let i = 0; i < this.arr.length; i++) {
  106. let tmp = this.arr[i].split('=')
  107. if (!res.includes(tmp[0])) {
  108. res.push(tmp[0])
  109. }
  110. }
  111. return res
  112. }
  113. /**
  114. * @param {string} name
  115. * @param {any} value
  116. */
  117. set(name, value) {
  118. let tmp = 0
  119. for (let i = 0; i < this.arr.length; i++) {
  120. if (this.arr[i].indexOf(name) == 0) {
  121. tmp += 1
  122. this.arr[i] = name + '=' + value
  123. }
  124. }
  125. if (tmp == 0) {
  126. this.arr.push(name + '=' + value)
  127. }
  128. return '?' + this.arr.join('&')
  129. }
  130. // sor all key/value pairs based on the keys
  131. sort() {
  132. let res = []
  133. for (let i = 0; i < this.arr.length; i++) {
  134. let tmp = this.arr[i].split('=')
  135. res.push(tmp)
  136. }
  137. res.sort(function (a, b) {
  138. if (a[0] > b[0]) {
  139. return 1
  140. } else {
  141. return -1
  142. }
  143. })
  144. let sum = []
  145. res.forEach(value => {
  146. sum.push(value.join('='))
  147. })
  148. this.arr = sum
  149. return res
  150. }
  151. /**
  152. * @return {string}
  153. */
  154. toString() {
  155. return this.arr.join('&')
  156. }
  157. /**
  158. * @return {Iterator} values
  159. */
  160. values() {
  161. let res = []
  162. for (let i = 0; i < this.arr.length; i++) {
  163. let tmp = this.arr[i].split('=')
  164. if (!res.includes(tmp[0])) {
  165. res.push(tmp[1])
  166. }
  167. }
  168. return res
  169. }
  170. }

手写 chunk

  1. function chunk(arr, size) {
  2. let temp = []
  3. let res = []
  4. arr.forEach((e, index) => {
  5. temp.push(e)
  6. if (temp.length === size || index === arr.length - 1) {
  7. res.push(temp)
  8. temp = []
  9. }
  10. })
  11. return res
  12. }

手写一个异步加法

  1. function asyncAdd(a, b, callback) {
  2. setTimeout(function () {
  3. callback(null, a + b)
  4. }, 500)
  5. }
  6. // 解决方案
  7. // 1. promisify
  8. const promiseAdd = (a, b) =>
  9. new Promise((resolve, reject) => {
  10. asyncAdd(a, b, (err, res) => {
  11. if (err) {
  12. reject(err)
  13. } else {
  14. resolve(res)
  15. }
  16. })
  17. })
  18. // 2. 串行处理
  19. async function serialSum(...args) {
  20. return args.reduce(
  21. (task, now) => task.then(res => promiseAdd(res, now)),
  22. Promise.resolve(0)
  23. )
  24. }
  25. // 3. 并行处理
  26. async function parallelSum(...args) {
  27. if (args.length === 1) return args[0]
  28. const tasks = []
  29. for (let i = 0; i < args.length; i += 2) {
  30. tasks.push(promiseAdd(args[i], args[i + 1] || 0))
  31. }
  32. const results = await Promise.all(tasks)
  33. return parallelSum(...results)
  34. }
  35. // 测试
  36. ;(async () => {
  37. console.log('Running...')
  38. const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  39. console.log(res1)
  40. const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  41. console.log(res2)
  42. console.log('Done')
  43. })()

原生手写一个 Modal

  1. <style>
  2. #popLayer {
  3. display: none;
  4. background-color: #b3b3b3;
  5. position: absolute;
  6. top: 0;
  7. right: 0;
  8. bottom: 0;
  9. left: 0;
  10. z-index: 1;
  11. opacity: 0.6;
  12. }
  13. #popBox {
  14. display: none;
  15. background: #fff;
  16. width: 200px;
  17. height: 200px;
  18. position: fixed;
  19. top: 50%;
  20. left: 50%;
  21. transform: translate(-50%, -50%);
  22. z-index: 10;
  23. }
  24. #popBox .close {
  25. text-align: right;
  26. margin-right: 5px;
  27. background-color: #f8f8f8;
  28. }
  29. #popBox .close a {
  30. text-decoration: none;
  31. color: #2d2c3b;
  32. }
  33. </style>
  34. <input type="button" name="popBox" value="弹出框" onclick="openBox()" />
  35. <div id="popLayer"></div>
  36. <div id="popBox">
  37. <div class="close">
  38. <a href="javascript:void(0)" onclick="closeBox()">关闭</a>
  39. </div>
  40. <div class="content">我是弹出层</div>
  41. </div>
  42. <script>
  43. var popBox = document.getElementById('popBox')
  44. var popLayer = document.getElementById('popLayer')
  45. function openBox() {
  46. popBox.style.display = 'block'
  47. popLayer.style.display = 'block'
  48. }
  49. /*点击关闭按钮*/
  50. function closeBox() {
  51. popBox.style.display = 'none'
  52. popLayer.style.display = 'none'
  53. }
  54. </script>

转换成树形结构

list2tree

  1. function list2tree(list) {
  2. const map = new Map()
  3. const tree = []
  4. list.forEach((item, index) => {
  5. map.set(item.id, item)
  6. })
  7. list.forEach((item) => {
  8. if (item.pid) {
  9. const parent = map.get(item.pid)
  10. if (parent) {
  11. parent.children = parent.children || []
  12. parent.children.push(item)
  13. }
  14. } else {
  15. tree.push(item)
  16. }
  17. })
  18. return tree
  19. }
  1. function list2tree(list) {
  2. const tree = []
  3. for (const node of list) {
  4. // 如果没有pid就可以认为是根节点
  5. if (!node.pid) {
  6. let p = { ...node }
  7. p.children = getChildren(p.id, list)
  8. tree.push(p)
  9. }
  10. }
  11. return tree
  12. }
  13. function getChildren(id, list) {
  14. const children = []
  15. for (const node of list) {
  16. if (node.pid === id) {
  17. children.push(node)
  18. }
  19. }
  20. // 注意这里遍历的是children
  21. for (const node of children) {
  22. const children = getChildren(node.id, list)
  23. if (children.length) {
  24. node.children = children
  25. }
  26. }
  27. return children
  28. }
  1. function list2tree(list) {
  2. list.forEach((child) => {
  3. const pid = child.pid
  4. if (pid) {
  5. list.forEach((parent) => {
  6. if (parent.id === pid) {
  7. parent.children = parent.children || []
  8. parent.children.push(child)
  9. }
  10. })
  11. }
  12. })
  13. return list.filter((n) => !n.pid)
  14. }

tree2list