跟着月影学JavaScript|青训营笔记

59 阅读4分钟

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

一、课堂重点内容

写好JS的原则

  • 各司其职:让HTML、CSS和JavaScript职能分离
  • 组件封装:好的UI组件具备正确性、拓展性、复用性
  • 过程抽象:应用函数式编程思想

算法

  • Leftpad事件
  • 洗牌算法
  • 分红包-切西瓜法
  • 分红包-抽牌法

二、详细知识点介绍

各司其职

  • HTML/CSS/JS 各司其责
  • 应当避免不必要的由 JS 直接操作样式
  • 可以用 class 来表示状态
  • 纯展示类交互寻求零 JS 方案

组件封装

  • 组件设计的原则:封装性、正确性、扩展性、复用性
  • 实现组件的步骤:结构设计、展现效果、行为设计
  • 三次重构
    1. 插件化
    2. 模板化
    3. 抽象化(组件框架)

过程抽象

image.png

洗牌算法

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

第一次随机切,然后每一次从剩余中的最大瓜切一刀。 相对均匀,不够刺激。

分红包-抽牌法: 从一个序列中随机插入几个断点将整体分成目标份数。 比如说有100元分给十个人,以0.01元为最小单位,100元包含10000份,然后用洗牌算法抽出十个值作为断点,然后排序,比如说第一个断点是49,第二个断点是160,那么第一份就是49*0.01=0.49元,第二份为(160-49)*0。01=1.11元。该方法充分体现了发红包的随机性,但缺点是空间复杂度较高。

三、实践联系例子

各司其职

例:写一段JS,控制一个网页,让它支持浅色和深色两种浏览模式。

版本一:通过JS控制该页面样式,属于反面教材,虽然达到了效果,但是通过js修改样式,后期需求变更会有麻烦

 const btn = document.getElementById('modeBtn');
  btn.addEventListener('click', (e) => {
    const body = document.body;
    if(e.target.innerHTML === '🌞') {
      body.style.backgroundColor = 'black';
      body.style.color = 'white';
      e.target.innerHTML = '🌜';
    } else {
      body.style.backgroundColor = 'white';
      body.style.color = 'black';
      e.target.innerHTML = '🌞';
    }
  });

版本二:通过JS修改body类名,在CSS中设置该类的样式

  const btn = document.getElementById('modeBtn');
  btn.addEventListener('click', (e) => {
    const body = document.body;
    if(body.className !== 'night') {
      body.className = 'night';
    } else {
      body.className = '';
    }
  });

版本三:通过修改checkbox的状态实现更新页面,但此处可以避免使用js

 <input id="modeCheckBox" type="checkbox">
  <div class="content">
    <header>
      <label id="modeBtn" for="modeCheckBox"></label>
      <h1>深夜食堂</h1>
    </header>
    <main>
      <div class="pic">
        <img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
      </div>
      <div class="description">
        <p>
            这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈
            眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返 [6]  。
        </p>
      </div>
    </main>
  </div>
  #modeCheckBox {
    display: none;
  }

  #modeCheckBox:checked + .content {
    background-color: black;
    color: white;
    transition: all 1s;
  }

四、课后个人总结

关于洗牌算法

网络上搜到的洗牌算法经常是以下思路

const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function shuffle(cards) {
  return [...cards].sort(() => Math.random() > 0.5 ? -1 : 1);
}

即采用sort方式,通过随机数与0,5比较决定是否交换位置,但是这样的方式会出现问题,因为每个数字出现在不同位次的几率不同

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

通过如上方式对1000000个数据进行了测试。 image.png

可以看到,index越小值越小。因为,拿0举例,它被交换到第9位的概率低于它保持原位的概率。根据正态分布也能得到这个结论。

关于各司其责

各司其责的原则不代表不同的代码必须写在不同的地方,而是不同的代码负责不同的工作。比如在html里写了一段js,该js只负责一个行为,那么也是复合各司其责的原则的

五、引用参考

正态分布_百度百科 (baidu.com)

洗牌算法 - 知乎 (zhihu.com)