2

Preface

I was looking for a job a few weeks ago, and I wrote more than 100 questions, here is a summary

1. Implement curry

https://bigfrontend.dev/zh/problem/implement-curry
const join = (a, b, c) => {
   return `${a}_${b}_${c}`
}

const curriedJoin = curry(join)

curriedJoin(1, 2, 3) // '1_2_3'

curriedJoin(1)(2, 3) // '1_2_3'

curriedJoin(1, 2)(3) // '1_2_3'

// 实现curry
const curry = (fn, ...initParams) => {
  return (...args) => {
    return ((params) => {
      return params.length >= fn.length ? fn(...params) : curry(fn, ...params) 
    })([ ...initParams, ...args])
  }
}

2. Implement curry() that supports placeholder

https://bigfrontend.dev/zh/problem/implement-curry-with-placeholder
/**
 * 合并参数(替换占位符)
 */
function merge(params1, params2) {
  for (let i = 0; i < params1.length; i += 1) {
    if (params2.length) {
      if (typeof params1[i] === 'symbol') {
        const first = params2.shift()
        params1[i] = first
      }
    } else {
        break
    }
  }
  return [...params1, ...params2]
}

/**
 * @param { (...args: any[]) => any } fn
 * @returns { (...args: any[]) => any }
 */
function curry(fn, ...initParams) {
  return (...args) => {
    const params = merge([...initParams], [...args])
    return ((params) => {
      params = params.slice(0, fn.length)
      // 判断是否可以执行
      const isCall = params.filter((item) => typeof item !== 'symbol').length >= fn.length
      return isCall ? fn(...params) : curry(fn, ...params) 
    })(params)
  }
}

curry.placeholder = Symbol()

3. Implement Array.prototype.flat()

https://bigfrontend.dev/zh/problem/implement-Array-prototype.flat
/**
 * @param { Array } arr
 * @param { number } depth
 * @returns { Array }
 */
function flat(arr, depth = 1) {
  const result = []
  if (depth) {
    let isExistArray = false;
    for (let i = 0; i < arr.length; i += 1) {
      const item = arr[i]
      if (Array.isArray(item)) {
        isExistArray = true
        result.push(...item)
      } else {
        result.push(item)
      }
    }
    if (isExistArray) {
      return flat(result, depth - 1)
    } else {
      return result
    }
  } else {
    return arr
  }
}

4. Handwriting throttle()

https://bigfrontend.dev/zh/problem/implement-basic-throttle
/**
 * @param {Function} func
 * @param {number} wait
 */
function throttle(func, wait) {
  let timer
  let lastArgs
  return function (...param) {
    if (!timer) {
      func.call(this, ...param)
      timer = window.setTimeout(() => {
        lastArgs && func.call(this, ...lastArgs)
        lastArgs = null
        timer = null
      }, wait)
    } else {
      lastArgs = [...param]
    }
  }
}

6. Handwritten debounce()

https://bigfrontend.dev/zh/problem/implement-basic-debounce
/**
 * @param {Function} func
 * @param {number} wait
 */
function debounce(func, wait) {
  let timer
  return function (...param) {
    function clear() {
      window.clearTimeout(timer)
      timer = null
    }
    function run() {
      timer = window.setTimeout(() => {
        func.call(this, ...param)
        clear()
      }, wait)
    }
    if (!timer) {
      run()
    } else {
      clear()
      run()
    }
  }
}

8. Handwriting shuffle() randomly shuffle an array (implement a shuffle algorithm)

https://bigfrontend.dev/zh/problem/can-you-shuffle-an-array

The conventional const randomIndex = Math.floor(Math.random() * arr.length) generating random indexes is not random enough

/**
  * @param {any[]} arr
  */
function shuffle(arr) {
  for (let i = 0; i < arr.length; i += 1) {
    const j = i + Math.floor(Math.random() * (arr.length - 1));
    [arr[i], arr[j]] = [arr[j], arr[i]]
  }
  return arr
}

9. Decrypt the message

https://bigfrontend.dev/zh/problem/decode-message
/**
 * @param {string[][]} message
 * @return {string}
 */
function decode(message) {
  // 为空的判断
  if (!message.length || !message[0].length) {
    return ''
  }

  let power = true
  let str = ''
  let i = 0
  let j = 0
  let direction = 'LowerRight' // LowerRight | UpperRight
  let w = message[0].length
  let h = message.length

  function lowerRight() {
    if (i + 1 < h && j + 1 < w) {
      i = i + 1
      j = j + 1
    } else {
      if (i - 1 > 0 && j + 1 < w) {
        direction = 'UpperRight'
        i = i - 1
        j = j + 1
      } else {
        power = false
      }
    }
  }

  function upperRight() {
    if (i - 1 > 0 && j + 1 < w) {
      i = i - 1
      j = j + 1
    } else {
      if (i + 1 < h && j + 1 < w) {
        direction = 'LowerRight'
        i = i + 1
        j = j + 1
      } else {
        power = false
      }
    }
  }

  while (power) {
    str += message[i][j]
    if (direction === 'LowerRight') {
      lowerRight()
    } else if (direction === 'UpperRight') {
      upperRight()
    }
  }
  return str
}

10. Find the first bad version

https://bigfrontend.dev/zh/problem/first-bad-version

Simple binary search

/*
 type TypIsBad = (version: number) => boolean
 */

/**
 * @param {TypIsBad} isBad 
 */
function firstBadVersion(isBad) {
  return (version) => {
    let start = 0
    let end = version
    let result = isBad(version) ? version : -1

    while (end >= start) {
      let midd = Math.floor((start + end) / 2)
      if (isBad(midd)) {
        // 如果是坏的
        end = midd - 1
        result = midd
      } else {
        // 如果是好的
        start = midd + 1
      }
    }
    return result
  }
}

11. What is Composition? Implement pipe()

https://bigfrontend.dev/zh/problem/what-is-composition-create-a-pipe

Implement a pipeline function

/**
 * @param {Array<(arg: any) => any>} funcs 
 * @return {(arg: any) => any}
 */
function pipe(funcs) {
    return function (arg) {
        let param = arg
        for (let i = 0; i < funcs.length; i += 1) {
            const func = funcs[i]
            param = func(param)
        }
        return param
    }
}

13. Use Stack to Create Queue

https://bigfrontend.dev/zh/problem/implement-a-queue-by-using-stack

You can use two Stacks to implement Queue

A simpler way is to save it every time you enqueue.


class Stack {
  constructor () {
    this.stack = []
  }
  push(element) {
    this.stack.push(element)
  }
  peek() {
    return this.stack[this.stack.length - 1]
  }
  pop() {
    return this.stack.pop()
  }
  size() {
    return this.stack.length
  }
}

// 使用Stack实现Queue
class Queue {
  constructor () {
    this.enqueueStack = new Stack()
    this.dequeueStack = new Stack()
  }

