这是我参加「第五届青训营 」伴学笔记创作活动的第 3 天。
今日学习课程:JavaScript
一、JavaScript编码原则:各司其职
演示demo: 主题切换,需求为白昼模式与夜间模式的切换 由于只涉及到样式切换,按照「各司其职」的原则,建议只修改CSS代码,不涉及JavaScript
<input type="checkbox" id="mode">
<div class="content">
<label for="mode">
</div>
#mode {
display: none;
}
#mode:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
利用单选框的伪类选择器和兄弟节点选择器,实现背景与文字颜色的切换效果。由此,我们可知:
- HTML/CSS/JavaScript各司其职
- 应当避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零JS解决方案
二、JavaScript编码原则:组件封装
什么是组件?
组件是指Web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元 好的组件具有:
- 封装性
- 正确性
- 扩展性
- 复用性
demo: 原生JS实现轮播图
行为: API
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();
行为: 控制流
自定义事件,使组件状态与图片状态解耦
由此,可知封装组件的基本方法:
- 结构设计
- 展示效果
- 行为设计
- 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));
}
slideTo(idx){
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
}
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){}
function pluginPrevious(slider){}
function pluginNext(slider){}
const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();
其作用在于将HTML模板化,更易于扩展
三次重构:
- 插件化
- 模板化
- 抽象化(组件框架)
三、JavaScript编码原则:过程抽象
用于处理局部细节控制的一些方法,是函数式编程思想的基础应用
demo:once高阶函数
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args)
fn = null
return ret
}
}
}
为了能够让只执行一次的需求覆盖不同的事件处理,我们可以将这个需求剥离出来,这个过程我们称之为过程抽象
高阶函数(HOF)
- 以函数为参数
- 以函数为返回值
- 常用于作为函数装饰器
过程抽象优势:将非纯函数转换为纯函数,方便测试与维护
四、编程范式
命令式
switch.onclick = function(evt) {
if(evt.target.className === 'on') {
evt.target.className === 'off'
} else {
evt.target.className === 'on'
}
}
声明式
function toggle(...actions) {
return function(...args) {
let action = actions.shift()
actions.push(action)
return action.apply(this, args)
}
}
switch.onclick = toggle(
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
)
声明式优点:可以方便的添加其他的状态,比命令式添加if-else分支快捷
五、代码优化之路
交通灯状态切换
版本1 setTimeout嵌套
回调地狱,不希望看见这样的代码
版本2 状态抽象
const stateList = [
{ state: 'wait', time: 1000 },
{ state: 'stop', time: 3000 },
{ state: 'pass', time: 3000 }
]
版本3 过程抽象
缺点:过于复杂
版本4 异步+函数式
function wait(time){
return new Promise(resolve => setTimeout(resolve, time));
}
function setState(state){
traffic.className = state;
}
async function start(){
//noprotect
while(1){
setState('wait');
await wait(1000);
setState('stop');
await wait(3000);
setState('pass');
await wait(3000);
}
}
start();
六、总结
这节课月影老师带领我们比较深入的领略了JavaScript编码的特点,包括三个主要特征和代码优化,让人受益匪浅。