想成为大厂前端?先学会这个—localStorage基础使用!

1,441 阅读9分钟

引言

在现代Web开发中,如何优雅地保存用户输入并确保其在页面刷新后仍然存在,是一个常见的需求。localStorage 提供了一个简单而强大的解决方案,它允许我们在浏览器中持久化存储数据。本文将通过一个生动的例子——“LOCAL TAPAS”应用,向您展示 localStorage 的魅力。

2.jpg

话不多说,结果展示

image.png

image.png

如上图所示,在重新进入界面时,之前输入的数据依然存在。这一功能的实现,正是得益于 localStorage 的使用。当用户离开页面后再回来时,他们不会丢失任何已经输入的信息,这大大提升了用户体验。而在这里我们可以通过以下步骤查看我们的localStorage在哪里,并以什么形式存储数据。

image.png

接下来我们将深入探讨这个页面的实现细节以及 localStorage 的具体用法。

代码演示

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta data-n-head="ssr" name="viewport"
        content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover">
    <title>localStorage</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        
        html {
            box-sizing: border-box;
            background: url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat;
            background-size: cover;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
            font-family: Futura, "Trebuchet MS", Arial, sans-serif;
        }

        *,
        *:before,
        *:after {
            box-sizing: inherit;
        }

        svg {
            fill: white;
            background: rgba(0, 0, 0, 0.1);
            padding: 20px;
            border-radius: 50%;
            width: 200px;
            margin-bottom: 50px;
        }

        .wrapper {
            padding: 20px;
            max-width: 350px;
            background: rgba(255, 255, 255, 0.95);
            box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1);
        }

        h2 {
            text-align: center;
            margin: 0;
            font-weight: 200;
        }

        .plates {
            margin: 0;
            padding: 0;
            text-align: left;
            list-style: none;
        }

        .plates li {
            border-bottom: 1px solid rgba(0, 0, 0, 0.2);
            padding: 10px 0;
            font-weight: 100;
            display: flex;
        }

        .plates label {
            flex: 1;
            cursor: pointer;
        }

        .plates input {
            display: none;
        }

        .plates input+label:before {
            content: '⬜';
            margin-right: 10px;
        }

        .plates input:checked+label:before {
            content: '🌮';
        }

        .add-items {
            margin-top: 20px;
        }

        .add-items input {
            padding: 10px;
            outline: 0;
            border: 1px solid rgba(0, 0, 0, 0.1);
        }
    </style>
</head>

<body>
    <div class="wrapper">
        <h2>LOCAL TAPAS</h2>
        <p></p>
        <ul class="plates">
            <li>Loading Taps...</li>
        </ul>
        <form class="add-items">
            <input type="text" name="item" placeholder="Item Name" required>
            <input type="submit" value="+ Add Item">
        </form>
        <script>
            const addItems = document.querySelector('.add-items');  //form
            const itemsList = document.querySelector('.plates');  //ul
            const items = JSON.parse(localStorage.getItem('items')) || [];
            
            //event 是事件对象 1.内部this指向提交的表单 2.运行时分配event对象
            function addItem(event) {
                //阻止默认行为
                event.preventDefault();
                //.querySelector()体现了性能 优化 
                const text = (this.querySelector('[name=item]')).value.trim();
                //创建新项对象
                const item = {
                    text,//es6
                    done: false
                }

                items.push(item);
                //更新DOM树
                populateList(items, itemsList);
                //存储数据 更新本地存储
                localStorage.setItem('items', JSON.stringify(items));
                清空输入框准备下一次输入
                this.reset();
            }

            //渲染ul
            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('');  //去除页面中的“,”
            }
            addItems.addEventListener('submit', addItem)
            populateList(items, itemsList);

            //// 处理复选框状态变更
            itemsList.addEventListener('change', function (event) {
                if (event.target.type === 'checkbox') {
                    const index = event.target.dataset.index;
                    items[index].done = event.target.checked;
                    localStorage.setItem('items', JSON.stringify(items));
                }
            });

        </script>
    </div>
</body>
</html>

代码流程解析

HTML 和 CSS 编写

HTML与CSS部分构建了页面的基础结构和视觉样式。这部分较为基础,主要定义了页面元素及其样式,如表单、无序列表等,并设置了全局样式规则以确保一致性和美观性,对于想要深入了解这部分内容的朋友,可以通过查阅相关代码获取更多信息。

JavaScript 实现逻辑详解

初始化与加载现有数据

在JavaScript中,我们首先使用querySelector方法选择DOM中的.add-items表单和.plates无序列表元素,并创建了一个名为items的常量。该常量从浏览器的localStorage中读取名为items的数据项。如果localStorage中没有找到对应的键值对,则初始化为空数组。这一步骤确保了用户返回页面时能看到之前添加的所有项。

添加新项到列表

function addItem(event) {
......
}

当用户提交表单时,addItem函数被触发。它首先阻止了表单的默认提交行为,然后获取输入框中的文本,并将其与一个done属性(初始为false)一起包装成一个新的对象item。接着,这个新对象被添加到items数组中,调用populateList函数更新DOM,将最新的数据保存回localStorage,并重置表单以便用户继续添加更多项。

渲染列表

function populateList(plates, platesList) {
    platesList.innerHTML = plates.map((plate, i) => {
       ......
    }).join('');  // 去除页面中的逗号
}