  _enqueueSyncDequeue () {
    const dequeueTemp = new Stack()
    const enqueueTemp = new Stack()
    while (this.enqueueStack.size()) {
      const p = this.enqueueStack.pop()
      dequeueTemp.push(p)
      enqueueTemp.push(p)
    }
    while (enqueueTemp.size()) {
      this.enqueueStack.push(enqueueTemp.pop())
    }
    this.dequeueStack = dequeueTemp
  }

  _dequeueSyncEnqueue () {
    const dequeueTemp = new Stack()
    const enqueueTemp = new Stack()
    while (this.dequeueStack.size()) {
      const p = this.dequeueStack.pop()
      dequeueTemp.push(p)
      enqueueTemp.push(p)
    }
    while (dequeueTemp.size()) {
      this.dequeueStack.push(dequeueTemp.pop())
    }
    this.enqueueStack = enqueueTemp
  }

  enqueue(element) { 
    this.enqueueStack.push(element)
    this._enqueueSyncDequeue()
  }

  peek() { 
    return this.dequeueStack.peek()
  }
  
  dequeue() {
    const p = this.dequeueStack.pop()
    this._dequeueSyncEnqueue()
    return p
  }

  size() { 
    return this.enqueueStack.size()
  }
}

// 改进版
class Queue {
  constructor () {
    this.stack = new Stack()
    this.queue = new Stack()
  }

  enqueue(element) { 
    while (this.queue.size()) {
      this.stack.push(this.queue.pop())
    }
    this.queue.push(element)
    while (this.stack.size()) {
      this.queue.push(this.stack.pop())
    }
  }

  peek() { 
    return this.queue.peek()
  }
  
  dequeue() {
    return this.queue.pop()
  }

  size() { 
    return this.queue.size()
  }
}

14. Implement memo()

https://bigfrontend.dev/zh/problem/implement-general-memoization-function
/**
 * @param {Function} func
 * @param {(args:[]) => string }  [resolver] - cache key generator
 */
function memo(func, resolver) {
  const map = new Map();
  return function (...params) {
    let key
    if (typeof resolver === 'function') {
      key = resolver(...params)
    } else {
      key = [...params].join('-')
    }
    if (map.has(key)) {
      return map.get(key)
    } else {
      const val = func.apply(this, [...params])
      map.set(key, val)
      return val
    }
  }
}

15. Implement a DOM wrapper similar to jQuery

https://bigfrontend.dev/zh/problem/implement-a-simple-DOM-wrapper-to-support-method-chaining-like-jQuery

/**
 * @param {HTMLElement} el - element to be wrapped
 */
function $(el) {
  el.css = function (key, value) {
    el.style[key] = value
    return el
  }

  return el
}

16. Implement an Event Emitter

https://bigfrontend.dev/zh/problem/create-an-Event-Emitter
// please complete the implementation
class EventEmitter {
  constructor () {
    this.map = {}
  }

  subscribe(eventName, callback) {
    const event = {eventName, callback}
    const that = this
    if (this.map[eventName]) {
      this.map[eventName].push(event)
    } else {
      this.map[[eventName]] = [event]
    }

    return {
      release () {
        that.map = {
          ...that.map,
          [eventName]: that.map[eventName].filter((e) => e !== event)
        }
      }
    }
  }
  
  emit(eventName, ...args) {
      if (this.map[eventName]) {
      this.map[eventName].forEach((event) => {
        const { callback } = event
        callback(...args)
      })
    }
  }
}

17. Implement a DOM element store

https://bigfrontend.dev/zh/problem/create-a-simple-store-for-DOM-node

Pay attention to the time and space complexity, the time complexity of the has method should be o(1)

How to implement a map polyfill

class NodeStore {
  constructor () {
    this.map = {}
  }

  /**
  * @param {Node} node
  * @param {any} value
  */
  set(node, value) {
    if (!node.__mapKey__) {
      node.__mapKey__ = Symbol()
      this.map[node.__mapKey__] = value
    } else {
      this.map[node.__mapKey__] = value
    }
  }

 /**
  * @param {Node} node
  * @return {any}
  */
  get(node) {
    return this.map[node.__mapKey__]
  }
 
 /**
  * @param {Node} node
  * @return {Boolean}
  */
 has(node) {
   return !!node.__mapKey__ && node.__mapKey__ in this.map
 }
}

18. Optimize a function

https://bigfrontend.dev/zh/problem/improve-a-function

Time complexity before optimization O(m * n)

// items是一个array
// 包含的元素有 >=3 个属性

let items = [
  {color: 'red', type: 'tv', age: 18}, 
  {color: 'silver', type: 'phone', age: 20},
  {color: 'blue', type: 'book', age: 17}
] 

// 一个由key和value组成的array
const excludes = [ 
  {k: 'color', v: 'silver'}, 
  {k: 'type', v: 'tv'}, 
  ...
] 

function excludeItems(items, excludes) { 
  excludes.forEach( pair => { 
    items = items.filter(item => item[pair.k] === item[pair.v])
  })
 
  return items
} 

Optimized:

function excludeItems(items, excludes) { 
  const excludesMap = {}
  excludes.forEach(({k, v}) => {
    if (excludesMap[k]) {
      excludesMap[k].add(v)
    } else {
      excludesMap[k] = new Set()
      excludesMap[k].add(v)
    }
  })

  return items.filter((item) => {
    return Object.keys(item).every((key) => {
      if (excludesMap[key]) {
        return !excludesMap[key].has(item[key])
      }
      return true
    })
  })
} 

19. Find the corresponding node on the DOM tree of the same structure

https://bigfrontend.dev/zh/problem/find-corresponding-node-in-two-identical-DOM-tree
/**
 * @param {HTMLElement} rootA
 * @param {HTMLElement} rootB - rootA and rootB are clone of each other
 * @param {HTMLElement} nodeA
 */
const findCorrespondingNode = (rootA, rootB, target) => {
  const paths = []
  let isSearchEnd = false
  let targetB = rootB

  const find = (node, index) => {
    if (index !== undefined && !isSearchEnd) {
      paths.push(index)
    }
    if (node !== target) {
      const children = [...node.children]
      children.forEach((item, index) => {
        find(item, index)
      })
      if (!isSearchEnd) {
        paths.pop()
      }
    } else {
      isSearchEnd = true
    }
  }

  find(rootA)

  if (paths.length !== 0) {
    while (paths.length) {
      const index = paths.shift()
      targetB = [...targetB.children][index]
    }
  }

  return targetB
}

20. Detect data type

https://bigfrontend.dev/zh/problem/detect-data-type-in-JavaScript
/**
 * @param {any} data
 * @return {string}
 */
function detectType(data) {
  if(data instanceof FileReader) {
    return 'object'
  }
  const type = Object.prototype.toString.call(data);
  return /^\[object\s+([A-Za-z]+)\]$/.exec(type)[1].toLocaleLowerCase()
}

21. Handwriting JSON.stringify()

https://bigfrontend.dev/zh/problem/implement-JSON-stringify
/**
 * @param {any} data
 * @return {string}
 */
