FormData文件上传多文件上传

一、简介
​ 通常情况下,前端在使用post请求提交数据的时候,请求都是采用application/json 或 application/x-www-form-urlencoded编码类型,分别是借助JSON字符串来传递参数或者key=value格式字符串(多参数通过&进行连接)来传递参数,确实足以覆盖大多数业务场景。但是在文件上传等特殊业务场景下,这两种编码类型就有些捉襟见肘了,例如选择JSON字符串传递参数,在使用JSON.stringify()格式化参数数据时,会将File和Blob对象转化成{},文件数据会丢失。所以此时我们就需要使用第三种编码类型multipart/form-data,使用FormData对象来传递参数。

​ FormData 提供了一种以 key/value键值对集合表示表单数据的数据构造方式,通过该方式我们可以将file、blob等不易传输的数据通过 ajax 请求轻松的发送到服务器端。

​ 当使用FormData 对象作为参数时,无需手动设置请求的编码类型,浏览器会自动将请求的编码类型Content-type设置为multipart/form-data。

浏览器兼容性:
在这里插入图片描述
二、相关方法
1、FormData()
​ FormData([form]) 方法是FormData 对象的构造函数,用来创建一个新的FormData 对象。

// 创建空的 FormData 对象
const formData = new FormData()

​ 该方法拥有一个可选参数form,值为页面HTML中的一个表单元素,当设置该参数时,创建的FormData对象将自动的将form表单中的值包含进去,包括file文件内容也会被编码之后包含进去。但是要注意给表单中所有的输入元素(、)设置name属性,否则无法被FormData对象包含,输入元素的name属性将会成为FormData对象中数据键值对的key,输入元素的值将会成为对应的value。

<!-- form表单元素 -->	
<form action="#" id="form1"><div><label for="name">姓名:</label><input type="text" id="name" name="name"></div><div><label for="age">年龄:</label><input type="text" id="age" name="age"></div><div><label for="sex">性别:</label><!-- 未设置name属性不会被 formData 包含 --><input type="text" id="sex"></div></form><br /><button onclick="logFormData()">输出formData对象</button>
<script>
// 输出 FormData 对象的数据
function logFormData () {// 获取表单元素const form = document.getElementById('form1')// 创建带有预置数据的 FormData 对象const formData = new FormData(form)// 输出formData对象中的所有键值对for (var pair of formData.entries()) {console.log(pair[0] + '----' + pair[1]);}
}
</script>

在这里插入图片描述
2、FormData.append()
​ FormData.append(name,value,[filename]) 方法用于向FormData 对象中添加一个新的值,该方法拥有两个必选参数name和value,以及一个可选参数filename。name对应FormData 对象中键值对数据的key,value对应键值对数据的值。如果name这个key在FormData中已经存在,则会将新值value添加到原有值集合的后面,先添加的值在前面,后添加的值在后面,多个值同时以集合的形式存在;如果name这个key在FormData中不存在,则会新增这个key,并赋予对应的值value。

// 创建空的 FormData 对象
const formData = new FormData()
// 添加一个键值对 此时并不存在对应的key 会新增这个key
formData.append('name', '张三')
// 给同一个key 再次添加值
formData.append('name', '李四')
// 输出这个key对应的所有value值
console.log("formData.getAll('name')----", formData.getAll('name'));

执行结果1
在这里插入图片描述
可选参数filename是当第二个参数value为Blob或file文件数据时,设置传给服务器端的文件名称。如果不设置该参数,则Blob类型默认文件名为blob,file类型的默认文件名为文件本身的名称。

// 创建空的 FormData 对象
const formData = new FormData()
// 添加一个file键值对数据 取默认文件名称
formData.append('file', file)
// 添加一个file键值对数据 并设置文件名称
formData.append('file', file, 'test.png')
// 输出这个key对应的所有value值
console.log("formData.getAll('file')----", formData.getAll('file'));

