3

前端JavaScript/css遵守各司其职原则

 3 years ago
source link: https://www.joynop.com/p/400.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

第一个故事:切换到夜间模式

在WEB开发中,HTML负责网页的结构,CSS负责网页上各个元素的展示样式,JS则负责网页和用户的交互。想要成为一名优秀的前端工程师,首先要做的就是遵守这三者各司其职的原则,让我们的代码易于维护和扩展。

6A0179B1-7DAA-46CA-9F45-0547B516AEE6.png

但是,有时候我们常常一不小心就破坏了这个原则。又或者,我们为了实现业务需求,根本不管这个规则。这都会导致我们的代码结构混乱,维护困难。那么下面,我就通过一个例子,来谈谈遵守各司其职这个原则的好处。
现在我们有一个任务,它的具体需求是这样的:给一个网页实现一个深色系和浅色系主题的切换,以使得在夜晚访问这个网页的读者能够使用“夜间模式”。
这个网页的HTML大概是这样的:

<!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> 
<style> 
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; 
} 
</style> 
</head> 
<body> 
<header> 
<button id="modeBtn">太阳</button> 
<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> 
</body> 
</html> 

现在的页面,在手机上看起来是这样的效果:

20201211104525.gif

任务的要求是当用户点击网页右上角的太阳太阳图标时,将网页变为深夜模式,即用深色背景、浅色字体来显示网页内容,同时太阳太阳标记变为月亮月亮标记。
这个任务很简单,我们可能非常快的就写下按钮的响应处理:

<script> 
const btn = document.getElementById("modeBtn"); 
console.log(btn); 
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 = "太阳"; 
} 
}); 
</script> 
JavaScript

以上这段代码给按钮注册了click事件,当用户点击按钮的时候,如果当前按钮文字是“太阳”,说明要从日间模式转换为夜间模式,那么将body的背景样式转换成深色,文字样式转换成浅色,否则将body的背景颜色转换为浅色,文字样式转换为深色
点击后出现的效果

94B89F81-4DF7-494B-B7D8-DF667587F1CB.png

看起来,我们完美地实现了产品的需求,可以交差了。但是实际上,上面的代码存在以下三个问题:

  1. 对于其他不了解需求的同事,阅读这段代码能够直接理解这个按钮按下的含义吗?
  2. 如果产品需求变更,要求用深灰色背景、浅黄色文字来显示夜间模式,JS代码可以避免修改吗?
  3. 如果要给切换过程增加动画效果,能方便添加吗?

作为读者的你,知道如何解决这些问题吗?

第二个故事:用class属性表示元素的业务状态

第一个故事中,我们直接用JS操作元素,让元素在夜间和白天模式互换:

body.style.backgroundColor = 'black'; 
body.style.color = 'white'; 
JavaScript

这样做的缺点是其他的程序员只知道这两个语句是将bodybackground样式改为了黑色,将color样式改为了白色,却并不知道这个样式代表的是什么业务需求或者状态。
之所以会这样,是因为我们将本该由CSS完成的工作交由JS来做了,本来应该由CSS设置元素的样式,却让JS代替了。所以,我们需要重构一下代码,让它能体现出业务的需求。
我们把夜间模式下元素的样式的设置还给CSS来完成:

body.night { 
background-color: black; 
color: white; 
} 

然后将JS代码重构为如下形式:

const btn = document.getElementById('modeBtn'); 
btn.addEventListener('click', (e) => { 
const body = document.body; 
if(body.className !== 'night') { 
body.className = 'night'; 
e.target.innerHTML = '月亮'; 
} else { 
body.className = ''; 
e.target.innerHTML = '太阳'; 
} 
}); 
JavaScript

