第十五天挑战(本地存储菜谱)
地址:https://javascript30.com/
所有内容均上传至gitee,答案不唯一,仅代表本人思路
中文详解:https://github.com/soyaine/JavaScript30
该详解是Soyaine及其团队整理编撰的,是对源代码的详解,强烈推荐大家观看学习!!!
本人gitee:https://gitee.com/thats-all-right-ha-ha/30-days—js-challenge
效果
-
样式分析
- 组件整体居中,组件内部由头部,菜单主体,菜单添加模块组成
-
逻辑分析
-
在输入框输入菜名,点击添加后,菜名在菜单主体内显示
-
输入框非空限定
-
点击菜单左侧的多选框,出现选中样式
-
菜单保存在本地,当页面刷新或关闭后,菜单内容和选中状态不会被重置
-
本人代码及思路分析
仅提供布局及逻辑代码
结构:
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<p></p>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
</div>
逻辑:
//获取元素
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const submit = document.querySelector('input[type="submit"]')
const items = JSON.parse(localStorage.getItem('Tapas')) || [];
//页面添加元素 + 本地存储
function addItem(e) {
e.preventDefault()
const text = document.querySelector('input[type="text"]').value
console.log(text)
let content = {
text,
isDone: false
}
template(content, items.length)
items.push(content)
localStorage.setItem('Tapas', JSON.stringify(items))
this.reset()
}
//模板函数
function template(dom, index) {
let check = ''
if (dom.isDone) check = 'checked'
itemsList.innerHTML += `<li>
<input type='checkbox' data-index=${index} id='item${index}' ${check}>
<label for='item${index}'>${dom.text} </label>
</li>`
}
//页面初始化
function getItem() {
items.forEach((item, index) => {
template(item, index)
})
}
getItem()
//事件标记,动态存储
function check(e) {
const checkbox = document.querySelectorAll('input[type="checkbox"]')
const index = e.target.dataset.index
console.log(index)
if(index || index === 0){
items[index].isDone = !items[index].isDone
localStorage.setItem('Tapas',JSON.stringify(items))
}
}
//事件监听
addItems.addEventListener('submit', addItem)
itemsList.addEventListener('click', check)
分析:
-
**整体思路:**当提交事件执行的时候,使用localStorage对输入的内容和内容状态进行本地存储,在页面初始化的时候获取本地存储的数据并进行渲染,
-
具体实现:
- 首先选定需要监听和修改的元素
- addItem:
- 进行提交的时候会对默认对页面进行刷新,先使用e.preventDefault阻止默认事件
- 获取输入框内的内容,并初始化需要添加的数据,并将数据添加到模板中,通过模板添加到页面中
- 将数据添加到数组中,再将该数组添加到本地,最后重置表单内容
- template:构建字符串模板,并初始化元素属性(data-index & item)和选中状态
- getItem:页面刷新或打开时,获取本地数据,并将他们通过template函数渲染到页面中
- check:
- 获取页面中所有的复选框
- 这里监听的是itemsList父级元素,target会返回父级元素中所包含的所有元素,这里会返回两个元素,分别是label和input
- 事先定义了data-index作为页面中添加元素的下标,并对其进行了唯一标识
- 首先判断其是否携带data-index属性来判断其是否是input标签,如果存在那么便从checkbox集合中通过index寻找出对应的被点击的input元素,并对其状态进行动态修改,修改后保存到本地
-
弊端分析(与官方方法对比):
- DOM操作频繁: 在模板函数中,每次添加一个待办事项都会重新设置itemsList的innerHTML,这样会引起DOM操作频繁,效率较低。
- 事件委托不足: 目前勾选完成待办事项的事件监听是直接添加在每个checkbox上的,当待办事项较多时,会导致事件监听器过多,影响性能。应该考虑使用事件委托,将事件监听器添加到父元素上,然后通过事件冒泡来处理。
- **页面初始化渲染性能开销大:**页面初始化渲染的时候使用的是forEach方法,该方法频繁对页面元素进行增加,消耗性能
官方代码
官方代码仅代表该案例原作者思路,不唯一
结构
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<p></p>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
</div>
逻辑
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const items = JSON.parse(localStorage.getItem('items')) || [];
function addItem(e) {
e.preventDefault();
const text = (this.querySelector('[name=item]')).value;
const item = {
text,
done: false
};
items.push(item);
populateList(items, itemsList);
localStorage.setItem('items', JSON.stringify(items));
this.reset();
}
function populateList(plates = [], platesList) {
platesList.innerHTML = plates.map((plate, i) => {
return `
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''} />
<label for="item${i}">${plate.text}</label>
</li>
`;
}).join('');
}
function toggleDone(e) {
if (!e.target.matches('input')) return; // skip this unless it's an input
const el = e.target;
const index = el.dataset.index;
items[index].done = !items[index].done;
localStorage.setItem('items', JSON.stringify(items));
populateList(items, itemsList);
}
addItems.addEventListener('submit', addItem);
itemsList.addEventListener('click', toggleDone);
populateList(items, itemsList);
分析
仅代表本人对该代码的分析
建议直接去看Soyaine的中文详解
-
**整体思路:**整体写法与上述保持一致
-
具体实现:
- addItem:与上述相比,这里是通过name属性来选择元素
- toggleDone:这里使用正则的方法对父级元素内的子元素进行过滤,保留input元素
- populateList:这里使用map方法对添加元素进行整合,最后整体添加到页面中
-
优点:
- 通过
map
方法一次性生成所有待办事项的HTML字符串,然后一次性插入到DOM中,减少了浏览器的重绘和重排次数,但在处理大量数据时仍有优化空间。 - 第二段代码通过在列表的父元素上监听点击事件,使用了事件委托的策略,这样做可以减少事件监听器的数量,提高性能,尤其是在待办事项数量较多时。
- 通过