写好JS的几大建议|青训营笔记

102 阅读4分钟

写好JS的几大建议|青训营笔记

mobilebanner.png

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

前言

javascript是一门具有函数优先的轻量级,解释型或即时编译型的编程语言,发展生态迅速,需要不断去学习其新特性与编程思想。要是想成为一位合格的前端工程师, 需要结合开发需求,全力以赴的把自己的代码变的优雅(性能好,可读性高等)。本文将探讨与总结青训营学习到的几点写好JS的关键点。

写好JS的三大原则

各司其职(让HTML、CSS、JavaScript职能分离)

这里说的各司其职并不是简单的把HTML、CSS、JavaScript分开三个文件来写,这里的意思是HTML只与开发项目的骨架有关,CSS只与开发项目的样式与动画有关、JS只负责项目里面的一些特殊行为效果。
下面将用三种方法完成页面换肤效果(案例取自青训营课堂) 第一种方案,比较好想到,用了JS操作DOM改变了CSS样式,没有做到让CSS与JS实现真正意义上的各司其职,如果做优化可以往这一方面发展去改进代码。


第二种方案,做到了CSS与JS的职能分离,用JS操作类名的改变从而改变CSS,这样的代码更加优雅了,可读性更高,做到了各司其职的要求。


第三种方案,一种业务需求只依靠HTML与CSS就可以完成,是一种极其巧妙地方法。利用input标签的checked属性与label标签的for属性完成了点击状态的关联,再利用到display:none隐藏起来checkedbox, 最后用到相邻选择器与直接组合选择器、过渡等CSS特性最终完成了业务需求。

深夜食堂案例总结

t0121cc1e818e11d365.png

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

组件封装(好的UI组件具备正确性、扩展性、复用性)

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

下面将利用轮播图案例讲述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

    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;
  }
  • 使用CSS绝对定位将图片重叠在一个位置
  • 轮播图切换的状态使用修饰符(modifier)
  • 轮播图的切换动画使用CSS transition

行为:JS

行为:API(功能)
 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);
  • Slider
    • +getSelectedItem()
    • +getSelectedItemIndex()
    • +slideTO()
    • +slideNext()
    • +slidePrevious()
行为:控制流(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)

重构:插件化

解耦:

  • 将控制元素抽取成插件
  • 插件与组件之间通过依赖注入方式建立联系
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模板化,更易于扩展

t0196498fb325ccb123.png

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

重构:抽象化

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

t0152a5c963a9854531.png

class Component{
    constructor(id, opts = {name, data:[]}){
        this.container = document.getElementById(id);
        this.options = opts;
        this.container.innerHTML = this.render(opts.data);
    }
    registerPlugins(...plugins){
        plugins.forEach(plugin => {
        const pluginContainer = document.createElement('div');
        pluginContainer.className = `.${name}__plugin`;
        pluginContainer.innerHTML = plugin.render(this.options.data);
        this.container.appendChild(pluginContainer);
        
        plugin.action(this);
        });
    }
    render(data) {
      /* abstract */
            return ''
        }
    }

过程抽象

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

t013bcb517ea0c6099a.png

高阶函数H(High)O(Order)F(Fuction)

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

t01bfb54063461f49db.png

function HOF0(fn) {
    return function(...args) {
        return fn.apply(this, args);
        }
    }

常用高阶函数(HOF)

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);
  }
}
Consumer(可用于延时调用场景)
function consumer(fn, time){
  let tasks = [],
      timer;
  
  return function(...args){
    tasks.push(fn.bind(this, ...args));
    if(timer == null){
      timer = setInterval(() => {
        tasks.shift().call(this)
        if(tasks.length <= 0){
          clearInterval(timer);
          timer = null;
        }
      }, time)
    }
  }
}
Iterative(重复/批量操作场景)
  return function(subject, ...rest) {
    if(isIterable(subject)) {
      const ret = [];
      for(let obj of subject) {
        ret.push(fn.apply(this, [obj, ...rest]));
      }
      return ret;
    }
    return fn.apply(this, [subject, ...rest]);
  }
}

为什么要用高阶函数呢?

高阶函数是一个纯函数(推荐使用),即当你输入参数a得到的结果一定是b,这样的结果就是可控的,可预期的。

t0191115630682cd36f.png

编程范式

  • 命令式(面向对象/面向过程)主要是强调怎么做
          let list = [1, 2, 3, 4];
          let mapl = [];
          for(let i = 0; i < list.length; i++) {
                mapl.push(list[i] * 2);
          }
  • 声明式(逻辑式/函数式)主要是强调做什么推荐使用
          let list = [1, 2, 3, 4];
          const double = x => x * 2;
          list.map(double);