Skip to content

Coding

LazyMan

ts
// 实现一个LazyMan,可以按照以下方式调用:
// LazyMan("Hank")输出:
// Hi! This is Hank!

// LazyMan("Hank").sleep(10).eat("dinner")输出
// Hi! This is Hank!
// //等待10秒..
// Wake up after 10
// Eat dinner~

// LazyMan("Hank").eat("dinner").eat("supper")输出
// Hi This is Hank!
// Eat dinner~
// Eat supper~

// LazyMan("Hank").sleepFirst(5).eat("supper")输出
// //等待5秒
// Wake up after 5
// Hi This is Hank!
// Eat supper
// 以此类推。


class _LazyMan {
  private que: Array<Function> = [];
  constructor(private name: string) {
    this.add(() => {
      console.log(`Hi This is ${name}`)
      this.next()
    })
    this.next()
  }

  eat(str: string) {
    this.add(() => {
      console.log(`Eat ${str}~`)
      this.next()
    })
    return this
  }

  sleepFirst(time: number) {
    this.que.unshift(() => {
      setTimeout(() => {
        console.log(`Wake up after ${time}`)
        this.next()
      }, time * 1000)
    })
    return this
  }

  sleep(time: number) {
    this.add(() => {
      setTimeout(() => {
        console.log(`Wake up after ${time}`)
        this.next()
      }, time * 1000)
    })
    return this
  }

  private add(fn: Function) {
    this.que.push(fn)
  }

  private next() {
    setTimeout(() => {
      const task = this.que.shift()
      task && task()
    }, 0)
  }
}

function LazyMan(name: string) {
  return new _LazyMan(name)
}

// LazyMan("Hank").sleep(10).eat("dinner")
// LazyMan("Hank").sleepFirst(5).eat("supper")
// LazyMan("Hank").eat("dinner").eat("supper")
// LazyMan("Hank").eat("supper").sleepFirst(5)

EventEmit

ts
interface IFn {
  (...args: unknown[]): unknown
  _fn?: Function
}

class EventEmit {
  private tasks: Record<string, IFn[]> = {}
  constructor() {

  }

  on(eventName: string, cb: IFn) {
    if (!this.tasks[eventName]) {
      this.tasks[eventName] = []
    }

    this.tasks[eventName].push(cb)
  }

  off(eventName: string, cb: IFn) {
    if (!this.tasks[eventName]) return
    const index = this.tasks[eventName].findIndex(i => i === cb || i === cb._fn)
    index >= 0 && this.tasks[eventName].splice(index, 1)
  }

  once(eventName: string, cb: IFn) {
    if (!this.tasks[eventName]) {
      this.tasks[eventName] = []
    }
    const fn = () => {
      cb()
      this.off(eventName, fn)
    }
    fn._fn = cb
    this.tasks[eventName].push(fn)
  }

  emit(eventName: string) {
    if(!this.tasks[eventName]) return
    console.log("tasks", this.tasks)
    this.tasks[eventName].forEach(i => i())
  }
}

const me = new EventEmit()

me.on('OK', () => console.log("OK"))
me.on('NO', () => console.log("NO"))
me.once('OK', () => console.log('once OK'))
me.emit('OK')
me.emit('OK')

函数柯里化

javascript
function curry(fn) {
  const _this = this
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(_this, args)
    } else {
      return (...args2) => {
        return curried.apply(_this, [...args, ...args2])
      }
    }
  }
}

const add = (a, b, c) => {
  console.log(a + b + c)
  return a + b + c
}
const curriedAdd = curry(add)

curriedAdd(1)(2)(3) // 输出 6
curriedAdd(1, 2)(3) // 输出 6
curriedAdd(1)(2, 3) // 输出 6
curriedAdd(1, 2, 3) // 输出 6

包装类

包装类: 如 String Boolean Number Symbol,为基础类型提供方法、属性,正常使用都是基础类型,出于性能考虑;

类会被继承

手写 new

涉及到原型链:

实例的 __proto__ 等于构造函数的 prototype

  • 创建一个空对象
  • 将空对象的 __proto__ 指向构造函数的 prototype
  • 执行构造函数,同时将构造函数的 this 指向该空对象,获取返回值
  • 如果返回值是对象,则返回该对象,否则返回空对象
js
function mockNew(constructor, ...args) {
  const obj = {}

  Object.setPrototypeOf(obj, constructor.prototype)

  const result = constructor.apply(obj, args)

  return Object.prototype.toString.call(result) === '[object Object]' ? result : obj
}

(symbol为什么不可以new?)

Symbol在设计上是为了创建唯一的标识符,而不是为了创建可操作的对象。

写一个函数,一new就会报错

js
function My() {
  // 实例化的this的原型,因此这里为true
  if (this instanceOf My) {
    throw new Error("Error")
  }
}

节流防抖

js
/**
 * 节流
 * 一定时间间隔只执行一次
 * 如:按钮点击事件,射击
 */
function throttle(fn, delay) {
  let timer = null
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args)
        timer = null
      }, delay)
    }
  }
}

/**
 * 防抖
 * 一定时间间隔,只触发最后一次
 * 如:输入框搜索
 */
function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
      clearTimeout(timer)
    }, delay)
  }
}

并发限制

实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:

要求最大并发数 maxNum

每当有一个请求返回,就留下一个空位,可以增加新的请求

所有请求完成后,结果按照 urls 里面的顺序依次打出

ts
/*
 * 函数返回一个 Promise
 * 边界情况 if (maxNum <= 0) return reject("xxx")
 * 创建一个任务队列 que,每个任务 task 都是 {id, request} ; id 记录任务原索引,request执行异步请求;
 * 创建一个当前执行中任务计数 count,用于标识当前的执行任务数
 * 创建一个结果数组 result,根据上面的任务id存储结果,最后返回;
 * 创建一个执行函数 exec():
 * 判断当 que.length === 0 && count === 0 时,表示队列全部执行完毕,resolve(result)
 * while判断,当 count < maxNum && que.length !== 0 时,
 * { 
 *   que 出队一个 task;
 *   执行这个task,同时count++;
 *   在 task then 和 catch时分别将结果根据索引存在result,同时count--,再递归调用 exec 
 * }
 * 
 * 上面4个创建完毕,执行 exec() 启动!
 */

function request(url: string): Promise<string> {
  return new Promise((resolve, reject) => setTimeout(() => {
    if (url === 'url5') reject("ggg")
    else resolve(url)
  }, Math.random() * 1000))
}

function multiRequest(urls: string[], maxNum: number): Promise<any[]> {
  return new Promise((resolve, reject) => {
    if (maxNum <= 0) return reject("xxx")
    const que = urls.map((i, index) => ({
      id: index,
      task: () => request(i)
    }))
    const result: string[] = []
    let count: number = 0
  
    const exec = () => {
      if (!que.length && !count) {
        return resolve(result)
      }

      while (count < maxNum && que.length) {
        const {id, task} = que.shift()
        count++
        console.log("开始执行", urls[id])
        task().then(res => {
          console.log("执行完成", urls[id])
          count--
          result[id] = res
          exec()
        }).catch((err) => {
          console.log("执行报错", urls[id])
          count--
          result[id] = err
          exec()
        })
      }
    }
    exec()
  })
}

multiRequest(new Array(10).fill(1).map((i, idx) => `url${i + idx}`), 3).then(res => {
  res.forEach(i => console.log(i))
})
ts
class PromiseQueue {
  private count: number = 0
  private que: (() => Promise<unknown>)[] = []
  constructor(private max = 1) {}

  add(cb: () => Promise<unknown>) {
    return new Promise((resolve, reject) => {
      this.que.push(() => {
        return cb().then(resolve, reject)
      })
      this.run()
    })
  }

  private run() {
    setTimeout(() => {
      while(this.count < this.max && this.que.length) {
        const task = this.que.shift()
        this.count++
        task().finally(() => {
          this.count--
          this.run()
        })
      }
    }, 0)
  }
}

const sleep = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(true), ms))

const que = new PromiseQueue(4)

new Array(10).fill(1).forEach((i, id) => {
  que.add(async () => {
    console.log('任务开始执行', id)
    await sleep(3000 + Math.random() * 3000)
  }).then(() => {
    console.log(`task 执行完毕:`, id)
  }).catch(() => {
    console.log(`task 执行失败:`, id)
  })
})

数组扁平化

ts
// flatten([1, [2, [3, [4]]]])
function flatten(arr: unknown[]) {
  while (arr.findIndex(i => Array.isArray(i)) !== -1) {
    arr = arr.flat()
  }
  return arr
}

const flatten1 = (arr: unknown[][]) => {
  if (arr.findIndex(i => Array.isArray(i)) !== -1) {
    return flatten(arr.flat())
  } else {
    return arr
  }
}

console.log(flatten([1, [2, [3, [4]]]]))

手写Promise及方法

js