function stringify(data) {
  if(typeof data === 'bigint') {
    throw new Error()
  } 
  if(typeof data === 'string') {
    return `"${data}"`
  } 
  if(typeof data === 'function') {
    return undefined;
  }
  if(data !== data) {
    return 'null'
  }
  if(data === Infinity) {
    return 'null'
  }
  if(data === -Infinity) {
    return 'null'
  }
  if(typeof data === 'number') {
   return `${data}`
  }
  if(typeof data === 'boolean') {
    return `${data}`
  }
  if(data === null) {
    return 'null'
  }
  if(data === undefined) {
    return 'null'
  }
  if(typeof data === 'symbol') {
    return 'null'
  }
  if(data instanceof Date) {
    return `"${data.toISOString()}"`
  }
  if (Array.isArray(data)) {
    const arr = data.map((item) => stringify(item))
    return `[${arr.join(',')}]`
  }
  if (typeof data === 'object') {
    const arr = []
    Object.entries(data).forEach(([key, value]) => {
      if (value !== undefined) {
        arr.push(`"${key}":${stringify(value)}`)
      }
    })
    return `{${arr.join(',')}}`
  }
}

22. Handwritten JSON.parse()

https://bigfrontend.dev/zh/problem/implement-JSON-parse
/**
 * @param {any} data
 * @return {string}
 */
function parse(data) {

  function equal(text) {
    if (text && text !== current) {
      throw new Error()
    }
    return true
  }

  function next() {
    current = text.charAt(index)
    index += 1
    return current
  }

  function white() {
    while (current && current == ' ') {
      next()
    }
  }

  function object() {
    let key
    let obj = Object.create(null)

    if (current === '{') {
      next()
      white()
      if (current === '}') {
        next()
        return obj
      }

      while (current) {
        key = string()
        // 读取下一个非空的字符
        white()
        // 下一个非空字符应该是":", 否则就抛出错误
        equal(':')
        next()
        obj[key] = value()
        // 读取下一个非空的字符
        white()
        if (current === '}') {
          next()
          return obj
        }
        // 如果不是'}', 下一个字符应该是',', 否则就抛出错误
        equal(',')
        // 读取下一个非空的字符
        white()
      }
    }

    throw new Error()
  }

  function string() {
    let str = ''
    if (current === '"') {
      let startIndex = index
      while (next()) {
        if (current === '"') {
          if (index - 1 > startIndex) {
            str += text.substring(startIndex, index - 1)
          }
          next()
          return str
        }
      }
    } else {
      throw new Error()
    }
  }

  function array() {
    let arr = []
    if (current === '[') {
      next()
      // 读取下一个非空字符串
      white()

      if (current === ']') {
        next()
        return arr
      }

      while (current) {
        // 读取数组的内容
        arr.push(value())
        // 读取下一个字符
        white()
        if (current === ']') {
          next()
          return arr
        }
        equal(',')
        next()
        white()
      }
    }
    throw new Error()
  }

  function number() {
    
    let number = ''
    let string = ''
    if (current === '-') {
      string = ''
      next()
    }

    let temp = Number(current)

    while (temp >= 0 && temp <= 9 && current) {
      string += current
      next()
      temp = Number(current)
    }

    if (current === '.') {
      string += '.'
      next()
      temp = Number(current)
      while (temp >= 0 && temp <= 9 && current) {
        string += current
        next()
        temp = Number(current)
      }
    }

    number = Number(string)
    return number
  }

  function word() {
    switch (current) {
      case 't':
        next()
        equal('r')
        next()
        equal('u')
        next()
        equal('e')
        next()
        return true
      case 'f':
        next()
        equal('a')
        next()
        equal('l')
        next()
        equal('s')
        next()
        equal('e')
        next()
        return false
      case 'n':
        next()
        equal('u')
        next()
        equal('l')
        next()
        equal('l')
        next()
        return null
    }
    throw new Error()
  }

  function value() {
    white()
    switch (current) {
      // 如果是对象
      case '{':
        return object()
      // 如果是数组
      case '[':
        return array()
      // 如果是字符串
      case '"':
        return string()
      // 如果包含负号,说明是数字
      case "-":
        return number()
      default:
        let temp = Number(current)
        if (temp >= 0 && temp <= 9) {
          return number()
        } else {
          return word()
        }
    }
  }

  let text = data + ''
  let index = 0
  let current = ' '
  let result = value()

  white()

  if (current) {
    throw new Error()
  }

  return result
} 

23. Implement a sum() method

https://bigfrontend.dev/zh/problem/create-a-sum
/**
 * @param {number} num
 */
function sum(num) {
  const fn = function (arg) {
    return sum(num + arg)
  }
  fn.toString = function () {
    return num
  }
  return fn
}

24. Hand write a Priority Queue with JavaScript

https://bigfrontend.dev/zh/problem/create-a-priority-queue-in-JavaScript

// complete the implementation
class PriorityQueue {
  /**
   * @param {(a: any, b: any) => -1 | 0 | 1} compare - 
   * compare function, similar to parameter of Array.prototype.sort
   */
  constructor(compare) {
    this.compare = compare;
  }

  /**
   * return {number} amount of items
   */
  size() {

  }

  /**
   * returns the head element
   */
  peek() {

  }

  /**
   * @param {any} element - new element to add
   */
  add(element) {
   
  }

  /**
   * remove the head element
   * @return {any} the head element
   */
  poll() {
    
  }
}

25. Update the order of the array

https://bigfrontend.dev/zh/problem/reorder-array-with-new-indexes
/**
 * @param {any[]} items
 * @param {number[]} newOrder
 * @return {void}
 */
// 不实用额外空间
function sort(items, newOrder) {
  // 使用简单的冒泡排序
  for (let i = 0; i < items.length; i += 1) {
    for (let j = i + 1; j < items.length; j += 1) {
      if (newOrder[i] > newOrder[j]) {
        // 更新items的顺序以及newOrder的顺序
        let otemp = newOrder[j]
        let itemp = items[j]
        newOrder[j] = newOrder[i]
        newOrder[i] = otemp
        items[j] = items[i]
        items[i] = itemp
      }
    }
  }
  return items
}

26. Implement Object.assign()

https://bigfrontend.dev/zh/problem/implement-object-assign
/**
 * @param {any} target
 * @param {any[]} sources
 * @return {object}
 */
 function objectAssign(target, ...sources) {
  if (target === null || target === undefined) {
    throw new Error()
  } 
  if (typeof target === 'number') {
    target = new Number(target)
  }
  if (typeof target === 'boolean') {
    target = new Boolean(target)
  }
  if (typeof target === 'string') {
    target = new String(target)
  }
  sources.forEach(source => {
    if (source === undefined || source === null) {
      return
    }
    Object.defineProperties(
      target,
      Object.getOwnPropertyDescriptors(source)
    )
  })
  return target
}

27. Implement completeAssign()

https://bigfrontend.dev/zh/problem/implement-completeAssign

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

