什么是好的 JS 代码:各司其职、组件封装、过程抽象 使用 JS 解决实际问题:如何评价一段代码的好坏、写代码最应关注什么
我们知道,前端 web 对于 HTML、CSS、JavaScript 的分工都很明确。
HTML 负责页面骨架、CSS 负责页面的渲染、JavaScript 负责页面的行为。
对于一个切换页面深色模式切换的需求,如果要用 JS,该怎么实现?
很容易想到:
使用 button,监听点击事件,更改页面背景颜色和文字颜色
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 = '🌞';
}
});
但是这个版本的实现语义不清,如果让别人来阅读这段代码,可能一时间不知道是在实现什么功能。
于是,我们想出优化方案:
同样使用 button,监听点击事件,但这次直接修改容器的 class,通过在 css 中写的 class 样式修改页面表现
const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
const body = document. body ;
if(body.className === 'night'){
body.className = '';
} else {
body.className = 'night';
}
});
很明显,这个版本已经比上个版本好多了,我们一眼就能看出来这段代码是在做什么。
但实际上,我们还有一种更好的解决方案 —— 只使用 CSS 实现:
checkbox
+ :checked
伪类 + 兄弟元素选择器来实现那么,实际上来说,表现层的工作就让负责表现层的 CSS 来做才是最好的
总结下来就是以下几点:
组件是指 Web 页面上抽出来一个个包含模版(HTML)、功能 (JS)和样式 (CSS) 的单元。
好的组件具备封装性、正确性、扩展性、复用性。
HTML 结构设计
<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 展现效果
#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;
}
行为设计:API
注意:API 设计应保证原子操作,职责单一,满足灵活性。
行为设计:Event 控制流
使用自定义事件来解耦
<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>
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
总的来说,就是要遵循以下基本方法:
一个🌰:操作次数限制
假如有一个需求,要求对某个函数的调用设置次数限制,我们可以直接在这个函数里面写上限制代码。
但是实际上,这个需求是可以通用的,如果对每一个函数都是有需求时更改内部代码,未免显得有点重复。
所以我们实际上可以通过一个代理函数 (高阶函数),写一个新的函数,接收一个函数参数,对其封装,并返回封装好的新函数,这样我们就完美地实现了这个需求。
function once(fn){
return (...args) => {
if(fn){
fn.apply(this, args);
fn = null;
}
}
}
函数分为两种,纯函数和非纯函数。
纯函数的意思是:任何时候,以相同的参数调用纯函数,输出也是相同的
那么其实非纯函数的意思就是相对的:非纯函数依赖外部环境,当外部环境参数改变时,即使用相同的参数调用,输出也会改变
显而易见,纯函数方便于后期的统一测试,而非纯函数还需要保证外部环境每次要统一(有时很难做到或很麻烦),所以现在更倾向于使用纯函数
// 纯函数
function add(a, b) {
return a + b;
}
// 非纯函数
let sum = function() {
let res = 0;
return (value) => res += value;
}
当使用高阶函数时,由于高阶函数一般都是纯函数,这样的话,由高阶函数封装的函数在测试时,就只需要测试原始函数即可,降低了测试成本
JavaScript 是一种既可以使用命令式又可以使用声明式的编程语言,例如:
// 命令式
function toggle(event) {
if(event.target.className === 'on'){
event.target.className = 'off';
}else{
event.target.className = 'on';
}
}
// 声明式
function toggleBuilder(...actions) {
return function(...args){
let action = actions.shift();
actions.push(action);
return action.apply(this, args);
}
}
let toggle = toggleBuilder(
event => event.target.className = 'off',
event => event.target.className = 'on'
);
那么我们应该使用什么编程范式呢?
思考这样一个问题,对于状态切换这个需求,如果我们需要调整状态的数量、先后,那么:
toggle
来说,我们需要直接修改这个函数,当需求频繁变化时,就会即为耗费人力和时间;
toggleBuilder
来说,我们只要在构建 toggle
时,调整传入的行为参数即可,既简单又直观。
发现了吗,声明式的函数要优于命令式的函数
但是在实际开发中,到底是使用哪种范式,还需要具体问题具体分析,在两种范式之间选择最适合的,才是最好的
先来看一段代码:
function isUnitMatrix2d(m) {
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
}
这段代码好不好?为什么?
其实有两种观点:
乍一看,对于简洁度和可读性来说,不好,明明可以使用更优雅的迭代来解决,却要用这么笨的方法
但是,其实这个函数的效率极高,如果这个函数需要在 requestAnimationFrame
中被高频调用,那么这种写法也不失为一种好的解决方案
风格 vs 效率
实际上我们应该根据使用场景来判断,对于效率优先的情况下,肯定要先考虑实现的效率问题,而如果多人协作开发和效率问题起冲突,那么我们就要在这两者之间做权衡了
所以其实没有绝对的判断代码好坏的标准,过度的优化、过度的设计难免会让理解成本成倍增加,所以一切都要从实际出发,结合实际考虑
抽象程度越高,可复用性就越高,同时理解成本也会越高