JavaScript 编码原则组件封装|青训营

111 阅读13分钟

JavaScript 编码原则组件封装

组件:组件是 Web 页面上所抽取的模版、功能与样式的单元。
自从 React,Vue 等前端框架在市面上大量使用之后,组件化开发逐渐成为了前端主流开发方式。

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

例子:用原生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

  • 使用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;
          }

1.#my-slider:轮播器的主容器,设置了宽度、高度和定位等样式。

2..slider-list ul:轮播器图片列表的样式,设置了宽度、高度、内外边距以及列表样式。

3..slider-list__item.slider-list__item--selected:轮播器中每个图片项的样式,使用绝对定位使其重叠,并设置了透明度过渡效果。

4..slide-list__control:控制按钮的样式,设置了背景颜色、圆角、居中和透明度等样式。

5..slide-list__next.slide-list__previous:下一张和上一张按钮的样式,设置了大小、居中、背景颜色、字体样式、透明度等。

6.#my-slider:hover .slide-list__previous#my-slider:hover .slide-list__next:在鼠标悬停在轮播器上时,按钮的透明度设置为1,显示按钮。

7..slide-list__previous:after.slide-list__next:after:为上一张和下一张按钮添加箭头。

8..slide-list__control-buttons.slide-list__control-buttons--selected:控制按钮的样式,设置了大小、圆角、背景颜色和选中样式。

行为: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);

image.png

JavaScript 类,它表示滑块组件。以下是每种方法的细分:

  • constructor(id):构造函数方法初始化对象。它采用一个参数,该参数是表示滑块的容器元素的 ID。它将容器元素分配给属性,并获取容器中的所有滑块项,并将它们分配给属性。Slider``id``this.container``this.items
  • getSelectedItem():此方法返回滑块中当前选定的项目。它使用该方法查找容器中具有类的项。querySelector``slider-list__item--selected
  • getSelectedItemIndex():此方法返回滑块中当前选定项的索引。它使用该方法查找数组中所选项的索引。indexOf``this.items
  • slideTo(idx):此方法将滑块滑动到给定索引的特定项目。它首先从当前选定的项(如果有)中删除该类,然后将该类添加到指定索引处的该项中。slider-list__item--selected
  • slideNext():此方法将滑块滑动到下一项。它使用 获取当前选定项的索引,并通过将当前索引递增 1 并使用取模运算符在需要时换行到第一项来计算下一项的索引。然后,它使用计算的索引进行调用以执行幻灯片。getSelectedItemIndex()``%``slideTo()
  • slidePrevious():此方法将滑块滑动到上一项。它与 类似,但它通过将当前索引减少 1 并使用模运算符绕到最后一项(如果需要)来计算前一项的索引。slideNext()``%

在代码结束时,使用容器 ID 创建类的实例,然后使用索引 调用该方法,这会将滑块滑动到索引 3 处的项。Slider``'my-slider'``slideTo()``3

行为:JS

行为设计:控制流 使用自定义事件解耦

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

HTML 代码表示与滑块相关的其他元素。它包括两个带有类的标签,通常用于滑块中的下一个和上一个按钮。它还包括一个带有类的元素,该类似乎包含几个表示滑块控制按钮的元素。<a>``slide-list__next``slide-list__previous``<div>``slide-list__control``<span>

JavaScript 代码片段创建一个对象,并将其调度到滑块的容器元素上。此代码的目的是发出一个名为“slide”的自定义事件,并附加一些附加数据 ()。该对象包含一个值为 .该值似乎是传递到此代码中的参数。通过调度此自定义事件,它允许代码的其他部分侦听“slide”事件并相应地处理它。CustomEvent``detail``detail``index``idx``idx

组件封装

总结:基本方法

    - 结构设计
    - 展现效果
    - 行为设计
      1. API (功能)
      1. 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();
             });
           }  
         }

提供的代码是一个名为“Slider”的 JavaScript 类,它表示幻灯片组件。它具有滑动到下一个或上一个项目、启动和停止幻灯片以及注册插件以添加额外功能的方法。

