写好JS的原则
各司其职
- html负责结构
- css负责表现
- JavaScript负责行为
举个例子:
我们想要实现通过点击太阳或者月亮来更改页面的模式
方案一:
我们可以捕获这个图标的点击事件,点击后修改字体颜色与背景颜色。
但这样就将js与css耦合了
方案二:
我们将js直接修改样式,改成了js修改元素的类,样式的转变就交给了css来进行。
方案三:
不使用js的纯css方案
这里使用input复选框加label的形式,完成状态的捕获
当我们点击label时,对应的复选框也会被点击
再通过复选框的选中状态伪类就能实现模式的切换
无论是第二种或第三种方案都比第一种方案好。
结论:
- HTML/CSS/JS 各司其责
- 应当尽量避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零js方案
组件封装
组件是指web页面上抽出来一个个包含模板(HTML),功能(JS),***样式(CSS)***的单元。
组件设计原则:
- 封装性
- 正确性
- 扩展性
- 复用性。
封装组件的时候不要一下就想着把组件做好。
我们可以从简单的功能入手,然后一步一步迭代,优化。
组件封装可以从一下几个步骤着手:
组件设计步骤
结构设计
展现效果
行为设计
- API(功能)
- Event(控制流)
下面以创建轮播图组件示例讲解组件封装过程。
结构设计
结构设计是第一步,我们要将需要展示的所有元素再html文档种体现出来
对于轮播图组件来说,就是一个容器,包含一组列表,列表中放入图片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="slider.css">
</head>
<body>
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp" alt="">
</li>
<li class="slider-list__item">
<img src="https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg" alt="">
</li>
<li class="slider-list__item">
<img src="https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp" alt="">
</li>
<li class="slider-list__item">
<img src="https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp" alt="">
</li>
</ul>
</div>
</body>
<script src="slider.js"></script>
</html>
复制代码
展示效果
将结构设计好之后,我们再浏览器看到的就是一堆元素的排列。
需要我们再根据设计需求,对不同元素进行样式调整。包括位置,尺寸,颜色等等
对于轮播图来说,我们要把图片设置为绝对定位,不显示的透明度为0,显示的透明度为1
#my-slider {
position: relative;
width: 590px;
}
.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后,所有的图片都集中起来了,已经有轮播图的感觉了,下面就是要控制显示哪张图片
行为设计
设计组件的行为,我们需要根据组件的功能来定。
封装时提供各种功能的api接口。
API(功能)
api的设计应保证原子操作,职责单一,满足灵活性。
对于轮播图来说,我们需要能够控制跳转到指定图片,跳转到前一个,跳转到后一个
就做了一下的封装设计
class Slider {
constructor(id) {
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
}
getSelectedItem() {
const select = this.container.querySelector(".slider-list__item--selected");
return select;
}
getSelectedItemIndex() {
return Array.from(this.items).indexOf(this.getSelectedItem());
}
sliderTo(index) {
const select = this.getSelectedItem();
if (select) {
select.className = "slider-list__item";
}
const item = this.items[index];
if (item) {
this.items[index].className = "slider-list__item--selected";
}
}
sliderNext() {
const index = this.getSelectedItemIndex();
const nextIndex = (index + 1) % this.items.length;
this.sliderTo(nextIndex);
}
sliderPrevious() {
const index = this.getSelectedItemIndex();
const previousIndex = (index + this.items.length - 1) % this.items.length;
this.sliderTo(previousIndex);
}
}
复制代码
Event(控制流)
做好前面的部分我们已经把轮播图的控制对象做好了
那要想真的实现轮播图的效果,我们还需要设计控制流
对于轮播图,我们主要设计三个控制功能
- 定时翻页
- 左右点击翻页
- 下方控制点翻页
有了这个控制思路,先把控制用到的元素写入html文档中
<a class="slider-list__previous"><</a>
<a class="slider-list__next">></a>
<div class="slider-list__control">
<span class="slider-list__control-buttons--selected"></span>
<span class="slider-list__control-buttons"></span>
<span class="slider-list__control-buttons"></span>
<span class="slider-list__control-buttons"></span>
</div>
复制代码
在给元素设置对应的样式
.slider-list__next,
.slider-list__previous {
position: absolute;
top: 200px;
line-height: 40px;
padding: 0 5px 5px 5px;
background-color: black;
opacity: 0;
font-size: 30px;
color: white;
}
.slider-list__next:hover,
.slider-list__previous:hover {
opacity: 0.7;
transition: opacity 0.5s;
}
.slider-list__next {
right: 0;
}
.slider-list__control {
position: absolute;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
bottom: 40px;
left: 260px;
height: 20px;
width: 70px;
background-color: rgba(185, 183, 183, 0.911);
border-radius: 10px;
}
.slider-list__control-buttons,
.slider-list__control-buttons--selected {
height: 10px;
width: 10px;
background-color: white;
border-radius: 5px;
}
.slider-list__control-buttons--selected {
background-color: red;
}
复制代码
有了这些呢,下面就是利用js驱动元素类的转变
对于定时翻页,其实就是设置一个定时器,定时执行翻页的方法。但是由于在控制过程中可能会使用左右翻页或者控制点翻页,所以我们不能让他们互相冲突。需要给定时翻页设置启停,在左右翻或者控制点翻的时候我们要先停,翻完之后再启动。
我们给Slider
类添加了两个方法
start() {
this.stop();
this._timer = setInterval(() => {
this.sliderNext();
}, this.cycle)
}
stop() {
clearInterval(this._timer);
}
复制代码
对于对于左右翻页比较好说,我们直接在Slider类初始化时,添加点击的监听事件,点击后先关闭定时器,翻页结束再开启定时器。
// 向前翻
const previous = this.container.querySelector(".slider-list__previous");
if (previous) {
previous.addEventListener('click', evt => {
this.stop();
this.sliderPrevious();
this.start();
evt.preventDefault();
})
}
// 向后翻
const next = this.container.querySelector(".slider-list__next");
if (next) {
next.addEventListener('click', evt => {
this.stop();
this.sliderNext();
this.start();
evt.preventDefault();
})
}
复制代码
对于控制点翻页,我们的控制点需要时刻与显示的当前图片进行对应,就涉及到当前控制点与当前图片的耦合。为了解耦,我们自己设计一个自定义事件。
当执行翻页方法后,会触发事件。
const detail = { index: index }
const event = new CustomEvent("slide", { bubbles: true, detail })
this.container.dispatchEvent(event);
复制代码
再设置事件的监听,当监听到翻页事件后,我们修改当前控制点。
this.container.addEventListener('slide', evt => {
const idx = evt.detail.index;
const selected = controller.querySelector('.slider-list__control-buttons--selected');
if (selected) {
selected.className = "slider-list__control-buttons";
}
buttons[idx].className = "slider-list__control-buttons--selected";
})
复制代码
做完这些,就基本完成了整个组件的封装
这里贴出完整代码
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="slider.css">
</head>
<body>
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp" alt="">
</li>
<li class="slider-list__item">
<img src="https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg" alt="">
</li>
<li class="slider-list__item">
<img src="https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp" alt="">
</li>
<li class="slider-list__item">
<img src="https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp" alt="">
</li>
</ul>
<a class="slider-list__previous"><</a>
<a class="slider-list__next">></a>
<div class="slider-list__control">
<span class="slider-list__control-buttons--selected"></span>
<span class="slider-list__control-buttons"></span>
<span class="slider-list__control-buttons"></span>
<span class="slider-list__control-buttons"></span>
</div>
</div>
</body>
<script src="slider.js"></script>
</html>
复制代码
css部分
#my-slider {
position: relative;
width: 590px;
height: 474px;
}
.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;
}
.slider-list__next,
.slider-list__previous {
position: absolute;
top: 200px;
line-height: 40px;
padding: 0 5px 5px 5px;
background-color: black;
opacity: 0;
font-size: 30px;
color: white;
}
.slider-list__next:hover,
.slider-list__previous:hover {
opacity: 0.7;
transition: opacity 0.5s;
}
.slider-list__next {
right: 0;
}
.slider-list__control {
position: absolute;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
bottom: 40px;
left: 260px;
height: 20px;
width: 70px;
background-color: rgba(185, 183, 183, 0.911);
border-radius: 10px;
}
.slider-list__control-buttons,
.slider-list__control-buttons--selected {
height: 10px;
width: 10px;
background-color: white;
border-radius: 5px;
}
.slider-list__control-buttons--selected {
background-color: red;
}
复制代码
js部分
class Slider {
constructor(id, cycle = 3000) {
this.cycle = cycle;
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
// 向前翻
const previous = this.container.querySelector(".slider-list__previous");
if (previous) {
previous.addEventListener('click', evt => {
this.stop();
this.sliderPrevious();
this.start();
evt.preventDefault();
})
}
// 向后翻
const next = this.container.querySelector(".slider-list__next");
if (next) {
next.addEventListener('click', evt => {
this.stop();
this.sliderNext();
this.start();
evt.preventDefault();
})
}
// 控制点
const controller = this.container.querySelector(".slider-list__control");
if (controller) {
const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
controller.addEventListener("mouseover", evt => {
const idx = Array.from(buttons).indexOf(evt.target);
if (idx >= 0) {
this.sliderTo(idx);
this.stop();
}
})
controller.addEventListener('mouseout', evt => {
this.start();
})
this.container.addEventListener('slide', evt => {
const idx = evt.detail.index;
const selected = controller.querySelector('.slider-list__control-buttons--selected');
if (selected) {
selected.className = "slider-list__control-buttons";
}
buttons[idx].className = "slider-list__control-buttons--selected";
})
}
}
getSelectedItem() {
const select = this.container.querySelector(".slider-list__item--selected");
return select;
}
getSelectedItemIndex() {
return Array.from(this.items).indexOf(this.getSelectedItem());
}
sliderTo(index) {
const select = this.getSelectedItem();
if (select) {
select.className = "slider-list__item";
}
const item = this.items[index];
if (item) {
this.items[index].className = "slider-list__item--selected";
}
const detail = { index: index }
const event = new CustomEvent("slide", { bubbles: true, detail })
this.container.dispatchEvent(event);
}
sliderNext() {
const index = this.getSelectedItemIndex();
const nextIndex = (index + 1) % this.items.length;
this.sliderTo(nextIndex);
}
sliderPrevious() {
const index = this.getSelectedItemIndex();
const previousIndex = (index + this.items.length - 1) % this.items.length;
this.sliderTo(previousIndex);
}
start() {
this.stop();
this._timer = setInterval(() => {
this.sliderNext();
}, this.cycle)
}
stop() {
clearInterval(this._timer);
}
}
const slider = new Slider("my-slider", 2000);
slider.start();
复制代码
重构一:插件化
前面虽然已经实现了我们想要的所有功能,但是我们可以看出再构造函数里有很多代码,这些代码除了一些Slider
的初始化之外还有一些控制元素的初始化。
这样控制元素与我们整个封装的耦合度太高,不利于后期与其他控制按钮的组合使用了。
所以我们要进行解耦
- 将控制元素抽取成插件
- 插件与组件之间通过依赖注入的方式建立联系
我们首先在Slider
类中增加依赖注入的方法
registerPlugins(...plugins) {
plugins.forEach(plugin => plugin(this));
}
复制代码
然后在类外三个插件
function pluginPrevious(slider) {
// 向前翻
const previous = slider.container.querySelector(".slider-list__previous");
if (previous) {
previous.addEventListener('click', evt => {
slider.stop();
slider.sliderPrevious();
slider.start();
evt.preventDefault();
})
}
}
function pluginNext(slider) {
// 向后翻
const next = slider.container.querySelector(".slider-list__next");
if (next) {
next.addEventListener('click', evt => {
slider.stop();
slider.sliderNext();
slider.start();
evt.preventDefault();
})
}
}
function pluginController(slider) {
// 控制点
const controller = slider.container.querySelector(".slider-list__control");
if (controller) {
const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
controller.addEventListener("mouseover", evt => {
const idx = Array.from(buttons).indexOf(evt.target);
if (idx >= 0) {
slider.sliderTo(idx);
slider.stop();
}
})
controller.addEventListener('mouseout', evt => {
slider.start();
})
slider.container.addEventListener('slide', evt => {
const idx = evt.detail.index;
const selected = controller.querySelector('.slider-list__control-buttons--selected');
if (selected) {
selected.className = "slider-list__control-buttons";
}
buttons[idx].className = "slider-list__control-buttons--selected";
})
}
}
复制代码
其实也很好理解,只是原本这三个函数是在Slider
类的构造函数中执行的,依赖于Slider
的this
。只不过我们将这三个函数单独抽离出来,然后利用Slider
的注入插件的方法registerPlugins
将Slider
的this
通过参数的方式传给他们。
const slider = new Slider("my-slider", 2000);
slider.registerPlugins(/*pluginController,*/ pluginPrevious, pluginNext)
slider.start();
复制代码
这样我们如果有哪个功能不想用了,只需要将它注释掉,如果想要新加一个按钮来控制,也只用写成插件的方式,然后将插件注册进slider
重构二:模板化
经过刚刚的插件化之后,我们其实将控制程序与Slider
类进行了解耦。
假如我们想要不使用控制点进行控制的话,我们仅需要在注册插件的地方注释掉注册
const slider = new Slider("my-slider", 2000);
slider.registerPlugins(/*pluginController,*/ pluginPrevious, pluginNext);
slider.start();
复制代码
这样我们控制点的方法就失效了,但是我们会发现,在页面上还是会存在控制点。而且控制点不会随着图片的更换而改变。
所以我们要将html的代码放到js中,让js来渲染结构。
这样我们的html就只剩下一行代码
<div id="my-slider" class="slider-list"></div>
复制代码
我们的css不用变动
js
class Slider {
constructor(id, opts = { images: [], cycle: 3000 }) {
this.options = opts;
this.container = document.getElementById(id);
this.container.innerHTML = this.render();
this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
this.cycle = opts.cycle || 3000;
this.sliderTo(0);
}
render() {
const images = this.options.images;
const content = images.map(image => `
<li class="slider-list__item">
<img src="https://juejin.cn/post/${image}">
</li>
`.trim())
return `
<ul>${content.join("")}</ul>
`
}
registerPlugins(...plugins) {
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div');
pluginContainer.className = "slider-list__plugin";
pluginContainer.innerHTML = plugin.render(this);
this.container.appendChild(pluginContainer);
plugin.action(this)
});
}
getSelectedItem() {
const select = this.container.querySelector(".slider-list__item--selected");
return select;
}
getSelectedItemIndex() {
return Array.from(this.items).indexOf(this.getSelectedItem());
}
sliderTo(index) {
const select = this.getSelectedItem();
if (select) {
select.className = "slider-list__item";
}
const item = this.items[index];
if (item) {
this.items[index].className = "slider-list__item--selected";
}
const detail = { index: index }
const event = new CustomEvent("slide", { bubbles: true, detail })
this.container.dispatchEvent(event);
}
sliderNext() {
const index = this.getSelectedItemIndex();
const nextIndex = (index + 1) % this.items.length;
this.sliderTo(nextIndex);
}
sliderPrevious() {
const index = this.getSelectedItemIndex();
const previousIndex = (index + this.items.length - 1) % this.items.length;
this.sliderTo(previousIndex);
}
start() {
this.stop();
this._timer = setInterval(() => {
this.sliderNext();
}, this.cycle)
}
stop() {
clearInterval(this._timer);
}
}
const pluginPrevious = {
render() {
return `<a class="slider-list__previous"><</a>`;
},
action(slider) {
// 向前翻
const previous = slider.container.querySelector(".slider-list__previous");
if (previous) {
previous.addEventListener('click', evt => {
slider.stop();
slider.sliderPrevious();
slider.start();
evt.preventDefault();
})
}
}
}
const pluginNext = {
render() {
return `<a class="slider-list__next">></a>`;
},
action(slider) {
// 向后翻
const next = slider.container.querySelector(".slider-list__next");
if (next) {
next.addEventListener('click', evt => {
slider.stop();
slider.sliderNext();
slider.start();
evt.preventDefault();
})
}
}
}
const pluginController = {
render(slider) {
const controller = slider.options.images.map((image, index) => {
if (slider.getSelectedItemIndex() == index) {
return `
<span class="slider-list__control-buttons--selected"></span>
`.trim();
} else {
return `
<span class="slider-list__control-buttons"></span>
`.trim();
}
})
return `<div class="slider-list__control">${controller.join("")}</div>`;
},
action(slider) {
// 控制点
const controller = slider.container.querySelector(".slider-list__control");
if (controller) {
const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
controller.addEventListener("mouseover", evt => {
const idx = Array.from(buttons).indexOf(evt.target);
if (idx >= 0) {
slider.sliderTo(idx);
slider.stop();
}
})
controller.addEventListener('mouseout', evt => {
slider.start();
})
slider.container.addEventListener('slide', evt => {
const idx = evt.detail.index;
const selected = controller.querySelector('.slider-list__control-buttons--selected');
if (selected) {
selected.className = "slider-list__control-buttons";
}
buttons[idx].className = "slider-list__control-buttons--selected";
})
}
}
}
const images = [
"https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp",
"https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg",
"https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp",
"https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp"
]
const slider = new Slider("my-slider", { images, cycle: 2000 });
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start();
复制代码
重构三:组件框架
到目前为止,如果说从轮播器的角度来说已经实现的很好了
但是如果我们在设计网站的时候肯定不止用到一个组件,所以我们可以抽象出一个更加通用的组件模型。用这个组件模型去创建更多的组件。
这样我们就可以写出一个抽象的组件类了
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 = `${this.name}__plugin`;
pluginContainer.innerHTML = plugin.render(this);
this.container.appendChild(pluginContainer);
plugin.action(this)
});
}
render(component) {
// 抽象
return "";
}
}
复制代码
利用这个抽象的组件类来设计Slider
类
class Slider extends Component {
constructor(id, opts = { name: "slider-list", data: [], cycle: 3000 }) {
super(id, opts);
this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
this.cycle = opts.cycle || 3000;
this.sliderTo(0);
}
render(data) {
const content = data.map(image => `
<li class="slider-list__item">
<img src="https://juejin.cn/post/${image}">
</li>
`.trim())
return `
<ul>${content.join("")}</ul>
`
}
getSelectedItem() {
const select = this.container.querySelector(".slider-list__item--selected");
return select;
}
getSelectedItemIndex() {
return Array.from(this.items).indexOf(this.getSelectedItem());
}
sliderTo(index) {
const select = this.getSelectedItem();
if (select) {
select.className = "slider-list__item";
}
const item = this.items[index];
if (item) {
this.items[index].className = "slider-list__item--selected";
}
const detail = { index: index }
const event = new CustomEvent("slide", { bubbles: true, detail })
this.container.dispatchEvent(event);
}
sliderNext() {
const index = this.getSelectedItemIndex();
const nextIndex = (index + 1) % this.items.length;
this.sliderTo(nextIndex);
}
sliderPrevious() {
const index = this.getSelectedItemIndex();
const previousIndex = (index + this.items.length - 1) % this.items.length;
this.sliderTo(previousIndex);
}
start() {
this.stop();
this._timer = setInterval(() => {
this.sliderNext();
}, this.cycle)
}
stop() {
clearInterval(this._timer);
}
}
const pluginPrevious = {
render() {
return `<a class="slider-list__previous"><</a>`;
},
action(slider) {
// 向前翻
const previous = slider.container.querySelector(".slider-list__previous");
if (previous) {
previous.addEventListener('click', evt => {
slider.stop();
slider.sliderPrevious();
slider.start();
evt.preventDefault();
})
}
}
}
const pluginNext = {
render() {
return `<a class="slider-list__next">></a>`;
},
action(slider) {
// 向后翻
const next = slider.container.querySelector(".slider-list__next");
if (next) {
next.addEventListener('click', evt => {
slider.stop();
slider.sliderNext();
slider.start();
evt.preventDefault();
})
}
}
}
const pluginController = {
render(slider) {
const controller = slider.options.data.map((image, index) => {
if (slider.getSelectedItemIndex() == index) {
return `
<span class="slider-list__control-buttons--selected"></span>
`.trim();
} else {
return `
<span class="slider-list__control-buttons"></span>
`.trim();
}
})
return `<div class="slider-list__control">${controller.join("")}</div>`;
},
action(slider) {
// 控制点
const controller = slider.container.querySelector(".slider-list__control");
if (controller) {
const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
controller.addEventListener("mouseover", evt => {
const idx = Array.from(buttons).indexOf(evt.target);
if (idx >= 0) {
slider.sliderTo(idx);
slider.stop();
}
})
controller.addEventListener('mouseout', evt => {
slider.start();
})
slider.container.addEventListener('slide', evt => {
const idx = evt.detail.index;
const selected = controller.querySelector('.slider-list__control-buttons--selected');
if (selected) {
selected.className = "slider-list__control-buttons";
}
buttons[idx].className = "slider-list__control-buttons--selected";
})
}
}
}
const images = [
"https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp",
"https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg",
"https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp",
"https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp"
]
const slider = new Slider("my-slider", { data: images, cycle: 2000 });
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start();
复制代码
涉及的代码可在03.js学习 · Craipy/青训营笔记代码 – 码…中查看
过程抽象
- 用来处理局部细节控制的方法
- 函数式编程思想的基础应用
我们把函数本身看成是控制器,我们关心控制器的输入和输出
通过例子来理解一下:
操作次数限制
假如我们做这样一个例子。一个任务列表,我们点击后代表任务完成,然后任务会渐隐的消失,等消失之后,在将这个元素从list列表中删除,我们很轻易就写出了右端的这个代码。
这个代码一般运行时没有问题的,但是如果连续点击两次同一个任务按钮,就会报错。
因为创建了两个异步任务,第一个异步任务把元素删除了,第二个异步任务就会报错了。
为了能够让只执行一次的需求覆盖到不同事件处理,我们可以将这个需求剥离出来,这个过程我们称为过程抽象
。
我们刚刚所讲的 各司其职、组件封装 其实大多都是数据抽象
,将数据抽象
出来,抽象出一个对象,然后把对象传给我们的插件。
实际上 过程抽象
我们可以把一个动作,函数 抽象出来然后给更多的任务去使用。
举个例子:
我们有一个人,有一个门,人要开门进房间。
如果我们把人和们抽象出来,这就是
数据抽象
;其实我们也可以把开门这个动作抽象出来,这就是
过程抽象
。
再看这个,只执行一次的需求,我们 可以写一个高阶函数。来实现就是说,每个函数执行一次之后就被清空了。那这样就解决了执行一次的问题,而且也具有通用性。
高阶函数
- 以函数为参数
- 以函数作为返回值
- 常用于作为函数装饰器
高阶函数的基础范式 –等价范式
常用的高阶函数
一般情况,我们碰到这种问题,没有想到要使用高阶函数。而是想到的是当前这个函数中怎么解决这些问题。
只考虑当前函数的话,这样我们就可能会定义一下全局的标志位。这样虽然能够实现,但是可能以后,另一个函数也要有这样的功能的时候,我们还要用同样的方法,同样的思想在写一遍。
但是当我们将函数看成一个整体,这个整体不变动的情况下,来解决这个问题,这时候我们可以写另外一个函数来调用原函数
像这样,如果只是普通的调用,那么扩展性并不强。如果将函数作为形参进行调用,那么我们可以将这种操作扩展到所有函数。
编程范式
总结上面的结论,我们可以把编程总结为两个范式
- 命令式
- 声明式
命令式更偏向于怎么做
声明式更偏向于做什么
用一个例子来看
写代码应该关注什么
- 风格
- 效率
- 约定
- 使用场景
- 设计