如上代码所示,当body元素的class属性不等于night时,表示(点击前)当前元素的状态是白天模式,所以现在需要将它的状态修改为夜间模式,于是我们只要将它的class属性设置为night,页面就会呈现夜间模式的样式。同理,当body元素的class属性等于night时,表示(点击前)body元素是夜间模式,所以需要将这个元素的状态修改为白天模式,也就是默认状态,即class属性等于空。
上面的代码,虽然改动十分微小,只是把之前的两行代码:

body.style.backgroundColor = 'black'; 
body.style.color = 'white'; 
JavaScript

替换成一行

body.className = 'night'; 
JavaScript

但是,它能解决前面提出的几个问题:

  • 首先,className设为night,这个操作本身透露了需求信息,它描述了这是一个夜间(night)模式的业务状态。这样就便于后来的维护者快速理解业务需求。
  • 其次,如果产品需求变更,把模式对应的颜色换了,我们不需要修改JS代码,只需要修改body.night的样式规则即可!
  • 第三,如果要增加切换过程的动画效果,可以使用CSS3支持的过渡动画,例如:
body { 
padding: 10px; 
box-sizing: border-box; 
transition: all 1s; 
} 
body.night { 
background-color: black; 
color: white; 
transition: all 1s; 
} 

bodybody.night都添加样式规则transition all 1s,就可以实现简单的切换动画了。
最后,实际上还有个细节可以改进,那就是e.target.innerHTML = '月亮';这样的切换也不是很好,也应该合并到CSS中。这个可以通过伪元素来实现:

#modeBtn::after { 
content: '太阳'; 
} 
body.night #modeBtn::after { 
content: '月亮'; 
} 
JavaScript

我们去掉<button id="modeBtn"></button>中间的文本内容,然后给它添加伪元素样式,这样我们JS代码简化成

const btn = document.getElementById('modeBtn'); 
btn.addEventListener('click', (e) => { 
const body = document.body; 
if(body.className !== 'night') { 
body.className = 'night'; 
} else { 
body.className = ''; 
} 
}); 
JavaScript

这时,JS代码只负责切换元素的状态,而不需要代替CSS改变元素的样式了。
完整的代码如下:

<!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> 
<style> 
body, html { 
width: 100%; 
height: 100%; 
padding: 0; 
margin: 0; 
overflow: hidden; 
} 
body { 
padding: 10px; 
box-sizing: border-box; 
transition: all 1s; 
} 
body.night { 
background-color: black; 
color: white; 
transition: all 1s; 
} 
div.pic img { 
width: 100%; 
} 
#modeBtn { 
font-size: 2rem; 
float: right; 
} 
#modeBtn::after { 
content: '太阳'; 
} 
body.night #modeBtn::after { 
content: '月亮'; 
} 
</style> 
</head> 
<body> 
<header> 
<button id="modeBtn"></button> 
<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> 
</body> 
<script> 
const btn = document.getElementById('modeBtn'); 
btn.addEventListener('click', (e) => { 
const body = document.body; 
if(body.className !== 'night') { 
body.className = 'night'; 
} else { 
body.className = ''; 
} 
}); 
</scrpit> 
</html> 

从这个故事,我们可以看出,元素的class属性不仅仅只是为了给CSS提供类选择器,还能表示元素的业务状态。这样,我们就可以将这些业务状态对应的展示样式交由CSS处理,而JS只需要处理状态的切换即可,从而保证了各司其职的原则,使得我们的代码既能体现业务的需求,也便于将来的维护和扩展。
对于这个例子来说,对于状态的切换,可不可以完全可以不使用JS,纯用CSS来实现呢?

第三个故事:最好的JS代码是没有JS代码

不使用JS,只使用CSS实现“夜间模式”效果,看起来是一个不小的挑战。不过仔细想想,也不是不可能做到。其实这个问题,最核心的部分是要使用CSS代替JS来切换并记住与用户交互的状态
让我们回忆了一下,在HTML中,能够完成状态切换的元素也是有的,比如表单元素中的选择框(checkbox)元素,那么……
尝试改变一下HTML文档结构:

