JavaScript 编码原则(一)| 青训营笔记

87 阅读4分钟
这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天✌

课堂笔记


本堂课重点内容:

  1. JavaScript 好代码的标准
  2. HTML/CSS/JS 各司其责

详细知识点介绍

1.写好js代码的一些原则

  • 各司其职:让HTML、CSS、JavaScript代码分离
  • 组件封装:好的UI组件具备正确性、扩展性、复用性。
    • 组件的定义解析及特征
    • 组件封装基本方法
    • 利用原生 JS 实现电商网站轮播图
  • 过程抽象:应用函数式编程实现
    • 过程抽象概念
    • 高阶函数使用模式
    • JavaScript 编程范式

2.各司其职

解耦HTML、CSS和JavaScript,HTML是数据,CSS是表现,JavaScript是行为,要做到做到数据层、表现层和行为层各司其职

具体举例子,实现白天☀/夜间🌙模可切换的阅读模式

方法一(基础版):

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的style(CSS),不符合各司其职的原则
  • 代码臃肿且难以复用
  • 不利于封装
方法二(进阶版):

JS部分代码:

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

改进之处:

仅仅修改了className,避免了使用JS直接操作CSS,符合代码松散解耦的特点。

方法三(推荐版):

推荐之处:

实现了零JS表现方案,符合编码各司其职的原则。

总结:

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

2. 组件封装

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

好的组件具备封装性正确性扩展性复用性

实现步骤:

①结构设计: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/t01abe3351db853eb3.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/t01331ac159b58f5478.jpg" />
        </li>
        <li class="https://slider-list__item">
            <img src="htttps://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(功能):

1)规则:
  • 保证原子操作
  • 职责要单一
  • 满足灵活性
2)定义一个Slider类,实现以下API:
  • +getSelectedItem( ) 当前选中被展现出的图

  • +getSelectedItemIndex( ) 当前选中被展现出的图的下表

  • +slideTo( ) 跳转并展示某个图

  • +slideNext( ) 切换下一张图

  • +slidePrevious( ) 切换上一张图

实现代码:

class Slider {
    constructor(id) {
        this.container = document.getElementById(id);
        this.items = this.container
        .querySelectorAll('.slider-list__item, .slider-list__item--selected');
    }
    getSlectedItem() {
        const selected = this.container
        .querySelector('.slider-list__item--selected');
        return selected;
    }
    getSlectedItemIndex() {
        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.getSlectedItemIndex();
        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);
    }
}

Event(控制流):

  • 使用自定义事件来解耦
 <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(控制流)

优化

①重构:插件化

解耦
  • 将控制元素抽取为插件
  • 插件与组件之间通过注入依赖方式建立联系
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();

②重构:模板化

解耦
  • 将HTML模板化,更易于扩展

例如:

如果想去掉轮播图中四个小圆点,利用模板化对数据内容实现抽离可以更加轻易实现:

render():渲染

action():初始化

只需注释掉(pluginController)即可实现

image.png

③组件框架

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

关系图: image.png

代码模板:

image.png

总结

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

课后个人总结

前端不仅仅是技术代码上的实现,也应该遵守标准的编码原则和思维上的标准规范,才能降低差异性,更利于团队协作。

引用参考

blog.csdn.net/guorui_java…

blog.csdn.net/weixin_4480…blogsobaiduweb~default-3-120303835.article_score_rank_blog&spm=1018.2226.3001.4450