这是我参与「第四届青训营 」笔记创作活动的第1天
写好JS的一些原则
各司其责
- 概念:各司其责就是就是让html,css,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 = '🌞';
}
});
以上代码通过操作dom元素来修改样式,也就是用js来操作了css样式,看起来没问题,用起来也没问题,接下来让我们看第二段代码
const btn = document.getElementById('modeBtn');
btn.addEventListener('click', (e) => {
const body = document.body;
if(body.className !== 'night') {
body.className = 'night';
} else {
body.className = '';
}
});
没有对比就没有伤害,第二段代码对比第一段,能够发现第二个代码则是定义了一个class,通过添加和删除class来修改其样式。接下来让我们看看第三个版本
//此处是html代码
<input id="modeCheckBox" type="checkbox">
<div class="content">
<header>
<label id="modeBtn" for="modeCheckBox"></label>
<h1>深夜食堂</h1>
</header>
<main>
<div class="pic">
<img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
</div>
<div class="description">
<p>
这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈
眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返 [6] 。
</p>
</div>
</main>
</div>
//此处是css代码
```
#modeCheckBox {
display: none;
}
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
```
用第三段对比前两个版本的代码,能够发现,版本3通过了纯html+css实现了该功能,没有用到js。那么第三个版本是如何实现的,其实是通过伪类选择器实现,通过CheckBox来修改样式。
组件封装
-
概念:好的UI组件应该具备正确性、扩展性、复用性。
-
基本方法
- 结构设计
- 展现效果
- 行为设计
- API
- Event
-
重构:插件化
- 将控制元素抽取成插件,插件组件之间通过依赖注入建立联系
-
重构:模板化
- 将HTML模板化,更易于扩展
- 组件框架
- 将组件通用模型抽象出来
- 总结:
- 组件设计的原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计
- 三次重构
- 插件化
- 模板化
- 抽象化(组件框架)
过程抽象
- 概念:应用函数式编程思想。
- 用来处理局部细节控制的一些方法
- 函数式编程思想的基础应用
高阶函数
Once
为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象。
function once(fn) {
//outer scope closure
return function(...args) {
//inner scope
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
HOF
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
function HOF0(fn) {
//默认的等价高阶函数,一般的高阶函数都是在该基础上做修改
return function(...args) {
return fn.apply(this, args);
}
}
常用的高阶函数
- Once
- Throttle
- 节流函数
- 使用场景:一些mousemove鼠标跟随事件,触发频率过高,而我们不需要这么高的频率,就可以使用节流函数。
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);
})
- Debounced
- 防抖
- 使用场景:适用于一些需要实时保存的需求,比如键盘输入内容并保存,每次保存会对服务器造成负担,需要等键盘敲击停下,内容不再变化的时候在进行保存
- 案例:小鸟根据鼠标移动,不会一直追踪,只有等到鼠标停止才会追踪上去
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,所以如果一直不到时间,会一直清除
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
- 将同步调用改成异步调用
- 使用场景:在游戏里面连续快速点击,使其每隔一段时间改变一次数据
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');
- 为什么要使用高阶函数:
- 纯函数:返回值只由它调用时的参数决定,结果可预期,无副作用
- 高阶函数一般都是纯函数
- 尽量用纯函数,减少非纯函数的使用
编程范式
命令式与声明式
- 命令式(更倾向于怎么做)
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];
let mapl = [];
for(let i = 0; i < list.length; i++) {
mapl.push(list[i] * 2);
}
案例
实现on和off的切换
- 命令式
switcher.onclick = function(evt){
if(evt.target.className === 'on'){
evt.target.className = 'off';
}else{
evt.target.className = 'on';
}
}
- 声明式
//JS代码
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'
);
//CSS代码
#switcher {
display: inline-block;
background-color: black;
width: 50px;
height: 50px;
line-height: 50px;
border-radius: 50%;
text-align: center;
cursor: pointer;
}
#switcher.on {
background-color: green;
}
#switcher.off {
background-color: red;
}
#switcher.on:after {
content: 'on';
color: white;
}
#switcher.off:after {
content: 'off';
color: white;
}
- 优缺点:当切换状态比较多的时候,命令式需要添加分支,而声明式只需要添加状态,声明式比命令式有更好的扩展性