<body> 
<input id="modeCheckBox" type="checkbox"> 
<div class="content"> 
<header> 
<button id="modeBtn"></button> 
<h1>深夜食堂</h1> 
</header> 
<main> 
<div class="pic"> 
<img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg"> 
</div> 
<div class="description"> 
<p> 
这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈 
眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返。 
</p> 
</div> 
</main> 
</div> 
</body> 

上面的代码中,我们在body的子元素中添加一个type="checkbox"input元素。当我们点击这个元素时,就有两种状态:普通状态和选中状态。其中,选中状态可以用伪类选择器#modeCheckBox:checked来标记。
由于<input>元素是body的第一个子元素,它后面的子元素可以通过CSS的兄弟节点选择器来命中。为了便于统一操作,我们给header和main元素外层增加一个<div class="content">的容器,这样我们就可以通过CSS选择器改变这个容器的样式:

/* 匹配checkbox选中状态下的.content */ 
#modeCheckBox:checked + .content { 
background-color: black; 
color: white; 
transition: all 1s; 
} 

上面的这条规则表示当checkbox选中状态下,.content元素的样式为黑底白字。
然后,微调一下上一版本的样式,将body中的padding移到.content容器中,将body.night的样式移到#modeCheckBox:checked + .content规则中。

body { 
box-sizing: border-box; 
} 
.content { 
padding: 10px; 
transition: all 1s; 
} 
#modeCheckBox:checked + .content { 
background-color: black; 
color: white; 
transition: all 1s; 
} 

这样我们就可以通过点击checkbox元素来进行“夜间模式”状态切换了:

616AD207-CFBC-477F-BB92-8B29D6E1D1DB.jpg

如上图所示,当checkbox被选中时,页面进入夜间模式;当checkbox不被选中时,页面又进入白天模式。
当然,这个效果和需求还有一定差距 —— 不能让用户点击页面顶部的checkbox来替代点击右上角太阳图标,这不符合业务需求。于是,我们打算使用label元素代替checkbox触发用户的点击行为。
通过label元素的for属性指定的id,能够将label元素与对应的表单元素绑定,这样当用户点击label元素的时候,就相当于直接点击对应的表单元素。

<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> 

如上代码所示,我们将原来的button元素用label元素代替,并将label元素的for属性指向checkbox的id(modeCheckBox)。这样就能够让label元素绑定checkbox元素。
然后,我们再将checkbox框隐藏起来

#modeCheckBox { 
display: none; 
} 

这样,我们就完美地实现了只用CSS,不使用JS代码实现的“夜间模式”切换。
完整代码如下:

<!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> 
<style> 
body, html { 
width: 100%; 
height: 100%; 
padding: 0; 
margin: 0; 
overflow: hidden; 
} 
body { 
box-sizing: border-box; 
} 
.content { 
padding: 10px; 
transition: all 1s; 
} 
div.pic img { 
width: 100%; 
} 
#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: '月亮'; 
} 
</style> 
</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> 
这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈 
眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返。 
</p> 
</div> 
</main> 
</div> 
</body> 
</html> 

现在,我们来比较一下这个版本和第二个故事中有JS的版本,它们各自的的优缺点在哪里:

  • JS版本更加简洁,虽然用了JS,但HTML结构更简单。而且JS版本的兼容性要好一些,因为CSS版本使用了兄弟结点选择器,在早期的浏览器上,可能不能被支持。
  • CSS版本不用写JS代码,这样就不用维护JS代码,也不可能有JS的bug,所以也是有优势的,尤其是在移动端页面,不用担心浏览器兼容性的前提下,使用这一版更加放心。

虽然这两个版本各有优劣,但是要知道,再简单的代码,也可能会有bug,唯一不会有bug的方式就是不用写代码。所以,最好的JS代码就是没有JS代码。 这个项目到这里就结束了,记住你学到的这些,就离高级前端工程师更近了一步。
示例:

codesandbox完整示例
示例1
示例2
示例3


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK