跟月影学Javascript|青训营笔记

57 阅读5分钟

这是我参与第五届青训营伴学笔记创作活动的第1天。

如何写好JS

本课重点内容

  • 写好JS的一些原则:各司其职、组件封装、过程抽象。
  • 代码质量优化:可读性与性能提升。

详细知识点介绍

JS编程原则之各司其职

在网页的渲染和交互逻辑中,让html、css、js各司其职:html只负责页面结构,css只负责页面样式,js只负责页面交互逻辑。

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

未遵循各司其职原则:通过dom事件触发,在js代码块中直接修改html元素的样式,代码冗杂度高。 优化1:通过dom事件触发,但不直接修改元素样式,而是通过更换元素类名的方式,将样式都放入css中,降低了js,html与css的耦合度。 优化2(最终版):用隐藏的checkbox作为开关,通过css的伪类实现元素状态改变时页面样式的变更,实现了零js的纯展示类交互。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>深夜食堂</title>
  <style>
    body, html {
      width: 100%;
      margin: 0;
      padding: 0;
      font-weight: 700;
    }
    body {
      box-sizing: border-box;
    }
    .content {
      height: 100%;
      padding: 10px;
    }
    div.pic img {
      width: 100%;
    }
    .btn {
      float: right;
      font-size: 2rem;
      border: none;
      background: transparent;
    }
    #modeCheckBox {
      display: none;
    }
    #modeCheckBox:checked + .content {
      background-color: black;
      color: white;
      transition: all 1s;
    }
    .btn {
      font-size: 2rem;
      float: right;
    }
    .btn::after {
      content: '🌞';
    }
    #modeCheckBox:checked + .content .btn::after {
      content: '🌜';
    }
  </style>
</head>
<body>
  <input type="checkbox" id="modeCheckBox">
  <div class="content">
    <header>
      <label for="modeCheckBox" class="btn"></label>
      <h1 class="title">深夜食堂</h1>
    </header>
    <main>
      <div class="pic">
        <img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
      </div>
      <div class="description">
        <p>
          这是一间营业时间从午夜十二点到早上七点的特殊食堂。
          这里的老板,不太爱说话,却总叫人吃得热泪盈眶。
          在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;
          轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;
          乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;
          一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。
          食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。
          每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返。
        </p>
      </div>
    </main>
  </div>
</body>
</html>

小结

  1. html/css/js各司其职
  2. 应当避免不必要的由JS直接操作样式
  3. 可以用class来表示状态
  4. 纯展示类交互寻求零JS方案

JS编程原则之组件封装

组件是指Web页面上抽出来的一个个包含模板(html)、功能(js)、样式(css)的单元。好的组件具备封装性、正确性、扩展性、复用性。

案例 用原生JS写一个电商网站的轮播图 image.png

结构:

     <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>
        <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>
      </div>

样式:

 #my-slider{
  position: relative;
  display: inline-block;
  width: 790px;
  height: 340px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  width: 100%;
  height: 100%;
  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;
}

.slide-list__control{
  position: relative;
  display: table;
  background-color: rgba(255, 255, 255, 0.5);
  padding: 5px;
  border-radius: 12px;
  bottom: 30px;
  margin: auto;
}

.slide-list__next,
.slide-list__previous{
  display: inline-block;
  position: absolute;
  top: 50%;
  margin-top: -25px;
  width: 30px;
  height:50px;
  text-align: center;
  font-size: 24px;
  line-height: 50px;
  overflow: hidden;
  border: none;
  background: transparent;
  color: white;
  background: rgba(0,0,0,0.2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .5s;
}

.slide-list__previous {
  left: 0;
}

.slide-list__next {
  right: 0;
}

#my-slider:hover .slide-list__previous {
  opacity: 1;
}


#my-slider:hover .slide-list__next {
  opacity: 1;
}

.slide-list__previous:after {
  content: '<';
}

.slide-list__next:after {
  content: '>';
}

