我写了我的第一个网络应用程序。请让我知道我应该改进什么。我想尽可能地写干净的代码。
App是关于从列表中添加和删除任务。后端用Django编写,前端用Sass和JavaScript编写。
在“项代码”视图下面。整个项目可以在链接中看到:
演示:
链接到heroku上的应用程序
项目代码视图:
// Start with first item
let counter = 0;
// Load 6 items at a time
const QUANTITY_START = 6;
const QUANTITY_TO_ADD_ON_SCROLL = 3;
let isListEmpty = true;
let removed = [];
let areMoreItems = true;
const HIDE_ANIMATION_DURATION_OPACITY = 1500
document.addEventListener('DOMContentLoaded', () => {
// SETTING LISTENERS:
window.onscroll = () => {
if (areMoreItems && isScrolledToBottom()){
const scroll = window.onscroll;
window.onscroll = "none";
load(QUANTITY_TO_ADD_ON_SCROLL);
setTimeout(() => {
window.onscroll = scroll;
}, 1000);
}
}
deleteItemListener();
confirmDeleteButtonListener();
// END OF SETTING LISTENERS
load(QUANTITY_START);
})
function confirmDeleteButtonListener() {
const confirm_button = document.querySelector(".confirm-delete-button");
confirm_button.addEventListener("click", () => {
confirm_button.value = removed.join();
})
}
function deleteItemListener() {
document.querySelector(".main").addEventListener("click", function(event) {
if(event.target.nodeName === "I") {
const itemElement = event.target.parentElement.parentElement;
const item_id = itemElement.querySelector('.id').innerHTML;
removed.push(item_id);
itemElement.style.animationPlayState = 'running';
// itemElement.addEventList;
document.querySelector(".confirm-delete-button").disabled = false;
}
});
}
function isScrolledToBottom() {
return window.innerHeight + window.scrollY >= document.body.offsetHeight
}
function load(quantity) {
const start = counter;
const end = start + quantity - 1;
counter = end + 1;
fetch(`itemslist?start=${start}&end=${end}`)
.then(response => response.json())
.then(data => {
if (data.itemslist.length > 0) {
isListEmpty = false;
data.itemslist.forEach(addItem);
} else {
areMoreItems = false;
if (isListEmpty) {
addEmptyListInfo();
}
}
});
}
function addEmptyListInfo() {
const item = document.createElement('section');
item.className = "empty-list";
item.innerText = "Your list is empty";
document.querySelector('.main-items').append(item)
}
function addItem(item) {
const itemNode = document.createElement('section');
itemNode.className = "todo-item";
itemNode.innerHTML =
`${item.id}
${item.name}
Details:
${item.details}
Deadline: ${item.date}`;
document.querySelector('.main-items').append(itemNode);
};.nav {
.li {
background-color: transparent;
border: none;
cursor: pointer;
font-family: 'Caveat', cursive;
color: #fff;
outline: none;
}
form {
display: flex;
display: -webkit-box;
display: -ms-flexbox;
flex-direction: row;
}
}
.main-items {
opacity: 0.9;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-direction: row !important;
justify-content: space-evenly;
flex-wrap: wrap;
.todo-item {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
color: #000;
height: 300px;
width: 400px;
padding: 20px;
margin: 0 15px;
position: relative;
-webkit-animation-name: hide;
animation-name: hide;
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-play-state: paused;
animation-play-state: paused;
background-image: url(../img/sticky1.png);
background-size: 400px 300px;
line-height: 100%;
.todo-item-name {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: flex-start;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 10px;
margin-left: 40px;
margin-right: 25px;
height: 40px;
font-size: 25px;
font-weight: bold;
margin-top: 20px;
padding: 5px;
outline: none;
color: #000;
overflow-y: auto;
scrollbar-width: thin;
scroll-padding: 0;
scroll-margin: 0;
white-space: nowrap;
cursor: default;
}
.details {
border-radius: 5px;
overflow-wrap: break-word;
overflow-y: auto;
height: 100px;
padding: 10px;
margin-left: 40px;
margin-right: 25px;
text-align: justify;
resize: none;
margin-bottom: 10px;
outline: none;
font-size: 22px;
scrollbar-width: thin;
scroll-padding: 0;
scroll-margin: 0;
cursor: default;
}
.x-wrapper {
position: absolute;
right: 0;
top: 0;
margin: 10px;
font-size: 20px;
-webkit-transition: all 0.2s;
transition: all 0.2s;
cursor: pointer;
border: none;
color: #e9c1c1;
background-color: transparent;
}
.x-wrapper:hover {
color: #fff;
}
}
.empty-list {
font-size: 44px;
margin-bottom: 40px;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
padding-right: 15px;
border-radius: 10px;
text-align: center;
pointer-events: none;
}
.todo-item label {
font-size: 20px;
margin-bottom: 2px;
margin-left: 40px;
margin-right: 25px;
}
.date {
text-align: right;
margin-right: 25px;
pointer-events: none;
}
}
@-webkit-keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}{% extends "TodoListApp/base.html" %}
{% load static %}
{% block style %}
Items
{% endblock %}
{% block body %}
Add new item
{% csrf_token %}
add 10 items
confirm deleting
Logout
Hello :{{ name }}:
Here is a list of your activities:
{% endblock %}发布于 2020-12-10 17:37:58
正如"CertainPerformance's“中所指出的,通过HTML添加内容在这种情况下可能会带来安全风险。
但是,应该始终避免通过innerHTML添加元素,因为它效率很低,噪音很大,很难阅读和维护(因为字符串中没有代码格式)。
使用一些简单的函数,您可以避免非常慢的setter .innerHTML,并以一种高效的方式添加内容,而无需使用DOM。
下面的一组函数可以满足大多数HTML创建需求
tag创建一个元素并添加属性,返回新标记。append将兄弟姐妹附加到元素中,并返回父元素。query查询返回查找元素的元素(如果找到)代码
const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
const query = (qStr, el = document) => el.querySelector(qStr);并将您的代码替换为
function load(quantity) {
const url = `itemslist?start=${counter}&end=${counter + quantity - 1}`;
const mainItems = query(".main-items");
counter = end + 1;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.itemslist.length > 0) {
isListEmpty = false;
append(mainItems, ...data.itemslist.map(addItem));
} else {
areMoreItems = false;
isListEmpty && append(mainItems, addEmptyListInfo());
}
});
}
function addEmptyListInfo() {
return tag("section", {className: "empty-list", textContent: "Your list is empty"});
}
function addItem(item) {
return append(tag("section", {className: "todo-item"}),
tag("p", {hidden: true, className: "id", textContent: item.id}),
append(tag("div", {name: "delete-button", value: item.id, className:"x-wrapper"}),
tag("i", {className: "far fa-times-circle"})
),
tag("h2", {name: "name", className: "todo-item-name", textContent: item.name}),
tag("label", {for: "details", textContent: "Details:"}),
tag("p", {className: "details", name:"details", textContent: item.details}),
tag("p", {className: "date", textContent: `Deadline: ${item.date}`}),
);
}就我个人而言,我使用一种修改,结合tag和query,并将其命名为$ (因为我从不使用jQuery),并用帮助器函数命名append $,以简化公共属性。例如$.text、$.classText、$.class、create属性、textContent、className和textContent以及className加上任何其他属性。
有几十种方法可以简化向页面中添加内容。最好是闪电般的快速,当使用DOM调用构建页面时,您真的可以注意到速度的提高。
function addItem(item) {
return ( $("", $.class("todo-item")),
$("", $.classText("id", item.id, {hidden: true})),
#qcStackCode#( $("", $.class("x-wrapper", {name: "delete-button", value: item.id}),
$("", $.class("far fa-times-circle"))
),
$("", $.classText("todo-item-name", item.name, {name: "name"})),
$("", $.text("Details:", {for: "details"})),
$("", $.classText("details", item.details, {name:"details"})),
$("", $.classText("date", `Deadline: ${item.date}`))
);
}发布于 2020-12-10 00:23:32
非常好的代码,我会删除变量isListEmpty,因为它根本不做任何事情,您的if else分支已经检查了空列表,然后去掉了一个丑陋的全局变量。
考虑通过包装函数来消除所有的全局变量,这些函数需要将它们转换为类或其他函数。这样,变量就不是全局的,甚至可以将其中一些变量作为参数传递(例如:QUANTITY_START)。
addEmptyListInfo和addItem有相同的第一行和最后一行。您可以将它们中的中间提取到单独的函数中,并创建第三个通用函数以避免冗余,并确保添加新的“节”只在一个地方。
我不喜欢fetch url硬编码到.js文件中。考虑将其作为数据属性提取到html中,这样就可以控制url是如何生成的,如果更改django路由,则不必在多个地方修改url。
考虑将滚动的侦听器提取为单独的函数,例如:https://stackoverflow.com/a/12009497/4070660
至于scss,您可以使用自动修复程序来处理供应商前缀,并稍微清理代码。
大多数情况下,最好使用其他单位而不是像素。考虑使用em/rem或vw/vh/
在检查html :-)时,什么都没有想到。)
发布于 2020-12-10 03:05:03
使用 const when (可能)--使用let是对代码读者的警告,表明将来可能会重新分配变量。如果实际情况并非如此,则对更好使用const变量进行removed;考虑一条短袜。
removed 数组?它的用途乍一看并不完全清楚,我必须阅读几节代码才能理解它正在做什么。考虑将它命名为itemIdsToBeRemoved。此外,counter可能更好地命名为lastItemIdLoaded或类似的东西。
使用 camelCase for普通变量,这是JS中压倒一切的惯例:例如,考虑使用itemId和confirmButton。(顶部的全大写数值常数很好。)
DOMContentLoaded部分在这里做了4件事:
deleteItemListener、confirmDeleteButtonListener和load要在风格上保持一致,请考虑将滚动监听器抽象为一个单独的函数,就像您对其他侦听器所做的那样。(但这些函数本身并不是事件侦听器-它们添加了事件侦听器,因此可能会调用它们,例如,addDeleteConfirmListener)
Load数量复杂性处理从服务器请求的动态项目数,对我来说似乎有点过于复杂了。除非您期望有大量的有效负载,否则我认为应该立即获取所有项目,然后对客户端上应该显示的内容进行排序。同时发送所有项目也将减少用户的可见延迟。
如果要立即发送所有项,请考虑将数据插入到HTML中,而不是在页面加载后使用请求。你本可以
{"itemslist": [{"id": 191, "user_id": 26, "name": "name", "details": "details", "date": "2020-12-09"}]}const items = JSON.parse(document.querySelector('.items-data').textContent);这将使页面为用户加载得更快;假设应用程序以前已经加载过,并且您有一个合理的缓存策略,那么他们将不必等待两次往返到您的服务器来显示待办事项,而只需要一次。
Return早期避免缩进地狱如下:
function deleteItemListener() {
document.querySelector(".main").addEventListener("click", function(event) {
if(event.target.nodeName === "I") {
// big block of code可以是
function deleteItemListener() {
document.querySelector(".main").addEventListener("click", function(event) {
if (event.target.nodeName !== "I") {
return;
}
// big block of code甚至是
function deleteItemListener() {
document.querySelector(".main").addEventListener("click", function(event) {
if (event.target.nodeName === "I") {
deleteItem(event.target);
}nodeName?不清楚nodeName of i是否对应于X图标。考虑给i一个说明它是什么的类(如delete-icon,允许您执行e.target.matches('.delete-icon') )。
使用 .closest当导航到父母时-传递给.closest的选择器可以提供更多的信息,说明您要导航到哪个父服务器,并且它比使用.parentElement.parentElement更短:
const itemElement = event.target.closest('section');innerHTML ** 、** textContent**和** innerText
innerText。它是几乎从来没有你想要的 -它有一些令人困惑的行为。除了在很少的情况下需要使用innerText的S特有的格式怪癖检索文本,最好使用.textContent或.innerHTML。.innerHTML。如果您只想设置或检索纯文本,请使用.textContent;.textContent更快、更安全,而且在语义上更合适。与上述有关:
innerHTML通过用户输入的安全风险我可以通过在todo中输入以下描述来让您的站点执行任意代码:
我可以在其他文本中伪装标记,然后告诉另一个用户:“嘿,尝试用这个描述做一个新的事情,你不会相信接下来会发生什么!”--然后如果他们上当了,就窃取他们的登录凭据。
问题是,您使用的是:
itemNode.innerHTML =
`${item.id}
...
${item.details}像这样的HTML字符串的直接连接应该总是值得怀疑的。如果插值的值不是100%可信的,那么您可能会遇到问题。要修复它,在创建元素后通过设置.textContent将不值得信任的值填充到DOM中,如下所示:
itemNode.innerHTML = `
${item.id}
Details:
Deadline: ${item.date}
`;
itemNode.querySelector('h2').textContent = item.name;
itemNode.querySelector('.details').textContent = item.details;如果id和date是通过后端以安全的形式保存在数据库中的话,我希望它们是安全的,尽管代码没有问题--比如id应该保存为数字,日期应该保存为unix时间戳或日期。如果它们不是,而且您也不能因为任何原因,那么在保存它之前,您需要手动清除来自用户的任何内容。如果由于某种原因也不能这样做,那么就必须使用DOM方法而不是通过HTML级联将id和date属性插入到上面的DOM中,但最好是确保它们提前达到预期的安全格式。
您还可以在将name和details保存到数据库之前,通过消毒液将它们直接连接起来。
https://codereview.stackexchange.com/questions/253287
复制相似问题