function completeAssign(target, ...sources) {
  if (target === null || target === undefined) {
    throw new Error()
  } 
  if (typeof target === 'number') {
    target = new Number(target)
  }
  if (typeof target === 'boolean') {
    target = new Boolean(target)
  }
  if (typeof target === 'string') {
    target = new String(target)
  }
  sources.forEach(source => {
    if (source === undefined || source === null) {
      return
    }
    Object.defineProperties(
      target,
      Object.getOwnPropertyDescriptors(source)
    )
  })
  return target
}

28. Implement clearAllTimeout()

https://bigfrontend.dev/zh/problem/implement-clearAllTimeout
const beforeSetTimeout = window.setTimeout

window.timers = new Set()

// 重写setTimeout
window.setTimeout = function (handler, timeout, ...arg) {
  const that = this
  const timer = beforeSetTimeout(
    function (...arg) {
      handler.apply(that, [...arg])
    },
    timeout,
    ...arg
  )
  window.timers.add(timer)
  return timer
}


/**
 * cancel all timer from window.setTimeout
 */
function clearAllTimeout() {
  window.timers.forEach((timer) => {
    window.clearTimeout(timer)
  })
}

29. Implement async helper- sequence()

https://bigfrontend.dev/zh/problem/implement-async-helper-sequence
/*
type Callback = (error: Error, data: any) => void

type AsyncFunc = (
   callback: Callback,
   data: any
) => void
*/

function promisify(func) {
  return function (num) {
    return new Promise(function(resolve, reject) {
      func(function (err, data) {
        if (err) {
          reject(err)
        } else {
          resolve(data)
        }
      }, num)
    })
  }
}

/**
 * @param {AsyncFunc[]} funcs
 * @return {(callback: Callback) => void}
 */
function sequence(funcs) {
  funcs = funcs.map(func => promisify(func))
  return async function (callback, data) {
    try {
      for (let i = 0; i < funcs.length; i += 1) {
        data = await funcs[i](data)
      }
      callback(undefined, data)
    } catch (error) {
      callback(error, undefined)
    }
  }
}

30. Implement async helper- parallel()

https://bigfrontend.dev/zh/problem/implement-async-helper-parallel
/*
type Callback = (error: Error, data: any) => void

type AsyncFunc = (
   callback: Callback,
   data: any
) => void

*/

function promisify(func) {
  return function (...args) {
    return new Promise(function(resolve, reject) {
      func(function (err, data) {
        if (err) {
          reject(err)
        } else {
          resolve(data)
        }
      }, ...args)
    })
  }
}

/**
 * @param {AsyncFunc[]} funcs
 * @return {(callback: Callback) => void}
 */
function parallel(funcs){
  funcs = funcs.map(func => promisify(func))
  return function (callback, data) {
    let count = 0
    let handleError = false
    const result = []
    for (let i = 0; i < funcs.length; i += 1) {
      funcs[i](data).then(function (res) {
        result[i] = res
        count += 1
        if (count === funcs.length) {
          callback(undefined, result)
        }
      }).catch(function (err) {
        if (!handleError) {
          callback(err, undefined)
          handleError = true
        }
      })
    }
  }
}

31. Implement async helper- race()

https://bigfrontend.dev/zh/problem/implement-async-helper-race
/*
type Callback = (error: Error, data: any) => void

type AsyncFunc = (
   callback: Callback,
   data: any
) => void

*/
function promisify(func) {
  return function (...args) {
    return new Promise(function(resolve, reject) {
      func(function (err, data) {
        if (err) {
          reject(err)
        } else {
          resolve(data)
        }
      }, ...args)
    })
  }
}

/**
 * @param {AsyncFunc[]} funcs
 * @return {(callback: Callback) => void}
 */
function race(funcs){
  funcs = funcs.map(func => promisify(func))
  return function (callback, data) {
    let handleEnd = false
    for (let i = 0; i < funcs.length; i += 1) {
      funcs[i](data).then(function (res) {
        if (!handleEnd) {
          callback(undefined, res)
        }
        handleEnd = true
      }).catch(function (err) {
        if (!handleEnd) {
          callback(err, undefined)
        }
        handleEnd = true
      })
    }
  }
}

32. Implement Promise.all()

https://bigfrontend.dev/zh/problem/implement-Promise-all
/**
 * @param {Array<any>} promises - notice input might have non-Promises
 * @return {Promise<any[]>}
 */
function all(promises) {
  return new Promise((resolve, reject) => {
    if (!promises.length) {
      return resolve([])
    }
    const result = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i] instanceof Promise ? promises[i] : Promise.resolve(promises[i])
      promise.then((res) => {
        result[i] = res
        count += 1
        if (count === promises.length) {
          resolve(result)
        }
      }).catch((err) => {
        reject(err)
      })
    }
  })
}

33. Implement Promise.allSettled()

https://bigfrontend.dev/zh/problem/implement-Promise-allSettled
/**
 * @param {Array<any>} promises - notice that input might contains non-promises
 * @return {Promise<Array<{status: 'fulfilled', value: any} | {status: 'rejected', reason: any}>>}
 */
function allSettled(promises) {
  return new Promise((resolve, reject) => {
    if (!promises.length) {
      return resolve([])
    }
    const result = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i] instanceof Promise ?
        promises[i] :
        Promise.resolve(promises[i])
      promise.then((res) => {
        result[i] = {status:"fulfilled",value: res}
        count += 1
        if (count === promises.length) {
          resolve(result)
        }
      }).catch((err) => {
        result[i] = {status:"rejected",reason: err}
        count += 1
        if (count === promises.length) {
          resolve(result)
        }
      })
    }
  })
}

34. Implement Promise.any()

https://bigfrontend.dev/zh/problem/implement-Promise-any
/**
 * @param {Array<Promise>} promises
 * @return {Promise}
 */
function any(promises) {
  return new Promise((resolve, reject) => {
    if (!promises.length) {
      return reject(
        new AggregateError(
          'No Promise in Promise.any was resolved', 
          []
        )
      )
    }
    const errors = [];
    let count = 0;
    for (let i = 0; i < promises.length; i += 1) {
      const promise = promises[i] instanceof Promise ? promises[i] : Promise.resolve(promises[i])
      promise.then((res) => {
        resolve(res)
      }).catch((err) => {
        errors[i] = err
        count += 1
        if (count === promises.length) {
          reject(
            new AggregateError(
              'No Promise in Promise.any was resolved', 
              errors
            )
          )
        }
      })
    }
  })
}

35. Implement Promise.race()

https://bigfrontend.dev/zh/problem/implement-Promise-race
/**
 * @param {Array<Promise>} promises
 * @return {Promise}
 */
function race(promises) {
  return new Promise((resolve, reject) => {
    if (promises.length) {
      for (let i = 0; i < promises.length; i += 1) {
        const promise = promises[i] instanceof Promise ?
          promises[i]
          :
          Promise.resolve(promises[i])
        promise.then((res) => {
          resolve(res)
        }).catch((err) => {
          reject(err)
        })
      }
    }
  })
}

37. Handwriting Binary Search (unique)

https://bigfrontend.dev/zh/problem/implement-Binary-Search-Unique
/**
 * @param {number[]} arr - ascending unique array
 * @param {number} target
 * @return {number}
 */
function binarySearch(arr, target){
  if (arr.length === 0) return -1
  
  const _binarySearch = (arr, base) => {
    if (arr.length) {
      const midd = Math.floor(arr.length / 2)
      if (arr[midd] === target) {
        return midd + base
      } else if (arr[midd] > target) {
        return _binarySearch(arr.splice(midd + 1), midd)
      } else {
        return _binarySearch(arr.splice(0, midd), base)
      }
    } else {
      return -1
    }
  }

  return _binarySearch([...arr], 0)
}

38. Implement jest.spyOn()

https://bigfrontend.dev/zh/problem/implement-spyOn
/**
 * @param {object} obj
 * @param {string} methodName
 */
function spyOn(obj, methodName) {
  if (!(obj[methodName] instanceof Function)) {
    throw new Error()
  }

  const before = obj[methodName]
  const calls = []

  obj[methodName] = function (...args) {
    calls.push([...args])
    before.call(this, ...args)
  }

  return {
    calls
  }
}

39. Handwriting range()

https://bigfrontend.dev/zh/problem/implement-range
/**
 * @param {integer} from
 * @param {integer} to
 */
function range(from, to) {
  const result = []
  while (from <= to) {
    result.push(from)
    from += 1
  }
  return result
}

40. Handwritten bubble sort

https://bigfrontend.dev/zh/problem/implement-Bubble-Sort

Bubble sort is a stable sort, with space complexity O(n) and time complexity O(n^2). Stable ordering means that the relative position of elements will not change

/**
 * @param {number[]} arr
 */
 function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i += 1) {
    for (let j = i + 1; j < arr.length; j += 1) {
      if (arr[i] > arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
  return arr
}

41. Handwritten merge sort

https://bigfrontend.dev/zh/problem/implement-Merge-Sort

Divide and conquer, merge sort is a stable sort, and the time complexity is O(nlogn)

Reference article: https://stackabuse.com/merge-sort-in-javascript

// Note: 需要直接在原数组上修改
function merge (left, right) {
  const arr = []
  while (left.length && right.length) {
    if (left[0] > right[0]) {
      arr.push(right.shift())
    } else {
      arr.push(left.shift())
    }
  }
  return [...arr, ...left, ...right]
}

/**
 * @param {number[]} arr
 */
function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr
  }

  const midd = Math.floor(arr.length / 2)
  const left = arr.splice(0, midd)

  return merge(mergeSort(left), mergeSort(arr))
}

// 原地修改
// 递归最外层的right就是原数组
function merge (left, right) {
  const arr = []
  while (left.length && right.length) {
    if (left[0] > right[0]) {
      arr.push(right.shift())
    } else {
      arr.push(left.shift())
    }
  }

  while (arr.length) {
    right.unshift(arr.pop())
  }

  if (left.length) {
    right.push(left.pop())
  }

  return right
}

/**
 * @param {number[]} arr
 */
function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr
  }

  const midd = Math.floor(arr.length / 2)
  const left = arr.splice(0, midd)

  return merge(mergeSort(left), mergeSort(arr))
}

42. Handwriting insertion sort

https://bigfrontend.dev/zh/problem/implement-Insertion-Sort

Insertion sort is a stable sort, and the time complexity is O(n^2)

/**
 * @param {number[]} arr
 */
function insertionSort(arr) {
  for (let i = 0; i < arr.length; i += 1) {
    for (let j = i + 1; j < arr.length; j += 1) {
      if (arr[i] > arr[j]) {
        const item = arr.splice(j,1)[0]
        arr.splice(i,0,item)
      }
    }
  }

  return arr
}

43. Handwriting Quick Sort

https://bigfrontend.dev/zh/problem/implement-Quick-Sort

Quicksort is an unstable sort, and the worst-case time complexity is O(n^2). The average time complexity is O(nlogn)

// Note: 需要直接在原数组上修改
/**
 * @param {number[]} arr
 */
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr
  }

  const referenceValue = arr[0]
  const max = []
  const min = []

  for (let i = 1; i < arr.length; i += 1) {
    if (arr[i] >= referenceValue) {
      max.push(arr[i])
    } else {
      min.push(arr[i])
    }
  }

  return quickSort(min).concat(referenceValue, quickSort(max))
}

// 原地修改
/**
 * @param {number[]} arr
 */
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr
  }

  const referenceValue = arr[0]
  let max = []
  let min = []

  for (let i = 1; i < arr.length; i += 1) {
    if (arr[i] >= referenceValue) {
      max.push(...arr.splice(i,1))
    } else {
      min.push(...arr.splice(i,1))
    }
    i -= 1
  }

  min = quickSort(min)
  max = quickSort(max)

  while (max.length) {
    arr.push(max.shift())
  }

  while (min.length) {
    arr.unshift(min.pop())
  }

  return arr
}

44. Handwriting selection sort

https://bigfrontend.dev/zh/problem/implement-Selection-Sort

https://www.geeksforgeeks.org/selection-sort/

Selection sort is unstable sort, and the time complexity is O(n^2)

/**
 * @param {number[]} arr
 */
function selectionSort(arr) {
  for (let i = 0; i < arr.length; i += 1) {
    let minIndex = i
    for (let j = i + 1; j < arr.length; j += 1) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    const min = arr.splice(minIndex, 1)[0]
    arr.splice(i, 0, min)
  }
  return arr
}

45. Find the Kth largest element in the unsorted array

https://bigfrontend.dev/zh/problem/find-the-K-th-largest-element-in-an-unsorted-array
// 分治

/**
 * @param {number[]} arr
 * @param {number} k
 */
function findKThLargest(arr, k) {
  let result

  const divideAndConquer = (arr, base) => {
    if (arr.length <= 1) {
      result = arr[0]
    }
    const min = []
    const max = []
    let maxLen
    const referenceValue = arr[0]
    for (let i = 1; i < arr.length; i += 1) {
      if (arr[i] > referenceValue) {
        max.push(arr[i])
      } else {
        min.push(arr[i])
      }
    }
    max.push(arr[0])
    maxLen = max.length + base
    if (maxLen >= k && max.length) {
      divideAndConquer(max, base)
    } else if (maxLen < k && min.length) {
      divideAndConquer(min, maxLen)
    }
  }

  divideAndConquer(arr, 0)

  return result
}

46. Implement _.once()

https://bigfrontend.dev/zh/problem/implement-once
/**
 * @param {Function} func
 * @return {Function}
 */
function once(func) {
  let result
  let once = true
  return function (...args) {
    if (once) {
      result = func.call(this, ...args)
      once = false
    }
    return result
  }
}

47. Reverse Linked List

https://bigfrontend.dev/zh/problem/Reverse-a-linked-list
/** 
 * class Node {
 *  new(val: number, next: Node);
 *    val: number
 *    next: Node
 * }
 */

/**
 * @param {Node} list
 * @return {Node} 
 */
const reverseLinkedList = (list) => {
    if (!list) return
    const initNode = list
    let newHead = initNode
    let newHeadNextNode = initNode

    while (initNode.next) {
        newHead = initNode.next
        if (newHead.next) {
            initNode.next = newHead.next
        } else {
            initNode.next = null
        }
        newHead.next = newHeadNextNode
        newHeadNextNode = newHead
    }

    return newHead
}

48. Return the position of the first occurrence of a specific element in an array with repeated elements

https://bigfrontend.dev/zh/problem/search-first-index-with-Binary-Search-duplicate-array
/**
 * @param {number[]} arr - ascending array with duplicates
 * @param {number} target
 * @return {number}
 */
function firstIndex(arr, target){
  if (arr.length === 0) return -1
  let index = -1

  const _firstIndex = (start, end) => {
    if (start <= end) {
      const midd = Math.floor((start + end) / 2)
      if (arr[midd] === target) {
        if (index === -1) {
          index = midd
        } else {
          index = Math.min(index, midd)
        }
        _firstIndex(start, midd - 1)
      } else if (arr[midd] > target) {
        _firstIndex(start, midd - 1)
      } else {
        _firstIndex(midd + 1, end)
      }
    }
  }

  _firstIndex(0, arr.length - 1)

  return index
}

49. Return the last position of a specific element in an array with repeated elements

https://bigfrontend.dev/zh/problem/search-last-index-with-Binary-Search-possible-duplicate-array

/**
 * @param {number[]} arr - ascending array with duplicates
 * @param {number} target
 * @return {number}
 */
function lastIndex(arr, target){
  if (arr.length === 0) return -1
  let index = -1

  const _lastIndex = (start, end) => {
    if (start <= end) {
      const midd = Math.floor((start + end) / 2)
      if (arr[midd] === target) {
        if (index === -1) {
          index = midd
        } else {
          index = Math.max(index, midd)
        }
        _lastIndex(midd + 1, end)
      } else if (arr[midd] > target) {
        _lastIndex(start, midd - 1)
      } else {
        _lastIndex(midd + 1, end)
      }
    }
  }

  _lastIndex(0, arr.length - 1)

  return index
}

50. Return the element before a specific element in an array with repeated elements

https://bigfrontend.dev/zh/problem/search-element-right-before-target-with-Binary-Search-possible-duplicate-array
/**
 * @param {number[]} arr - ascending array with duplicates
 * @param {number} target
 * @return {number}
 */
function firstIndex(arr, target){
  if (arr.length === 0) return -1
  let index = -1

  const _firstIndex = (start, end) => {
    if (start <= end) {
      const midd = Math.floor((start + end) / 2)
      if (arr[midd] === target) {
        if (index === -1) {
          index = midd
        } else {
          index = Math.min(index, midd)
        }
        _firstIndex(start, midd - 1)
      } else if (arr[midd] > target) {
        _firstIndex(start, midd - 1)
      } else {
        _firstIndex(midd + 1, end)
      }
    }
  }

  _firstIndex(0, arr.length - 1)

  return index
}

/**
 * @param {number[]} arr - ascending array with duplicates
 * @param {number} target
 * @return {number}
 */
function elementBefore(arr, target){
  if (arr.length === 0) return undefined

  const _firstIndex = firstIndex(arr, target)

  if (_firstIndex === 0 || _firstIndex === -1) return undefined

  return arr[_firstIndex - 1]
}

51. Return elements after a specific element in an array with repeated elements

https://bigfrontend.dev/zh/problem/search-element-right-after-target-with-Binary-Search-possible-duplicate-array
/**
 * @param {number[]} arr - ascending array with duplicates
 * @param {number} target
 * @return {number}
 */
function lastIndex(arr, target){
  if (arr.length === 0) return -1
  let index = -1

  const _lastIndex = (start, end) => {
    if (start <= end) {
      const midd = Math.floor((start + end) / 2)
      if (arr[midd] === target) {
        if (index === -1) {
          index = midd
        } else {
          index = Math.max(index, midd)
        }
        _lastIndex(midd + 1, end)
      } else if (arr[midd] > target) {
        _lastIndex(start, midd - 1)
      } else {
        _lastIndex(midd + 1, end)
      }
    }
  }

  _lastIndex(0, arr.length - 1)

  return index
}


/**
 * @param {number[]} arr - ascending array with duplicates
 * @param {number} target
 * @return {number}
 */
function elementAfter(arr, target){
  if (arr.length === 0) return undefined

  const _lastIndex = lastIndex(arr, target)

  if (_lastIndex === arr.length - 1 || _lastIndex === -1) return undefined

  return arr[_lastIndex + 1]
}

53. Implement middleware

https://bigfrontend.dev/zh/problem/create-a-middleware-system
class Middleware {

  constructor () {
    this.callbacks = []
    this.handleErrors = []
    this.handleNextError = null
  }

  // 洋葱模型
  _compose(funcs, type) {
    if (funcs.length === 0) {
      return arg => arg 
    }
    if (funcs.length === 1) {
      return funcs[0]
    }
    if (type === 'callback') {
      return funcs.reduce((a, b) => {
        return (req, next) => {
          return a(req, (err) => {
            if (err) {
              this.handleNextError(err)
            } else {
              b(req, next)
            }
          })
        }
      })
    } else if (type === 'handleError') {
      return funcs.reduce((a, b) => {
        return (err, req, next) => {
          return a(err, req, (err) => {
            b(err, req, next)
          })
        }
      })
    }
  }

  /**
   * @param {MiddlewareFunc} func
   */
  use(func) {
    if (func.length === 2) {
      this.callbacks.push(func)
    } else {
      this.handleErrors.push(func)
    }
  }

  /**
   * @param {Request} req
   */
  start(req) {
    const callback = this._compose(this.callbacks, 'callback')
    const handleError = this._compose(this.handleErrors, 'handleError')
    this.handleNextError = (err) => {
      handleError(err, req, () => {})
    }
    try {
      callback(req, (err) => {
        if (err) {
          this.handleNextError(err)
        }
      })
    } catch (err) {
      handleError(err, req, () => {})
    }
  }
}
const middleware = new Middleware()

middleware.use((req, next) => {
   console.log(1)
   next()
   console.log(2)
})

// since error occurs, this is skipped
middleware.use((req, next) => {
  console.log(3)
  next(new Error())
  console.log(4)
})


// since error occurs, this is called
middleware.use((error, req, next) => {
   console.log(error)
   console.log(req)
})

middleware.start({})
class Middleware {

  constructor () {
    this.callbacks = []
    this.handleErrors = []
  }

  /**
   * @param {MiddlewareFunc} func
   */
  use(func) {
    if (func.length === 2) {
      this.callbacks.push(func)
    } else {
      this.handleErrors.push(func)
    }
  }

  /**
   * @param {Request} req
   */
  start(req) {
    let callbackIndex = 0
    let handleErrorIndex = 0
    const next = (error) => {
      const args = [req, next]
      let func
      if (error) {
        func = this.handleErrors[handleErrorIndex]
        args.unshift(error)
        handleErrorIndex += 1
      } else {
        func = this.callbacks[callbackIndex]
        callbackIndex += 1
      }

      try {
        func && func(...args)
      } catch (err) {
        next(err)
      }
    }

    next()
  }
}

🐜 Ant Financial Interview Questions: Realize compose

function f(next) {console.log(1);next();console.log(2);}
function g(next) {console.log(3);next();console.log(4);}
function h(next) {console.log(5);next();console.log(6);}


// ((next) => {
//   ((next) => {
//     return f(() => {
//       g(next)
//     })
//   })(() => { h(next) })
// })(() => {
//   console.log('ok')
// })

// 最终需要实现的目标
// f(() => {
//   g(() => {
//     h(() => {
//     })
//   })
// })

// 实现compose
function compose(...funcs) {
  r
  return function () {
    return funcs.reduce((a,b) => {
      return (next) => {
        return a(() => {
          b(next)
        })
      }
    })(() => {})
  }
}

// 1,3,5,6,4,2
// 第一次返回(next) => f(() => g(next))
// 第二次a: (next) => f(() => g(next)) b: h
// 第二次返回 (next) => ((next) => f(() => g(next))(() => h(next))
// (next) =>f(() => g(() => h(next)))
compose(f,g,h)()

53. Implement extends

https://bigfrontend.dev/zh/problem/write-your-own-extends-in-es5
const myExtends = (SuperType, SubType) => {
  function Child (...args) {
    SuperType.call(this, ...args)
    SubType.call(this, ...args)
    // 这里Child其实本质还是SubType,还是这里要修改下
    Object.setPrototypeOf(this, SubType.prototype)
  }

  SubType.prototype = Object.create(SuperType.prototype)
  Child.prototype = Object.create(SubType.prototype)

  SubType.prototype.constructor = SubType
  Child.prototype.constructor = Child

  // 模拟es6额外的继承链
  Object.setPrototypeOf(Child, SuperType)

  return Child
}

55. Highlight keywords in HTML strings

https://bigfrontend.dev/zh/problem/highlight-keywords-in-HTML-string
// 获取所有关键词的排列组合
/**
 * @param {string} html
 * @param {string[]} keywords
 */
function highlightKeywords(html, keywords) {
  const combination = []
  let arr = html.split(' ')

  const getCombination = (head, arr) => {
    for (let i = 0; i < arr.length; i += 1) {
      const temp = [...arr]
      temp.splice(i, 1)
      const name = `${head}${arr[i]}`
      combination.push(name)
      getCombination(name, temp)
    }
  }

  getCombination('', keywords)

  arr = arr.map((item) => {
    if (combination.includes(item)) {
      return `<em>${item}</em>`
    } else if (keywords.some((keyword) => item.includes(keyword))) {
      for (let i = 0; i < keywords.length; i += 1) {
        const keyword = keywords[i]
        if (item.includes(keyword)) {
          const reg = new RegExp(keyword, 'g')
          item = item.replace(reg, `<em>${keyword}</em>`)
          break
        }
      }
      return item
    } else {
      return item
    }
  })

  return arr.join(' ')
};

// 单纯的使用正则

58. Return the height of the DOM tree

https://bigfrontend.dev/zh/problem/get-DOM-tree-height

/**
 * @param {HTMLElement | null} tree
 * @return {number}
 */
function getHeight(tree) {
  if (!tree) return 0
  const result = []

  const bfs = (nodes) => {
    if (nodes.length) {
      let childs = []
      for (let i = 0; i < nodes.length; i += 1) {
        const children = nodes[i].children
        childs = [...childs, ...children]
      }
      result.push(childs)
      bfs(childs)
    }
  }

  bfs([tree])

  return result.length       
}

59. Realize browser history

https://bigfrontend.dev/zh/problem/create-a-browser-history
class BrowserHistory {
  constructor(url) {
    this.history = [url || undefined]
    this.index = 0
  }

  visit(url) {
    if (this.index === this.history.length - 1) {
      this.history.push(url)
    } else {
      this.history = this.history.splice(0, this.index + 1)
      this.history.push(url)
    }
    this.index = this.history.length - 1
  }
  
  get current() {
    return this.history[this.index]
  }
  
  goBack() {
    this.index -= 1
    this.index = this.index < 0 ? 0 : this.index
    return this.history[this.index]
  }
  
  forward() {
    this.index += 1
    this.index = this.index > this.history.length - 1 ? this.history.length - 1 : this.index
    return this.history[this.index]
  }
}

60. Realize your own new

https://bigfrontend.dev/zh/problem/create-your-own-new-operator
/**
 * @param {Function} constructor
 * @param {any[]} args - argument passed to the constructor
 * `myNew(constructor, ...args)` should return the same as `new constructor(...args)`
 */
const myNew = (constructor, ...args) => {
  const obj = Object.create({})
  const returnValue = constructor.call(obj, ...args)
  Object.setPrototypeOf(obj, constructor.prototype)
  return returnValue || obj
}

61. Implement Function.prototype.call

https://bigfrontend.dev/zh/problem/create-call-method
Function.prototype.mycall = function(thisArg, ...args) {
  if (thisArg === undefined || thisArg === null) {
    thisArg = window
  }
  if (typeof thisArg === 'string') {
    thisArg = new String(thisArg)
  }
  if (typeof thisArg === 'number') {
    thisArg = new Number(thisArg)
  }
  if (typeof thisArg === 'boolean') {
    thisArg = new Boolean(thisArg)
  }
  const key = Symbol()
  thisArg[key] = this
  const result = thisArg[key](...args)
  delete thisArg[key]
  return result
}

Handwriting apply

Function.prototype.myapply = function (thisArg, argsArray = []) {
  if (!Array.isArray(argsArray)) {
    throw new Error()
  }
  if (thisArg === undefined || thisArg === null) {
    thisArg = window
  }
  if (typeof thisArg === 'string') {
    thisArg = new String(thisArg)
  }
  if (typeof thisArg === 'number') {
    thisArg = new Number(thisArg)
  }
  if (typeof thisArg === 'boolean') {
    thisArg = new Boolean(thisArg)
  }
  const key = Symbol()
  thisArg[key] = this
  const result = thisArg[key](...argsArray)
  delete thisArg[key]
  return result
}

Handwriting bind

Function.prototype.mybind = function (thisArg, ...initArgs) {
  if (thisArg === undefined || thisArg === null) {
    thisArg = window
  }
  if (typeof thisArg === 'string') {
    thisArg = new String(thisArg)
  }
  if (typeof thisArg === 'number') {
    thisArg = new Number(thisArg)
  }
  if (typeof thisArg === 'boolean') {
    thisArg = new Boolean(thisArg)
  }
  const that = this
  return function (...args) {
    const key = Symbol()
    thisArg[key] = that
    const result = thisArg[key](...initArgs, ...args)
    delete thisArg[key]
    return result
  }
}

63. Handwriting _.cloneDeep()

https://bigfrontend.dev/zh/problem/create-cloneDeep
// 规避循环引用 Avoid circular references
const hash = new WeakMap()

function isObject(value) {
  return value != null && (typeof value === "object" || typeof value === "function")
}

function getSymbolKeys(value) {
  let keys = Object.getOwnPropertySymbols(value)
  keys = keys.filter((key) => value.propertyIsEnumerable(key))
  return keys
}

function getAllKeys(value) {
  let keys = Object.keys(value)
  keys = [...keys, ...getSymbolKeys(value)]
  return keys
}

function cloneDeep(data) {
  let result = null

  if (!isObject(data)) {
    return data
  }

  const isArray = Array.isArray(data)

  if (isArray) {
    result = []
  } else {
    result = Object.create(Object.getPrototypeOf(data))
  }

  if (hash.has(data)) {
    return hash.get(data)
  } else {
    hash.set(data, result)
  }

  const keys = getAllKeys(data)

  for (let i = 0; i < keys.length; i += 1) {
    const key = keys[i]
    const val = data[key]
    result[key] = cloneDeep(val)
  }

  return result
}

64. Automatic retry when Promise reject

https://bigfrontend.dev/zh/problem/retry-promise-on-rejection
/**
 * @param {() => Promise<any>} fetcher
 * @param {number} maximumRetryCount
 * @return {Promise<any>}
 */
function fetchWithAutoRetry(fetcher, maximumRetryCount) {
  return new Promise(async (resolve, reject) => {
    let error
    for (let i = 0; i <= maximumRetryCount; i += 1) {
      await fetcher().then((res) => {
        resolve(res)
      }).catch((err) => {
        error = err
      })
    }
    reject(error)
  })
}

65. Add thousands separator

https://bigfrontend.dev/zh/problem/add-comma-to-number
/**
 * @param {number} num
 * @return {string}
 */
function addComma(num) {
  num = `${num}`
  const reg = /\B(?=(\d{3})+$)/g
  const decimalPlaces = num.split('.')[1]
  let integerBits = num.split('.')[0]
  integerBits = integerBits.replace(reg, ',')

  if (decimalPlaces) {
    return `${integerBits}.${decimalPlaces}`
  }
  return `${integerBits}`
  
}

66. Remove duplicate elements in the array

https://bigfrontend.dev/zh/problem/remove-duplicates-from-an-array
/**
 * @param {any[]} arr
 */
function deduplicate(arr) {
  const map = new Map()
  for (let i = 0; i < arr.length; i += 1) {
    if (map.has(arr[i])) {
      arr.splice(i, 1)
      i -= 1
    } else {
      map.set(arr[i], true)
    }
  }
  return arr
}

69. Implement _.isEqual()

https://bigfrontend.dev/zh/problem/implement-deep-equal-isEqual
const getSymbolKeys = (obj) => {
  let symbolKeys = Object.getOwnPropertySymbols(obj)
  symbolKeys = [...symbolKeys].filter((key) => {
    return obj.propertyIsEnumerable(key)
  })
  return symbolKeys
}

const getAllKeys = (obj) => {
  let keys = Object.keys(obj)
  keys = [...keys, ...getSymbolKeys(obj)]
  return keys
}

function isObject(value) {
  return value != null && (typeof value === "object" || typeof value === "function")
}

const hash = new WeakMap()

/**
 * @param {any} a
 * @param {any} b
 * @return {boolean}
 */
function isEqual(a, b) {
  let result = true

  const equal = (a, b) => {
    if (!isObject(a) || !isObject(b)) {
      result = (a === b)
      return 
    }

    if (hash.has(a) && hash.has(b)) {
      return true
    }

    const aKeys = getAllKeys(a)
    const bKeys = getAllKeys(b)

    if (aKeys.length !== bKeys.length) {
      result = false
      return
    }

    hash.set(a, true)
    hash.set(b, true)

    for (let i = 0; i < aKeys.length; i += 1) {
      const key = aKeys[i]
      const aValue = a[key]
      const bValue = b[key]
      if (!isObject(aValue) || !isObject(bValue)) {
        if (aValue !== bValue) {
          result = false
          break
        }
      } else {
        isEqual(aValue, bValue)
      }
    }
  }

  equal(a, b)
  
  return result
}

79. Convert snake_case to camelCase

https://bigfrontend.dev/zh/problem/convert-snake_case-to-camelCase
/**
 * @param {string} str
 * @return {string}
 */
function snakeToCamel(str) {
  const reg = /([^_])_([^_])/g
  return str.replaceAll(reg, (_, p1, p2) => `${p1}${p2.toUpperCase()}`)
}

Convert camelCase to snake_case

Not tested by boundary conditions
/*
 * @param {string} str
 * @return {string}
 */
function camelToSnake(str) {
  const reg = /\B(\w)([A-Z])\B/g
  return str.replaceAll(reg, (_, p1, p2) => `${p1}_${p2.toLowerCase()}`)
}

81. Combine Sorted Arrays

https://bigfrontend.dev/zh/problem/merge-sorted-arrays

/**
 * @param {number[][]} arrList
 * non-descending integer array
 * @return {number[]} 
 */
function merge(arrList) {
  let result = []
  let offset = false

  const isMerge = () => {
    let count = 0
    for (let i = 0; i < arrList.length; i += 1) {
      if (arrList[i].length > 0) count += 1
      if (count >= 2) break
    }
    return count >= 2 ? true : false
  }

  offset = isMerge()

  while (offset) {
    let index = 0
    let min = Number.MAX_VALUE
    for (let i = 0; i < arrList.length; i += 1) {
      if (arrList[i][0] <= min) {
        index = i
        min = arrList[i][0]
      }
    }
    result.push(...arrList[index].splice(0, 1))
    offset = isMerge()
  }

  for (let i = 0; i < arrList.length; i += 1) {
    result = [...result, ...arrList[i]]
  }


  return result 
}

85. Implement _.get()

https://bigfrontend.dev/zh/problem/implement-lodash-get
/**
 * @param {object} source
 * @param {string | string[]} path
 * @param {any} [defaultValue]
 * @return {any}
 */
function get(source, path, defaultValue = undefined) {
  if (typeof path === 'string') {
    path = path.split('.')
  }

  const newPath = []
  const reg = /^([A-Za-z]+)\[([0-9]+)]$/g
  let result = source

  for (let i = 0; i < path.length; i += 1) {
    if (path[i].includes('[')) {
      const arr = reg.exec(path[i])
      newPath.push(arr[1])
      newPath.push(arr[2])
    } else {
      newPath.push(path[i])
    }
  }

  for (let i = 0; i < newPath.length; i += 1) {
    if (result === undefined || result === null) {
      result = defaultValue
      break
    }
    result = result[newPath[i]]
  }

  console.log(result)

  return result ? result : defaultValue
}

87. Return the longest unique substring

https://bigfrontend.dev/zh/problem/longest-substr


已注销
518 声望187 粉丝

想暴富