.slide-list__control-buttons,
.slide-list__control-buttons--selected{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  margin: 0 5px;
  background-color: white;
  cursor: pointer;
}

.slide-list__control-buttons--selected {
  background-color: red;
}

行为:API设计应保证原子操作,职责单一,满足灵活性

image.png

 class Slider{
      constructor(id, cycle = 3000){
        this.container = document.getElementById(id);
        this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
        this.cycle = cycle;

    const controller = this.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){
          this.slideTo(idx);
          this.stop();
        }
      });
      
      controller.addEventListener('mouseout', evt=>{
        this.start();
      });
      
      this.container.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';
      })
    }
    
    const previous = this.container.querySelector('.slide-list__previous');
    if(previous){
      previous.addEventListener('click', evt => {
        this.stop();
        this.slidePrevious();
        this.start();
        evt.preventDefault();
      });
    }
    
    const next = this.container.querySelector('.slide-list__next');
    if(next){
      next.addEventListener('click', evt => {
        this.stop();
        this.slideNext();
        this.start();
        evt.preventDefault();
      });
    }
  }
  getSelectedItem(){
    let selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    let selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    let item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }
  
    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  slideNext(){
    let currentIdx = this.getSelectedItemIndex();
    let nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    let currentIdx = this.getSelectedItemIndex();
    let previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  start(){
    this.stop();
    this._timer = setInterval(()=>this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer);
  }
}

组件封装基本方法结构设计、展示效果、行为设计。

重构:

1.插件化:解耦,将控制元素抽取成插件,插件与组件之间通过依赖注入方式建立联系。 image.png 2.模板化:将html模板化,更易于扩展。 image.png 3.组件框架:抽象,将通用的组件模型抽取出来。 image.png 小结

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

JS编程原则之过程抽象

过程抽象即用来处理局部细节的一些方法,是函数式编程的基础应用。

高阶函数即是用过程作为参数,实现过程抽象。定义:以函数作为参数,以函数作为返回值,常用于作为函数装饰器。

常用高阶函数

Once保证传入的函数只执行一次。

 function once(fn) {
   return function(...args) {
    if(fn) {
      const ret = fn.apply(this, args);
      fn = null;
      return ret;
    }
  }
}

Throttle节流(节省交互沟通,需要在过程中进行触发回调,但是又不需要那么的频繁)

function throttle(fn, time = 500){
        let timer;
        return function(...args){
            if(timer == null){
                fn.apply(this,  args);
                timer = setTimeout(() => {
                    timer = null;
                }, time)
            }
        }
}

Debounce防抖,即当持续触发事件时,在一定时间内如果没有触发该事件,事件处理函数会执行一次,当在一定时间内触发该事件时,会清除定时器,只在最后执行一次。

function debounce(fn, dur){
        dur = dur || 100;
        var timer;
        return function(){
            clearTimeout(timer);
            timer = setTimeout(() => {
            fn.apply(this, arguments);
            }, dur);
        }
    }

编程范式

命令式告诉计算机如何做

switcher.onclick = function(evt){
            if(evt.target.className === 'on'){
                evt.target.className = 'off';
            }else{
                evt.target.className = 'on';
            }
        }

声明式告诉计算机你想要什么

 function toggle(...actions){
            return function(...args){
                let action = actions.shift();
                actions.push(action);
                return action.apply(this, args);
            }
        }

        switcher.onclick = toggle(
        evt => evt.target.className = 'off',
        evt => evt.target.className = 'on'
        );

代码质量优化

写代码应该关注:风格,效率,约定,设计,使用场景。

当年Left-pad事件:代码效率低,复杂

image.png 优化:代码更简洁,效率提升

image.png

总结

本次课程主要从代码角度对JS的编程原则和编程规范作了举例和讲解,同时涉及到一些重要的编程思想。初次创作,文章若有错误和不足,欢迎大家批评指正哈!