这是我参与「第四届青训营 」笔记创作活动的的第3天
写好JS的一些原则
- 各司其职 让HTML CSS JavaScript职能分离
- 组件封装 好的UI组件具备正确性、拓展性、复用性
- 过程抽象 应用函数式编程思想
各司其职
写一段JS,控制一个网页,让他支持深色浅色两种浏览模式。
版本一
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 = '';
}
});
css代码:
body.night {
background-color: black;
color: white;
transition: all 1s;
}
版本一与版本二,功能都已完成,但二与一的区别就在于二是写了一个night的样式,点击之后根据类名切换,默认白天点击黑夜。
版本二只操作了元素状态,而版本一直接操作了css样式,这里就表现出了结构表现分离的原则。优势就在于更直观的表明了代码的功能。
当然如果是纯css样式变动的话,也可以不使用JS代码,直接对css进行操作。
结论:
- HTML/CSS/JS各司其责
- 应当避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零JS方案
组件封装
用原生JS写一个电商网站的轮播图
组件是指Web页面上抽出来一个个包含模版(HTML)、功能((JS)和样式(CSS)的单元。 好的组件具备封装性、正确性、扩展性、复用性。
-
结构:HTML 使用无序列表ol实现
-
表现:CSS
- 使用CSS绝对定位将图片重叠在同一个位置
- 轮播图切换的状态使用修饰符(modifier)
- 轮播图的切换动画使用CSS transition
-
行为:JS 过程抽象
-
用来处理局部细节控制的一些方法
-
函数式编程思想的基础应用 操作次数限制
-
一些异步交互
-
一次性的HTTP请求
一个学习列表功能,点击表示任务完成,从列表中消失
function once(fn) {
return function(...args) {
if(fn){
const ret =fn.apply(this, args);
fn = null;
return ret;
}
}
}
const list=docunent.querySelector('ul');
const buttons =list.querySelectorAll("button");
buttons.forEach((button) => {
button.addEventListener('click', once((evt)=>{
const target = evt.target;
target.parentNode.className = "completed";
setTimeout(() => {
list.romoveChild(target.parentNode);
}, 2000);
}));
});
经典的过程抽象,抽象了once这个过程,参数fn是函数,作用为把里面的函数在任何函数进行调用时只被调用一次。 once保证只进行一次调用。
高阶函数 HOF
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
function HOFO(fn){
return function(...args){
return fn.apply(this, args);
}
}
常用高阶函数
- Once
- Throttle
- Debounce
- Consumer/2
- lterative
当一定时间间隔内只需触发一次时使用 Throttle
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null) {
fn.apply(this,args);
timer = setTineout(()=>{
timer = null;
}, time)
}
}
}
btn.onclick = throttle(function(e){
circle.innerHTML = parseInt(circle.innerHTML)+1;
circle.classNane= 'fade';
setTimeout(()=>cirele.className = '',250);
});
实现每500只能调用一次的功能 其余的高阶函数就不多介绍
实现多个交通灯状态切换功能
版本一
const traffic = document.getElementById('traffic');
(function reset(){
traffic.className= 's1';
setTimeout(function(){
traffic.className= 's2';
setTimeout(function(){
traffic.classNane = 's3';
setTimeout(function(){
traffic.className = 's4';
setTimeout(function(){
traffic.classMane = 's5';
setTimeout(reset, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000)
})();
多个异步嵌套,较难维护
版本二(数据抽象)
<ul id="traffic" class="wait">
<li></li>
<li></li>
<li></li>
</ul>
#traffic {
display: flex;
flex-direction: column;
}
#traffic li {
display: inline-block;
width: 50px;
height: 50px;
background-color: gray;
margin: 5px;
border-radius: 50%;
}
#traffic.stop li:nth-child(1) {
background-color: #a00;
}
#traffic.wait li:nth-child(2) {
background-color: #aa0;
}
#traffic.pass li:nth-child(3) {
background-color: #0a0;
}
const traffic = document.getElementById('traffic');
const stateList = [
{state: 'wait', last: 1000},
{state: 'stop', last: 3000},
{state: 'pass', last: 3000},
];
function start(traffic, stateList){
function applyState(stateIdx) {
const {state, last} = stateList[stateIdx];
traffic.className = state;
setTimeout(() => {
applyState((stateIdx + 1) % stateList.length);
}, last)
}
applyState(0);
}
start(traffic, stateList);
版本三(过程抽象)
const traffic = document.getElementById('traffic');
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function poll(...fnList){
let stateIndex = 0;
return async function(...args){
let fn = fnList[stateIndex++ % fnList.length];
return await fn.apply(this, args);
}
}
async function setState(state, ms){
traffic.className = state;
await wait(ms);
}
let trafficStatePoll = poll(setState.bind(null, 'wait', 1000),
setState.bind(null, 'stop', 3000),
setState.bind(null, 'pass', 3000));
(async function() {
// noprotect
while(1) {
await trafficStatePoll();
}
}());
版本四(异步+函数式)
const traffic = document.getElementById('traffic');
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();