这是我参与「第四届青训营 」笔记创作活动的的第3天
本文主要记录课上一些重点内容,方便日后巩固和复习~
本堂课的重点内容:
-
如何做到让HTML,CSS和JS的职能分离。
-
以轮播图为例,展现了完整实现一个组件封装的过程。
-
通过高阶函数的介绍,学会将过程抽象运用到实际代码中。
-
对实际场景下的一些算法代码的思考,例如洗牌,分红包。
JS设计相关知识点:
职能分离
老师通过一个简单的深色浅色切换的例子,分别从三种不同写法出发,引出了职能分离的好处。
版本一
虽然实现了功能,但代码中有多处对CSS属性的控制,代码可读性较差,不利于后续扩展。
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 = '🌞';
}
});
版本二
版本二相比一,将CSS属性抽取成类,可读性和扩展性更强,但JS在功能中的职能很低。
const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
const body = document.body;
if(body.className !== 'night') {
body.className = 'night';
} else {
body.className = '';
}
});
版本三
版本三将JS从功能中剥离,通过纯CSS实现切换。
收获知识点:1、label标签可通过for属性控制input标签 2、通过CSS伪类可改变其兄弟结点的样式。
结论
组件封装
老师通过对组件的正确性、扩展性、复用性三方面重构代码,展现了组件封装的大致流程。
正确性
通过将轮播图抽象成类和API,实现轮播的基本操作,并通过自定义事件,将图片和圆点轮播解耦。
收获知识点:学会用自定义事件对组合功能解耦。
扩展性
原先代码的构造函数十分复杂,且大部分代码都是注册事件。我们可以将注册不同的事件以插件的形式依赖注入组件中,这样可以使代码扁平化,且扩展性高。
收获知识点:将组件中的自定义事件抽取成插件,依赖注入组件。
复用性
在插件化重构的基础上,我们发现封装后的组件仍有大量的标签,且如果我们想要改变轮播的图片或取消圆点轮播等,需要去修改代码,不符合组件的复用性。
收获知识点:可在组件封装中去操作DOM挂载,以适应组件属性的变化。
总结
这个组件目前的泛用性还有改进空间,比如轮播的可以是视频或其他组件等。我个人有两种思路:可以用
插槽机制或对每个轮播图再封装一个轮播项组件,提供子组件功能。
过程抽象
将某一过程需求剥离出来。
常用高阶函数
- Once
//只执行一次,保证接口幂等性
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
- Throttle
//也称节流阀,可限制网络请求
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
- Debounce
//防抖动,可用于自动保存
function debounce(fn, dur){
dur = dur || 100;
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
}
}
- Consumer / 2
//等待上一次fn执行,有点像MQ的消费者
function consumer(fn, time){
let tasks = [],
timer;
return function(...args){
tasks.push(fn.bind(this, ...args));
if(timer == null){
timer = setInterval(() => {
tasks.shift().call(this)
if(tasks.length <= 0){
clearInterval(timer);
timer = null;
}
}, time)
}
}
}
- Iterative
//同时操作多个
function iterative(fn) {
return function(subject, ...rest) {
if(isIterable(subject)) {
const ret = [];
for(let obj of subject) {
ret.push(fn.apply(this, [obj, ...rest]));
}
return ret;
}
return fn.apply(this, [subject, ...rest]);
}
}
//对原fn装饰一层
const setColor = iterative((el, color) => {
el.style.color = color;
});
//给hof传参
setColor(els, 'red');
收获知识点:了解常用hof函数的使用,以及一般适用场景。
JS算法相关知识点:
评判代码好坏
代码的好坏不是三言两语就能确定的,取决于风格,效率,约定,使用场景及设计等多方面因素。
美观
我们不能从代码外观去判断代码好坏,应该基于实际场景而考虑。如下代码中对m矩阵的判断尽管不是很美观,但是满足较好渲染条件下的最优写法,时间复杂度较低。
效率
代码的效率也不是评判代码好坏的唯一标准,如下代码中尽管时间复杂度是O(n),但实际使用场景中,需要处理的字符串也不会太长,也能达到较好的效果。
优化:上述代码的最优思路是用快速幂实现的,以2次幂的方式补字符。
洗牌算法
算法思路:每次在i + 1个数里随机选,能够保证概率是均等的。
function shuffle(cards) {
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
}
return c;
}
分红包 ── 分西瓜法
每次挑选数值最大的分。
分红包 ── 洗牌法
将红包数值扩大100倍,通过洗牌,对红包数值切分n - 1次,得到n个随机红包。