Javascript编码原则 | 青训营笔记

44 阅读4分钟

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

第三课 Javascript编码原则

一、Javascript编码原之各司其职

  • 让HTML、CSS和Javascript职能分离
// 深色模式切换版本一
	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控制CSS,这个对于样式的改变更加复杂,对于以后的更改更加困难

	// 神色模式切换版本二
	const btn2 = document.getElementById('modeBtn');
	btn2.addEventListener('click' ,(e)=>{
		const body = document.body;
		if(body.className !== 'night'){
			body.className = 'night';
		}else{
			body.className = '';
		}
	})
	// 更加简洁,更加各司其职

	// 深色模式切换第三版
	// 使用CSS的伪类checked状态和checkedbox
  • 应当避免不必要的由JS直接操作样式
  • 可以用class来表示状态
  • 纯展示类交互寻求零JS方案

二、Javascript编码原则之组件封装

  • 组件是指Web界面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元。
  • 好的组件具备封装性、正确性、扩展性、复用性

轮播图案例

1、结构:HTML

  • 使用无序列表ul、li元素来实现

2、表现:CSS

  • 使用CSS绝对定位将图片重叠在同一个位置
  • 轮播图切换的状态使用修饰符(modifier)
  • 轮播图的切换动画使用CSS transition

3、行为:JS API

  • Slider这个对象包含
    • getSelectedItem()
    • getSelectedItemIdex()
    • slideTo()
    • slideNext()
    • sledePrevious()
// 我们使用 class 关键字来创建一个类,类体在一对大括号 {} 中,我们可以在大括号 {} 中定义类成员的位置,如方法或构造函数。
//每个类中包含了一个特殊的方法 constructor(),它是类的构造函数,这种方法用于创建和初始化一个由 class 创建的对象。
//创建一个类的语法格式如下:定义好类后,我们就可以使用 new 关键字来创建对象:
class Runoob {
  constructor(name, url) {
    this.name = name;
    this.url = url;
  }
}
 
let site = new Runoob("菜鸟教程",  "https://www.runoob.com");
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 => {
          // 监听slide事件,图片的位置对应的小圆点要变红
            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();

4、行为:JS 行为:控制流

  • 使用自定义事件来解耦
<a class="slide-list__next"></a>
<a class="slide-list__previous"></a>
<div class="slide-list__control">
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons--selected"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
</div>

5、重构:插件化

  • 这个组件并不灵活,比如左右箭头和组件是绑定在一起的,要想办法解耦
  • 解耦
    • 将控制元素抽取成插件
    • 插件与组件之间通过依赖注入方式建立联系
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));
  }
    
  // 注册插件的接口
    
  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';
    }

    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  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);  
  }
  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){
  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();
    });
  }  
}


const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
// 这样可以去删除,添加插件,再写一些其他的插件注册进去
slider.start();

6、重构:模块化

  • 再进一步优化:

  • 解耦

    • 将HTML模板化、更易于扩展

    • 基于以上的插件化,再抽象出一个render方法去根据传入的几张图片去具体生成HTML语言,再插入到HTML中

    • 把插件注册进来后,去调用插件的render方法,也可以把图片传进去,插件也会生成对应的HTML模板,放到插件的container中,再把这个container放到页面的HTML中

      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>`;
      }
      
      registerPlugins(...plugins){
          plugins.forEach(plugin => {
            const pluginContainer = document.createElement('div');
            pluginContainer.className = '.slider-list__plugin';
            pluginContainer.innerHTML = plugin.render(this.options.images);
            this.container.appendChild(pluginContainer);
            
            plugin.action(this);
            // 插件初始化
          });
        }
      
      action(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';
            });
          }    
        }
      
    • 这样的好处就是直接把插件注释掉,不用删HTML就可以实现添加删除

6、抽象

  • 将组件通用模型抽象出来

把slider抽象成通用的组件,slider继承Component

f729a6ac-a07d-4c18-913e-0c26b13b7619.jpg

组件封装总结

  • 实现组件的步骤:结构设计、展现效果、行为设计
  • 三次重构
    • 插件化
    • 模板化
    • 抽象化(组件框架)