这是我参与「第四届青训营 」笔记创作活动的的第3天
JS原则
各司其责:HTML、CSS和JS职能分离
组件封装:正确性、扩展性、复用性
过程抽象:函数式编程思想
各司其责
Q:夜间模式的实现
职能分离原则,采用JS控制类名添加
📌 纯CSS实现
总结
- HTML/CSS/JS 各司其责
- 应当避免不必要的由 JS 直接操作样式
- 可以用 class 来表示状态
- 纯展示类交互寻求零 JS 方案
组件封装
📌 控制UI组件
-
轮播图
- 使用绝对定位使多张图片重叠在一起
- 轮播图切换的状态使用修饰符(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>
#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;
}
- 组件封装
/*
- Slider
- +getSelectedItem()
- +gerSelectedItemIndex()
- +slideTo()
- +slideNext()
- +slidePrevious()
*/
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:控制流
-
代码耦合度高 → JS插件化+依赖注入
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();
});
}
}
- 数据控制视图 → 模板化+render函数
class Slider{
constructor(id, opts = {images:[], cycle: 3000}){
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML = this.render();
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = opts.cycle || 3000;
this.slideTo(0);
}
render(){
const images = this.options.images;
const content = images.map(image => `
<li class="slider-list__item">
<img src="${image}">
</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 ''
}
}
总结
-
结构设计、展现效果、行为设计
-
三次重构,逐步深入,不改变各司其责的思想
- 插件化
- 模板化
- 抽象化(组件框架)
过程抽象
📌 处理局部细节(典例:react hooks)
高阶函数
关于高阶函数的介绍www.jianshu.com/p/acb132ec9…
HOF(Higher-Order Function),可以把函数作为参数,或是将函数作为返回值的函数。
📌 后续传递风格(Continuation Passing Style):函数装饰器
常用高阶函数:
- Once:操作次数限制,常用于异步交互和HTTP请求
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
- Debounce:防抖,触发事件停止后一段时间才执行
var i = 0;
setInterval(function(){
bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
function debounce(fn, dur){
dur = dur || 100;
var timer;
return function(...arguments){
clearTimeout(timer);
// 不再触发函数时,间隔dur之后就会执行函数
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));
- Throttle:节流,触发事件大于等于间隔时间触发
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
// 让函数正常执行
fn.apply(this, args);
// 然后封印函数的触发time时间
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
btn.onclick = throttle(function(e){
circle.innerHTML = parseInt(circle.innerHTML) + 1;
circle.className = 'fade';
setTimeout(() => circle.className = '', 250);
});
- Consumer
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:可迭代
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');
为什么要用高阶函数解决这些问题?
什么是纯函数?
A pure function is a function which:
- Given the same input, always returns the same output.
- Produces no side effects.
个人理解:无副作用(例如用闭包来清理、隔离变量)的有限状态机
纯函数用什么好处?
可预测(给定输入就知道输出,可预测的状态机);
测试难度高,维护性差;
开销小。
📌 高阶函数的输入输出是确定的,return只由输入fn决定。便于测试。
编程范式:命令式 vs. 声明式
命令式强调怎么做(HOW)
每多一种情况,就需要增加逻辑分支
let list = [1, 2, 3, 4];
let mapl = [];
for(let i = 0; i < list.length; i++) {
mapl.push(list[i] * 2);
}
声明式强调做什么
增加一种情况只需增加一个状态
let list = [1, 2, 3, 4];
const double = x => x * 2;
list.map(double);
📌 声明式相比于命令式具有天然的可扩展的优势
this指针问题
原型继承和类继承的区别:构造函数调用。
写代码应该关注什么?
- 风格
- 效率
- 约定
- 使用场景
- 设计