以下是该类及其方法的细分:

  1. 类构造函数:

    • 接受两个参数:(滑块容器元素的 ID)和(自动滑动的时间间隔,默认值为 3000ms)。id``cycle
    • 初始化类属性:(对滑块容器元素的引用)、(滑块项数组)和(自动滑动的时间间隔)。container``items``cycle
  2. registerPlugins(...plugins)方法:

    • 接受多个插件函数作为参数。
    • 遍历插件并调用每个插件函数,将滑块实例作为参数传递。
  3. getSelectedItem()方法:

    • 检索滑块中当前选定的项目。
    • 返回选定的项元素。
  4. getSelectedItemIndex()方法:

    • 检索滑块中当前选定项的索引。
    • 以数字形式返回索引。
  5. slideTo(idx)方法:

    • 接受索引参数并滑动到该索引处的项。idx
    • 从当前选定项中删除“选定”类。
    • 将“选定”类添加到指定索引处的项。
    • 调度一个名为“slide”的自定义事件,并将索引作为详细信息对象。
    • 该事件在滑块容器元素上调度。
  6. slideNext()方法:

    • 滑动到滑块中的下一项。
    • 使用模算术计算下一项的索引。
    • 使用计算索引调用该方法。slideTo()
  7. slidePrevious()方法:

    • 滑动到滑块中的上一项。
    • 使用模算术计算前一项的索引。
    • 使用计算索引调用该方法。slideTo()
  8. addEventListener(type, handler)方法:

    • 将事件侦听器附加到滑块容器元素。
    • 接受类型参数(事件类型)和处理程序函数。
    • 将事件侦听器添加到容器元素。
  9. start()方法:

    • 停止任何正在进行的幻灯片放映(清除间隔计时器)。
    • 通过基于属性定期调用方法来启动幻灯片放映。slideNext()``cycle
  10. stop()方法:

    • 停止正在进行的幻灯片放映(清除间隔计时器)。

该代码还提供了一个轮播图插件的控制器。它包括三个插件: pluginController、pluginPrevious和pluginNext。

pluginController函数用于处理轮播图的控制功能。 它首先获取轮播图容器中的控制器元素,然后监听鼠标悬停事件。 当鼠标悬停在控制按钮上时,根据按钮的索引值切换到相应的轮播项,并停止轮播动画。 当鼠标离开控制器区域时,重新开始轮播。

这段代码是一个名为pluginController的函数,它接受一个slider参数。该函数的作用是为滑动器添加控制器功能。 函数中首先通过slider.container.querySelector('.slide-list__control')找到包含控制器的元素,如果找到了该元素,则继续执行下面的代码。

接下来,函数定义了一个变量buttons,通过controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected')获取了所有的控制按钮元素。然后给控制器元素添加了一个mouseover事件监听器,在鼠标移入控制器区域时触发。

mouseover事件处理函数中,函数首先获取当前鼠标所在按钮的索引idx,然后调用slider.slideTo(idx)方法将滑动器切换到对应的索引页面。同时,函数调用slider.stop()方法停止滑动器的自动播放。

接着,函数给控制器元素添加了一个mouseout事件监听器,在鼠标移出控制器区域时触发。

mouseout事件处理函数中,函数调用slider.start()方法重新启动滑动器的自动播放。

最后,函数给滑动器添加了一个slide事件监听器,在滑动器切换页面时触发。

slide事件处理函数中,函数首先获取当前页面的索引idx,然后找到控制器中被选中的按钮元素,并将其类名改为'slide-list__control-buttons',同时将对应索引的按钮元素类名改为'slide-list__control-buttons--selected'

总结来说,这段代码实现了给滑动器添加控制器功能的逻辑,包括通过控制器按钮切换滑动器页面、停止和启动自动播放功能,并且在页面切换时更新控制器按钮的样式。

pluginPrevious函数用于处理轮播图的向前切换功能。 它获取轮播图容器中的向前切换按钮元素,并监听点击事件。 当按钮被点击时,停止轮播动画,切换到上一个轮播项,然后重新开始轮播。

pluginNext函数用于处理轮播图的向后切换功能。 它获取轮播图容器中的向后切换按钮元素,并监听点击事件。 当按钮被点击时,停止轮播动画,切换到下一个轮播项,然后重新开始轮播。

最后,通过创建一个Slider实例并将这三个插件注册到实例中,启动轮播图的自动播放。

优点是可以直接自由添加插件slider.registerPlugins(pluginController, pluginPrevious, pluginNext);

