Blob、File、ArrayBuffer、Data URL、Object URL、FileReader、URL使用
Blob对象和File对象
Blob(Binary Large Object)表示二进制类型的大对象。Blob 类型的对象表示不可变的类似文件对象的原始数据。
File 接口基于 Blob,继承了Blob 功能并将其扩展为支持用户系统上的文件。
let blobParts = ["<html><h2>Hello welcome</h2></html>"];
let blob = new Blob(blobParts, { type: "text/html", endings: "transparent" });
console.log("size: " + blob.size + " bytes ;", "type: " + blob.type); //size: 35 bytes ; type: text/html
blobParts = [JSON.stringify({ name: "aa" })];
blob = new Blob(blobParts, { type: "application/json", endings: "transparent" });
console.log("size: " + blob.size + " bytes ;", "type: " + blob.type); //size: 13 bytes ; type: application/json
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二进制格式的 "hello"
blob = new Blob([hello, "world"], { type: "text/plain" });
const file = new File(["a".repeat(1000000)], "test.txt");
document.querySelector("#file5").addEventListener("change", (e) => {
const file = e.target.files[0];
});
ArrayBuffer 与 TypedArray
ArrayBuffer 对象用来表示「通用的、固定长度的」原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象 或 DataView 对象来操作,该对象以特定格式表示缓冲区,并使用该对象读取和写入缓冲区的内容。
ArrayBuffer 本身只是一行 0 和 1 串。ArrayBuffer 不知道该数组中第一个元素和第二个元素之间的分隔位置。
let arrayBuffer = new ArrayBuffer(8);
let uint8 = new Uint8Array(arrayBuffer);
console.log(uint8.length, uint8[0]); //8 0
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(blob);
fileReader.onload = function (res) {
const arrayBuffer = res.target.result;
const uint8 = new Uint8Array(arrayBuffer);
console.log(uint8.length, uint8[0]); //10 72
};
Data URL和Object URL
Data URL 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身
在 Web 项目开发过程中,为了减少 HTTP 请求的数量,对应一些较小的图标,我们通常会考虑使用 Data URL 的形式内嵌到 HTML 或 CSS 文件中。
但需要注意的是:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为该图片经过 base64 编码后的字符串非常大,会明显增大 HTML 页面的大小,从而影响加载速度。」

Object URL 是一种伪协议,也被称为 Blob URL。它允许 Blob 或 File 对象用作图像,下载二进制数据链接等的 URL 源。
在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL。
浏览器内部为每个通过 URL.createObjectURL 生成的 URL 存储了一个 「URL → Blob」 映射。因此,此类 URL 较短,但可以访问 Blob。生成的 URL 仅在当前文档打开的状态下才有效。
如果我们创建一个 Blob URL,即使不再需要该 Blob,它也会存在内存中。针对这个问题,我们可以调用 URL.revokeObjectURL(url) 方法,从内部映射中删除引用,从而允许删除 Blob(如果没有其他引用),并释放内存。
const objectURL = URL.createObjectURL(blob);
console.log(objectURL); //blob:http://localhost:3000/fdd9687a-f914-4e4c-841d-f48966d2db60
FileReader
readAsDataURL 图片预览
<fieldset style="margin-top: 2rem">
<legend>图片预览:</legend>
<input type="file" id="file" accept="image/*" />
<img src="" id="img" style="width: 50px" />
</fieldset>
<script>
document.querySelector("#file").addEventListener("change", (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (res) => {
document.querySelector("#img").src = res.target.result;
};
});
</script>
readAsText 读取文本文件
<fieldset style="margin-top: 2rem">
<legend>读取文本文件( txt,csv):</legend>
<input type="file" id="file1" />
<p id="text-progress"></p>
<p id="text" style="min-height: 100px; height: auto; border: 1px solid silver"></p>
</fieldset>
<script>
document.querySelector("#file1").addEventListener("change", (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsText(file);
reader.onloadstart = (res) => {
if (res.total < 1) {
reader.abort();
}
};
reader.onload = (res) => {
document.querySelector("#text").innerHTML = res.target.result;
};
reader.onprogress = (res) => {
const timer = setInterval(function () {
const progress = (res.loaded / res.total).toFixed(2);
if (progress == "1.00") clearInterval(timer);
document.querySelector("#text-progress").innerHTML = progress * 100 + "%";
}, 100);
};
reader.onabort = (res) => {
console.log("aborted");
};
reader.onloadend = (res) => {
console.log("loaded");
};
});
</script>
readAsArrayBuffer
<fieldset style="margin-top: 2rem">
<legend>检测图片真实类型:</legend>
<input type="file" id="file2" />
<p>图片类型为:<span id="text-type1"></span></p>
<p>MIME类型为:<span id="text-type2"></span></p>
</fieldset>
<script>
document.querySelector("#file2").addEventListener("change", (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (res) => {
const uint8Array = new Uint8Array(res.target.result);
document.querySelector("#text-type1").innerText = check(uint8Array);
document.querySelector("#text-type2").innerText = file.type;
};
reader.readAsArrayBuffer(file.slice(0, 8));
});
function check(buffers) {
const headers = {
JPEG: [0xff, 0xd8, 0xff],
PNG: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
GIF: [0x47, 0x49, 0x46, 0x38],
BMP: [0x42, 0x4d],
};
for (let type in headers) {
if (headers[type].every((header, index) => header === buffers[index])) {
return type;
}
}
return "";
}
</script>


