[字节青训营]前端方向Day8-前端入门 - 基础语言篇 - 如何写好JavaScript-代码质量优化之路 | 豆包MarsCode AI刷题

65 阅读4分钟

JS代码质量优化

  • 交通灯切换

    • 每个setTimeout都是异步的,一直嵌套

image.png

  • 将数据抽象出来

image.png

image.png

写的代码很多,但是易扩展,更具灵活性。

image.png

  • 判断一个整数是否是4的幂 源码:code.juejin.cn/pen/7108197…

    • 最简单的方法:(性能不好)

      function isPowerOfFour(num) {
        num = parseInt(num);
      ​
        while(num > 1) {
          if(num % 4) return false;
          num /= 4;
        }
        return num === 1;
      }
      
    • 按位与,看一下最后两位是否是0,如果不是0就说明不是4的幂,如果是0的话就右移。

      function isPowerOfFour(num) {
        num = parseInt(num);
      ​
        while(num > 1) {
          if(num & 0b11) return false;
          num >>>=2;
        }
        return num === 1;
      }
      
    • O(1)时间复杂度的一个算法

      两个相邻的数进行 按位与操作的时候,会只保留大的数中最后一个1前面的数

      111110000

      111101111

      上面两个数进行按位与操作,会只保留111100000

      如果进行与操作之后是0,说明肯定是2的倍数,意思就是所有的数都没留下,大的数中最后一个1前面没数

      100000

      011111

      进行按位与操作,结果是0,说明原数是只有1个1,其余都是0,现在只能保证都是2的倍数,下面来保证是4的倍数。

      如果1在偶数位上,那么就都是2的奇次幂。如果1在奇数位上,那么就都是4的偶次幂。

      1 0 0 0 0 2的4次幂 也就是4的2次幂

      所以我们需要找一个中间量,来判断1是否位于奇数位

      那么就只需要和偶数位全为1的数来进行按位与操作,如果结果是0说明1位于奇数位。

      所以找一个数1010101010101010......1010偶数位全是0

      那么找的这个数得是多少位呢。看一下JS有精度的整数是多少位,JS是64位的浮点数,有11位的小数位,所以精度的整数位为53位,需要13个A

      function isPowerOfFour(num){
        num = parseInt(num);
        
        return num > 0 &&
               (num & (num - 1)) === 0 &&
               (num & 0xAAAAAAAAAAAAA) === 0;
      }
      
    • 把num不当成数字,当成一个字符串,直接用正则表达式去匹配。

      ^$ 分别表示字符串的开头和结尾。

      1 表示以数字 1 开头。

      (?:00)* 表示后面跟随若干对的 0,即 00 可以重复 0 次或多次。

      (?: ... ) 是一个非捕获组,* 表示重复 0 次或多次。

      1 开头,后面跟随成对的 0,且整个字符串从头到尾都是符合条件的。

      function isPowerOfFour(num) {
        num = parseInt(num).toString(2);
        
        return /^1(?:00)*$/.test(num);
      }
      
  • 洗牌

    • 错误写法:

      const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
      ​
      function shuffle(cards) {
          // -1 表示交换位置 1 表示不交换位置
          // 这是一个分布不均匀的算法 
        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];   // 把每次的十个数字放在result对应的十行中,然后加起来
        }
      }
      ​
      console.table(result);
      

image.png

  • 正确写法:

    从十张牌里取一张牌,放到最后一张,再从剩下九张牌,放到倒数第二张....以此类推

    第一次概率是 1/10 , 第二次抽到牌的概率是1/9,最后一次是1/1,相当于每个排列出现的概率都是1/10!

    算法生成每一种可能的排列的概率都是 1/10!,从而保证了等概率洗牌。

    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;
    }
    ​
    console.log(shuffle(cards));
    
    const result = Array(10).fill(0);
    ​
    for(let i = 0; i < 10000; i++) {
      const c = shuffle(cards);
      for(let j = 0; j < 10; j++) {
        result[j] += c[j];
      }
    }
    ​
    console.table(result);
    

image.png

  • 抽奖

    • 生成器版本(在洗牌的基础上,只取出一次的结果,然后抽出几个数)

      const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
      ​
      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);
      // console.log([...result]);
      console.log(result.next().value,
            result.next().value,
            result.next().value
      )
      
  • 红包生成器

    不能完全随机,因为还需要考虑,把钱分完的话,后面的红包还能不能继续分。发100元10个包,肯定不能第一个包就100元。

    • 采用切西瓜法O(m*n)。code.juejin.cn/pen/7108203…

      先把西瓜切成两半,然后再切大的,分成两半,这样就不会有不够分的情况了。

      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--;
        }
        return ret;
      }
      
    • 抽牌法O(n):code.juejin.cn/pen/7108204…

      想刺激一点,让大的足够大,小的足够小。

      把10000份,看成是0-9999的数列,然后在数列里插入九个分隔符。

      如果第一个分隔符在 50,把5块分给第一个人,如果在 200,把200-5 分给第二个人,以此类推。

      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];
        }
      }
      ​
      // 0, 1, 2....9999
      // 49 199
      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 = [];
          // 只能抽count - 1 次
        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;
      }