执行结果2
在这里插入图片描述
3、FormData.set()
​ FormData.set(name,value,[filename]) 方法与FormData.append()方法类似,都是用于向FormData 对象中添加一个新的值,如果name这个key在FormData中不存在,则会新增这个key,并赋予对应的值value;但是如果name这个key在FormData中已经存在,那么该方法会直接覆盖掉原来的value,无论原有值集合有几个数据,全都被覆盖。

​ 其余用法与FormData.append()方法相同。

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对 此时并不存在对应的key 会新增这个key
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', '李四')
// 输出这个key对应的所有value值
console.log("append()两次数据后----", formData.getAll('name'));
// 使用set()给同一个key 设置值 会覆盖之前的值
formData.set('name', '王五')
// 输出这个key对应的所有value值
console.log("set()一次数据后----", formData.getAll('name'));

在这里插入图片描述
4、FormData.delete()
​ FormData.delete(name) 方法用于从FormData对象中删除name这个key及其对应的所有value。

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对 此时并不存在对应的key 会新增这个key
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', '李四')
// 输出这个key对应的所有value值
console.log("append()两次数据后----", formData.getAll('name'));
// 使用delete()删除一个key及其所有的value
formData.delete('name')
// 再次输出这个key
console.log("delete()删除一次后----", formData.getAll('name'));

在这里插入图片描述
5、FormData.entries()
​ FormData.entries() 方法用于获取一个由FormData对象中所有键值对组成的iterator(迭代器)对象,然后通过该对象可以遍历访问所有的键值对数据。

​ 该方法获取的iterator(迭代器)对象,需要通过for…of…的形式来进行遍历,每个遍历元素都是数组类型,数组中有两个元素,第一个为key,另一个为value。如果FormData对象中的某个key有多个

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', '李四')
// 使用append()添加另外一个键值对
formData.append('sex', '男')
// 获取迭代器对象
const entries = formData.entries()
// 输出迭代器对象
console.log('entries-----', entries);
// 遍历迭代器对象
for (var pair of entries) {// 输出遍历元素console.log('pair---', pair);// 输出元素的key和valueconsole.log(pair[0] + '----' + pair[1]);
}

在这里插入图片描述
除了该方法外,我们还可以通过for…of…形式直接遍历FormData对象,其作用与结果与该方法完全相同:

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', '李四')
// 使用append()添加另外一个键值对
formData.append('sex', '男')
// 遍历formData对象
for (var pair of formData) {console.log('当前遍历元素---', pair);console.log(pair[0] + '----' + pair[1]);
}

在这里插入图片描述
6、FormData.keys()
​ FormData.keys() 方法用于获取一个由FormData对象中所有键值对中的key组成的iterator(迭代器)对象,然后通过该对象可以遍历访问所有的key,类型为String。与entries()方法相同的是:如果FormData对象中的某个key有多个value,则该key会被遍历多次,每次对应一个value。

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', '李四')
// 使用append()添加另外一个键值对
formData.append('sex', '男')
// 获取key组成的迭代器对象
const keys = formData.keys()
// 输出迭代器对象
console.log('keys-----', keys);
// 遍历迭代器对象
for (var key of keys) {console.log('key---', key);
}

在这里插入图片描述
7、FormData.values()
​ FormData.values() 方法用于获取一个由FormData对象中所有键值对中的value组成的iterator(迭代器)对象,然后通过该对象可以遍历访问所有的value,类型为String、File、Blob。如果FormData对象中的某个key有多个value,则每个value都会遍历一次

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', 333444)
// 使用append()添加另外一个键值对
formData.append('sex', '男')
// 获取value组成的迭代器对象
const values = formData.values()
// 输出迭代器对象
console.log('values-----', values);
// 遍历迭代器对象
for (var value of values) {console.log('value---', value);
}

