最好的JS代码就是没有JS代码
前端工程师进阶笔记 Pary One
Part One
WEB 开发中
- HTML 负责网页结构
- CSS 负责网页元素的样式
- JS 负责网页与用户的交互
要成为优秀的前端工程师,需要遵守这三者各司其职的原则,让代码易于维护和拓展。
切换状态
有一个任务,它的具体需求是这样的:给一个网页实现一个深色系和浅色系主题的切换,以使得在夜晚访问这个网页的读者能够使用“夜间模式”。
这个网页的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>HTTP 与 HTTPS</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>
<div>
<header>
<button id="modeBtn">🌞</button>
<h1>HTTP 和 HTTPS</h1>
</header>
<main>
<div class="description">
<p>
http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。 https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性
</p>
</div>
</main>
</div>
</body>
</html>
一般而言,我们直观的想法是通过点击事件来替换HTML的背景颜色和按钮图标。
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 = '🌞';
}
});
这段代码给按钮注册了click事件,当用户点击按钮时,如果当前按钮文字为🌞,说明是要从日间模式换成夜间模式,将body背景颜色换为深色,文字样式换为浅色。
但上述代码有几个问题:
- 其他不了解需求的人,阅读这段代码能否直接理解这个按钮按下的含义?
- 产品需求发生更改,如要求用其他颜色背景显示夜间模式,JS 代码是否可以避免修改?
- 如果要给切换过程增加动画效果,是否方便添加?
用 class 表示元素的业务状态
故事一中通过 JS 代码来操作元素,缺点显而易见:其他阅读代码的人很难通过代码理解样式代表的业务需求或者状态。
出现这种情况的主要原因是,我们将本该由 CSS 完成的任务交给了 JS 来做 ,原本应该由 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 = '🌞';
}
});
上述代码通过点击事件切换元素状态来实现白天模式和夜间模式的切换,虽然代码改动非常小,只是把之前的两行代码替换为一行:
body.style.backgroundColor = 'black';
body.style.color = 'white';
=>
body.className = 'night';
但是它却解决了我们前面提到的三点问题:
- 首先 classname 设为 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;
}
最后还可以通过添加为元素将e.target.innerHTML = '🌜';
这样的切换给放到 CSS 中:
#modeBtn::after {
content: '🌞';
}
body.night #modeBtn::after {
content: '🌜';
}
去掉 HTML 中 <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 = '';
}
});
元素的 class 属性不仅为了给 CSS 提供元素选择器,至此,我们实现了 JS 只负责切换元素状态,而将元素样式的改变重新交还给 CSS ,保证了各司其职的原则,是我们代码即能体现业务的需求也有利于将来的维护和拓展。
最好的 JS 代码是没有 JS 代码
那么是否存在一种方式,我们只使用 CSS 实现“夜间模式”效果,对于切换夜间模式的需求而言,其核心问题是使用 CSS 代替 JS 来切换并记住与用户交互的状态 。
HTML 中,能完成状态切换的元素第一时间可以想到的是表单中的 选择框(checkbox) ,对 HTML 进行修改:
<input id="modeCheckBtn" type="checkbox">
<div class="content">
<header>
<label id="modeBtn" for="modeCheckBtn"></label>
<h1>HTTP 和 HTTPS</h1>
</header>
<main>
<div class="description">
<p>
http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。 https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性
</p>
</div>
</main>
</div>
我们在上述代码中添加了一个 type = "checkbox"
的 input 元素,通过伪类选择器可以标记元素的被选中状态。
由于 <input>
元素是 body 的第一个子元素,它后面的子元素可以通过 CSS 的兄弟节点选择器来选中。
调整上一版本的样式,将 body 的样式移动到 .content
容器中,将 body.night
的样式一移动到 #modeCheckBtn:checked + .content
规则中。
body {
box-sizing: border-box;
}
.content {
padding: 10px;
transition: all 1s;
}
#modeBtn {
font-size: 2rem;
float: right;
}
#modeCheckBtn:checked+.content {
background-color: black;
color: white;
}
#modeBtn::after {
content: '🌞';
}
#modeCheckBtn:checked+.content #modeBtn::after {
content: '🌜';
}
#modeCheckBtn {
display: none;
}
这样便可以实现点击 checkbox 进行 “夜间模式” 的切换,但不可能让用户通过点击选择框来进行切换,因此我们可以使用 label 元素替代 checkbox 触发用户的点击行为。
通过 label 元素的 for 属性制定的 id, 能够将 label 元素与对应的表单元素进行绑定。
<header>
<label id="modeBtn" for="modeCheckBtn"></label>
<h1>HTTP 和 HTTPS</h1>
</header>
然后将 选择框 checkbox 隐藏起来:
#modeCheckBtn {
display: none;
}
完整代码如下:
<!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;
}
body.night {
background-color: black;
color: white;
transition: all 1s;
}
.content {
padding: 10px;
transition: all 1s;
}
#modeBtn {
font-size: 2rem;
float: right;
}
#modeCheckBtn:checked+.content {
background-color: black;
color: white;
}
#modeBtn::after {
content: '🌞';
}
#modeCheckBtn:checked+.content #modeBtn::after {
content: '🌜';
}
#modeCheckBtn {
display: none;
}
</style>
</head>
<body>
<input id="modeCheckBtn" type="checkbox">
<div class="content">
<header>
<label id="modeBtn" for="modeCheckBtn"></label>
<h1>HTTP 和 HTTPS</h1>
</header>
<main>
<div class="description">
<p>
http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。 https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性
</p>
</div>
</main>
</div>
</body>
</html>
再简单的代码,都有可能出 bug,唯一能够避免 bug 的办法便是不要代码,因此,最好的 JS 代码就是没有 JS 代码。