如何写好js|青训营笔记

87 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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/装饰器
  • 命令式/声明式