在这里插入图片描述
8、FormData.has()
​ FormData.has() 该方法用于判断FormData 对象中是否含有某个key,返回值为一个布尔值。

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对
formData.append('name', '张三')
// 使用append()添加另外一个键值对
formData.append('sex', '男')
// 使用has()判断是否存在某个key
console.log('has()判断是否存在name---', formData.has('name'));
// 使用delete()删除一个key及其所有的value
formData.delete('sex')
// 使用has()判断一个已经被删除的key
console.log('has()判断被delete()删除的sex---', formData.has('sex'));
// 使用has()判断一个不存在的key
console.log('has()判断不存在的age---', formData.has('age'));

在这里插入图片描述
9、FormData.get()
​ FormData.get(name) 方法用于获取FormData 对象中name这个key所对应的value集合里的第一个value,value集合中值的顺序,按照添加的顺序进行排序。

// 创建空的 FormData 对象
const formData = new FormData()
// 使用append()添加一个键值对
formData.append('name', '张三')
// 使用append()给同一个key 再次添加值
formData.append('name', 333)
// 使用append()添加另外一个键值对
formData.append('sex', '男')
// 使用get()获取name对应的第一个value
console.log('get()获取name对应的第一个value---', formData.get('name'));
// 使用get()获取sex对应的第一个value
console.log('get()获取sex对应的第一个value---', formData.get('sex'));

在这里插入图片描述
在这里插入图片描述
一、进阶知识
在前一篇博客中,我讲解了FormData对象的基础概念、相关方法和基本用法,本篇博客我将讲解一些FormDate对象相关的进阶知识,主要包含FormData与其他对象结合使用的各类场景,以及一些使用技巧。

1、FormData对象、JSON字符串、key=value字符串 三种参数形式对比
① 使用FormData对象传递参数
​ 该参数形式对应请求头Content-type类型中的 multipart/form-data,以键值对的形式存储参数数据,参数中允许包含File、Blob类型的数据。

// 发送FormData对象参数
function ajaxFormData () {// 创建空的 FormData 对象const formData = new FormData()// 创建Blob对象var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组var blob = new Blob(aFileParts, { type: 'text/html' });// 添加一个Blob键值对数据  并设置文件名称formData.append('content', blob)// 添加一个字符串键值对数据formData.append('name', '张三')// 添加一个字符串键值对数据formData.append('age', '18')// 创建 XMLHttpRequest 实例对象const xhr = new XMLHttpRequest();// 设置发送POST请求的URL地址const url = 'http://example.com/api/user';// 配置请求对象xhr.open('POST', url);// 无需设置请求头信息 浏览器会自动设置 Content-type 为 multipart/form-data// 设置请求完成后的回调xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText);}};// 发送请求并将参数加入进去xhr.send(formData);
}

浏览器查看请求头和请求参数:
在这里插入图片描述
② 使用JSON字符串传递参数
​ 该参数形式对应请求头Content-type类型中的 application/json,参数中的File、Blob类型的数据会被转换成{},数据会丢失。当然我们也可以通过将File、Blob对象转成base64格式的方式来传递数据,但是不够优雅,而且在转换格式的时候,如果文件过大,会占用大量内存,影响浏览器性能,因此并不推荐采用这种形式来传递File、Blob类型数据。

function ajaxJSON () {// 创建Blob对象var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组var blob = new Blob(aFileParts, { type: 'text/html' });// 创建一个 FileReader 对象var reader = new FileReader();// 当以 DataURL 格式读取成功后,执行回调函数reader.onload = (event) => {// 将blob对象转换为bas64字符串var blobBase64 = event.target.result// 创建要发送的参数对象var params = {name: '张三',age: 18,content: blob,contentBase64: blobBase64}// 将参数对象转换为JSON字符串var JSONParams = JSON.stringify(params)// 创建 XMLHttpRequest 实例对象const xhr = new XMLHttpRequest();// 设置发送POST请求的URL地址const url = 'http://example.com/api/user';// 配置请求对象xhr.open('POST', url);// 设置请求头信息xhr.setRequestHeader('Content-type', 'application/json')// 设置请求完成后的回调xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText);}};// 发送请求并将参数加入进去xhr.send(JSONParams);};// 以 DataURL 的形式读取 Blob 数据reader.readAsDataURL(blob);
}

