如何写好JavaScript(2)|青训营笔记

135 阅读3分钟

这是我参与「第四届青训营」笔记创作活动的第五天

二、如何写好JavaScript(2)

当年的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 模块粒度
  • 代码风格
  • 代码质量/效率

新版本

            function leftpad(str, len, ch) {
                str = "" + str;
                const padLen = len - str.length;
                if(padLen <= 0) {
                  return str;
                }
                return (""+ch).repeat(padLen)+str;
            } 
  • 代码更简洁
  • 效率提升
            /*! https://mths.be/repeat v1.0.0 by @mathias */

            'use strict';

            var RequireObjectCoercible = require('es-abstract/2019/RequireObjectCoercible');
            var ToString = require('es-abstract/2019/ToString');
            var ToInteger = require('es-abstract/2019/ToInteger');

            module.exports = function repeat(count) {
              var O = RequireObjectCoercible(this);
              var string = ToString(O);
              var n = ToInteger(count);
              // Account for out-of-bounds indices
              if (n < 0 || n == Infinity) {
                throw RangeError('String.prototype.repeat argument must be greater than or equal to 0 and not be Infinity');
              }

              var result = '';
              while (n) {
                if (n % 2 == 1) {
                  result += string;
                }
                if (n > 1) {
                  string += string;
                }
                n >>= 1;
              }
              return result;
            };
  • 性能更好
            /**
             * String.prototype.repeat() polyfill
             https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat#Polyfill
            */
            if (!String.prototype.repeat) {
              String.prototype.repeat = function(count) {
                'use strict';
                if (this == null)
                  throw new TypeError('can\'t convert ' + this + ' to object');

                var str = '' + this;
                // To convert string to integer.
                count = +count;
                // Check NaN
                if (count != count)
                  count = 0;

                if (count < 0)
                  throw new RangeError('repeat count must be non-negative');

                if (count == Infinity)
                  throw new RangeError('repeat count must be less than infinity');

                count = Math.floor(count);
                if (str.length == 0 || count == 0)
                  return '';

                // Ensuring count is a 31-bit integer allows us to heavily optimize the
                // main part. But anyway, most current (August 2014) browsers can't handle
                // strings 1 << 28 chars or longer, so:
                if (str.length * count >= 1 << 28)
                  throw new RangeError('repeat count must not overflow maximum string size');

                var maxCount = str.length * count;
                count = Math.floor(Math.log(count) / Math.log(2));
                while (count) {
                  str += str;
                  count--;
                }
                str += str.substring(0, maxCount - str.length);
                return str;
              }
            }
  • 性能更好

交通灯状态切换

实现一个切换多个交通灯状态切换的功能

交通灯:版本一

html:
<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

css:
#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li{
  list-style: none;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.s1 li:nth-child(1) {
  background-color: #a00;
}

#traffic.s2 li:nth-child(2) {
  background-color: #aa0;
}

#traffic.s3 li:nth-child(3) {
  background-color: #0a0;
}

#traffic.s4 li:nth-child(4) {
  background-color: #a0a;
}

#traffic.s5 li:nth-child(5) {
  background-color: #0aa;
}

js:
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);
})();

交通灯:版本二(数据抽象)

html:
<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
</ul>

css:
#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li {
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.stop li:nth-child(1) {
  background-color: #a00;
}

#traffic.wait li:nth-child(2) {
  background-color: #aa0;
}

#traffic.pass li:nth-child(3) {
  background-color: #0a0;
}

js:
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);

交通灯:版本三(过程抽象)

html:
<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
</ul>

css:
#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li{
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.stop li:nth-child(1) {
  background-color: #a00;
}

#traffic.wait li:nth-child(2) {
  background-color: #aa0;
}

#traffic.pass li:nth-child(3) {
  background-color: #0a0;
}

js:
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();
  }
}());

交通灯:版本四(异步+函数式)

html:
<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
</ul>

css:
#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li{
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.stop li:nth-child(1) {
  background-color: #a00;
}

#traffic.wait li:nth-child(2) {
  background-color: #aa0;
}

#traffic.pass li:nth-child(3) {
  background-color: #0a0;
}

js:
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();

判断是否是4的幂

html:
<input id="num" value="65536"></input>
<button id="checkBtn">判断</check>

css:
#num {
  color: black;
}

#num.yes {
  color: green;
}

#num.no {
  color: red;
}

js:
// 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);
// }

function isPowerOfFour(num){
  num = parseInt(num);
  
  return num > 0 &&
         (num & (num - 1)) === 0 &&
         (num & 0xAAAAAAAAAAAAA) === 0;
}

num.addEventListener('input', function(){
  num.className = '';
});

checkBtn.addEventListener('click', function(){
  let value = num.value;
  num.className = isPowerOfFour(value) ? 'yes' : 'no';
});

洗牌

洗牌-错误写法

html:
<div id="app">洗牌-错误写法</div>
<hr/>
<div id="log"></div>
<script>
  window.console = JCode.logger(log);
</script>

js:
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);

洗牌-正确写法

html:
<div id="app">洗牌-正确写法</div>
<hr/>
<div id="log"></div>
<script>
  window.console = JCode.logger(log);
</script>

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);

洗牌-使用生成器

html:
<div id="app">洗牌-生成器</div>
<hr/>
<div id="log"></div>
<script>
  window.console = JCode.logger(log);
</script>

js:
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]);

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

『温故知新,与君共勉』