前言
就地编辑是一道B站的面试题,它很好的考虑了用户的交互体验,即:平时为文本状态,当mouseover(鼠标悬浮)时,点击一下进入编辑状态,而点击别处时就会退出编辑状态并保存内容。这真是一个巧妙的用户需求,使得用户不需要繁琐的进入到个人状态编辑下来仅仅只是改动个人签名,方便又快捷。
我们利用JS也可以简单的完成这一需求,不过还需要了解一些知识
基础知识
1. npm init -y
npm init -y 是一个在Node.js项目中快速初始化package.json文件的命令。当你在命令行中执行这条命令时,会发生以下事情:
- 自动创建
package.json文件:该命令会在当前目录下自动生成一个package.json文件,而无需你一步步回答配置问题。这意味着它会使用默认设置来创建文件,包括默认的名称(通常是当前目录名)、版本(通常是 "1.0.0")、描述(空)、入口文件(index.js)等。 - 跳过交互式提问:
-y或--yes参数告诉npm接受所有默认选项,从而跳过了npm init命令原本会进行的一系列交互式提问过程,这让初始化过程变得非常快。
package.json 文件对于Node.js项目至关重要,因为它存储了项目的元数据(如名称、版本)、依赖关系(项目所需的npm包及其版本号)、脚本命令以及其他配置信息。一旦创建,你随时可以手动编辑这个文件来添加或修改信息。
2. Json-server
在这个项目中,初始化项目后,就要有一个服务器模拟账号了,而JS的Json-server则很适合这一个轻量的项目。
json-server 是一个非常实用的工具,它能够快速搭建一个RESTful API服务器,这个服务器基于JSON文件存储数据。这对于前端开发者在开发阶段模拟后端接口、进行API测试或者创建原型时特别有用。你不需要配置数据库或编写复杂的后端代码,只需一个JSON文件作为数据源即可。
使用步骤很简单:
-
安装:首先,你需要全局安装
json-server。打开命令行工具,运行以下命令:npm install -g json-server -
创建数据文件:接下来,创建一个包含数据的JSON文件,比如
db.json。这个文件将作为你的数据库:{ "users": [ { "id": "1", "name": "ace", "signature": "帅的惊动党中央" }, { "id": "2", "name": "ade", "signature": "帅的惊动联合国" } ] } -
启动服务器:在你的JSON文件所在的目录下,运行以下命令来启动
json-server:json-server --watch db.json这将启动一个API服务器,默认监听3000端口。
-
访问API:现在你可以通过HTTP请求来访问这些资源了。例如,获取users下id为1的用户可以使用:
url http://localhost:3000/users/1这就访问到users下id为1的对象了
或在浏览器中访问
http://localhost:3000/users/1。
json-server 支持CRUD操作(创建Create、读取Read、更新Update、删除Delete),这就为我们的就地编辑打好了服务器基础。
loclStorge(本地存储)
localStorage 是 Web 浏览器提供的一种存储机制,属于 Web 存储 API 的一部分。它允许前端 JavaScript 代码在用户的浏览器上存储数据,这些数据会以键值对的形式存在,并且即使在浏览器关闭后再重新打开,数据依然会保留。这意味着 localStorage 提供了一种持久化的本地存储方式,非常适合用来保存用户偏好设置、登录状态等信息。请注意,每个域下的 localStorage 是隔离的,也就是说,来自不同网站的数据不会互相干扰。
而这里利用本地存储来实现我们的小功能就很合适,而它也很简单,就几个关键字
- setItem(key,value)
要向 localStorage 中保存数据,你可以使用 setItem 方法,它接受两个参数:一个是你为数据指定的键(key),另一个是你要保存的数据(value)(通常是以字符串形式)
// 保存数据到
localStorage localStorage.setItem('username', 'John Doe');
localStorage.setItem('age', '30');
- getItem(key)
要从
localStorage中读取数据,可以使用getItem方法,传入之前设定的键名。
// 从 localStorage 获取数据
var username = localStorage.getItem('username');
var age = localStorage.getItem('age');
console.log(username); // 输出: John Doe
console.log(age); // 输出: 30
- removeItem(key)
如果需要删除某个键值对,可以使用
removeItem方法,传入对应的键名。
// 删除名为 'username' 的数据
localStorage.removeItem('username');
- clear()
如果你想清空该域名下的所有
localStorage数据,可以调用clear方法。
// 清空所有 localStorage 数据
localStorage.clear();
这就是localStorage的基本用法了,让我们进入今天的主题---edit in place(就地编辑)
edit in place(就地编辑)
html部分
<div id="signature"></div>
<script src="./edit_in_place.js"></script>
<script>
const STORAGEKEY = 'signature'
fetch("http://localhost:3000/users/1")
.then(res=> res.json())
.then(data=>{
// console.log(data,'////');
const {signature} = data
const stoSignature = localStorage.getItem(STORAGEKEY)
if(signature !== stoSignature) {
localStorage.setItem(STORAGEKEY,signature)
}
new EditInPlace(STORAGEKEY,
document.getElementById('signature'),signature)
})
首先是存放了一个id为signature的div容器
然后引入edit_in_place这个JS文件,其中需要有就地编辑需要的一些功能
最后这里就比较重要了,我来一一讲解:
-
const STORAGEKEY = 'signature':定义了一个常量STORAGEKEY,用于存储在localStorage中的键名。 -
fetch("http://localhost:3000/users/1"):使用fetch函数从本地服务器(假设地址为http://localhost:3000)的/users/1端点获取数据。这是一个promise的一个Api,详解请见。 -
.then(res=> res.json()):当fetch请求成功返回(即HTTP状态码为200-299之间)时,这个.then会被调用,将响应体转换为JSON格式。 -
.then(data=> { ... }):进一步处理从服务器获取的数据。在这个回调函数中:- 首先通过解构赋值
const {signature} = data获取data对象中的signature属性。 - 然后,使用
localStorage.getItem(STORAGEKEY)获取当前存储在localStorage中的signature值,存储在变量stoSignature中。 - 接下来,比较从服务器得到的
signature是否与localStorage中存储的signature不同,如果不同,则使用localStorage.setItem(STORAGEKEY, signature)更新localStorage中的signature值。 - 最后,调用了
EditInPlace函数(这是个自定义的构造函数,由edit_in_place.js传入,用于实现就地编辑功能),传入了STORAGEKEY、一个DOM元素(通过document.getElementById('signature')获取)以及当前的signature值作为参数。
- 首先通过解构赋值
edit_in_place.js
这个文件需要一个构造函数,通过原型链(详见请见)将切换文本状态与编辑状态、保存等功能传给我们的实例, 代码如下:
function EditInPlace(storageKey, container, value = 'ace') {
// console.log('-----');
this.storageKey = storageKey
this.container = container;
this.value = value
//将动态创建文本和编辑input的dom封装,代码管理好!
this.createElement();
// console.log(this.value);
this.attachEvents();
}
EditInPlace.prototype = {
//就地编辑需要的动态dom
createElement: function () {
this.editElement = document.createElement('div');
//添加一个子元素
this.container.appendChild(this.editElement);
// signature文本值
this.staticElement = document.createElement('span');
this.staticElement.innerHTML = this.value;
this.editElement.appendChild(this.staticElement);
// input
this.fieldElement = document.createElement('input');
this.fieldElement.type = 'text';
this.fieldElement.value = this.value;
this.editElement.appendChild(this.fieldElement)
// 确定按钮
this.saveButton = document.createElement('input');
this.saveButton.type = 'button';
this.saveButton.value = '保存';
this.editElement.appendChild(this.saveButton);
// 取消按钮
this.cancelButton = document.createElement('input');
this.cancelButton.type = 'button';
this.cancelButton.value = '取消';
this.editElement.appendChild(this.cancelButton);
// 初始文本状态
this.converTotext();
},
// 切换文本状态
converTotext: function () {
this.staticElement.style.display = 'inline';
this.fieldElement.style.display = 'none';
this.saveButton.style.display = 'none';
this.cancelButton.style.display = 'none';
},
// 切换编辑状态
converToedit: function () {
this.staticElement.style.display = 'none';
this.fieldElement.style.display = 'inline';
this.saveButton.style.display = 'inline';
this.cancelButton.style.display = 'inline';
},
// 事件监听
attachEvents: function () {
// 用that存储this
// let that = this;
// this.staticElement.addEventListener('click',function(){
// that.converToedit();
// })
// this指向实例本身
this.staticElement.addEventListener('click', () => {
this.converToedit();
})
this.saveButton.addEventListener('click', () => {
this.save();
})
this.cancelButton.addEventListener('click', () => {
this.converTotext();
})
},
save: function () {
this.value = this.fieldElement.value;
this.staticElement.innerHTML = this.value;
this.converTotext();
// html5 localStorge 存储
localStorage.setItem(this.storageKey, this.value)
this.saveData()
},
saveData: function () {
let value = this.value;
// restful = url定义方式+Method
// GET 读 POST创建 PUT更新 PATCH 局部更新 DELETE删除
// 看到这个url就知道什么资源
// 修改资源
fetch('http://localhost:3000/users/1', {
method: 'PATCH',
// 请求头,发送的内容 格式是JSON
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
signature: value
})
})
.then(res=>res.json())
.then(data=>{
console.log(data,'保存成功');
})
}
}
这段代码定义了一个名为 EditInPlace 的类(通过原型链继承的方式实现),旨在实现实时编辑(就地编辑)功能,允许用户直接在网页上点击文本并编辑其内容,编辑完成后可选择保存或取消。编辑后的数据不仅更新到页面上,还会同步存储到浏览器的 localStorage 中,并通过 AJAX 调用发送到服务器更新数据。以下是按阶段分析的详细说明:
初始化阶段 (EditInPlace 构造函数)
- 构造函数: 接收三个参数——
storageKey(用于本地存储的键名)、container(DOM容器元素)和可选的默认value。它初始化实例属性,并调用createElement方法来构建编辑界面,然后调用attachEvents方法绑定事件监听。 - 创建元素:
createElement方法动态创建一系列DOM元素(包括显示文本的<span>、编辑用的<input type="text">、保存和取消按钮),并将这些元素添加到指定的容器中。同时,初始化界面为文本显示模式。 - 绑定事件:
attachEvents方法为创建的元素绑定点击事件。点击静态文本区域会切换至编辑模式;点击保存按钮会保存编辑内容并恢复为文本显示模式,同时更新本地存储及向服务器发送更新请求;点击取消按钮则不保存更改,直接恢复文本显示模式。
编辑与保存阶段
- 切换模式:
converToedit和converTotext方法分别用于在文本显示模式和编辑模式间切换界面显示。 - 保存逻辑:
save方法在用户点击保存按钮时被调用,它首先更新实例的value属性为当前输入框的值,同步更新页面上的静态文本显示,然后调用converTotext恢复文本显示,并使用localStorage.setItem更新本地存储中的签名信息。 - 数据同步到服务器:
saveData方法负责将编辑后的数据发送到服务器。这里使用了fetch发起一个PATCH请求到http://localhost:3000/users/1,请求体包含JSON格式的签名数据。服务器响应被转换为JSON并打印出来,表明保存操作的成功。
这就是一个就地编辑功能的实现了.
总结
这是一道B站经典的面试题,不说很难,但很多细节还是要注意的,例如:把实现编辑功能的构造函数封装为一个文件,代码的复用性还是很重要的;还有对于this、fetch、loaclStorage的运用也是很重要的......等等。
如果我的分享对你有用,不妨点个赞吧,你的赞对我很重要!!!
我是Ace,我们下次再见!