这是我参与「第四届青训营 」笔记创作活动的第2天
功能实现--代码优化
为什么需要代码优化?
- 提高性能
- 增强可维护性
- 提高复用性
优化方法
可以考虑以下几个方面:
- 运算复杂度
- 效率
- 简洁性
- 严禁度(BUG)
数据抽象
数据抽象是指:把需要用到的数据通过对象的形式用字符串或者数组抽象描述出来。 比如:交通灯功能实现:
const stateList = [
{state: 'wait', last: 1000},
{state: 'stop', last: 3000},
{state: 'pass', last: 3000},
];
过程抽象(便于维护和扩展)
过程抽象是指:把每一个功能抽离出来单独作为一个函数。最后通过不同功能的函数组合实现最终效果。
缺陷:代码量多,较为复杂
const traffic = document.getElementById('traffic');
//通过promise的方式替代计时器进行延时操作
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();
}
}());
🌰判断是否是四的幂
// function isPowerOfFour(num) {
// num = parseInt(num);
// while(num > 1) {
// if(num % 4) return false;
// num /= 4;
// }
// return num === 1;
// }
// function isPowerOfFour(num) {
// num = parseInt(num);
// while(num > 1) {
// if(num & 0b11) return false;
// num >>>=2;
// }
// return num === 1;
// }
// function isPowerOfFour(num) {
//转换为二进制字符串,然后用正则进行匹配
// num = parseInt(num).toString(2);
// return /^1(?:00)*$/.test(num);
// }
🌰洗牌
错误写法:使用sort进行随机排序。(实际上是不均匀的,索引值越大,出现的概率也越大),本质原因是sort方法每个位置交换的次数不一样。
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
return [...cards].sort(() => Math.random() > 0.5 ? -1 : 1);
}
正确写法:每次随机抽取一张牌放到最后,末尾的排保持不变。后面依次选一张放到倒数第二位,类推。这样在一次抽取中,抽到每一张牌的概率相同。
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
}
return c;
}
生成器版:不需要洗完所有的牌,取出十张就行
function * draw(cards){
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
yield c[i - 1];
}
}
const result = draw(cards);
🌰分红包
切西瓜法(均匀性大)
思路:第一次随机切成两部分,第二次从较大的那一块开始切。每次切都从最大的那部分开始分。
初版本:
function generate(amount, count){
let ret = [amount];
while(count > 1){
//挑选出最大一块进行切分
let cake = Math.max(...ret),
idx = ret.indexOf(cake),
part = 1 + Math.floor((cake / 2) * Math.random()),
rest = cake - part;
ret.splice(idx, 1, part, rest);
//切一次count减一
count--;
}
return ret;
}
抽牌法(空间复杂度高)
思路:所有的钱看作一个序列,通过随机插入10个值。(此处以10份举例) 每两个相邻值的差为分到的钱
function * draw(cards){
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
yield c[i - 1];
}
}
function generate(amount, count){
if(count <= 1) return [amount];
const cards = Array(amount - 1).fill(0).map((_, i) => i + 1);
const pick = draw(cards);
const result = [];
for(let i = 0; i < count - 1; i++) {
result.push(pick.next().value);
}
result.sort((a, b) => a - b);
result.push(amount);
for(let i = result.length - 1; i > 0; i--) {
result[i] = result[i] - result[i - 1];
}
return result;
}