Web 应用程序的一个主要的痛点是无法操作用户计算机上的文件。 2000 年之前, 处理文件的唯一方式是把 < input type = "file" > 放到一个表单里, 仅此而已。 File API 与 Blob API 是为了让 Web 开发者能以安全的方式访问客户端机器上的文件, 从而更好地与这些文件交互而设计的。
一、File 类型
File API 仍然以表单中的文件输入字段为基础, 但是增加了直接访问文件信息的能力。 HTML5 在DOM 上为文件输入元素添加了 files 集合。 当用户在文件字段中选择一个或多个文件时, 这个 files集合中会包含一组 File 对象, 表示被选中的文件。 每个 File 对象都有一些只读属性。
name: 本地系统中的文件名。
size: 以字节计的文件大小。
type: 包含文件 MIME 类型的字符串。
lastModifiedDate: 表示文件最后修改时间的字符串。 这个属性只有 Chome 实现了。
例如, 通过监听 change 事件然后遍历 files 集合可以取得每个选中文件的信息:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" multiple id="files-list">
<script>
let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
let files = event.target.files,
i = 0,
len = files.length;
while (i < len) {
const f = files[i];
console.log(`${f.name} (${f.type}, ${f.size} bytes)`);
i++;
}
});
</script>
</body>
</html>
File API 还提供了 FileReader 类型, 让我们可以实际从文件中读取数据。
二、 FileReader 类型
FileReader类型表示一种异步文件读取机制。 可以把FileReader 想象成类似于XMLHttpRequest,只不过是用于从文件系统读取文件, 而不是从服务器读取数据。 FileReader 类型提供了几个读取文件数据的方法。
因为这些读取方法是异步的, 所以每个 FileReader 会发布几个事件, 其中 3 个最有用的事件是progress、 error 和 load, 分别表示还有更多数据、 发生了错误和读取完成。
progress 事件每 50 毫秒就会触发一次, 其与 XHR 的 progress 事件具有相同的信息: lengthComputable、 loaded 和 total。 此外, 在 progress 事件中可以读取 FileReader 的 result属性, 即使其中尚未包含全部数据。
error 事件会在由于某种原因无法读取文件时触发。 触发 error 事件时, FileReader 的 error属性会包含错误信息。 这个属性是一个对象, 只包含一个属性: code。 这个错误码的值可能是 1( 未找到文件)、 2( 安全错误)、 3( 读取被中断)、 4( 文件不可读) 或 5( 编码错误)。
load 事件会在文件成功加载后触发。 如果 error 事件被触发, 则不会再触发 load 事件。 下面的
例子演示了所有这 3 个事件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 50px;
}
#output {
margin-top: 20px;
border: 1px solid #ccc;
padding: 20px;
min-height: 100px;
background-color: #f9f9f9;
}
#progress {
margin-top: 10px;
font-size: 14px;
}
</style>
</head>
<body>
<input type="file" multiple id="files-list">
<div id="progress">Progress: 0/0</div>
<div id="output"></div>
<script>
let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
let info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = event.target.files,
type = "default",
reader = new FileReader();
if (/image/.test(files[0].type)) {
reader.readAsDataURL(files[0]);
type = "image";
} else {
reader.readAsText(files[0]);
type = "text";
}
reader.onerror = function () {
output.innerHTML = "Could not read file, error code is " +
reader.error.code;
};
reader.onprogress = function (event) {
if (event.lengthComputable) {
progress.innerHTML = `${event.loaded}/${event.total}`;
}
};
reader.onload = function () {
let html = "";
switch (type) {
case "image":
html = `<img src="${reader.result}">`;
break;
case "text":
html = reader.result;
break;
}
output.innerHTML = html;
};
});
</script>
</body>
</html>
以上代码从表单字段中读取一个文件, 并将其内容显示在了网页上。 如果文件的 MIME 类型表示它是一个图片, 那么就将其读取后保存为数据 URI, 在 load 事件触发时将数据 URI 作为图片插入页面中。如果文件不是图片, 则读取后将其保存为文本并原样输出到网页上。
progress 事件用于跟踪和显示读取文件的进度, 而 error 事件用于监控错误。
如果想提前结束文件读取, 则可以在过程中调用 abort() 方法, 从而触发 abort 事件。 在 load、
error 和 abort 事件触发后, 还会触发 loadend 事件。 loadend 事件表示在上述 3 种情况下, 所有读 取操作都已经结束。 readAsText() 和 readAsDataURL() 方法已经得到了所有主流浏览器支持。
三、FileReaderSync 类型
顾名思义, FileReaderSync 类型就是 FileReader 的同步版本。 这个类型拥有与 FileReader
相同的方法, 只有在整个文件都加载到内存之后才会继续执行。 FileReaderSync 只在工作线程中可用,因为如果读取整个文件耗时太长则会影响全局。
假设通过 postMessage() 向工作线程发送了一个 File 对象。 以下代码会让工作线程同步将文件
读取到内存中, 然后将文件的数据 URL 发回来:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Worker 文件处理示例</title>
</head>
<body>
<input type="file" id="fileInput" />
<div id="result"></div>
<script>
const fileInput = document.getElementById('fileInput');
const resultDiv = document.getElementById('result');
fileInput.addEventListener('change', function (e) {
const file = e.target.files[0];
if (file) {
const worker = new Worker('worker.js');
worker.onmessage = function (event) {
const dataUrl = event.data;
if (dataUrl.startsWith('data:application/pdf')) {
const pdfIframe = document.createElement('iframe');
pdfIframe.src = dataUrl;
pdfIframe.width = '100%';
pdfIframe.height = '600px';
resultDiv.innerHTML = '';
resultDiv.appendChild(pdfIframe);
} else {
resultDiv.innerHTML = `
<p>读取结果: \${dataUrl}</p>`;
}
};
worker.postMessage(file);
}
});
</script>
</body>
</html>
// worker.js
onmessage = (messageEvent) => {
const syncReader = new FileReaderSync();
console.log(syncReader); // FileReaderSync {}
// 读取文件时阻塞工作线程
const result = syncReader.readAsDataURL(messageEvent.data);
// PDF 文件的示例响应
console.log(result); // data:application/pdf;base64,JVBERi0xLjQK...
// 把 URL 发回去
postMessage(result);
};
四、Blob 与部分读取
某些情况下, 可能需要读取部分文件而不是整个文件。 为此, File 对象提供了一个名为 slice()
的方法。 slice() 方法接收两个参数: 起始字节和要读取的字节数。 这个方法返回一个 Blob 的实例, 而 Blob 实际上是 File 的超类。
blob 表示二进制大对象( binary larget object), 是 JavaScript 对不可修改二进制数据的封装类型。 包含字符串的数组、 ArrayBuffers、 ArrayBufferViews, 甚至其他 Blob 都可以用来创建 blob。 Blob构造函数可以接收一个 options 参数, 并在其中指定 MIME 类型:
<script>
console.log(new Blob(['foo']));
// Blob {size: 3, type: ""}
console.log(new Blob(['{"a": "b"}'], {
type: 'application/json'
}));
// {size: 10, type: "application/json"}
console.log(new Blob(['<p>Foo</p>', '<p>Bar</p>'], {
type: 'text/html'
}));
// {size: 20, type: "text/html"}
</script>
Blob 对象有一个 size 属性和一个 type 属性, 还有一个 slice() 方法用于进一步切分数据。 另外也可以使用 FileReader 从 Blob 中读取数据。 下面的例子只会读取文件的前 32 字节:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>读取文件前32字节示例</title>
</head>
<body>
<input type="file" id="files-list" />
<div id="output"></div>
<div id="progress"></div>
<script>
// 模拟的blobSlice函数
function blobSlice(blob, start, end) {
if (blob.slice) {
return blob.slice(start, end);
} else if (blob.mozSlice) {
return blob.mozSlice(start, end);
} else if (blob.webkitSlice) {
return blob.webkitSlice(start, end);
}
return null;
}
let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
let info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = event.target.files,
reader = new FileReader(),
blob = blobSlice(files[0], 0, 32);
if (blob) {
reader.readAsText(blob);
reader.onerror = function () {
output.innerHTML = "Could not read file, error code is " +
reader.error.code;
};
reader.onload = function () {
output.innerHTML = reader.result;
};
} else {
console.log("Your browser doesn't support slice().");
}
});
</script>
</body>
</html>
五、对象 URL 与 Blob
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件选择并显示图片示例</title>
</head>
<body>
<!-- 文件选择输入框 -->
<input type="file" id="files-list">
<!-- 用于显示输出结果 -->
<div id="output"></div>
<!-- 用于显示进度,虽然此处未实际使用进度功能 -->
<div id="progress"></div>
<script>
// 获取文件选择输入框元素
let filesList = document.getElementById("files-list");
// 为文件选择输入框添加 change 事件监听器
filesList.addEventListener("change", (event) => {
let info = "";
// 获取用于显示输出结果的元素
let output = document.getElementById("output");
// 获取用于显示进度的元素
let progress = document.getElementById("progress");
// 获取用户选择的文件列表
let files = event.target.files;
// 创建一个 FileReader 实例,虽然此处未使用它来读取文件
let reader = new FileReader();
// 为用户选择的第一个文件创建一个对象 URL
let url = window.URL.createObjectURL(files[0]);
if (url) {
// 判断文件类型是否为图片
if (/image/.test(files[0].type)) {
// 如果是图片,将图片显示在页面上
output.innerHTML = `<img src="${url}">`;
} else {
// 如果不是图片,显示提示信息
output.innerHTML = "Not an image.";
}
} else {
// 如果浏览器不支持创建对象 URL,显示提示信息
output.innerHTML = "Your browser doesn't support object URLs.";
}
});
</script>
</body>
</html>
六、 读取拖放文件

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件拖放示例</title>
<style>
#droptarget {
width: 300px;
height: 200px;
border: 2px dashed #ccc;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
margin-bottom: 20px;
}
#output {
min-height: 100px;
}
</style>
</head>
<body>
<!-- 拖放目标区域 -->
<div id="droptarget">将文件拖放到这里</div>
<!-- 用于显示拖放文件的信息 -->
<div id="output"></div>
<script>
// 获取拖放目标区域元素
let droptarget = document.getElementById("droptarget");
function handleEvent(event) {
let info = "";
// 获取用于显示文件信息的元素
let output = document.getElementById("output");
let files, i, len;
// 阻止事件的默认行为
event.preventDefault();
if (event.type === "drop") {
// 获取拖放的文件列表
files = event.dataTransfer.files;
i = 0;
len = files.length;
while (i < len) {
// 拼接文件信息
info += `${files[i].name} (${files[i].type}, ${files[i].size} bytes)<br>`;
i++;
}
// 将文件信息显示在页面上
output.innerHTML = info;
}
}
// 为拖放目标区域添加 dragenter、dragover 和 drop 事件监听器
droptarget.addEventListener("dragenter", handleEvent);
droptarget.addEventListener("dragover", handleEvent);
droptarget.addEventListener("drop", handleEvent);
</script>
</body>
</html>
File API 和 Blob API 是浏览器提供的两个重要的 Web API,它们都与文件处理和二进制数据处理密切相关,但功能和应用场景有所不同。下面详细解释这两个 API 的功能及其常见的相关方法。
1. File API
File API 主要用于处理用户在浏览器中选择的文件。它提供了一些功能,使得开发者能够访问、读取和操作用户的文件(例如上传的文件)。
常见的接口和方法
-
File:代表用户选择的文件,是 Blob 对象的子类,表示文件数据的一个实例。
- 属性:
name
: 文件的名称。lastModified
: 文件的最后修改时间戳。size
: 文件的大小,单位为字节。type
: 文件的 MIME 类型。
- 属性:
-
FileReader:用于异步读取文件内容。
-
方法:
readAsText()
: 将文件读取为文本(字符串)。readAsDataURL()
: 将文件读取为 Data URL(通常用于图像文件)。readAsArrayBuffer()
: 将文件读取为原始二进制数据。readAsBinaryString()
: 将文件读取为二进制字符串(较少使用)。
-
事件:
onload
: 文件成功读取后触发。onerror
: 读取过程中出错时触发。onloadend
: 读取结束后触发,无论成功或失败。
-
-
FileList:代表多个文件的集合,通常由
<input type="file">
元素的files
属性返回。 -
FormData:允许你构造一个用于提交的表单数据,包括文件,适用于与服务器的文件上传操作。
应用场景
- 文件上传:通过文件选择器(
<input type="file">
)和 FileReader API 实现文件上传和预览。 - 图像预览:选择图片后通过 FileReader 读取为 Data URL,实现文件预览功能。
- 文件操作:获取文件的元数据(如大小、类型、修改日期等)并进行相应的处理。
2. Blob API
Blob(Binary Large Object)API 主要用于处理不可变的原始二进制数据。它可以存储文件、图像、音频、视频等多种格式的数据。在 File API 中,文件本质上是一个特殊的 Blob 对象。
常见的接口和方法
-
Blob:表示原始的二进制数据。Blob 对象的创建通常不依赖于用户的输入,而是由程序生成。
- 构造函数:
Blob(array, options)
array
: 一个包含多个数据片段的数组(例如:字符串、ArrayBuffer、Uint8Array 等)。options
: 一个可选的对象,包含type
和endings
属性。type
: 指定 MIME 类型(例如image/jpeg
)。endings
: 设置为native
表示使用平台特定的换行符。
- 构造函数:
-
URL.createObjectURL():创建一个临时的 URL,用于引用一个 Blob 对象或 File 对象。通常用于将 Blob 数据转化为一个可以在
<img>
、<video>
等标签中显示的 URL。- 例如:
const url = URL.createObjectURL(blob);
。然后可以将url
赋值给<img>
标签的src
属性。
- 例如:
-
Blob.slice():通过指定起始和结束位置来创建 Blob 的切片。
slice(start, end, contentType)
:创建一个从start
到end
的新 Blob,并可以指定 MIME 类型。
应用场景
- 数据传输:Blob 用于表示二进制数据,通常用于服务器和客户端之间的数据传输(例如:文件上传、下载、视频流等)。
- 文件生成:Blob 可以生成大文件(如音频、视频等),并通过
URL.createObjectURL()
将它们提供给浏览器进行展示或下载。 - 文件切割:Blob.slice() 用于大文件的分割上传,避免单次上传时数据过大。
File API 与 Blob API 的区别
- File 是 Blob 的子类:
File
是继承自Blob
的对象,除了拥有Blob
的属性和方法外,它还包含了文件的名称、最后修改时间等元数据。简单来说,所有的File
对象都是Blob
对象,但并非所有的Blob
对象都是文件。 - File API 主要用于浏览器端处理用户上传的文件,包括读取、预览和上传文件等。
- Blob API 更广泛地用于处理和生成二进制数据,不一定需要用户交互,更多应用在动态生成数据、流媒体等场景中。
总结
- File API 主要用于处理文件操作,允许浏览器与用户的文件进行交互,特别是文件上传、读取等功能。
- Blob API 提供了对二进制数据的处理能力,它支持数据的生成、切割和传输,适用于更广泛的二进制数据操作场景。
这两者可以一起使用,特别是在需要处理文件上传、生成文件下载或处理二进制数据时。