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);
JavaScript 类,它表示滑块组件。以下是每种方法的细分:
constructor(id):构造函数方法初始化对象。它采用一个参数,该参数是表示滑块的容器元素的 ID。它将容器元素分配给属性,并获取容器中的所有滑块项,并将它们分配给属性。Slider``id``this.container``this.itemsgetSelectedItem():此方法返回滑块中当前选定的项目。它使用该方法查找容器中具有类的项。querySelector``slider-list__item--selectedgetSelectedItemIndex():此方法返回滑块中当前选定项的索引。它使用该方法查找数组中所选项的索引。indexOf``this.itemsslideTo(idx):此方法将滑块滑动到给定索引的特定项目。它首先从当前选定的项(如果有)中删除该类,然后将该类添加到指定索引处的该项中。slider-list__item--selectedslideNext():此方法将滑块滑动到下一项。它使用 获取当前选定项的索引,并通过将当前索引递增 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 类,它表示幻灯片组件。它具有滑动到下一个或上一个项目、启动和停止幻灯片以及注册插件以添加额外功能的方法。
以下是该类及其方法的细分:
类构造函数:
- 接受两个参数:(滑块容器元素的 ID)和(自动滑动的时间间隔,默认值为 3000ms)。
id``cycle- 初始化类属性:(对滑块容器元素的引用)、(滑块项数组)和(自动滑动的时间间隔)。
container``items``cycle
registerPlugins(...plugins)方法:
- 接受多个插件函数作为参数。
- 遍历插件并调用每个插件函数,将滑块实例作为参数传递。
getSelectedItem()方法:
- 检索滑块中当前选定的项目。
- 返回选定的项元素。
getSelectedItemIndex()方法:
- 检索滑块中当前选定项的索引。
- 以数字形式返回索引。
slideTo(idx)方法:
- 接受索引参数并滑动到该索引处的项。
idx- 从当前选定项中删除“选定”类。
- 将“选定”类添加到指定索引处的项。
- 调度一个名为“slide”的自定义事件,并将索引作为详细信息对象。
- 该事件在滑块容器元素上调度。
slideNext()方法:
- 滑动到滑块中的下一项。
- 使用模算术计算下一项的索引。
- 使用计算索引调用该方法。
slideTo()
slidePrevious()方法:
- 滑动到滑块中的上一项。
- 使用模算术计算前一项的索引。
- 使用计算索引调用该方法。
slideTo()
addEventListener(type, handler)方法:
- 将事件侦听器附加到滑块容器元素。
- 接受类型参数(事件类型)和处理程序函数。
- 将事件侦听器添加到容器元素。
start()方法:
- 停止任何正在进行的幻灯片放映(清除间隔计时器)。
- 通过基于属性定期调用方法来启动幻灯片放映。
slideNext()``cycle
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模板化,更易于扩展
数据传给组件,所以方便修改添加图片
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
- 在构造函数中,使用提供的容器元素检索并分配给属性。然后,调用该方法为滑块生成 HTML 内容,然后将其分配给容器元素的属性。
id``this.container``render``innerHTML - 该方法根据参数中提供的数组生成元素列表。每个元素都包含一个标记,其中源属性设置为相应的图像 URL。生成的 HTML 内容以字符串形式返回。
render``li``images``opts``li``img - 为该属性分配了在容器元素中查询类名为“slider-list__item”和“slider-list__item–selected”的所有元素的结果。这允许轻松访问各个幻灯片元素。
items - 该属性设置为参数中的值或默认值 3000(表示幻灯片切换之间的持续时间(以毫秒为单位)。
cycle``cycle``opts - 最后,使用参数 0 调用该方法,这会将初始幻灯片设置为第一张幻灯片(索引 0)。
slideTo()``slideTo(0)
总之,给定的代码片段定义了一个创建图像滑块的类。它允许通过容器 ID、图像 URL 和幻灯片切换持续时间等选项进行自定义。该方法为滑块生成 HTML 结构,并且该方法可能处理过渡到特定幻灯片的逻辑。Slider``render()``slideTo()
组件框架
抽象
- 将组件通用模型抽象出来
registerPlugins注册插件 render渲染 组件+控制插件
给定的代码定义一个类,该类用作创建自定义组件的基类。让我们分解代码并了解其功能:
Component
- 该类有一个构造函数,该构造函数采用两个参数:id和(选项对象)。表示将在其中呈现组件的容器元素的 ID。该参数是可选的,包含(字符串)和(数组)等属性。
Component``id``opts``id``opts``name``data - 在构造函数内部,使用提供的容器元素访问并分配给属性。
id``this.container - 参数将分配给属性。如果未提供任何选项,则分配具有默认值的对象。
opts``this.options - 该方法使用 as 参数调用。此方法未在给定的代码片段中定义,但应由类的子类实现。此方法的目的是根据提供的数据生成组件的 HTML 内容。然后将生成的 HTML 内容分配给容器元素的属性,从而有效地呈现容器中的组件。
render()``opts.data``Component``innerHTML - 创建该方法是为了注册组件的插件功能。它使用 rest 参数语法将可变数量的插件对象作为参数。该方法使用该方法遍历提供的插件。
registerPlugins(...plugins)``forEach() - 对于每个插件,都会创建一个新元素作为插件容器。插件容器的类名设置为 ,其中 是对象中的属性值。但是,代码中似乎存在错误,因为无法直接访问。它应该是相反的。
<div>``${name}__plugin``${name}``name``opts``name``this.options.name - 调用该方法以使用对象提供的 为插件生成 HTML 内容。然后将生成的 HTML 内容分配给插件容器的属性。
plugin.render(this.options.data)``data``opts``innerHTML - 插件容器作为子容器追加到组件的主容器元素。
- 最后,调用该方法以执行特定于插件的某些操作。此方法的目的取决于插件实现。
plugin.action(this) - 该方法被定义为具有空实现的抽象方法。它应该被类的子类覆盖,以便为特定组件提供必要的 HTML 呈现逻辑。
render(data)``Component
总之,该类提供了在 JavaScript 中创建组件的基本结构。它允许在指定的容器内呈现HTML内容,并提供一种机制来注册插件以获得其他功能。类的子类可以重写该方法,为其特定组件生成自定义 HTML 内容。Component``Component``render()
组件封装
总结
- 组件设计的原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计
- 三次重构
插件化
模板化
抽象化(组件框架)
思考:改进空间?怎样重构这个轮播图组件?
CSS也可以组件化