这是我参与「第五届青训营 」笔记创作活动的第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();
在这个版本中,我们只使用异步和瞬时两种方式,兼顾了可读性和效率,也有可基础的可复用性,可以算是比较优秀的代码方式了。
总结
我们可以看到,实现一个业务逻辑代码,我们可以有无数种方式去实现,而我们最应该追求的是,拥有可复用性和可读性,不违背我们的直觉和思维,并且有基本的效率,这样的代码是我们应该追求的。