这是我参与「第四届青训营 」笔记创作活动的第26天
如何写好JavaScript代码?(二)
0 一段代码
<script>
// 判断一个mat2d矩阵是不是单位矩阵
function isUnit(m) {
return m[0] === 1 && m[1] === 0 && m[2] === 0
&& m[3] === 1 && m[4] === 0 && m[5] === 0;
}
</script>
- 比for循环更高性能
1 写代码最关注什么?
- 风格
- 效率
- 约定
- 使用场景
- 要根据使用场景来评价代码的好坏,在该场景下更关注什么性能,以此为判断标准。
- 设计
2 Leftpad事件
- 目的:补齐数字
- 原始代码:
- 代码风格
- 代码质量/效率
<script>
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;
}
</script>
- 改进代码:
- 代码更简洁
- 效率提升
<script>
function leftpad(str, len, ch) {
str = "" + str;
const padLen = len - str.length;
if(padLen <= 0) {
return str;
}
return (""+ch).repeat(padLen)+str;
}
</script>
- 实际上,在该事件中,提升效率意义不大,更应该关注代码可读性
3 交通灯例子
- 实现一个切换多个交通灯状态切换的功能
- 版本1: 很难维护
<script>
const traffic = document.getElementById('traffic');
(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);
})();
</script>
- 版本2:数据抽象
<script>
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);
</script>
- 版本3:过程抽象,但是太复杂
<script>
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();
}
}());
</script>
- 版本4:异步+函数式,更清晰简单
<script>
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();
</script>
4 洗牌例子
- 错误写法:分布不均匀 sort算法 导致牌留下的可能性更高
<script>
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
return [...cards].sort(() => Math.random() > 0.5 ? -1 : 1);
}
console.log(shuffle(cards));
const result = Array(10).fill(0);
for(let i = 0; i < 1000000; i++) {
const c = shuffle(cards);
for(let j = 0; j < 10; j++) {
result[j] += c[j];
}
}
console.table(result);
</script>
- 正确写法:随机抽出,放到最后,每张牌抽到可能性均等,留下和抽到的可能性相等;
- 使用生成器:不跑完for循环,取几张牌,跑几次,提升性能
5 分红包例子
- 切西瓜法:每次只分最大的那一部分,不会出现不够分的情况,时间复杂度O(m*n);但最终分配结果比较均匀
- 改进:如果小的够分,适当选择小的拆分
- 抽牌法:生成一个0-999的随机数列,取牌,对其进行排序作为当前红包的金额,则会有大有小,时间复杂度O(n),但是空间复杂度较高,因为需要生成一个大的数列
总结:好的代码应该考虑什么?
- 使用场景:
- 不同场景,需求不同。以满足当前需求为导向,比如:性能、更灵活等,编写代码,是写好代码的核心。
- 算法的重要性
- 好的算法思维能够帮助我们更好地理解解决问题的逻辑,从而写出更优雅的代码,因此很重要!