在写一个项目的时候,遇到了头像修改这个功能的需求,在最开始的学习中发现可以通过type为file的input文件读取图片,然后将其转换为DataUrl格式,最终作为Ima元素的src即可在页面上展示图片。但到后面开始写交互的时候发现DataUrl格式的图片数据太长了,发送请求不是请求头太大就是数据库塞不下,然后开始学习后面的multipart/form-data这种数据传输方式(虽然没用,但学了doge^-^)
前端开发中的 DataUrl:原理、应用与实践
一、DataUrl 是什么
DataUrl,即数据 URL,是一种将数据直接嵌入 URL 的方式,其目的是将一些小的数据,直接嵌入到网页中,从而避免额外的网络请求。它的格式遵循特定的规范,基本格式如下:
data:[<mediatype>][;base64],<data>
其中,mediatype是数据的 MIME 类型,例如image/png表示 PNG 图片,text/plain表示纯文本等;base64是一个可选的参数,当数据需要进行 Base64 编码时使用,一般图片等二进制数据会采用 Base64 编码;最后的<data>部分就是实际的数据内容。
举个简单的例子,一个表示绿色像素点的 DataUrl 如下:
data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
在这个 DataUrl 中,image/gif表明数据是 GIF 图片类型,base64表示后面的数据经过了 Base64 编码,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7就是编码后的图片数据。
二、DataUrl 的原理
DataUrl 的实现原理基于浏览器对 URL 协议的支持和解析。当浏览器遇到一个 DataUrl 时,它会按照上述格式规则对其进行解析,提取出 MIME 类型和数据部分。如果数据是经过 Base64 编码的,浏览器会自动对其进行解码,然后根据 MIME 类型来处理数据。
以图片为例,当一个 HTML 页面中使用 DataUrl 作为img标签的src属性值时,浏览器解析到该 DataUrl 后,会解码数据并将其渲染为图片,整个过程不需要发起额外的网络请求去获取图片资源。这种方式对于一些小的、不经常变化的资源来说,能够有效减少网络请求次数,提升页面的加载速度。
三、常见使用场景
(一)小图标嵌入
在网页设计中,常常会用到一些小图标,如按钮图标、导航图标等。将这些小图标转换为 DataUrl,然后直接嵌入到 CSS 或 HTML 中,可以避免为每个小图标单独发起网络请求。例如,在 CSS 中设置背景图片:
.icon {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...);
}
这样,当页面加载时,图标会随着 CSS 样式的解析一起呈现,无需额外请求,提高了页面渲染效率。
(二)内联样式和脚本
对于一些简单的 CSS 样式或 JavaScript 脚本,也可以使用 DataUrl 的形式嵌入到 HTML 中。比如,在 HTML 的style标签中嵌入一段 Base64 编码后的 CSS 样式:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><style>body {background: url(data:text/css;base64,LmltZyB7cGFkZGluZzogMTAwcHggMTAwcHg7fQ==);}</style><title>DataUrl示例</title></head><body><!-- 页面内容 --></body></html>
虽然这种方式在实际项目中不常大规模使用,但在某些特定场景下,如生成动态页面且样式内容较小时,能简化资源管理。
四、优缺点分析
(一)优点
- 减少网络请求:正如前面提到的,将小资源转换为 DataUrl 嵌入页面,避免了额外的 HTTP 请求,对于页面加载速度有显著提升,尤其是在网络环境较差的情况下效果更明显。
- 便于资源管理:将资源直接嵌入代码中,不需要单独管理资源文件,在一些小型项目或特定场景下,能简化项目结构和开发流程。
- 动态生成资源:可以根据运行时的数据动态生成 DataUrl,实现一些灵活的功能,如根据用户输入生成特定的图片或下载文件。
(二)缺点
- 增大文件体积:Base64 编码会使数据体积增加约 1/3 左右,对于较大的资源,转换为 DataUrl 后会使 HTML 或 CSS 文件体积大幅增大,反而影响加载速度,这同时也意味着其不太适合作为请求的数据发送。
- 缓存问题:由于 DataUrl 直接嵌入在代码中,浏览器无法像对独立资源文件那样进行有效的缓存管理。如果 DataUrl 中的数据经常变化,会导致每次页面加载都需要重新处理这部分数据。
- 可读性和可维护性差:大量的 Base64 编码数据嵌入代码中,会使代码变得冗长且难以阅读和维护,不利于项目的长期迭代和团队协作。
五、FileReader 对象与 DataUrl
FileReader是 JavaScript 中用于异步读取文件内容的对象,常与DataUrl结合使用,实现将本地文件转换为DataUrl,从而在前端进行处理和展示。
(一)基本用法
FileReader对象有几个重要的方法和事件:
- readAsDataURL(file):读取指定的文件,并将文件内容以 DataUrl 的形式返回。
- onload事件:当文件读取成功完成时触发,在该事件回调函数中可以获取到转换后的 DataUrl。
- onerror事件:当文件读取发生错误时触发。
以下是一个简单示例,演示如何使用FileReader将用户选择的图片文件转换为 DataUrl 并展示在页面上:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>FileReader与DataUrl示例</title></head><body><input type="file" id="fileInput"><img id="previewImage" src="" alt="图片预览"><script>const fileInput = document.getElementById('fileInput');const previewImage = document.getElementById('previewImage');fileInput.addEventListener('change', function (e) {const file = e.target.files[0];if (file) {const reader = new FileReader();reader.onload = function (event) {const dataUrl = event.target.result;previewImage.src = dataUrl;};reader.onerror = function (error) {console.log('文件读取错误: ', error);};reader.readAsDataURL(file);}});</script></body></html>
在上述代码中,当用户选择一个图片文件后,通过FileReader的readAsDataURL方法将文件读取为 DataUrl,读取成功后在onload事件中,将得到的 DataUrl 设置为img标签的src属性,从而实现图片的预览功能。
(二)实际应用场景拓展
在实际项目中,FileReader与DataUrl结合可以应用于更多场景。例如,在图片上传功能中,在将图片发送到服务器之前,可以先使用FileReader将图片转换为 DataUrl 进行本地预览,确认无误后再上传,提升用户体验。还可以在一些表单提交场景中,将用户上传的文件转换为 DataUrl 格式,与其他表单数据一起以 JSON 格式发送到服务器,简化数据传输和处理流程。
六、multipart/form-data
(一)原理
multipart/form-data是一种在 HTTP 协议中用于发送表单数据的编码方式,特别是在需要上传文件的场景中广泛应用。它将表单数据分割成多个部分,每个部分都有自己的头部信息,描述了数据的类型、名称等信息,各部分之间使用特定的边界字符串进行分隔。服务器接收到数据后,根据边界字符串解析出各个部分的数据,并进行相应的处理 。
例如,当用户在网页上提交一个包含文件和文本字段的表单时,浏览器会按照multipart/form-data的格式将数据封装成类似以下的结构发送给服务器:
--boundary
Content-Disposition: form-data; name="username"
JohnDoe
--boundary
Content-Disposition: form-data; name="userfile"; filename="example.txt"
Content-Type: text/plain
这是文件的内容
--boundary--
其中,boundary是自定义的边界字符串,用于区分不同的数据部分。
(二)使用场景
- 文件上传:最常见的应用场景就是文件上传,无论是单个文件还是多个文件上传,multipart/form-data都能很好地处理文件数据与其他表单数据(如文件名、文件描述等文本字段)的混合传输。
- 表单提交含二进制数据:当表单中除了普通文本数据,还包含图片、音频、视频等二进制数据时,multipart/form-data是理想的选择,能确保各类数据准确无误地发送到服务器。
(三)与 DataUrl、FileReader 的对比
- 数据传输方式
- DataUrl:将数据直接嵌入 URL 中,适用于小数据的内联展示或简单的数据导出,数据会随着 HTML、CSS 等页面代码一起传输。
- FileReader:主要用于在前端将本地文件读取为特定格式(如 DataUrl),方便在前端进行数据处理和展示,本身不涉及数据向服务器的传输。
- multipart/form-data:专门用于在 HTTP 请求中将表单数据(包括文件和普通数据)发送到服务器,是一种数据传输协议。
- 适用场景
- DataUrl:适合小图标嵌入、内联样式脚本、小型数据导出等场景。
- FileReader:常用于需要在前端对本地文件进行处理,如预览图片等场景。
- multipart/form-data:主要用于文件上传以及包含二进制数据的表单提交。
- 优缺点
- DataUrl:优点是减少网络请求、便于资源管理;缺点是增大文件体积、缓存困难、可读性差。
- FileReader:优点是方便前端处理本地文件;缺点是仅适用于前端操作,不涉及数据传输到服务器。
- multipart/form-data:优点是能高效传输复杂表单数据,支持文件上传;缺点是数据格式相对复杂,解析处理需要一定开销。
(四)示例
以下是一个使用multipart/form-data进行文件上传的 HTML 表单示例:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>multipart/form-data示例</title></head><body><form action="upload.php" method="post" enctype="multipart/form-data"><label for="username">用户名:</label><input type="text" id="username" name="username" required><br><label for="userfile">选择文件:</label><input type="file" id="userfile" name="userfile" required><br><input type="submit" value="上传"></form></body></html>
在上述代码中,通过设置表单的enctype="multipart/form-data"属性,指定了使用multipart/form-data编码方式发送表单数据。服务器端(如upload.php)可以根据这种编码格式解析接收到的数据,提取出用户名和上传的文件。
七、multipart/form-data在js里面的使用:Form-data
(一)原理
FormData
是HTML5新增的一个接口,用于创建和操作表单数据。它提供了一种简单的方式来收集表单元素的数据,并将其格式化为multipart/form-data
或application/x-www-form-urlencoded
格式,以便通过AJAX请求发送到服务器。
FormData
对象的核心原理是模拟HTML表单的提交过程,将表单中的各个字段(包括文本字段、文件字段等)收集起来,并按照一定的格式组织成键值对。这些键值对可以包含普通文本数据,也可以包含文件对象,然后可以通过XMLHttpRequest或Fetch API发送到服务器。
(二)常用方法与属性
-
构造函数
new FormData()
:创建一个空的FormData
对象。new FormData(form)
:从一个HTML表单元素中收集数据,创建FormData
对象。
-
方法
append(name, value)
:向FormData
中添加一个字段,字段名为name
,值为value
。append(name, file, filename)
:向FormData
中添加一个文件字段,file
是一个File对象,filename
是可选的文件名。delete(name)
:从FormData
中删除所有字段名为name
的字段。get(name)
:获取FormData
中第一个字段名为name
的字段的值。getAll(name)
:获取FormData
中所有字段名为name
的字段的值,返回一个数组。has(name)
:检查FormData
中是否存在字段名为name
的字段。set(name, value)
:设置FormData
中字段名为name
的字段的值,如果该字段不存在则添加。set(name, file, filename)
:设置FormData
中字段名为name
的文件字段的值。entries()
:返回一个迭代器,用于遍历FormData
中的所有键值对。keys()
:返回一个迭代器,用于遍历FormData
中的所有字段名。values()
:返回一个迭代器,用于遍历FormData
中的所有字段值。
(三)应用场景
- AJAX表单提交:通过
FormData
可以方便地将表单数据通过AJAX请求发送到服务器,而不需要手动序列化表单数据。 - 文件上传:
FormData
可以轻松处理文件上传,将文件对象添加到FormData
中,然后通过AJAX请求发送到服务器。 - 动态表单数据处理:在一些需要动态添加或修改表单数据的场景中,
FormData
提供了灵活的API来操作表单数据。
(四)与其他技术的对比
-
与传统表单提交对比
- 传统表单提交:表单数据会直接提交到服务器,页面会刷新。
- FormData+AJAX:可以在不刷新页面的情况下异步提交表单数据,提升用户体验。
-
与手动序列化表单数据对比
- 手动序列化:需要自己编写代码将表单数据转换为字符串格式,处理文件上传时比较复杂。
- FormData:自动处理表单数据的格式,包括文件上传,简化了开发过程。
-
与DataUrl对比
- DataUrl:适合将小数据嵌入到URL中,用于内联展示或简单数据导出。
- FormData:适合处理复杂的表单数据,尤其是包含文件的表单数据,并将其发送到服务器。
(五)示例
以下是几个使用FormData
的示例:
1. 从HTML表单创建FormData并提交
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>FormData示例1</title><!-- 引入jQuery --><script src="https://cdn.tailwindcss.com"></script>
</head><body><form id="myForm"><input type="text" name="username" value="John"><input type="email" name="email" value="john@example.com"><input type="file" name="avatar"><button type="button" id="submitBtn">提交</button></form><script>const form = document.getElementById('myForm');const submitBtn = document.getElementById('submitBtn');submitBtn.addEventListener('click', function() {const formData = new FormData(form);$.ajax({url: '/submit',type: 'POST',data: formData,processData: false, // 不处理数据contentType: false, // 不设置内容类型success: function(response) {console.log(response);},error: function(error) {console.error('Error:', error);}});});</script>
</body></html>
2. 动态创建FormData并添加数据
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>FormData示例2</title><!-- 引入jQuery --><script src="https://cdn.tailwindcss.com"></script>
</head><body><input type="text" id="username" value="Alice"><input type="file" id="fileInput"><button type="button" id="submitBtn">提交</button><script>const usernameInput = document.getElementById('username');const fileInput = document.getElementById('fileInput');const submitBtn = document.getElementById('submitBtn');submitBtn.addEventListener('click', function() {const formData = new FormData();// 添加文本字段formData.append('username', usernameInput.value);// 添加文件字段if (fileInput.files.length > 0) {formData.append('file', fileInput.files[0]);}// 添加额外的数据formData.append('timestamp', new Date().toISOString());$.ajax({url: '/upload',type: 'POST',data: formData,processData: false, // 不处理数据contentType: false, // 不设置内容类型success: function(response) {console.log(response);},error: function(error) {console.error('Error:', error);}});});</script>
</body></html>
3. 结合FileReader和FormData实现图片预览和上传
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>FormData示例3</title><!-- 引入jQuery --><script src="https://cdn.tailwindcss.com"></script>
</head><body><input type="file" id="imageInput" accept="image/*"><img id="preview" src="" alt="图片预览" style="max-width: 300px;"><button type="button" id="uploadBtn">上传图片</button><script>const imageInput = document.getElementById('imageInput');const preview = document.getElementById('preview');const uploadBtn = document.getElementById('uploadBtn');let selectedFile = null;imageInput.addEventListener('change', function() {if (this.files.length > 0) {selectedFile = this.files[0];// 使用FileReader预览图片const reader = new FileReader();reader.onload = function(e) {preview.src = e.target.result;};reader.readAsDataURL(selectedFile);}});uploadBtn.addEventListener('click', function() {if (!selectedFile) {alert('请先选择图片');return;}const formData = new FormData();formData.append('image', selectedFile);$.ajax({url: '/upload-image',type: 'POST',data: formData,processData: false, // 不处理数据contentType: false, // 不设置内容类型success: function(response) {console.log('上传成功:', response);alert('图片上传成功');},error: function(error) {console.error('上传失败:', error);alert('图片上传失败');}});});</script>
</body></html>
头像的修改的话把修改img的src步骤放到请求成功的回调函数里面
最后本周感悟(也是本月doge//_\\),这段时间做那个新闻资讯类的项目收获很多,但缺点也暴露了出来,代码格式问题不是很大,但复用部分做的不咋滴,这点要改善一下。下一个学习计划已经发下来了,node.js的学习我一定会加倍努力的\*—*/