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

55 阅读3分钟

这是我参加「第五届青训营 」伴学笔记创作活动的第 3 天。
今日学习课程:JavaScript

一、JavaScript编码原则:各司其职

演示demo: 主题切换,需求为白昼模式与夜间模式的切换 由于只涉及到样式切换,按照「各司其职」的原则,建议只修改CSS代码,不涉及JavaScript

<input type="checkbox" id="mode">
<div class="content">
    <label for="mode">
</div>
#mode {
    display: none;
}
#mode:checked + .content {
    background-color: black;
    color: white;
    transition: all 1s;
}

利用单选框的伪类选择器兄弟节点选择器,实现背景与文字颜色的切换效果。由此,我们可知:

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

二、JavaScript编码原则:组件封装

什么是组件?

组件是指Web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元 好的组件具有:

  • 封装性
  • 正确性
  • 扩展性
  • 复用性

demo: 原生JS实现轮播图

行为: API

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

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

行为: 控制流
自定义事件,使组件状态与图片状态解耦

由此,可知封装组件的基本方法:

  1. 结构设计
  2. 展示效果
  3. 行为设计
    • API(功能)
    • Event(控制流) 但这样的组件不够灵活。有什么方法优化?

重构:插件化

解耦:将控制元素抽取成插件,插件与组件之间通过依赖注入方式建立连接

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;
  }
  registerPlugins(...plugins){
    plugins.forEach(plugin => plugin(this));
  }
  slideTo(idx){
    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  addEventListener(type, handler){
    this.container.addEventListener(type, handler)
  }
  start(){
    this.stop();
    this._timer = setInterval(()=>this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer);
  }
}

function pluginController(slider){}

function pluginPrevious(slider){}

function pluginNext(slider){}

const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();

其作用在于将HTML模板化,更易于扩展

image.png

三次重构:

  1. 插件化
  2. 模板化
  3. 抽象化(组件框架)

三、JavaScript编码原则:过程抽象

用于处理局部细节控制的一些方法,是函数式编程思想的基础应用

image.png

demo:once高阶函数

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

为了能够让只执行一次的需求覆盖不同的事件处理,我们可以将这个需求剥离出来,这个过程我们称之为过程抽象

高阶函数(HOF)

  • 以函数为参数
  • 以函数为返回值
  • 常用于作为函数装饰器

image.png

过程抽象优势:将非纯函数转换为纯函数,方便测试与维护

四、编程范式

image.png

命令式

switch.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)
    }
}
switch.onclick = toggle(
    evt => evt.target.className = 'off',
    evt => evt.target.className = 'on'
)

声明式优点:可以方便的添加其他的状态,比命令式添加if-else分支快捷

五、代码优化之路

交通灯状态切换

版本1 setTimeout嵌套

回调地狱,不希望看见这样的代码

版本2 状态抽象

const stateList = [
    { state: 'wait', time: 1000 },
    { state: 'stop', time: 3000 },
    { state: 'pass', time: 3000 }
]

版本3 过程抽象

image.png

缺点:过于复杂

版本4 异步+函数式

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

六、总结

这节课月影老师带领我们比较深入的领略了JavaScript编码的特点,包括三个主要特征和代码优化,让人受益匪浅。