这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
本次课程从实践维度解读在实际编码过程中何种类型的 JavaScript 代码称之为“好代码”,并从 JS 出发,总结其他语言编码可遵循的共性原则。
写好JS的三大原则
- 各司其职:让HTML、CSS和JavaScript三者分离(指自己处理自己应该处理的事)
- 组件封装:组件应当具有准确性、可拓展性和复用性
- 过程抽象:函数式编程思想
各司其职(深夜食堂)
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代码可能看起来不是很复杂,但是却违背了js三大原则之一的各司其职原则,通过js来直接更改css中的属性值或类名。
改进
//html
<input id="modeCheckBox" type="checkbox">
<div class="content">
<header>
<label id="modeBtn" for="modeCheckBox"></label>
<h1>深夜食堂</h1>
</header>
<main>
<div class="pic">
<img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
</div>
<div class="description">
<p>
这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈眶。
</p>
</div>
</main>
</div>
//css
#modeCheckBox {
display: none;
}
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
#modeBtn {
font-size: 2rem;
float: right;
}
#modeBtn::after {
content: '🌞';
}
#modeCheckBox:checked + .content #modeBtn::after {
content: '🌜';
}
使用css中的伪类选择器来实现样式的改变,做到了用css来更改css样式。
注
- HTML/CSS/JS各司其职
- 应当避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零JS方案
组件封装
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;
const controller = this.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){
this.slideTo(idx);
this.stop();
}
});
controller.addEventListener('mouseout', evt=>{
this.start();
});
this.container.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';
})
}
const previous = this.container.querySelector('.slide-list__previous');
if(previous){
previous.addEventListener('click', evt => {
this.stop();
this.slidePrevious();
this.start();
evt.preventDefault();
});
}
const next = this.container.querySelector('.slide-list__next');
if(next){
next.addEventListener('click', evt => {
this.stop();
this.slideNext();
this.start();
evt.preventDefault();
});
}
}
getSelectedItem(){
let selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx){
let selected = this.getSelectedItem();
if(selected){
selected.className = 'slider-list__item';
}
let 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(){
let currentIdx = this.getSelectedItemIndex();
let nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
let currentIdx = this.getSelectedItemIndex();
let previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
this.slideTo(previousIdx);
}
start(){
this.stop();
this._timer = setInterval(()=>this.slideNext(), this.cycle);
}
stop(){
clearInterval(this._timer);
}
}
const slider = new Slider('my-slider');
slider.start();
上述代码能完成轮播图效果的制作,但存在着不足,上述代码中,控制元素和组件是绑定在一块的,当用户不想要某个控制元素,如下面的控点,需要修改很多代码。
通过将控制元素抽取成插件,插件与组件之间通过依赖注入的方式来建立联系
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();
});
}
}
这样子,当我们想要新增一个插件时,就不需要更改很多代码,只需将组件注册到代码中即可,但当我们要删除部分组件时,还需到html中删除部分标签。
通过将HTML模板化,方便进行扩展
class Slider{
constructor(id, opts = { images: [], cycle: 3000 }) {
...
}
render() {
const images = this.options.images;
const content = images.map(image => `
<li class="slider-list__item">
<img src="${iamge}" />
</li>
`.trim());
return `<ul>${content.join('')}</ul>`;
}
...
}
将通用的组件模型抽象出来,用来优化代码。
class Component{
constructor(id, opts = {name, data:[]}){
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML = this.render(opts.data);
}
registerPlugins(...plugins){
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div');
pluginContainer.className = `.${name}__plugin`;
pluginContainer.innerHTML = plugin.render(this.options.data);
this.container.appendChild(pluginContainer);
plugin.action(this);
});
}
render(data) {
/* abstract */
return ''
}
}
过程抽象
用来处理局部细节控制
在想象过程抽象中,可以抽象成有一个房间,房间里面的门,窗,然后房间空间本身都是数据,但是开门或者开窗的开这个动作就是过程,也就说我们不仅可以将门,窗,空间抽象成数据,开这个过程也是可以来作为抽象对象的。
操作次数限制
通过高阶函数,以函数作为参数,就能得到写出一个只执行一次或者限制次数的函数,例如下面Once()函数,这个函数的参数是一个函数fn,然后执行时会返回一个函数出去,在返回函数中间进行判断fn是否存在,如果存在则用实际参数执行fn,然后将fn=null这样下次就不会执行该函数。
function once(fn){
return function(...args){
if(fn){
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
总结
对于JS来讲,我以前学的只是一些皮毛,通过课程,我了解了编码的三大原则,也对JS有了一个更深入的了解。与此同时,我也看到了自己的不足,需要更加深入地去学习一下JS。