class MockPromise {
  constructor(fn) {
    this.status = 'pending'
    this.value = void 0
    this.reason = void 0
    this.resolveCallbacks = []
    this.rejectCallbacks = []
    const resolve = (value) => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
        this.resolveCallbacks.forEach(i => i())
      }
    }
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'
        this.rejectCallbacks.forEach(i => i())
      }
    }
    try {
      fn(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    const promise2 = new MockPromise((resolve, reject) => {
      // 暂存
      if (this.status === 'pending') {
        this.resolveCallbacks.push(() => {
          const x = onFulfilled(this.value)
          this.resolvePromise(promise2, x, resolve, reject)
        })
        this.rejectCallbacks.push(() => {
          const x = onRejected(this.value)
          this.resolvePromise(promise2, x, resolve, reject)
        })
      } else if (this.status === 'fulfilled') {
        if (!onFulfilled) return
        const x = onFulfilled(this.value)
        this.resolvePromise(promise2, x, resolve, reject)
      } else if (this.status === 'rejected') {
        if (!onRejected) return
        const x = onRejected(this.reason)
        this.resolvePromise(promise2, x, resolve, reject)
      }
    })
    return promise2
  }

  resolvePromise(promise, x, resolve, reject) {
    // todo
    if (x instanceof MockPromise) {
      x.then((res) => this.resolvePromise(promise, res, resolve, reject))
    }
    else {
      resolve(x)
    }
  }

  static all(promises) {
    return new MockPromise((resolve, reject) => {
      const arr = []
      let process = 0
      promises.forEach((i, idx) => {
        i.then((res) => {
          arr[idx] = res
          process++
          if (process === promises.length) resolve(arr)
        }, () => reject())
      })
    })
  }

  static race(promises) {
    return new MockPromise((resolve, reject) => {
      promises.forEach(i => {
        i.then(resolve, reject)
      })
    })
  }

  static resolve(value) {
    return new MockPromise(resolve => resolve(value))
  }

  static reject(value) {
    return new MockPromise((resolve, reject) => reject(value))
  }
}

const handler = (timeout, id, isMock) => (resolve, reject) => {
  const Pro = isMock ? MockPromise : Promise
  // return new Pro((res => res()))
  setTimeout(() => resolve(id), timeout)
}

const a = new Promise(handler(500, 1))
const b = new Promise(handler(1000, 2))

const mock = new MockPromise(handler(1100, 3))
const mock1 = new MockPromise(handler(1000, 4))

a.then((res) => {
  // 1
  console.log("promise resolve", res)
  return new Promise((resolve, reject) => {
    resolve(2)
  })
}, (reason) => {
  console.log("promise reject", reason)
}).then((res) => {
  console.log("promise then resolve", res)
}, (reason) => {
  console.log("promise then reason", reason)
})

mock.then((res) => {
  console.log("MockPromise resolve", res)
  // return null
  return new MockPromise((resolve, reject) => {
    resolve(20)
  })
}, (reason) => {
  console.log("MockPromise reject", reason)
}).then((res) => {
  console.log("MockPromise then resolve", res)
}, (reason) => {
  console.log("MockPromise then reason", reason)
})

// mock.then((res) => {
//   console.log("MockPromise1 resolve", res)
// }, (reason) => {
//   console.log("MockPromise1 reject", reason)
// })

// Promise.race = function(promises){
//   return new Promise((resolve,reject)=>{
//     for(let i=0;i<promises.length;i++){
//       promises[i].then(resolve,reject)
//     };
//   })
// }

// Promise.race([a, b]).then((res) => {
//   console.log("promise.race", res)
// })

// Promise.all([a, b]).then((res) => {
//   console.log("promise.all", res)
// })

// MockPromise.race([mock, mock1]).then((res) => {
//   console.log("MockPromise.race", res)
// })

// MockPromise.all([mock, mock1]).then((res) => {
//   console.log("MockPromise.all", res)
// })

all

ts
const promiseAll = (promise: unknown[]) => {
  return new Promise((resolve, reject) => {

  if (!Array.isArray(promise)) {
    throw new TypeError("xxx")
  }
  const result: unknown[] = []
  let count = 0
    promise.forEach((i, index) => 
      Promise.resolve(i)
        .then(res => {
          result[index] = res
          count++
          if (count === promise.length) return resolve(result)
        })
        .catch((error) => {
          return reject(error)
        })
    )
  })
}

const sp = (ms: number) => new Promise(rs => setTimeout(() => rs(2), ms))

// @ts-ignore
// Promise.all(1).then(res => console.log(res))

promiseAll([Promise.resolve(1), sp(10), 3]).then(res => console.log(res)) // 1 2 3
// @ts-ignore
promiseAll(1).then(res => console.log(res)) // TypeError: xxx

