【青训营】写好JS——做好抽象

简介: 【青训营】写好JS——做好抽象

所谓"抽象化",就是指从具体问题中,提取出具有共性的模式,再使用通用的解决方法加以处理。


一个例子:交通灯切换


让你用原生JS实现一个交通灯切换的组件,怎么抽象?怎么提高扩展性和复用性?


image.png


新手入门


你可能会觉得红绿灯很简单,三个状态用setTimeout()嵌套一下就可以了:


const traffic = document.getElementById('traffic');
(function reset() {
  traffic.className = 's1';
  setTimeout(function () {
    traffic.className = 's2';
    setTimeout(function () {
      traffic.className = 's3';
      setTimeout(reset, 1000)
    }, 1000)
  }, 1000);
})();
复制代码

但是,这时候如果需要复用,再加两个灯怎么办?再嵌套两个状态?


(function reset() {
  traffic.className = 's1';
  setTimeout(function () {
    traffic.className = 's2';
    setTimeout(function () {
      traffic.className = 's3';
      setTimeout(function () {
        traffic.className = 's4';
        setTimeout(function () {
          traffic.className = 's5';
          setTimeout(reset, 1000)
        }, 1000)
      }, 1000)
    }, 1000)
  }, 1000);
})();
复制代码


这样显然不行,多个异步函数回调会造成“callback hell”,有同学可能会说那用setInterval()就可以了,但是如果每个灯的持续时间不同呢?


数据抽象


第二个版本我们对交通灯做一个数据的抽象封装:


const traffic = document.getElementById('traffic');
const stateList = [
  { state: 'wait', last: 1000 },
  { state: 'stop', last: 3000 },
  { state: 'pass', last: 3000 },
];
function start(traffic, stateList) {
  function applyState(stateIdx) {
    const { state, last } = stateList[stateIdx];
    traffic.className = state;
    setTimeout(() => {
      applyState((stateIdx + 1) % stateList.length);
    }, last)
  }
  applyState(0);
}
start(traffic, stateList);
复制代码


通过传参调用setTimeout()来创建新的状态和持续时间,这样我们就得到了一个通用的版本。如果需要新的状态就在stateList里添加即可:


const stateList = [
  { state: 'wait', last: 1000 },
  { state: 'stop', last: 3000 },
  { state: 'pass', last: 3000 },
  { state: 'new', last: 500}
];
复制代码


不过因为我们没有做模板的抽象,所以要手动地添加部分HTML和CSS代码。


过程抽象


第三个版本我们使用刚讲过的过程抽象将其抽象成一个轮询的函数,每次去执行异步函数就可以了:


const traffic = document.getElementById('traffic');
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function poll(...fnList) {
  let stateIndex = 0;
  return async function (...args) {
    let fn = fnList[stateIndex++ % fnList.length];
    return await fn.apply(this, args);
  }
}
async function setState(state, ms) {
  traffic.className = state;
  await wait(ms);
}
let trafficStatePoll = poll(
  setState.bind(null, 'wait', 1000),
  setState.bind(null, 'stop', 3000),
  setState.bind(null, 'pass', 3000)
);
(async function () {
  // noprotect
  while (1) {
    await trafficStatePoll();
  }
}());
复制代码


简化通用


过度的抽象是一种负担。


wait()是异步的,setState()是瞬间的,这样既简化了代码,也更符合人的直觉:


const traffic = document.getElementById('traffic');
function wait(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}
function setState(state) {
  traffic.className = state;
}
async function start() {
  //noprotect
  while (1) {
    setState('wait');
    await wait(1000);
    setState('stop');
    await wait(3000);
    setState('pass');
    await wait(3000);
  }
}
start();
复制代码


除了第一个版本不太行,其他版本都有自己的优缺点。抽象程度高,复用性高,但理解成本相应的也会很高,在很多时候我们需要做一个平衡。


我们追求的代码状态是:它既是一个优雅的代码,又不违背我们的直觉和思维习惯。

像本例中,我们只要把切换状态和等待两个函数切开来看,就很容易写出最后这个两全其美的代码。

目录
相关文章
|
3月前
|
存储 缓存 自然语言处理
深入理解JS | 青训营笔记
深入理解JS | 青训营笔记
38 0
|
数据采集 缓存 负载均衡
【青训营】-🥝Node.js基础入门
【青训营】-🥝Node.js基础入门
169 3
【青训营】-🥝Node.js基础入门
|
监控 JavaScript 前端开发
Node.js入门 | 青训营
Node.js入门 | 青训营
127 0
Node.js入门 | 青训营
|
算法 JavaScript
【青训营】写好JS——保证正确
【青训营】写好JS——保证正确
98 0
【青训营】写好JS——保证正确
|
算法 JavaScript
【青训营】写好JS——学好算法
【青训营】写好JS——学好算法
88 0
【青训营】写好JS——学好算法
|
JavaScript
【青训营】写好JS——写代码最应该关注什么?
【青训营】写好JS——写代码最应该关注什么?
116 0
【青训营】写好JS——写代码最应该关注什么?
|
JavaScript 测试技术
【青训营】写好JS——过程抽象
【青训营】写好JS——过程抽象
248 0
【青训营】写好JS——过程抽象
|
JavaScript 前端开发 API
【青训营】写好JS——组件封装(下)
【青训营】写好JS——组件封装(下)
272 0
【青训营】写好JS——组件封装(下)
|
JavaScript 前端开发 API
【青训营】写好JS——组件封装(上)
【青训营】写好JS——组件封装(上)
234 0
【青训营】写好JS——组件封装(上)
|
JavaScript 前端开发
【青训营】写好JS——各司其责
【青训营】写好JS——各司其责
92 0
【青训营】写好JS——各司其责