JavaScript代码质量优化之路 | 青训营笔记

85 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第4天

判断代码的优秀与否

写代码最应该关注什么

判断一个代码的优秀与否,不单单是看代码的优雅性,也就是复用性是否高,代码量是否小,是否用少的办法做到。而是得结合使用场景,我们需要关注的方面有:

  • 风格
  • 效率
  • 约定
  • 使用场景
  • 设计

left-pad事件

当年npm中有一个叫left-pad的模块,但是这个模块只有十一行代码,仅仅是个简单的字符串处理

module.exports = leftpad;
function leftpad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

首先是npm模块粒度问题,11行代码便构成了一个模块,这对于前端工程化来说,或许是不是太细了。其次是代码风格,虽然可读性高,但是效率不高,时间复杂度为O(n),而这种底层代码最好是能更加的优化。

然后便出现了改进过的版本,他通过repeat()方法简化的同时提高效率

function leftpad(str, len, ch=""){
  str = "" + str;
  const padLen = len - str.length;

  if (padLen <= 0){
    return str;
  } else {
    return ("" + ch).repeat(padLen) + str;
  }
}

在MDN关于repeat()的核心代码是:

var rpt = "";
for() {
  if ((count & 1) == 1) {
    rpt += str;
  }
  count >>>= 1;
  
  if (count == 0){
    break;
  }
  str += str;
}

通过 位运算 减少循环次数,时间复杂度为O(log2N)

总结

总的来说,关于判断代码是否优秀,我们还是得看业务场景,在底层的一些代码,我们需要争取尽量的高效率,越到表层的时候,我们最好能兼顾代码风格和效率,能做到易读和易改才是更加重要的。

交通灯案例

使用原生JS实现一个交通灯切换组件。

回调地狱方式

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);
})();

这是在ES6还未出现之时使用的回调地狱的方式,这样是最不推荐的写法,因为假如我们需要添加一个新的灯,我们会无限的向右走,而且代码冗余

数据抽象

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);

在这个版本里,我们通过定义一个 stateList的数据,将我们需要控制的灯状态和时间放在一个数组对象里,如果要添加新的状态直接添加新的数据即可。

过程抽象

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();
  }
}());

我们将这个过程抽象成轮询的函数,每次使用去执行异步函数,但是我们可以看到,简单的一个功能却使用了如此之多的代码,而且阅读性还不高

简化过程

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();

在这个版本中,我们只使用异步和瞬时两种方式,兼顾了可读性和效率,也有可基础的可复用性,可以算是比较优秀的代码方式了。

总结

我们可以看到,实现一个业务逻辑代码,我们可以有无数种方式去实现,而我们最应该追求的是,拥有可复用性和可读性,不违背我们的直觉和思维,并且有基本的效率,这样的代码是我们应该追求的。