JavaScript 从入门到放弃
360前端星计划-第五课
主讲:月影老师
什么才是好的 JS 代码
各司其职
- HTML=》结构
- CSS=》样式
- JavaScript=》行为
复杂 UI 组件的设计
了解其原理,能够更好的对其应用
例子–京东轮播图
- 这样的 UI 组件如何去写
步骤1:结构设计HTML- 图片结构是一个列表型结构,所以主体用
- 使用 css 绝对定位将图片重叠在同一个位置
- 轮播图切换的状态使用修饰符(modifier)
- 轮播图的切换动画使用 css transition
- 图片结构是一个列表型结构,所以主体用
<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>
- API 设计
class Slider{
constructor(id){
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
}
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';
}
}
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);
}
}
const slider = new Slider('my-slider');
setInterval(() => {
slider.slideNext()
}, 3000)
- 控制流设计
控制结构=》自定义事件
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();
- 优化1:插件/依赖注入
- 优化2:改进插件/模板化
- 优化3:组件模型抽象
局部细节控制
例子:消失的方块–处理“只能执行一次”
- 有很多“只允许执行一次”的函数操作,如何进行统一的抽象?
过程抽象
once函数
function once(fn){
return function(...args){
if(fn){
let ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
应用–点击一次
block.onclick = once(function(evt){
console.log('hide');
evt.target.className = 'hide';
setTimeout(function(){
document.body.removeChild(block);
}, 2000);
});
节流throttle
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
防抖debounce
function debounce(fn){
let timer = null
return function(...args){
if(timer != null) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, 300)
}
}
声明式(Declarative)编程和命令式(imperative)编程
imperative:How to do?
let list = [1, 2, 3, 4];
let map1 = [];
for(let i = 0; i < list.length; i++){
map1.push(list[i] * 2);
}
Declarative:What to do?
let list = [1, 2, 3, 4];
const double = x => x * 2;
list.map(double);
高阶函数
它们自身输入函数或返回函数,被称为高阶函数
once、throttle、debounced、consumer、iterative等
总结
- 如何写好 JavaScript?
各司其职:JavaScript 尽量只做状态管理
结构、API、控制流分离设计 UI 组件
插件和模板化,并抽象出组件模型
运用过程抽象的技巧来抽象并优化局部 API