图片处理
<fieldset style="margin-top: 2rem">
<legend>图像灰度化、压缩:</legend>
<img id="preview1" style="height: 50px" />
<canvas id="canvas" width="80" height="50"></canvas>
<img id="preview2" style="height: 50px" />
<img id="compress" style="height: 50px" />
</fieldset>
<script>
const image1 = document.querySelector("#preview1");
const image2 = document.querySelector("#preview2");
fetch("https://www.baidu.com/img/flexible/logo/pc/result.png")
.then((response) => response.blob())
.then((blob) => {
image1.src = URL.createObjectURL(blob);
image1.onload = () => {
draw(image1);
compress();
};
});
fetch("https://www.baidu.com/img/flexible/logo/pc/result.png")
.then((response) => response.arrayBuffer())
.then((buffer) => {
const blob = new Blob([buffer]);
const objectURL = URL.createObjectURL(blob);
image2.src = objectURL;
});
function draw(image) {
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, 80, 50);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const grayscale = function () {
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
ctx.putImageData(imageData, 0, 0);
};
grayscale();
}
function compress(quality = 80, mimeType = "image/webp") {
const compressImage = document.querySelector("#compress");
const canvas = document.querySelector("#canvas");
const imageDataURL = canvas.toDataURL(mimeType, quality / 100);
compressImage.src = imageDataURL;
}
</script>

大文件分片上传
<fieldset style="margin-top: 2rem">
<legend>大文件分片上传:</legend>
<input type="file" id="file5" />
<p id="text5" style="height: 50px"></p>
</fieldset>
<script>
//大文件分片上传
document.querySelector("#file5").addEventListener("change", (e) => {
// const file = new File(["a".repeat(1000000)], "test.txt");
const file = e.target.files[0];
const chunkSize = 400000;
const url = "http://localhost/test.php";
async function chunkedUpload() {
const totalChunk = Math.ceil(file.size / chunkSize);
let i = 0;
const files = [];
for (let start = 0; start < file.size; start += chunkSize) {
i++;
const chunk = file.slice(start, start + chunkSize);
const fd = new FormData();
fd.append("part", chunk);
fd.append("filename", file.name);
fd.append("total", totalChunk);
fd.append("index", i);
files.push(await fetch(url, { method: "post", body: fd }).then((res) => res.text()));
}
await fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ files: files, filename: file.name }),
})
.then((res) => res.text())
.then((res) => {
document.querySelector("#text5").innerHTML = res;
});
}
chunkedUpload();
});
</script>
后端php:
<?php
if (isset($_FILES['part'])) {
//formdata格式
$to = './uploads/' . time() . random_int(1000, 9999) . '.part';
move_uploaded_file($_FILES['part']['tmp_name'], $to);
echo json_encode(['index' => $_REQUEST['index'], 'total' => $_REQUEST['total'], 'file' => $to]);
die;
} else {
//json格式
$data = json_decode(file_get_contents('php://input'), true);
$filename = $data['filename'];
$tmp = explode('.', $filename);
$type = $tmp[count($tmp) - 1];
$res = [];
foreach ($data['files'] as $item) {
$res[] = json_decode($item, 1);
}
usort($res, function ($itemA, $itemB) {
return $itemA['index'] - $itemB['index'];
});
$outFile = "./test." . $type;
$out = fopen($outFile, 'a');
foreach ($res as $item) {
$part = fopen($item['file'], 'rb');
flock($part, LOCK_EX);
$contents = fread($part, filesize($item['file']));
flock($part, LOCK_UN);
fwrite($out, $contents);
@unlink($item['file']);
}
fclose($out);
echo json_encode(['code' => 0, 'msg' => 'success']);
die;
}
URL
<div>
<input type="file" id="file" accept="image/*" />
<br />
<img id="img" src="" alt="" width="100px" />
</div>
<script>
let urlstring = "this is not a URL";
let url;
try {
url = new URL(urlstring);
} catch {
console.log("not a url");
}
url = URL.canParse(urlstring) && new URL(urlstring);
console.log(url); //false
urlstring = "http://localhost:3000/html-demos/test-url.html?a=2&b=0#aa";
url = URL.canParse(urlstring) && new URL(urlstring);
console.log(url); //URL对象
console.log("href", url.href); //http://localhost:3000/html-demos/test-url.html?a=2&b=0#aa
console.log("protocol", url.protocol); //http:
console.log("host", url.host); //localhost:3000
console.log("hostname", url.hostname); //localhost
console.log("port", url.port); //3000
console.log("pathname", url.pathname); ///html-demos/test-url.html
console.log("search", url.search); //?a=2&b=0
console.log("hash", url.hash); //#aa
// url.searchParams 是URLSearchParams对象,有append、delete、set、get、getAll、has、entries、keys、values、forEach、sort等方法
for (let [key, value] of url.searchParams) {
console.log(key, value);
}
urlstring = "ftp://user:pass@192.168.1.120:60021/path/to/file.jpg";
url = URL.canParse(urlstring) && new URL(urlstring);
console.log(url);
console.log("protocol", url.protocol); //http:
console.log("username", url.username); //user
console.log("password", url.password); //pass
urlstring = "https://www.baidu.com/admin";
url = new URL(urlstring);
url = new URL("login", url);
console.log(url); //https://www.baidu.com/login
document.querySelector("#file").onchange = function () {
const file = this.files[0];
const elem = document.querySelector("#img");
const url = URL.createObjectURL(file);
elem.src = url;
// URL.revokeObjectURL(url);
};
</script>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix