这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天✌
课堂笔记
本堂课重点内容:
- JavaScript 好代码的标准
- HTML/CSS/JS 各司其责
详细知识点介绍
1.写好js代码的一些原则
- 各司其职:让HTML、CSS、JavaScript代码分离
- 组件封装:好的UI组件具备正确性、扩展性、复用性。
- 组件的定义解析及特征
- 组件封装基本方法
- 利用原生 JS 实现电商网站轮播图
- 过程抽象:应用函数式编程实现
- 过程抽象概念
- 高阶函数使用模式
- JavaScript 编程范式
2.各司其职
解耦HTML、CSS和JavaScript,HTML是数据,CSS是表现,JavaScript是行为,要做到做到数据层、表现层和行为层各司其职
具体举例子,实现白天☀/夜间🌙模可切换的阅读模式
方法一(基础版):
JS部分代码:
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 = '';
}
});
弊端:
- JS直接操作body的style(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 = '';
}
});
改进之处:
仅仅修改了className,避免了使用JS直接操作CSS,符合代码松散解耦的特点。
方法三(推荐版):
推荐之处:
实现了零JS表现方案,符合编码各司其职的原则。
总结:
- HTML/CSS/JS各司其职
- 应当避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零JS方案(可以用CSS实现
2. 组件封装
组件:指Web页面上抽出来的一个个包含模板(HTML)、功能(JS)、样式(CSS)的单元
好的组件具备封装性、正确性、扩展性和复用性。
实现步骤:
①结构设计: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>
<li class="https://slider-list__item">
<img src="htttps://p4.ssl.qhimg.com/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(功能):
1)规则:
- 保证原子操作
- 职责要单一
- 满足灵活性
2)定义一个Slider类,实现以下API:
-
+getSelectedItem( ) 当前选中被展现出的图
-
+getSelectedItemIndex( ) 当前选中被展现出的图的下表
-
+slideTo( ) 跳转并展示某个图
-
+slideNext( ) 切换下一张图
-
+slidePrevious( ) 切换上一张图
实现代码:
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);
}
}
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)
总结:基本方法
- 结构设计
- 展现效果
- 行为设计
- API(功能)
- Event(控制流)
优化
①重构:插件化
解耦
- 将控制元素抽取为插件
- 插件与组件之间通过注入依赖方式建立联系
class Slider{
constructor(id, cycle = 3000){
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = cycle;
}
registerPlugins(...plugins){
plugins.forEach(plugin => plugin(this));
}
getSelectedItem(){
const selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
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';
}
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
}
slideNext(){
const currentIdx = this.getSelectedItemIndex();
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);
}
addEventListener(type, handler){
this.container.addEventListener(type, handler)
}
start(){
this.stop();
this._timer = setInterval(()=>this.slideNext(), this.cycle);
}
stop(){
clearInterval(this._timer);
}
}
// 封装控制器函数
function pluginController(slider){
const controller = slider.container.querySelector('.slide-list__control');
if(controller){
const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
controller.addEventListener('mouseover', evt=>{
const idx = Array.from(buttons).indexOf(evt.target);
if(idx >= 0){
slider.slideTo(idx);
slider.stop();
}
});
controller.addEventListener('mouseout', evt=>{
slider.start();
});
slider.addEventListener('slide', evt => {
const idx = evt.detail.index
const selected = controller.querySelector('.slide-list__control-buttons--selected');
if(selected) selected.className = 'slide-list__control-buttons';
buttons[idx].className = 'slide-list__control-buttons--selected';
});
}
}
// 封装左滑函数
function pluginPrevious(slider){
const previous = slider.container.querySelector('.slide-list__previous');
if(previous){
previous.addEventListener('click', evt => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
}
// 封装右滑函数
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();
});
}
}
const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
②重构:模板化
解耦
- 将HTML模板化,更易于扩展
例如:
如果想去掉轮播图中四个小圆点,利用模板化对数据内容实现抽离可以更加轻易实现:
render():渲染
action():初始化
只需注释掉(pluginController)即可实现
③组件框架
- 将组件通用模型抽象出来
关系图:
代码模板:
总结
- 组件设计的原则: 封装性、正确性、扩展性、复用性;
- 实现组件的步骤: 结构设计、展现效果、行为设计;
- 三次重构:
- 插件化
- 模板化
- 抽象化(组件框架)
课后个人总结
前端不仅仅是技术代码上的实现,也应该遵守标准的编码原则和思维上的标准规范,才能降低差异性,更利于团队协作。