写好JavaScript的一些准则
[这是我参与「第四届青训营 」笔记创作活动的的第4天]
前端基础准则-各司其责
写好前端得让HTML CSS JavaScript各司其职
HTML(Hyper Text Markup Language),是页面的骨架;
CSS则是使用各种选择器和各种dom关系以及标签状态等为不同的标签设置各自的样式,它来进行样式的控制
JavaScript则是在浏览器上运行的编程语言,浏览器编译运行js代码对用户的各种行为做出反应,比如点击按钮提交表单后跳转到新的页面,验证用户在浏览某些网页之前是否登录了账号等。
实现控制网页深色和浅色浏览模式的这个例子你能很好的说明HTML CSS JavaScript各司其职的好处下面是代码。
代码例子
HTML结构
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>深夜食堂</title>
</head>
<body>
<header>
<button id="modeBtn">🌞</button>
<h1>深夜食堂</h1>
</header>
<main>
<div class="pic">
<img src="https://p5.ssl.qhimg.com/t015b445bef7dede221.jpg" alt="">
</div>
<div class="content">
<p>
这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈
眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返。
</p>
</div>
</main>
</body>
</html>
css样式
body, html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
body {
padding: 10px;
box-sizing: border-box;
}
div.pic img {
width: 100%;
}
#modeBtn {
font-size: 2rem;
float: right;
border: none;
background: transparent;
}
复制代码
js代码
//获得控制颜色的按钮
const btn=document.getElementBuId('bottonName');
//为该按钮绑定点击事件,实现点击切换页面颜色
btn.addEventListener('click',(e)=>{
const body=dcument.body;
if(e.target.innerHTML==='🌞'){
//修改背景颜色为黑色
body.style.backgroundColor='black';
//修改文字颜色为白色
body.style.color='white';
e.target.innerHTML='🌙';
}else{
//修改背景颜色为白色
body.style.bakckgroundColor='white';
//修改文字颜色为黑色
body.style.color='black';
e.target.innerHTML='🌞'
}
});
复制代码
效果:
在js中我们通过给按钮添加点击事件直接修改CSS样式,实现了点击页面让背景颜色变化的功能但是违背各司其职的原则。
这段js代码存在bug:
- 后续想要添加更多样式的话只能在这里堆砌代码,会对后期维护造成困难.
- 性能问题,每一次触发点击事件都会查询DOM树找到Body节点,为其修改样式后触发重绘.
改进方法:纯CSS实现,通过使用伪类选择器来完成功能。
第一我们在HTML中设置一个checkbox的input组件,css样式设置为display: none;
其次,通过使用label组件的for属性来修改这个checkbox的状态,来进行背景颜色的改变
最后,在CSS中指定一个 伪类+兄弟选择器,在checkbox的状态为checked的情况下选中后面类名为content的组件修改样式,从而实现暗黑模式。
选中暗黑模式时,由于该选择器最特殊,因此该选择器指定的样式会被选择。
最终版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>深夜食堂</title>
</head>
<body>
<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>
</body>
</html>
CSS样式
#modeCheckBox {
display: none;
}
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
#modeBtn {
font-size: 2rem;
float: right;
}
#modeBtn::after {
content: '🌞';
}
#modeCheckBox:checked + .content #modeBtn::after {
content: '🌜';
}
这是因为我们使用label属性与input绑定了,当我们点击页面上方时触发input,然后利用css的伪类以及兄弟选择器修改页面颜色。
好处:
- HTML |CSS |JavaScript各司其职,代码简洁方便易懂。
- 避免由js直接操作样式。
前端基础准则-组件封装
好的UI组件必须具备正确性、拓展性、复用性
例子
1 轮播图 设计
-
HTML结构:
轮播图是一个典型的列表结构,我们可以使用无序列表`ul`元素来实现。
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png">
</li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg">
</li>
<li class="slider-list__item">
<img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg">
</li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg">
</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行为
用类封装Slider组件,设计API
- Slider
- +getSelectedItem() // 获取被选中的项
- +getSelectedItemIndex() // 获取被选中的项的下标
- +slideTo() // 滑到某一项
- +slideNext() // 滑到后一项
- +slidePrevious() // 滑到前一项
可以使用自定义事件来实现 控制Slider的组件的状态 和 Slider类 之间的解耦
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);
2 轮播图 重构:插件化
-
为什么要插件化?
之前的设计修改一处,其他地方也要修改,麻烦 -
方法:
将控制元素抽取成插件
插件与组件之间通过依赖注入方式建立联系
将控制插件依赖的Slider类实例注入到控制插件中
方便添加或减少控制插件
3 轮播图 模板化重构
- 将HTML模板化,更易于扩展,用数据进行驱动更符合实际的开发
4 轮播图 组件框架
- 将组件通用模型抽象出来
- 将组件和插件统一用一个类来实现从而实现父插件可以有子插件的嵌套
总结
-
组件设计的原则:封装性、正确性、扩展性、复用性
-
实现组件的步骤:结构设计、展现效果、行为设计
-
三次重构
- 插件化
- 模板化
- 抽象化(组件框架)
前端基础准则-过程抽象
过程抽象是用来处理局部细节控制的一些方法,是函数式编程思想的基础应用
为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为——过程抽象
高阶函数
HOF
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
常用高阶函数
高阶函数once:多次调用只执行一次
function once(fn) {
return function(...args) {
//看执行了没
if(fn) {
const ret = fn.apply(this, args);
//执行完一次后,把参数设置为空,保证只执行一次
fn = null;
return ret;
}
}
}
高阶函数throttle:截流函数(防止请求过多)
- 若是高频操作(像是鼠标点击),隔一段时间记录一次操作
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
高阶函数debounce:防抖函数
var i = 0;
setInterval(function(){
bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
function debounce(fn, dur){
// 默认等待时间为100毫秒
dur = dur || 100;
var timer;
return function(){
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);
// 每执行一次包装后的fn,都会重新计时执行fn前的等待时间(1s)
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));
为什么要使用高阶函数?
使用高阶函数可以大大减少我们使用非纯函数的可能性