浏览器查看请求头和请求参数:
在这里插入图片描述
③ 使用key=value字符串传递参数
​ 该参数形式对应请求头Content-type类型中的 application/x-www-form-urlencoded,传递过程中只能传递字符串类型的参数,参数组成key=value格式字符串,多个参数之间通过&进行连接。参数中的File、Blob类型的数据会被转换成[object File]、[object Blob]字符串,数据会丢失。同理,我们也可以通过将File、Blob对象转成base64格式的方式来传递数据,但缺点也相同,在转换格式的时候,如果文件过大,会占用大量内存,影响浏览器性能,因此并不推荐采用这种形式来传递File、Blob类型数据。

function ajaxString () {// 创建Blob对象var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组var blob = new Blob(aFileParts, { type: 'text/html' });// 创建一个 FileReader 对象var reader = new FileReader();// 当以 DataURL 格式读取成功后,执行回调函数reader.onload = (event) => {// 将blob对象转换为bas64字符串var blobBase64 = event.target.result// 创建要发送的参数字符串var params = 'name=张三&age=18&content=' + blob + '&contentBase64=' + blobBase64// 创建 XMLHttpRequest 实例对象const xhr = new XMLHttpRequest();// 设置发送POST请求的URL地址const url = 'http://example.com/api/user';// 配置请求对象xhr.open('POST', url);// 设置请求头信息xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')// 设置请求完成后的回调xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText);}};// 发送请求并将参数加入进去xhr.send(params);}// 以 DataURL 的形式读取 Blob 数据reader.readAsDataURL(blob);
}

浏览器查看请求头和请求参数:
在这里插入图片描述
2、对FormData对象中的数据进行过滤
​ 我们使用FormData对象向服务端传输数据时,通常是为了进行文件上传,以及一些相关数据的上传。为了数据安全,我们需要对要加入到FormData中的数据进行校验过滤,比如对文件名、文件类型、文件内容等等进行过滤,只有过滤后的数据才能加入到FormData中,并发送到服务端。

① 文件名过滤
​ 文件名过滤可以防止用户上传的文件名中包含违规内容和字符等,具体实现可以结合正则表达式和字符串操作两者来实现。

​ 例如:上传文件的文件名不能包含&、#和%三个特殊字符,且文件名不能包含sb和2b两个违规词。

// 声明FormData
const formData = new FormData();// 省略...// 获取文件对象
var file = e.target.files[0]
// 获取文件名
var fileName = file.name
// 校验文件名中是否包含sb或2b 两个违规词
const pattern = /^(?!.*([sS]b|2[Bb])).*$/i;
// 进行文件名过滤 不能含有违规词 且不能含有特殊字符
if (pattern.test(fileName) && fileName.indexOf('&') === -1 && fileName.indexOf('#') === -1 && fileName.indexOf('%') === -1) {formData.append('file',file)
} else {alert('文件名不符合规范,请修改后再上传~');
}

② 文件类型过滤
​ 文件类型过滤可以防止用户上传不支持的文件类型,虽然前端可以通过标签的accept属性来限制用户选择的文件类型,但是这并不严谨,用户可以通过操作文件选择框的选项来解除限制,所以在文件上传之前对文件类型进行过滤是有必要的。

​ 例如:上传文件的类型限制为图片类型,且只能为.jpg、.png、.gif三种类型的文件。

// 创建空的 FormData 对象
const formData = new FormData()// 省略...// 获取文件对象
var file = e.target.files[0]
// 获取文件名
var fileName = file.name
// 获取文件类型
var fileType = file.type
// 校验文件名是否以规定格式 jpg、png、gif 结尾
const pattern = /\.jpg$|\.png$|\.gif$/i;
// 进行文件类型过滤
if (pattern.test(fileName) && fileType.indexOf('image') === 0) {formData.append('file', file)
} else {alert('文件类型不符,请修改后再上传~');
}

③ 文件内容过滤
​ 文件内容过滤可以防止用户上传包含恶意代码和违规内容的文件,可以使用JS来过滤部分文件的内容,也可以借助一些完善第三方的库来检查文件内容,如:js-xss、Filter.js等等。

​ 例如:对用户上传的.txt文件,进行简单的敏感词汇校验过滤。

// 创建空的 FormData 对象
const formData = new FormData()
// 调起文件选择框
document.getElementById('file').click()
// 监听文件选择框的change事件
document.getElementById('file').onchange = function (e) {// 获取文件对象var file = e.target.files[0]// 声明一个 FileReader 对象const reader = new FileReader();// 当以文本形式读取成功后,执行回调函数reader.onload = (event) => {const content = event.target.result;console.log('原文件内容---', content);// 过滤敏感词汇const filteredContent = content.replace(/sb|智障|2B/gi, '**');// 显示过滤后的内容console.log('过滤后的文件内容---', filteredContent);// 将过滤后的内容写入FormDataformData.append('fileText', filteredContent)};// 以文本形式读取文件内容reader.readAsText(file);}

④ 白名单过滤
​ 白名单过滤是指根据过滤条件批量设置允许名单,只有符合白名单的数据才能通过过滤。

​ 例如:设置文件类型白名单,只允许jpg、png、gif类型的图片文件加入到FormData中。

// 创建空的 FormData 对象
var formData = new FormData()
// 声明一个白名单数组
const whitelist = ['image/jpg', 'image/png', 'image/gif'];
// 调起文件选择框
document.getElementById('file').click()
// 监听文件选择框的change事件
document.getElementById('file').onchange = function (e) {// 获取文件对象var file = e.target.files[0]// 获取文件类型var fileType = file.type// 判断文件类型是否在白名单中if (whitelist.indexOf(fileType) > -1) {formData.append('file', file)} else {alert('文件类型不符,请修改后再上传~');}
}

⑤ 黑名单过滤
​ 黑名单过滤是指根据过滤条件批量设置禁止名单,凡是符合黑名单的数据都禁止通过。

​ 例如:设置文件类型黑名单,禁止.exe和.bat类型的文件加入到FormData中。

//

 创建空的 FormData 对象
var formData = new FormData()
// 声明一个黑名单数组
const blackList = ['application/x-msdownload', 'application/x-msdos-program',];
// 调起文件选择框
document.getElementById('file').click()
// 监听文件选择框的change事件
document.getElementById('file').onchange = function (e) {// 获取文件对象var file = e.target.files[0]// 获取文件类型var fileType = file.type// 判断文件类型是否在黑名单中if (blackList.indexOf(fileType) === -1) {formData.append('file', file)} else {alert('文件类型不允许上传~');}
}

3、FormData对象结合同步token预防CSRF攻击
​ CSRF(Cross-site Request Forgery,跨站请求伪造)攻击是一种常见的网络攻击,攻击者通过伪造用户的身份,利用用户在某些站点上的登录状态,来构造并发送篡改数据的请求。防范CSRF攻击方式有很多,在涉及表单提交的页面中,我们常用的是FormData对象结合同步token(又称CSRF token)的防范策略,来防范攻击者恶意伪造表单数据提交,具体操作步骤如下:

​ ① 当用户请求访问表单页面时,服务端生成一个随机且唯一的token,服务端存储一份,并将该token存储在cookie之中,发送给前端。

​ ② 前端从cookie中获取token,然后将token添加到要提交的FormData对象中。

​ ③ 前端触发表单提交接口,发送FormData对象,服务端收到请求后,对比FormData对象中的token与服务端存储的token是否一致,如果一致,则认为是合法请求,否则,认为是CSRF攻击,拒绝请求。

​ 该防范策略的核心在于攻击者虽然在调用提交接口时能携带相关的cookie信息(接口携带的cookie取决于接口的域名),但是无法通过js获取相关cookie的值(js只能获取当前页面域名下的cookie),因而也就拿不到有效的token,无法构造有效的表单数据,请求就会被服务端所拒绝。

​ 该防范策略的优点在于安全性高、操作简单、支持性好,缺点在于需要增加额外的计算量和存储开销。

​ 除此之外,我们还可以给存储token的那个cookie设置SameSite=Strict或lax,进一步防范CSRF攻击。

4、FormData对象结合input实现选择文件夹,批量上传文件
​ 之前我们批量上传文件时,都是让用户一个个的去选择文件,操作繁多;或者就是让用户将文件放到文件夹下,统一打包成压缩包,作为一个文件上传,但是文件的压缩格式有很多,服务端基本不可能全部支持,因此也有一定的局限性。所以我想到了另一种方案就是:让用户直接去选择文件夹,然后前端获取文件夹中的所有文件,逐一加入到FormData对象中,最后统一上传到服务端。我们还可以结合黑白名单过滤的方式,对文件夹中的文件进行过滤,只保留允许上传的文件,发送到服务端。

​ 想要通过实现文件夹上传需要借助该元素的webkitdirectory属性,设置该属性后,将限制用户只能选择文件夹,而无法选择文件。但是该属性并非标准属性,所以请慎用!!!
浏览器兼容性:
在这里插入图片描述
示例代码

<input type="file" id="folder" name="folder" webkitdirectory />
<div id="showBox">文件夹内文件展示区域
</div>
// 创建空的 FormData 对象
var formData = new FormData()
// 声明一个白名单数组 表示可以上传的文件后缀名
const whiteList = ['ppt', 'pptx', 'txt', 'xlsx'];
// 调起文件选择框
document.getElementById('folder').click()
// 监听文件选择框的change事件
document.getElementById('folder').onchange = function (e) {// 获取文件列表类数组对象let files = e.target.files// 输出文件列表类数组对象console.log(files);// 将类数组对象转换为数组 且对文件后缀名进行过滤files = Array.from(files).filter(item => {// 过滤掉文件夹对象if (item.type !== "" && item.name !== '.DS_Store') {// 获取文件后缀名const suffix = item.name.split('.').pop()// 过滤掉不足在白名单中的文件if (whiteList.indexOf(suffix) > -1) {return true}}})// 输出过滤后的文件对象列表console.log('选择文件夹中的所有文件过滤后的结果---', files);// 用于显示的html字符串let html = ''// 遍历文件对象列表files.forEach(item => {// 将文件对象的信息拼接到html字符串中html = html + `<p>文件名:${item.name} <br />文件路径:${item.webkitRelativePath}</p>`// 将文件对象添加到FormData中formData.append('file', item)})// 将html字符串渲染到页面中document.getElementById('showBox').innerHTML = html// 后续上传文件的逻辑...

选择文件夹上传后,首先浏览器会弹窗获取用户授权(Safari浏览器在本地环境时无需授权,线上环境未验证):

在这里插入图片描述
用户授权之后,我们可以监听标签的onchange事件,然后通过event.target.files获取所选文件夹本身及其的所有子文件和子文件夹组成的文件类数组,在进行相关处理时,建议使用Array.from()转换真正的数组类型。

原始目录层级:

在这里插入图片描述
获取的文件类数组以及过滤后的文件结果:
在这里插入图片描述
页面渲染结果:
在这里插入图片描述
从上面的示例中可以看出获取文件列表中,包含一种name为.DS_Store并且type为""的特殊文件,这类特殊文件文件表示的就是文件夹,我们可以通过该文件的webkitdirectory属性来获取文件夹的真实名称。

​ 而且此时获取的各文件之间无法体现原始目录层级关系,但是我们可以通过每个file文件的webkitRelativePath属性来得知每个文件的层级关系,各级路径之间通过 / 连接,我们可以通过/ 分割webkitRelativePath属性值,从而还原文件夹的原始层级关系。

​ 注意: 文件名和文件夹名最好不要包含/、\等特殊字符,因为获取的File中的name和webkitRelativePath属性,会将他们转义,很有可能会影响层级的拆分和判断。例如:/在File中的name和webkitRelativePath中都会被转义为:,\在File中的name中会被转义为\,在webkitRelativePath中会被转义为/?(奇奇怪怪的规则(╯°□°)╯︵┻━┻)。

5、FormData对象结合dataTransfer实现拖拽文件夹,批量上传文件
可以实现,但其中涉及知识点太多,暂时还没完全搞懂,想了解的建议查阅最后一篇相关资料。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/234971.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据分析22、23真题回忆

2022 学长描述 1. 一个很简单的据估计 2. 算一个决策树 3. Cypher图 4.Hadoop和Spark的区别 2023 真题回忆 1. 大数据分析的定义 说出大数据分析三个层次 2.大数据分析流程 预处理部分包含哪几个步骤 3.Spark核心部件和应用库有哪些 并简要说明功能 4. 主成分分析和因…

HTML5+CSS3小实例:纯CSS实现锚点平滑过渡

实例:纯CSS实现锚点平滑过渡 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"&…

【无语】Microsoft Edge 浏览器不显示后台返回的数值数据

Microsoft Edge 禁用 JSON 视图 写在前面禁用 JSON 视图 写在前面 遇到一个有意思的事情&#xff0c;在用 Microsoft Edge 浏览器发送请求测试时发现&#xff0c;后端返回的数值数据没有正常展示&#xff0c;而是类似查看源码的结果&#xff0c;只显示了一个行号1&#xff0c;…

最优化理论复习--对偶单纯形方法及灵敏度分析

对偶单纯形方法 定义&#xff1a;设 x ( 0 ) x^{(0)} x(0) 是(L)问题的基本解&#xff08;不一定是可行解&#xff08;极点&#xff09;&#xff09;&#xff0c;如果它的对偶问题的解释可行的&#xff0c;则称 x ( 0 ) x^{(0)} x(0) 为原问题的对偶可行基本解 从而衍生出结…

Chappyz 生态迎重磅利好:多链应用程序启动、100% 收入共享计划开启

“首款由人工智能驱动的社区建设工具 Chappyz&#xff0c;即将在 12 月 21 日推出全新的多链平台并向社区开放&#xff0c;同时制定了生态收入 100% 向 Stakers 共享的计划&#xff0c;这不仅是 Chappyz 生态的一个全新进展&#xff0c;也是 Chappyz 生态发展的重磅利好。” 构…

0137 - 跳转控制语句 break、continue、return

文章目录 1 break1.1 基本介绍1.2 基本语法1.3 注意事项和细节说明 2 continue2.1 基本介绍2.2 基本语法 3 return 1 break 1.1 基本介绍 break 语句用于终止某个语句块的执行&#xff0c;一般使用在 switch 或者循环[for , while , do-while]中 1.2 基本语法 { ……break…

新时代体育场馆的未来之路——气膜体育馆

近年来&#xff0c;我国正全面贯彻实施全民健身国家战略&#xff0c;秉持“发展群众体育&#xff0c;服务健康中国”的理念&#xff0c;深入推动群众参与的体育活动&#xff0c;努力实现全民健身与全民健康的深度融合发展。在这一大潮中&#xff0c;体育场馆建设成为业内的一项…

tensorflow入门

一、怎样入手TensorFlow TensorFlow是一个用于机器学习和深度学习的开源框架&#xff0c;它提供了一种灵活的方式来构建和训练神经网络模型。以下是一些TensorFlow框架入门的建议&#xff1a; 学习Python语言&#xff1a;TensorFlow主要使用Python语言进行开发&#xff0c;因此…

欧美电商平台Depop如何入驻?

对标美国二手闲鱼平台Mercia,PoshMark、东南亚Etsy&#xff0c;Depop是英国的一个面向创意人群的二手时尚市场&#xff0c;类似于Instagram&#xff0c;但更专注于买卖二手服装、配饰和艺术品。 有研究显示,由于购物预算减少,高达65%的受访者表示乐意在圣诞节购买或收到二手礼…

JDK bug:ciObjectFactory::create_new_metadata:原因完全解析

文章目录 1、问题2.详细日志2.关键日志3.结论4.JDK&#xff1a;bug最终bug链接&#xff1a; 京东遇到过类似bug各位大佬如果有更详细的解答可以留言。 1、问题 服务不通&#xff0c;接口404&#xff0c;查看日志有一下截图&#xff0c;还有一个更详细的日志 2.详细日志 # #…

cpp_04_类_对象_this指针_常对象_常(成员)函数

1 类 1.1 类的定义 类的作用是抽象事物&#xff08;抽取事物特征&#xff09;的规则。 类的外化表现是用户自定义的复合数据类型&#xff08;包括成员变量、成员函数&#xff09;&#xff1a; 成员变量用于表达事物的属性&#xff0c;成员函数用于表达事物的行为。 类的表现…

智慧食堂餐卡充值文件生成器使用说明

智慧食堂餐卡充值文件生成器 下载地址&#xff1a; https://download.csdn.net/download/boysoft2002/88646277 或者百度网盘下载&#xff1a; https://pan.baidu.com/s/16cxOa5aq0CU0T0xOr2A7-A 操作使用说明 一、文件结构 下载.rar文件后&#xff0c;释放到非系统盘符的…

TensorFlow(2):Windows安装TensorFlow

1 安装python环境 这一步请自行安装&#xff0c;这边不做介绍。 2 安装anaconda 下载路径&#xff1a;Index of /&#xff0c;用户自行选择自己的需要的版本。 3 环境配置 3.1 anaconda环境配置 找到设置&#xff0c;点击系统->系统信息->高级系统设置->环境变量…

ThinkPad E550c

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

基于YOLOv5的吸烟检测系统设计与实现

一、项目背景 吸烟检测作为保障公共健康和环境安全的重要任务之一&#xff0c;一直备受关注。传统的吸烟检测方法往往依赖人工判断&#xff0c;存在准确性低和实时性差的问题。为了解决这些问题&#xff0c;本项目基于深度学习技术进行了吸烟检测系统的设计与实现&#xff0c;…

buuctf-Misc 题目解答分解91-93

91.[SUCTF2018]followme 下载完就是一个流量包 &#xff0c;用wireshark 打开 直接导出 http对象 这里面 有很多的这样的文件 里面都是参数 直接搜索 关键字 suctf grep -r "SUCTF" 得到flag SUCTF{password_is_not_weak} 92.[MRCTF2020]CyberPunk 用notepad 打开…

【Ehcache技术专题】「入门到精通」带你一起从零基础进行分析和开发Ehcache框架的实战指南(5-检索开发)

系列文章目录 本系列课程主要针对于Ehcache缓存框架功能的开发实践全流程技术指南&#xff01; 第一节&#xff1a;Ehcache缓存框架的基本概念和简介第二节&#xff1a;Ehcache缓存框架的配置分析和说明第三节&#xff1a;Ehcache缓存框架的缓存方式的探索第四节&#xff1a;E…

PostgresSQL数据库中分区和分表的区别以及PostgresSQL创建表分区分表示例

1.分区分表理解 数据库分区和分表都是数据库中常用的数据分散存储技术&#xff0c;但它们的实现方式和应用场景有所不同。 分表&#xff1a;将一个大的表拆分成多个小的表&#xff0c;每个子表存储一部分数据。分表可以减轻单个表的数据量&#xff0c;提高查询效率&#xff0c…

计算机组成原理(复习题)

更多复习详情请见屌丝笔记 一、选择题 计算机系统概述 1、至今为止&#xff0c;计算机中的所有信息仍以二进制方式表示的理由是&#xff08; C &#xff09;。 A.运算速度快 B.信息处理方便 C.物理器件性能所致 D.节约元件 2、运算器的核心功能部件是&#xff08; D &am…

【离散数学】——期末刷题题库(树其二)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…