这是我参与「第四届青训营」笔记创作活动的第1天
内容
各司其责
这里列举了三个版本的“深夜食堂”切换深浅色背景的解决方案
版本一
const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
const body = document.body;
if(e.target.innerHTML === '🌞') {
body.style.backgroundColor = 'black';
body.style.color = 'white';
e.target.innerHTML = '🌜';
} else {
body.style.backgroundColor = 'white';
body.style.color = 'black';
e.target.innerHTML = '🌞';
}
});
版本一是最直接的解决方式,一般我们拿到需求第一时间想到的就是这个。因为目前写的都是一些练手的东西,所以大概率不会继续优化。
版本二
const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
const body = document.body;
if(body.className !== 'night') {
body.className = 'night';
} else {
body.className = '';
}
});
版本二定义了深色模式和浅色模式的样式,使js代码更简洁。但其实第二版并不只有这一个优点,当项目迭代迅速的时候,很可能其他人读到第一版的代码并不清楚其中的含义,第二版增强了代码的可读性。并且,如果需要增加需求,比如更换深色模式的样式,第二版能更迅速的找到切入点。
版本三
#modeCheckBox {
display: none;
}
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
如果说第二版的改进已经将需求很好的完成,那么第三版则是各司其责的完美展现——让css和js完成它们自己的责任,很多时候为了节省时间我们将能用css完成的需求通过堆积js代码的方式实现。虽说完成了要求,但是代码的可读性很差。优秀的代码要向**“各司其责”**靠拢!
结论
- HTML/CSS/JS 各司其责
- 应当避免不必要的由 JS 直接操作样式
- 可以用 class 来表示状态
- 纯展示类交互寻求零 JS 方案
组件封装
轮播图是我们学习前端时几乎必做的一个demo,想当年我也是做了两次不同方法的轮播图。但是由于当时没有学习面向对象,所以没有使用面向对象的方法实现。
版本一
首先使用无需列表实现轮播图的结构,通过改变class来实现切换。将切换的行为封装为一个Slider对象。因为上面的轮播图与下面的小圆点是对应的,所以使用自定义事件来解耦。
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
通过上面的案例,总结出基本方法:
- 结构设计
- 展现效果
- 行为设计(API(功能)、Event(控制流))
经上,我们将功能全部实现。但是还没有结束,我们需要思考还有没有可以改进的。可以发现,上述功能在一起实现,图片、小圆点以及左右的 > 的逻辑都放在了一起,这时候当我们需要进改动,比如不需要下面的圆点的时候将十分麻烦。
版本二
由上,引出重构的方法——插件化。将控制元素抽取成插件,插件与组件之间通过依赖注入方式建立联系,由此完成解耦。
下面是一部分代码
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模板化,通过动态生成的方式生成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>`;
}
}
其实这个案例还有很多改进的空间,现在就先说到这里了。
过程抽象
过程抽象是用来处理局部细节控制的一些方法,是函数式编程思想的基础应用。
例子
const list = document.querySelector("ul");
const buttons = list.querySelectorAll("button");
buttons.forEach((button) => {
button.addEventListener("click", (evt) => {
const target = evt.target;
target.parentNode.className = "completed";
setTimeout(() => {
list.removeChild(target.parentNode);
}, 2000);
});
});
上面是点击删除的例子,当点击的时候,删除一个节点。看起来没有什么问题,但是当瞬间点击多次的时候,控制台会报错,因为我们想要删除已经删除的元素。向这种例子,或者是对请求有限制的需求,我们可以将函数封装成once高阶函数。
Once
function once(fn) {
return function (...args) {
if (fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
};
}
为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象。
高阶函数 HOF
- 以函数最为参数
- 以函数作为返回值
- 常用于作为函数装饰器
常用高阶函数
- Once
- Throttle(节流)
- Debounce(防抖 )
- Consumer/2
- Iterative
为什么要使用高阶函数
高阶函数可以减少非纯函数(非纯函数测试困难,维护成本高)
编程范式
JS作为现代化编程语言既支持命令式也支持声明式
两种对比:
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);
总结
今天的课真的是带给我更深层次的知识,很多内容还需要继续理解。