写好原生JavaScript(二)

156 阅读2分钟

怎样设计一个好的api?

假设现在有一个需求如下:

要实现一个自动切换的交通信号灯

版本1
var light = document.querySelector('#light')

;(function reset() {
  light.className = 'stop'
  
  setTimeout(() => {
    light.className = 'wait'
    setTimeout(() => {
      light.className = 'pass'
      setTimeout(reset, 2000)
    }, 2000)
  }, 2000)
})()

相信很多人都会像方式1这么写,实现简单,思路也清晰,但如果我想交换信号的顺序,就需要修改代码逻辑部分,而且如果要添加多个状态,显然会出现很多的嵌套回调,这样写显然不好

版本2
changeState(light, 'stop', 'wait', 'pass')

function changeState(list, ...state) {
  let i = 0
  let timer = setInterval(() => {
    list.className = state[i++ % state.length]
  }, 2000)
}

版本2很好的解决了版本1的问题,而且封装性也比较好,但是这样并不是很优秀

版本3
function poll(...atcs) {
  let index = 0
  
  return (...args) => {
    let fn = atcs[index++ % atcs.length]
    return fn.apply(this, args)
  }
}
let setState = function(state) {
  light.className = state;
}
let changeState = poll(
  setState.bind(null, 'stop'),
  setState.bind(null, 'wait'),
  setState.bind(null, 'pass')
)
let timer = setInterval(changeState, 2000)

版本4用到了一个高阶函数apply,poll函数设计的十分精妙,完全抽象,复用性极强,但是毕竟需求可能还会变,比如我要不同的时间间隔,我想1s后stop,2s后wait...,怎么办...? 难道我们要回到版本1,当然不行。接下来看版本4

版本4
function wait(time) {
  return new Promise(resolve => setTimeout(resolve, time))
}

function setState(state) {
  light.className = state
}

let reset = async () => {
//      Promise.resolve()
//        .then(setState.bind(null, 'stop'))
//        .then(wait.bind(null, 1000))
//        .then(setState.bind(null, 'wait'))
//        .then(wait.bind(null, 1500))
//        .then(setState.bind(null, 'pass'))
//        .then(wait.bind(null, 2000))
//        .then(reset)
  setState('stop')
  await wait(1000)
  setState('wait')
  await wait(1500)
  setState('pass')
  await wait(2000)
  reset()
}
reset()

版本4巧妙的将等待这个动作给抽象了出来,解决了时间间隔问题,由此我们可以封装一个动画类

class Animate {
      constructor(el, args) {
        this.el = el
        this.states = args
      }
      async start() {
        let states = this.states
        let setState = this.setState
        let wait = this.wait
        let el = this.el
        for(let s of states) {
          console.log(s.state)
          await new Promise(async resolve => {
            setState(el, s.state)
            await wait(s.duration)
            resolve()
          })
        }
        this.start()
      }
      
      wait(time) {
        return new Promise(resolve => setTimeout(resolve, time))
      }
      setState(el, state) {
        el.className = state
      }
    }
    const test = new Animate(light, [
      {state: 'stop', duration: 1000},
      {state: 'wait', duration: 1500},
      {state: 'pass', duration: 2000},
    ])
    test.start()

利用这个可以实现一些动画例如这个轮播图