这是我参与「第五届青训营 」伴学笔记创作活动的第 16 天
-
如何写好JavaScript
-
各司其责
-
组件封装
-
过程抽象
-
-
写代码应该关注的要点
什么才是好的javascript代码?
入门:
- javascript权威指南(犀牛书)
- javascript高级程序设计(红宝书)
javascript the good parts (js精髓)薄
写好js的一些原则
- 各司其责 让html , css, js 职能分离
- 组件封装 好的UI组件具备正确性、扩展性、复用性。
- 过程抽象 应用函数式编程思想。
各司其责
e.g写一段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 = '';
}
});
版本二比版本一可读性更高,符合“各司其责”原则
版本三:html + css
#modeCheckBox: checked + .content{
background-color:black;
color:white;
transition:all 1s;
}
样式更改可以直接使用纯css,而不使用js代码
结论
- html / css /js 各司其职
- 应当避免不必要的由js直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零js方案
组件封装
做一个电商平台的轮播图 组件是指web页面上抽出来的一个个包含模版(html)、功能(js)和样式(css)的单元
好的组件具备封装性、正确性、扩展性、复用性
结构设计(html)
轮播图是一个列表结构,可以使用无序列表<ul>元素实现
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="xxx.png">
</li>
<li class="slider-list__item">
<img src="xxx.png">
</li>
<li class="slider-list__item">
<img src="xxx.png">
</li>
<li class="slider-list__item">
<img src="xxx.png">
</li>
</ul>
</div>
展现效果 (css)
使用css绝对定位将图片重叠在同一个位置 轮播图切换的状态使用修饰符(modifier) 轮播图的切换动画用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设计应保证 API设计应保证原子操作,职责单一,满足灵活性
- Slider
- +getSelectedItem()
- +getSelectedItemIndex()
- +slideTo()
- +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 = 'slder-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);
}
}
行为设计:控制流
- 使用自定义事件来解耦。
<a class="slide-list__next"></a>
<a class="slide-list__previous"></a>
<div class="slide-list__control">
<span class="slide-list__control-button--selected"></span>
<span class="slide-list__control-button"></span>
<span class="slide-list__control-button"></span>
<span class="slide-list__control-button"></span>
</div>
const detail = {index: idx}
const event = new CustomEvent('slide',{bubbles:true, detail})
this.container.dispatchEvent(event)
重构
重构:插件化
解耦:
- 将控制元素抽取成插件
- 插件与组件之间通过[[依赖注入]]方式建立联系
function pluginCotroller(subject){
}
function pluginNext(subject){
}
function pluginPrevious(subject){
}
重构:模版化
解耦:
- 将html模版化,更易于扩展
- ![[Pasted image 20230227204234.png]]
class Slider{
constructor(id ,opts = {images: [], cycle: 3000}){
//...
}
reder(){
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>`;
}
//...
}
重构:组件框架
抽象:
- 将通用的组件模型抽象出来 ![[Pasted image 20230227204542.png]]
class Component{
constructor(id,opts={name,data:[]}){
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML =this.render(opts.data);
}
registerPlugin(...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){
return ''
}
}
总结
- 组件设计原则:
- 封装性
- 正确性
- 扩展性
- 复用性
- 实现组件的步骤
- 结构设计
- 展现效果
- 行为设计
- 三次重构
- 插件化
- 模版化
- 抽象化(组件框架)
过程抽象
用来处理局部细节控制的一些方法 [[函数式编程思想]]的基础应用
操作次数限制
- 一些异步交互
- 一次性的http请求
Once
为了能够让“只执行一次”的需求覆盖不同的事件处理,我们可以将这个需求剥离出来,这个过程我们称为[[过程抽象]]
高阶函数
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
常用高阶函数
Once
Throttle
Debounce
Consumer/2
Iterative
为什么要使用高阶函数
编程范式
命令式与声明式
例子
Toggle-命令式
Toggle-声明式
Toggle-三态
总结
- 过程抽象/HOF/装饰器
- 命令式/声明式