实现retry

js
// 题目
function getFlag () {
  return Math.random() > 0.5 ? Promise.resolve(1) : Promise.reject(-1);
};
 
Promise.retry(getFlag, 10)
js
function retry (fn, count) {
  let process = 0
  return new Promise((resolve, reject) => {
    function run() {
      fn().then((res) => resolve(res)).catch((error) => {
        process++
        console.log("process error:", process)
        if (process < count) run()
        else reject(error)
      })
    }
    run()
  })
}

function getFlag () {
  return Math.random() > 0.9 ? Promise.resolve(1) : Promise.reject(-1);
};

retry(getFlag, 10).then((res) => console.log("success", res)).catch((error) => console.log("error", error))

实现instanceof

js
// 模拟instanceof
// 核心是一个实例化生成的对象me,me.__proto__ === Person.prototype

const myInstanceof = (target, obj) => {
  let temp = target.__proto__
  while (temp) {
    // target.__proto__不等于obj.prototype,下一步则判断target的原型的__proto__是否等于obj.prototype
   if (temp === obj.prototype) {
    return true
   }
   temp = temp.__proto__
  }
  return false
}

const ins = (target, obj) => {
  if (!target.__proto__) return false
  if (target.__proto__ === obj.prototype) return true
  return ins(target.__proto__, obj)
}

console.log(myInstanceof([1], Object))
console.log(myInstanceof([1], String))

带过期时间的localStorage

ts
class LC {
  constructor() {}

  setItem(key: string, value: string, duration?: number) {
    localStorage.setItem(key, JSON.stringify({
      value,
      expireTime: duration ? +new Date() + duration : 0
    }))
  }

  getItem(key: string): string | null {
    const result = localStorage.getItem(key)
    if (!result) return null
    try {
      const obj = JSON.parse(result)
      if (obj.expireTime === 0 || obj.expireTime > +new Date()) {
        return obj.value
      } else {
        return null
      }
    } catch (error) {
      return result
    }
  }

  removeItem(key: string) {
    localStorage.removeItem(key)
  }

  clear() {
    localStorage.clear()
  }
}

const myLocalStorage = new LC()

myLocalStorage.setItem("KEY", '77')

localStorage.getItem("KEY")

计算帧率

通过 requestAnimationFrame 每秒执行多少次,来计算帧率

ts
function fps() {
  let now = 0
  let end = 0

  let times = 0
  const exec = (_now: number) => {
    if (!now) {
      now = _now
      end = now + 1000
    }
    times++
    if (_now >= end) {
      console.warn(times)
      now = _now
      end = now + 1000
      times = 0
    }
    window.requestAnimationFrame(exec)
  }

  window.requestAnimationFrame(exec)
}

fps()

复原IP地址

函数结果缓存

js
const memoize = (fn: Function) => {
  const obj = Object.create(null)

  return (...args: unknown[]) => {
    const key = JSON.stringify(args);
    let res = obj[key]
    if (!res) {
      obj[key] = fn(...args)
      res = obj[key]
    }
    console.log(res)
    return res
  }
}

const add = (a, b) => a+b

const calc = memoize(add);
const num1 = calc(100,200)
const num2 = calc(100,200) // 缓存得到的结果

深拷贝

js
function deepClone(obj, hash = new WeakMap()) {

  // 存在循环引用的情况,引入 WeakMap 解决
  if (hash.get(obj)) {
    return hash.get(obj)
  }

  if (obj instanceof Date) {
    return new Date(obj)
  }

  if (obj instanceof RegExp) {
    return new RegExp(obj)
  }

  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  let copy = Array.isArray(obj) ? [] : {}

  hash.set(obj, copy)

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key], hash)
    }
  }

  return copy
}

但是这里存在循环引用的情况,上面引入 WeakMap 哈希表来解决。

WeakMap 是 JavaScript 中的一种数据结构,它和普通的 Map 类似,都是键值对的集合,但有几个关键的区别。

  • WeakMap 的键必须是对象。你不能使用原始值(如字符串或数字)作为键。

  • WeakMap 是弱引用的。这意味着如果没有其他地方引用 WeakMap 中的键对象,那么这个键对象就可以被垃圾回收,相应的键值对也会被从 WeakMap 中移除。这对于防止内存泄漏非常有用。

  • WeakMap 对象中的键值对是不可枚举的。这意味着你不能使用常规的方法(如 for...in 循环或 Object.keys() 方法)来获取 WeakMap 中的所有键或所有值。

在处理循环引用问题时,WeakMap 非常有用,因为它可以自动清理不再需要的引用,防止内存泄漏。