缺点是 如果不需要某个插件了,不仅需要修改JS部分,还要修改HTML代码部分。所以仍有改进进空间。

重构:模板化

解耦

      - 将HTML模板化,更易于扩展

     数据传给组件,所以方便修改添加图片
   

image (2).png

image (3).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>`;
            }
            ...
          }

给定的代码片段是滑块组件的类定义,可用于创建图像的幻灯片。让我们分解代码并了解其功能:

1.该类有一个构造函数,该构造函数接受两个参数:(滑块的容器元素的 ID)和(包含滑块选项的可选对象)。默认情况下,该对象具有两个属性:(图像 URL 数组)和(幻灯片切换之间的时间间隔(以毫秒为单位)。Slider``id``opts``opts``images``cycle

  1. 在构造函数中,使用提供的容器元素检索并分配给属性。然后,调用该方法为滑块生成 HTML 内容,然后将其分配给容器元素的属性。id``this.container``render``innerHTML
  2. 该方法根据参数中提供的数组生成元素列表。每个元素都包含一个标记,其中源属性设置为相应的图像 URL。生成的 HTML 内容以字符串形式返回。render``li``images``opts``li``img
  3. 为该属性分配了在容器元素中查询类名为“slider-list__item”和“slider-list__item–selected”的所有元素的结果。这允许轻松访问各个幻灯片元素。items
  4. 该属性设置为参数中的值或默认值 3000(表示幻灯片切换之间的持续时间(以毫秒为单位)。cycle``cycle``opts
  5. 最后,使用参数 0 调用该方法,这会将初始幻灯片设置为第一张幻灯片(索引 0)。slideTo()``slideTo(0)

总之,给定的代码片段定义了一个创建图像滑块的类。它允许通过容器 ID、图像 URL 和幻灯片切换持续时间等选项进行自定义。该方法为滑块生成 HTML 结构,并且该方法可能处理过渡到特定幻灯片的逻辑。Slider``render()``slideTo()

组件框架

抽象

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

image (4).png registerPlugins注册插件 render渲染 组件+控制插件 image (5).png 给定的代码定义一个类,该类用作创建自定义组件的基类。让我们分解代码并了解其功能:Component

  1. 该类有一个构造函数,该构造函数采用两个参数:id和(选项对象)。表示将在其中呈现组件的容器元素的 ID。该参数是可选的,包含(字符串)和(数组)等属性。Component``id``opts``id``opts``name``data
  2. 在构造函数内部,使用提供的容器元素访问并分配给属性。id``this.container
  3. 参数将分配给属性。如果未提供任何选项,则分配具有默认值的对象。opts``this.options
  4. 该方法使用 as 参数调用。此方法未在给定的代码片段中定义,但应由类的子类实现。此方法的目的是根据提供的数据生成组件的 HTML 内容。然后将生成的 HTML 内容分配给容器元素的属性,从而有效地呈现容器中的组件。render()``opts.data``Component``innerHTML
  5. 创建该方法是为了注册组件的插件功能。它使用 rest 参数语法将可变数量的插件对象作为参数。该方法使用该方法遍历提供的插件。registerPlugins(...plugins)``forEach()
  6. 对于每个插件,都会创建一个新元素作为插件容器。插件容器的类名设置为 ,其中 是对象中的属性值。但是,代码中似乎存在错误,因为无法直接访问。它应该是相反的。<div>``${name}__plugin``${name}``name``opts``name``this.options.name
  7. 调用该方法以使用对象提供的 为插件生成 HTML 内容。然后将生成的 HTML 内容分配给插件容器的属性。plugin.render(this.options.data)``data``opts``innerHTML
  8. 插件容器作为子容器追加到组件的主容器元素。
  9. 最后,调用该方法以执行特定于插件的某些操作。此方法的目的取决于插件实现。plugin.action(this)
  10. 该方法被定义为具有空实现的抽象方法。它应该被类的子类覆盖,以便为特定组件提供必要的 HTML 呈现逻辑。render(data)``Component

总之,该类提供了在 JavaScript 中创建组件的基本结构。它允许在指定的容器内呈现HTML内容,并提供一种机制来注册插件以获得其他功能。类的子类可以重写该方法,为其特定组件生成自定义 HTML 内容。Component``Component``render()

组件封装

总结

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

思考:改进空间?怎样重构这个轮播图组件?

    CSS也可以组件化