这是我参与「第五届青训营 」伴学笔记创作活动的第3天
一、主要要点
- 写好JS的原则
- 组件封装---轮播图
- 过程抽象
- 高阶函数
- 编程范式
二、详细介绍
2.1 写好JS原则
-
各司其责
- HTML/CSS/JS 各司其责
- 应当避免不必要的由js直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求JS方案
-
组件封装
-
过程抽象
下面就来说一下组件封装以及过程抽象.
2.2 组件封装--以轮播图为例
组件是指Web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元.
- 封装性
- 正确性
- 扩展性
- 复用性
注意:将html、css、js代码copy即可运行,js文件引用时,要放在html结构的下面。
结构设计:HTML
轮播图是一个典型的列表结构,我们可以使用无序列表ul元素来实现.
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85f068e8d7824908a68b03af694f4195~tplv-k3u1fbpfcp-zoom-1.image" />
</li>
<li class="slider-list__item">
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/967634ea049343dea6cd530cfe0edad5~tplv-k3u1fbpfcp-zoom-1.image" />
</li>
<li class="slider-list__item">
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45ad0d7b1d0a484a9076aafdb8ebb732~tplv-k3u1fbpfcp-zoom-1.image" />
</li>
<li class="slider-list__item">
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6cbab4d58a4407494a1aa01f79c6796~tplv-k3u1fbpfcp-zoom-1.image" />
</li>
</ul>
</div>
展现效果:CSS
- 使用CSS绝对定位将图片重叠在同一个位置
- 轮播图切换的状态使用修饰符(modofier)
- 轮播图的切换动画使用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
- API设计应保证原子操作,职责单一,满足灵活性
- Slider
- getSelectedItem() ---------------------得到当前选中的图片元素
- getSelectedItemIndex() ----------------得到当前选中图片元素在列表中的下标
- slide To()-----------------------------到某个特定index的元素上
- slideNext()----------------------------向后轮播下一张图片
- slidePrevious()------------------------向前轮播上一张图片
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()
}, 2000)
以上html+css+js实现的效果缺少左右按钮和底部圆点
控制流
添加左右按钮和底部圆点
- 使用自定义事件来解耦
完整代码:
重构:插件化
解耦
- 将控制元素抽取成插件
- 插件与组件之间通过依赖注入方式建立联系
重构:模板化
解耦
- 将HTML模板化,封装在自定义函数render()内,更易于扩展
重构:组件框架
抽象
- 将通用的组件模型抽象出来 完整代码:
2.3 过程抽象
-
用来处理局部细节控制的一些方法
-
函数式编程思想的基础应用
举例:操作次数限制
-
一些异步交互
-
一次性的HTTP请求
2.4 高阶函数
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
常用高阶函数
once
为了能够让'只执行一次'的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们成为过程抽象.
Throttle
地址:code.h5jun.com/gale/1/edit…
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
btn.onclick = throttle(function(e){
circle.innerHTML = parseInt(circle.innerHTML) + 1;
circle.className = 'fade';
setTimeout(() => circle.className = '', 250);
});
Debounce
地址:code.h5jun.com/wik/edit?js…
var i = 0;
setInterval(function(){
bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
function debounce(fn, dur){
dur = dur || 100;
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
}
}
document.addEventListener('mousemove', debounce(function(evt){
var x = evt.clientX,
y = evt.clientY,
x0 = bird.offsetLeft,
y0 = bird.offsetTop;
console.log(x, y);
var a1 = new Animator(1000, function(ep){
bird.style.top = y0 + ep * (y - y0) + 'px';
bird.style.left = x0 + ep * (x - x0) + 'px';
}, p => p * p);
a1.animate();
}, 100));
Consumer/2
地址:code.h5jun.com/roka/7/edit…
function consumer(fn, time){
let tasks = [],
timer;
return function(...args){
tasks.push(fn.bind(this, ...args));
if(timer == null){
timer = setInterval(() => {
tasks.shift().call(this)
if(tasks.length <= 0){
clearInterval(timer);
timer = null;
}
}, time)
}
}
}
function add(ref, x){
const v = ref.value + x;
console.log(`${ref.value} + ${x} = ${v}`);
ref.value = v;
return ref;
}
let consumerAdd = consumer(add, 1000);
const ref = {value: 0};
for(let i = 0; i < 10; i++){
consumerAdd(ref, i);
}
Iterative
地址:code.h5jun.com/kapef/edit?…
const isIterable = obj => obj != null
&& typeof obj[Symbol.iterator] === 'function';
function iterative(fn) {
return function(subject, ...rest) {
if(isIterable(subject)) {
const ret = [];
for(let obj of subject) {
ret.push(fn.apply(this, [obj, ...rest]));
}
return ret;
}
return fn.apply(this, [subject, ...rest]);
}
}
const setColor = iterative((el, color) => {
el.style.color = color;
});
const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');
2.5 编程范式
- 命令式
- 声明式
例子:Toggle
1.命令式
if(evt.target.className === 'on'){
evt.target.className = 'off';
}else{
evt.target.className = 'on';
}
}
2.声明式
function toggle(...actions){
return function(...args){
let action = actions.shift();
actions.push(action);
return action.apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);
三、 总结
再次体验到了组件封装的重要性,在写JS的同时,代码优化也极为重要;这节课中,也再次重温理解了高阶函数,对防抖和节流又有了新的认识。