如何写好JavaScript
写好JS的一些原则
- 让html、css、js各司其责
- 好的UI组件具备正确性、拓展性、复用性
- 应用函数式编程思想
深夜食堂代码优化
版本一有什么问题?
拿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 = '';
}
});
结论
- HTML、CSS、JS各司其责
- 应当避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互零js方案
组件封装
什么叫组件?
指web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元。好的组件具备封装性、正确性、拓展性、复用性。
轮播图实现
- 结构:HTML
- 无序列表
- 表现:CSS
- 绝对定位、修饰符modifier、切换动画transition
- 行为:JS
- 行为设计:API(getSelectedItem、getSelectedItemIndex、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');
slider.slideTo(3);
- 行为:控制流 使用自定义时间来解锁耦
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
<a class="slide-list__next"></a>
<a class="slide-list__previous"></a>
<div class="slide-list__control">
<span class="slide-list__control-buttons--selected"></span>
<span class="slide-list__control-buttons"></span>
<span class="slide-list__control-buttons"></span>
<span class="slide-list__control-buttons"></span>
</div>
总结:基本方法
- 结构设计
- 展现效果
- 行为设计
- API
- Event
思考:改进空间
重构:插件化
解耦
- 将控制元素抽取成插件
- 插件与组件之间通过依赖注入方式建立联系
- 将HTML模板化,更易于扩展
抽象
将通用的组件模型抽象出来
总结
- 组建设计的原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计
- 三次重构
- 插件化
- 模板化
- 抽象化(组件框架)
过程抽象
- 用来处理局部细节控制的一些方法
- 函数式编程思想的基本应用
例子:操作次数限制
- 一些异步交互
- 一次性的HTTP请求
Once
为了能够让“只执行一次”的需求覆盖不同的时间处理,我们可以将这个需求剥离出来,这个过程我们称为过程抽象。
高阶函数
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数修饰器
常用的高阶函数
- Once
- Throttle
- Debounce
- Consumer
- Iterative
为什么要使用高阶函数
使用高阶函数(Higher-Order Functions)具有许多好处和优势,下面是其中的一些原因:
-
增强代码复用性:高阶函数可以接受其他函数作为参数或返回函数作为结果。这使得我们可以编写通用的函数,通过传递不同的函数作为参数来实现不同的功能。这可以大大增强代码的复用性,减少重复编写相似功能的代码。
-
提升代码的抽象程度:高阶函数能够提供更高层次的抽象,将常见的功能模式封装为可复用的函数。这可以使代码更加简洁和可读,将复杂的逻辑抽象为简单的函数调用。通过提升代码的抽象程度,可以更好地理解和维护代码。
-
实现函数的组合和管道:高阶函数使得函数的组合和管道操作变得更加容易。我们可以将多个函数组合在一起,以便按照特定的顺序应用它们,并将结果传递给下一个函数。这种函数组合和管道的方式可以使代码更加模块化和可组合。
-
支持函数式编程范式:高阶函数是函数式编程的重要概念之一。函数式编程强调将计算视为函数的应用,而不是一系列可变状态的操作。通过使用高阶函数,我们可以更加自然地编写函数式风格的代码,避免副作用和共享状态,使代码更加可靠和易于测试。
-
扩展函数的功能:高阶函数允许我们在不改变原始函数定义的情况下,通过包装函数来扩展其功能。这种思想通常被称为函数装饰器(Function Decorators),可以用于添加日志记录、缓存、错误处理等功能,从而增强原始函数的能力。
总而言之,使用高阶函数可以提升代码的复用性、抽象程度和可组合性,支持函数式编程范式,并扩展函数的功能。通过灵活运用高阶函数,我们可以编写更加模块化、可读性强且容易维护的代码。
编程范式
命令式与声明式
- 命令式编程: 命令式编程是一种通过编写明确的指令和算法来实现程序的方式。
- 声明式编程: 声明式编程是一种通过描述所需的结果,而非具体的步骤和算法来实现程序的方式。
例子
- Toggle-命令式
switcher.onclick = function(evt){
if(evt.target.className === 'on'){
evt.target.className = 'off';
}else{
evt.target.className = 'on';
}
}
- Toggle-声明式
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'
);
- Toggle-三态
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 = 'warn',
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);