JavaScript代码进阶(二)| 青训营笔记

83 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第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),但是空间复杂度较高,因为需要生成一个大的数列

总结:好的代码应该考虑什么?

  1. 使用场景:
    • 不同场景,需求不同。以满足当前需求为导向,比如:性能、更灵活等,编写代码,是写好代码的核心。
  2. 算法的重要性
    • 好的算法思维能够帮助我们更好地理解解决问题的逻辑,从而写出更优雅的代码,因此很重要!