消除坏味道
代码重构
1 文件归类
采用原生前端三件套写 Web 应用的时候一般要将 HTML、CSS、JavaScript 分别归为一个文件夹,对于图片、视频等资源也需要进行归类。
源代码将所有文件均放到同一文件夹下,导致代码文件夹显得非常臃肿。
新建文件夹 HTML、CSS、JS、img,将文件归类即可。
2 命名部分
HTML 和 CSS 的 class 类名通常采用 kebab-case 命名法,JavaScript 有小驼峰(camelCase)和大驼峰(PascalCase)之分。变量和函数名一般用小驼峰,构造函数和类名用大驼峰。
对于文件命名,HTML 和 CSS 一般也采用 kebab-case 命名法,对于 JavaScript 文件,一般类文件采用大驼峰命名法,其他文件采用小驼峰命名法。
另外,对于入口文件,我们一般命名为:index.html / styles.css / index.js
2.1 对于文件名:
源代码命名举例如:
Atodo.html;
cbody.css;
dtodo.js;
TODO1.jpg;
可以更改为:
index.html;
styles.css;
index.js;
bg.jpg;
2.2 对于文件内变量命名:
源代码命名示例:
<div class="bodysearch">
<i class="tubiao1" id="checkbutton"
><ion-icon name="checkmark-circle-sharp"></ion-icon
></i>
<input type="text" placeholder="What Needs To Be Done?" id="search" />
</div>
.pchange {
text-decoration: line-through;
}
var deletebtn = newPage.querySelector(".deletebtn");
分别更改为:
<div class="search-box">
<i class="icon-1" id="checkButton"
><ion-icon name="checkmark-circle-sharp"></ion-icon
></i>
<input type="text" placeholder="What Needs To Be Done?" id="search" />
</div>
/* 注意这里需要将对应类名进行更换 */
.para-change {
text-decoration: line-through;
}
// 此外,尽量不要采用简写
var deleteButton = newPage.querySelector(".deletebtn");
3 过长函数
JavaScript 中过长的函数也就意味着没有封装、代码紧耦合,导致逻辑复杂、可读性差、变量难以管理。脚本的编码原则之一就是函数各司其职。
这里节选出之前写的脚本中的一段过长函数来进行修改:
// 监听搜索栏按键事件
input.addEventListener("keydown", function (event) {
if (event.keyCode === 13 && input.value.trim() !== "") {
var inputValue = input.value;
localStorage.setItem("todo_" + num, inputValue);
var newPage = document.createElement("div");
newPage.classList.add("page");
newPage.innerHTML =
'<input type="checkbox" class="checkbox"><p class="p">' +
inputValue +
'</p><i class="deletebtn"><ion-icon name="close-circle-outline"></ion-icon></i>';
bodyPage.appendChild(newPage);
input.value = "";
// 更新计数器
num++;
updateNumCounter();
// 取消footer部分的隐藏
if (num != 0) {
footer.style.display = "flex";
}
// 监听checkbox状态变化
var checkbox = newPage.querySelector(".checkbox");
checkbox.addEventListener("change", function (event) {
var page = event.target.parentNode;
if (checkbox.checked) {
page.classList.add("changed");
num--;
} else {
page.classList.remove("changed");
num++;
}
updateNumCounter();
});
// 监听删除按钮点击事件
var deletebtn = newPage.querySelector(".deletebtn");
deletebtn.addEventListener("click", function (event) {
var page = event.target.parentNode.parentNode;
page.remove();
var index = Array.prototype.indexOf.call(bodyPage.children, page);
localStorage.removeItem("todo_" + index);
if (!checkbox.checked) {
num--;
}
updateNumCounter();
});
}
});
修改后:
function addNum(num) {
num += 1;
}
function updateNumCounter() {
let numCounter = document.getElementById("numCounter");
numCounter.innerHTML = "<p>" + num + " items left</p>";
}
function footerController(num) {
if (num === 0) {
footer.style.display = "none";
} else {
footer.style.display = "flex";
}
}
function checkboxWatcher(num) {
let checkbox = newPage.querySelector(".checkbox");
checkbox.addEventListener("change", function (event) {
let page = event.target.parentNode;
if (checkbox.checked) {
page.classList.add("changed");
num--;
} else {
page.classList.remove("changed");
num++;
}
updateNumCounter();
});
}
function deleteButtonWatcher(num) {
let deletebtn = newPage.querySelector(".deletebtn");
let checkbox = newPage.querySelector(".checkbox");
deletebtn.addEventListener("click", function (event) {
var page = event.target.parentNode.parentNode;
page.remove();
var index = Array.prototype.indexOf.call(bodyPage.children, page);
localStorage.removeItem("todo_" + index);
if (!checkbox.checked) {
num--;
}
updateNumCounter();
});
}
function inputContentSetter() {
var inputValue = input.value;
var newPage = document.createElement("div");
localStorage.setItem("todo_" + num, inputValue);
newPage.classList.add("page");
newPage.innerHTML =
'<input type="checkbox" class="checkbox"><p class="p">' +
inputValue +
'</p><i class="deletebtn"><ion-icon name="close-circle-outline"></ion-icon></i>';
bodyPage.appendChild(newPage);
input.value = "";
}
let shouldProcess = (event) =>
event.key === "Enter" && input.value.trim() !== "";
上面我们将源代码函数分别封装,接下来组合为:
input.addEventListener("keydown", function (event) {
if (shouldProcess(event)) {
inputContentSetter();
addNum(num);
updateNumCounter();
footerController(num);
checkboxWatcher(num);
deleteButtonWatcher(num);
} else {
return;
}
});
这里其实可以进行更细粒度的封装,不过之前的代码实在太臃肿,很多基础函数都不具备,我们暂且将代码优化为上面这样。
4 重复代码
重复代码容易在 CSS 里出现,例如源代码中控制 body 样式的文件中:
.b1 {
background-color: rgb(127, 127, 127);
border-radius: 10px;
border-color: rgb(10, 236, 210);
font-size: 15px;
}
.b2 {
background-color: rgb(127, 127, 127);
border-radius: 10px;
border-color: rgb(10, 236, 210);
font-size: 15px;
}
.b3 {
background-color: rgb(127, 127, 127);
border-radius: 10px;
border-color: rgb(10, 236, 210);
font-size: 15px;
}
.b4 {
background-color: rgb(127, 127, 127);
border-radius: 10px;
border-color: rgb(10, 236, 210);
font-size: 15px;
}
.b1 p {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}
.b2 p {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}
.b3 p {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}
.b4 p {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}
我们有两种修改方法:
- 修改 HTML 类名
- 合并 CSS 代码
这里我们采用后面一种方法进行修改:
.b1,
.b2,
.b3,
.b4 {
background-color: rgb(127, 127, 127);
border-radius: 10px;
border-color: rgb(10, 236, 210);
font-size: 15px;
}
.b1 p,
.b2 p,
.b3 p,
.b4 p {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}
除此之外,设置字体时其实不需要设置这么多,因为全都是自带字体,没有自己注册的字体,只需要考虑浏览器对不同的字体兼容性就行。
5 脚本分装
在源代码中我把所有 JavaScript 代码都写在同一个 js 文件中,这其实是很不好的。
我们采用了 localStorage 来实现待办事项的本地存储,同时,浏览器加载 JavaScript 脚本是根据 HTML 解析顺序进行,故而可以把渲染待办事项的脚本和其它脚本分开。
6 修改注释
所有 JavaScript 代码都写在同一个 js 文件中带来的另外一个问题是注释繁杂,同时有很多无意义的代码分割线如:
// ***************************************************************************************************************************
// ***************************************************************************************************************************
其次,还存在有过长的注释如:
// 从字符串key的第5个字符开始截取到末尾,并将截取的部分转换为整数
// parseInt(key.substring(5)) 将键名中的数字部分提取出来,并转换为整数类型,赋值给 index 变量。这个数字部分代表了待办事项的索引
解决方案:
- 删掉代码分隔符
- 采用 @param 来注释变量,缩短注释长度
7 JavaScript特性处理
由于JavaScript的Var关键字特性,它会导致变量的作用域提升,我们应该去采用最新的let和const来声明变量。
8 状态管理
前面提到了JavaScript的Var关键字特性会导致变量的作用域提升,故而在这个脚本中有很多全局变量,这可以通过let和const来进行解决。
而对于需要在多个函数中使用的全局变量(如num,代表待办事项的数量),我们可以在最外层定义一个对象global来存储这些状态变量:
const global = {
num : 0,
...
}
除此之外也可以通过引入外部状态管理库来实现这一目标。
9 垃圾处理
代码文件夹有太多未使用到的资源,可以把这些图片给删掉,也可以写.gitignore禁止上传这些资源到Github