写好JS的原则
各司其职
- 让HTML、CSS和JavaScript职能分离
- 应当避免不必要的由JS直接操作样式
- 可以用 class 来表示状态
- 纯展示类交互寻求零JS解决方案
【例】写一段JS,控制一个网页,让它支持浅和深色两种浏览模式。
版本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 = '🌞';
}
});
样式和行为没有分离,即没有做到职责分离。更改样式只能改动JavaScript中的代码。
版本2
const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
const body = document.body;
if(body.className !== 'night') {
body.className = 'night';
} else {
body.className = '';
}
});
通过不同类名绑定不同的CSS样式,达到样式变更的目的。尽可能的把样式都抽离到 CSS 当中,样式变更只需要修改对应类的CSS代码,职责分离,代码可维护性提升。
版本3
<body>
<input id="modeCheckBox" type="checkbox">
<div class="content">
<header>
<label id="modeBtn" for="modeCheckBox"></label>
<h1>深夜食堂</h1>
</header>
<main>
...
</main>
</div>
</body>
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
样式的更改在HTML的基础上完全依靠CSS实现,没有使用JavaScript代码,但可维护性没有下降,真正体现了HTML\CSS\JavaScript各司其职。
组件封装:
- 组件是指 Web 页面上抽出来一个个包含模版(HTML)、功能(JS)和样式(CSS)的单元。
- 好的组件具备封装性、正确性、扩展性、复用性。
【例】用原生 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/t01abe3351db853eb3.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/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;
}
行为:JS
API:
class Slider {
constructor(id) {
this.container = document.getElementById(id);
this.items = this.container
.querySelectorAll('.slider-list__item, .slider-list__item--selected');
}
getSlectedItem() {
const selected = this.container
.querySelector('.slider-list__item--selected');
return selected;
}
getSlectedItemIndex() {
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.getSlectedItemIndex();
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);
}
}
控制流 使用自定义事件来进行解耦。
<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)
总结:基本方法
- 结构设计 HTML
- 展现效果 CSS
- 行为设计 JS,包含 API(功能)和 Event(控制流)
上述代码存在构造函数太复杂,可扩展性和灵活性相对较小的问题—— 重构
-
插件化
解耦:将控制元素抽取成插件;插件与组件之间通过依赖注入方式建立联系
-
模版化
解耦:将HTML模板化,更易于扩展
-
组件框架
抽象:将通用的组件模型抽象出来
总结:
- 组件设计的原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计
- 三次重构:插件化、模板化、抽象化(组件框架)
过程抽象:
- 用来处理局部细节控制的一些方法
- 函数式编程思想的基础应用
为了能够让“只执行一次”的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象。
高阶函数(HOF):以函数作为参数;以函数作为返回值;常用于作为函数装饰器
-
Once 一次函数:只执行一次
-
Throttle 节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数
-
Denounce 防抖:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次
-
Consumer:消费数据
-
Iterative:迭代器
使用高阶函数可以大大减少使用非纯函数的可能性。
纯函数:函数的返回结果只依赖于它的参数;如果输入的值是唯一的,且输出的值也是唯一的;并且在执行过程里面没有副作用。
使用纯函数能提高代码的可维护性。
编程范式
命令式与声明式
命令式编程:命令机器如何去做事情,这样不管你想要的是什么,它都会按照你的命令实现。
声明式编程:告诉机器你想要的是什么,让机器想出如何去做,这种方式的可扩展性会更高。
【例】
学习心得:
过程抽象是指将一系列操作或功能抽象成一个高级的、可复用的功能单元,以提高代码的可维护性和复用性。在JavaScript中,过程抽象常常通过高阶函数来实现。过程抽象能够让我们将一些通用的操作抽象出来,使代码更加简洁、易读,并提高代码的可维护性和扩展性。它是函数式编程思想的基础应用,能够帮助我们更好地组织和管理代码。