populateList函数负责根据当前items数组的状态构建HTML列表。它遍历plates数组,为每个元素创建一个<li>标签,包括一个复选框和标签文字。这些标签随后被合并成一个完整的字符串并赋值给platesList.innerHTML,从而实现DOM的批量更新。

用户交互:更新项状态

itemsList.addEventListener('change', function(event) {
......
});

为了保持复选框状态的一致性,我们在itemsList上监听change事件。每当复选框状态改变时,会找到对应的项并更新其done属性,最后再次保存更新后的items数组到localStorage中,以确保状态的持久化。

关键代码解释

  • const items = JSON.parse(localStorage.getItem('items')) || []; :这段代码尝试从localStorage中检索名为items的数据项,并使用JSON.parse()方法解析成JavaScript对象或数组。如果不存在这样的数据项或解析失败,则初始化为空数组。
  • localStorage.setItem('items', JSON.stringify(items)); :这里使用了JSON.stringify()方法将items数组转换为字符串格式,再通过localStorage.setItem()方法保存到浏览器的本地存储中。这是为了让复杂的数据结构(例如对象或数组)可以被正确地序列化和保存。
  • platesList.innerHTML = plates.map((plate, i) => {}).join(''); :此代码片段用于动态生成HTML内容。它遍历plates数组,为每个元素创建相应的HTML标记,最后通过join('')方法将所有标记合并成一个完整的字符串,赋值给platesList.innerHTML,以此方式更新DOM树。

对于代码考点的思考

HTML5 localStorage 简介

它允许开发者以键值对的形式保存数据,并且这些数据不会因为浏览器关闭或页面刷新而丢失。每个源都有大约5MB的存储空间可用。

  • 存储与获取:通过setItem方法可以将字符串形式的数据存储到localStorage中,而getItem则用于检索对应的值。
  • 序列化与反序列化:由于localStorage只能存储字符串,因此使用JSON.stringify()将对象或数组转换成字符串进行存储,再用JSON.parse()将其解析回原来的数据结构。
  • 表单重置this.reset()用于重置表单元素,使得用户可以连续添加多个项目而不必手动清空输入框。

HTML5 特性

文档类型声明

<!DOCTYPE html>这是HTML5的标准文档类型声明,确保浏览器按照最新的标准渲染页面,提高兼容性和性能。

视口设置

<meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover">这段元标签定义了移动设备上的视口行为:

  • user-scalable=no禁止用户缩放页面,提供更一致的用户体验,尤其是在移动端应用中,这种做法很常见,因为它避免了不必要的复杂操作,随着移动互联网的发展,专门为移动端优化的网站逐渐成为主流,这使得PC端的一些设计习惯(如频繁缩放)变得不那么必要。

表单增强功能

HTML5增加了诸如placeholderrequired等属性,改善了表单的用户体验,placeholder提供了提示信息,而required确保用户必须填写该字段才能提交表单。

JavaScript 性能考量

JavaScript中的DOM编程确实是最耗性能的部分之一,尤其是涉及到查找和修改DOM节点时。为了优化性能,我们可以采取以下措施:

  • 减少DOM操作:尽可能批量更新DOM,比如一次性构建完整的HTML字符串然后插入DOM树,而不是频繁地修改单个元素。
  • 缩小查找范围:利用querySelector方法可以在特定上下文中查找元素,从而减少了全局搜索带来的开销。
  • 采用虚拟DOM技术:虽然当前代码没有使用框架如Vue.js,但在大型应用中,虚拟DOM可以显著提升性能,因为它只更新实际发生变化的部分。

良好的代码风格不仅有助于团队协作,还能让代码更容易维护和扩展。以下几点建议旨在帮助您写出既专业又易于理解的代码:

代码风格建议

命名清晰的事件处理函数

为事件处理函数选择有意义的名字至关重要。一个恰当的函数名能够在不查看具体实现的情况下传达其功能,即使在项目规模扩大时也能快速理解代码意图,清晰的命名如同路标,指引开发者快速找到所需逻辑。

充分利用ES6特性

现代JavaScript(ES6及以上版本)引入了许多简洁而强大的语法糖,如对象字面量简化写法、箭头函数等。合理使用这些特性可以使代码更加紧凑且易读。比如,当对象属性名与值同名时,可以省略重复的键值对书写;利用解构赋值简化参数获取等。这种做法不仅减少了冗余,还提高了代码的可读性和开发效率。

遵循单一职责原则

保持函数简短精炼,专注于单一任务。这不仅能提升代码的可读性和可维护性,还便于单元测试和功能复用。大型应用中,每个函数都应像一个乐高积木块,小而独立,组合起来构建复杂逻辑。避免过长的函数,因为它们会让阅读者感到如同在水下憋气般难受,难以一口气理解整个逻辑。

  • 多拆分函数:将复杂的功能分解成多个小型函数,使得每个函数只做一件事。
  • 方便复用:小型函数更可能被其他地方复用,减少冗余代码。
  • 提高可读性:简短的函数更容易理解和审查,降低了错误发生的几率。
  • 增强可测试性:单一职责的函数更容易编写针对性的单元测试。

结语

本项目不仅实现了数据持久化存储,更展示了如何在实际开发中运用现代Web技术。这一过程不仅是技术实现的展示,更是对开发者解决问题能力和代码质量追求的体现,希望这篇文章不仅能为您提供有价值的知识摄入,还能为你的大厂之路提供素材。

16.jpg