如何写好JavaScript|青训营笔记

55 阅读3分钟

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

一、如何写好JavaScript

写好JS的原则

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

1.各司其职

写一段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 = '🌞';
            }
          });

版本二:

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

版本三:

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

结论:

05032784ab87387ad4dab1d1bcc66cc.jpg

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

2.组件封装

用原生JS写一个电商网站的轮播图

结构:HTML

轮播图是一个典型的列表结构,我们可以使用无序列表ul元素来实现

            <div id="my-slider" class="slider-list">
              <ul>
                <li class="slider-list__item--selected">
                  <img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
                </li>
                <li class="slider-list__item">
                  <img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
                </li>
                <li class="slider-list__item">
                  <img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
                </li>
                <li class="slider-list__item">
                  <img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
                </li>
              </ul>
            </div>

表现:CSS

  • 使用CSS 绝对定位将图片重叠在同一个位置
  • 轮播图切换的状态使用修饰符(modifier)
  • 轮播图的切换动画使用CSS transition
          #my-slider{
            position: relative;
            width: 790px;
          }

          .slider-list ul{
            list-style-type:none;
            position: relative;
            padding: 0;
            margin: 0;
          }

          .slider-list__item,
          .slider-list__item--selected{
            position: absolute;
            transition: opacity 1s;
            opacity: 0;
            text-align: center;
          }

          .slider-list__item--selected{
            transition: opacity 1s;
            opacity: 1;
          }

行为:JS

行为:API

  • Slider
    • +getSelectedltem()
    • +getSelectedltemlndex()
    • +slideTo()
    • +slideNext()
    • +slidePrevious()
          class Slider{
            constructor(id){
              this.container = document.getElementById(id);
              this.items = this.container
              .querySelectorAll('.slider-list__item, .slider-list__item--selected');
            }
            getSelectedItem(){
              const selected = this.container
                .querySelector('.slider-list__item--selected');
              return selected
            }
            getSelectedItemIndex(){
              return Array.from(this.items).indexOf(this.getSelectedItem());
            }
            slideTo(idx){
              const selected = this.getSelectedItem();
              if(selected){ 
                selected.className = 'slider-list__item';
              }
              const item = this.items[idx];
              if(item){
                item.className = 'slider-list__item--selected';
              }
            }
            slideNext(){
              const currentIdx = this.getSelectedItemIndex();
              const nextIdx = (currentIdx + 1) % this.items.length;
              this.slideTo(nextIdx);
            }
            slidePrevious(){
              const currentIdx = this.getSelectedItemIndex();
              const previousIdx = (this.items.length + currentIdx - 1)
                % this.items.length;
              this.slideTo(previousIdx);  
            }
          }

          const slider = new Slider('my-slider');
          slider.slideTo(3);

行为:控制流

  • 使用自定义事件来解耦
          <a class="slide-list__next"></a>
          <a class="slide-list__previous"></a>
          <div class="slide-list__control">
            <span class="slide-list__control-buttons--selected"></span>
            <span class="slide-list__control-buttons"></span>
            <span class="slide-list__control-buttons"></span>
            <span class="slide-list__control-buttons"></span>
          </div>
          const detail = {index: idx}
          const event = new CustomEvent('slide', {bubbles:true, detail})
          this.container.dispatchEvent(event)

总结:基本方法

  • 结构设计
  • 展现效果
  • 行为设计
    • API(功能)
    • Event(控制流)

重构:插件化

解耦

  • 将控制元素抽取成插件
  • 插件与组件之间通过依赖注入方式建立联系
          function pluginController(slider){
            const controller = slider.container.querySelector('.slide-list__control');
            if(controller){
              const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
              controller.addEventListener('mouseover', evt=>{
                const idx = Array.from(buttons).indexOf(evt.target);
                if(idx >= 0){
                  slider.slideTo(idx);
                  slider.stop();
                }
              });

              controller.addEventListener('mouseout', evt=>{
                slider.start();
              });

              slider.addEventListener('slide', evt => {
                const idx = evt.detail.index
                const selected = controller.querySelector('.slide-list__control-buttons--selected');
                if(selected) selected.className = 'slide-list__control-buttons';
                buttons[idx].className = 'slide-list__control-buttons--selected';
              });
            }  
          }

          function pluginPrevious(slider){
            const previous = slider.container.querySelector('.slide-list__previous');
            if(previous){
              previous.addEventListener('click', evt => {
                slider.stop();
                slider.slidePrevious();
                slider.start();
                evt.preventDefault();
              });
            }  
          }

          function pluginNext(slider){
            const next = slider.container.querySelector('.slide-list__next');
            if(next){
              next.addEventListener('click', evt => {
                slider.stop();
                slider.slideNext();
                slider.start();
                evt.preventDefault();
              });
            }  
          }

重构:模板化 解耦——将HTML模板化,更易于扩展

1b0cf31e46e45d1cc9388aeac1b5072.jpg
          class Slider{
            constructor(id, opts = {images:[], cycle: 3000}){
              this.container = document.getElementById(id);
              this.options = opts;
              this.container.innerHTML = this.render();
              this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
              this.cycle = opts.cycle || 3000;
              this.slideTo(0);
            }
            render(){
              const images = this.options.images;
              const content = images.map(image => `
                <li class="slider-list__item">
                  <img src="${image}"/>
                </li>    
              `.trim());
              
              return `<ul>${content.join('')}</ul>`;
            }
            ...
          }

组件框架
抽象——将组件通用模型抽象出来

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

『温故知新,与君共勉』