如何写好JavaScript | 青训营笔记

92 阅读4分钟

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

笔记有摘自月影老师课堂PPT上的内容,并附带一些自己的思考。

outline

  • 职能分离
  • 组件封装
    • 插件化
    • 模板化
    • 抽象化
  • 过程抽象与高阶函数

职能分离

  • HTML、CSS、JS的职能分离——并非三者分开不同的文件,而是各自要做属于自己范畴内的事;
  • 如可以用JS操控class呈现不同的样式,避免直接使用JS修改CSS

组件封装

  • 好的组件———封装性、复用性、扩展性、正确性;
  • 根据场景,设计场景下需要的功能支持;
  • 实现步骤:
    • 设计总体的结构,根据所需功能设计对应API和控制流;
    • 进行三次重构:插件化、模板化、抽象化;
  • 如月影老师课堂上讲解的轮播图例子,构造函数中确定了操纵的轮播图对象,播放时间间隔,控制按钮定义等;并暴露控制API。
    class Slider{
      constructor(id, cycle = 3000){
        //获取要操纵的元素以及轮播间隔
        //为轮播图下标按钮添加事件:当鼠标移入/移出/轮播时
        //获取按钮并添加事件
      }
      getSelectedItem(){}   //当前选中页
      getSelectedItemIndex(){}   //当前选中页下标
      slideTo(idx){}   //去到固定页数
      slideNext(){}    //下一页
      slidePrevious(){}    //上一页
      start(){}   //开始轮播
      stop(){}    //暂停轮播
    }
    const slider = new Slider('my-slider');
    slider.start();
    

插件化

  • 将控制元素抽取成插件;
  • 插件与组件之间通过依赖注入方式建立联系;
  • 比如轮播图中,将可以跳转轮播图页面的下标圆点、上下页按钮作为控制元素抽取出来封装成插件;插件在组件中注册后的this指向轮播图类,即可以使用该类中的API,如下为例:
    class Slider(){
          constructor(id, cycle = 3000){...}
          registerPlugins(...plugins){
            plugins.forEach(plugin => plugin(this));  //注册插件,即可让插件调用类中API
          }
          ...
    }
    //上一页功能封装插件
    function pluginPrevious(slider){
      const previous = slider.container.querySelector('.slide-list__previous');
      if(previous){
        previous.addEventListener('click', evt => {
          slider.stop();  //直接使用slide的方法
          ...
        });
      }  
    }
    slider.registerPlugins(pluginController, pluginPrevious, pluginNext);  //注入
    

模板化

  • 把HTML部分抽离出来,即HTML模板化,下图摘自月影老师上课时所用图片。slide类中有专门的渲染函数,其把数据插入在含HTML文档的模板字面量中,最终返回包含元素标签的HTML文档。 t0196498fb325ccb123.png

  • 在轮播图的例子中,轮播的图片数据作为参数传入构造函数中,在Slide类对象中调用render函数返回HTML模板。

  • 包括插件中也配置了对应的render方法。

    class Slider(){
      constructor(id, opts = {images:[], cycle: 3000}){
        ...
        this.options = opts; 
        this.container.innerHTML = this.render();  //渲染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 => { ... plugin.action(this);});
      }
    }
    //在插件中也拥有自己的render方法
    const pluginPrevious = {
      render(){
        return `<a class="slide-list__previous"></a>`;
      },
      action(slider){ ... }
    };
    const slider = new Slider('my-slider', 
        {images: ["图片URL列表"], cycle:3000});  //作为输入数据
    slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
    

优点:

  • 可以防止数据写死;
  • 减少HTML代码量,模板化提高可复用率。

抽象化

  • 把组件通用的模型抽象出来,即把一些公有、常用的属性或方法提取成一个父类;子类继承后可以使用父类的属性和方法,并添加自己的独有属性和方法让自己更具象化;
  • 如下为轮播图例子中,Component中封装了常用的方法:
    class Component{  
      constructor(id, opts = {name, data:[]}){  ...  }
      registerPlugins(...plugins){  ... }
      render(data) {
        return ''
      }
    }
    
  • slide继承Component,注册插件时可以直接调用在Component上的方法:
    class Slider extends Component{
      constructor(id, opts = {name: 'slider-list', data:[], cycle: 3000}){
        super(id, opts);   //继承调用
        ...
      }
      render(data){
        ...
      }
      ....
    }
    

优点

  • 其他相似的类也可以继承父类属性和方法,可以提高组件可维护性、复用性和多样性,在大型的项目中可以减少冗余重复的代码量;
  • 其次在修改代码时可以做局部修改而不影响全局代码。

过程抽象与高阶函数

  • 在许多事件中,可能会用到相同的逻辑,从而把相同的逻辑给抽取出来形成一个函数,这样不同的事件都可以调用这个函数,故可以叫做过程抽象
  • 高阶函数参数是函数,返回值也为函数,可以想象函数中套了函数后形成闭包。
  • 比如高阶函数Once
  • 在第一次调用后,Once的参数fn在返回的函数中变成了null,已经形成闭包,故第二次调用时不会执行return语句。
    function once(fn) {
      return function(...args) {
        if(fn) {
          const ret = fn.apply(this, args);
          fn = null;
          return ret;
        }
      }
    }
    
  • Once表示只被请求一次,可以应用在异步请求的场景中,比如防止用户多次请求时阻塞或元素添加、移除时报错,可以看出许多业务都会用到只需要执行一次的逻辑,故把该逻辑抽象出来形成Once函数。
    button.addEventListener('click', once((evt) => {
        setTimeout(() => {}, 2000);
    }));
    

总结

课程受益匪浅,难度对自己来说稍微有点大,在反复浏览代码的过程中逐渐理解了模块、封装等思想,在未来做项目时可以尝试应用。

下篇笔记会深入写写对高阶函数的理解。