# 和上传下载有关的一些对象
# blob
Blob的全称是binary large object,表示二进制大对象.
一个Blob对象就是一个包含有只读原始数据的类文件对象。可以直接将Blob看做是一个不可修改的二进制文件。
var aBlob = new Blob( array, options );
- array(可选):是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体。
- options(可选):一个对象,用来设置Blob的一些属性。主要的是一个type属性,表示Blob的类型。简单来说,就是可以通过向new Blob()传一堆数据,生成一个Blob对象
# 属性
- Blob.size(只读):Blob对象中包含的数据大小(字节)
- Blob.type(只读):表明该Blob对象所包含数据的MIME类型。例如若为图片,此字段就类似为’image/jpeg‘。如果类型未知,则该值为空字符串。
# 方法
Blob只有一个slice方法,实现对文件的分割(注意这里并不违背Blob的只读性,slice只是只是复制指定范围内的Blob数据)。
Blob.slice(start, end ,contentType)
- start:开始索引,可以为负数,语法类似于数组的slice方法。默认值为0。
- end:结束索引,可以为负数,语法类似于数组的slice方法。默认值为最后一个索引。
- contentType:新的Blob对象的MIME类型,这个值将会成为新的Blob对象的type属性的值,默认为一个空字符串。
# 使用blob切割上传文件
目前:txt,md正常,execl图片等需要对应的库做额外处理
var http=require('http')
var fs=require('fs')
// formidable是nodejs中用来上传图片的模块
// path是路径模块
var path=require('path')
http.createServer(function(req,res){
// 如果请求的方法为post (在form中的method='post')
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
});
if(req.method.toLowerCase()=='post'){
console.log("post")
let data;
req.on("data",(chunk)=>{
data+=chunk;
console.log(1)
})
req.on("end",()=>{
console.log(2)
fs.writeFile(path.join(__dirname,'uploadImg', 'temp.txt'), data,{flag:'a'}, (err) => {
if (err) {
// 读文件是不存在报错
// 意外错误
// 文件权限问题
// 文件夹找不到(不会自动创建文件夹)
console.log(err);
res.end("err")
} else {
console.log('success');
res.end("ok")
}
});
})
}else{
res.end("action")
}
}).listen(9000,'127.0.0.1')
<html>
<body class="m-2">
<label for="a" class="btn btn-primary">点击上传</label>
<input id='a' name="file" type="file"
style="display:none;" multiple='multiple'>
<script>
initUpload();
//初始化上传
function initUpload() {
var chunk = 100 * 1024; //每片大小
var input = document.getElementById("a"); //input file
input.onchange = function (e) {
var file = this.files[0];
var query = {};
var chunks = [];
if (!!file) {
var start = 0;
//文件分片
for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
var end = start + chunk;
chunks[i] = file.slice(start , end);
start = end;
}
// 采用post方法上传文件
// url query上拼接以下参数,用于记录上传偏移
// post body中存放本次要上传的二进制数据
query = {
fileSize: file.size,
dataSize: chunk,
nextOffset: 0
}
upload(chunks, query, successPerUpload);
}
}
}
// 执行上传
function upload(chunks, query, cb) {
var queryStr = Object.getOwnPropertyNames(query).map(key => {
return key + "=" + query[key];
}).join("&");
var xhr = new XMLHttpRequest();
console.log(queryStr)
xhr.open("POST", "http://localhost:9000/upload?" + queryStr);
// xhr.setRequestHeader('Content-Type', 'multipart/form-data');
// xhr.overrideMimeType("application/octet-stream");
//获取post body中二进制数据
var index = Math.floor(query.nextOffset / query.dataSize);
getFileBinary(chunks[index], function (binary) {
if (xhr.sendAsBinary) {
// Firefox 私有的非标准方法 sendAsBinary() 将二进制数据以异步模式传输了出去
xhr.sendAsBinary(binary);
} else {
xhr.send(binary);
}
});
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr)
var resp = JSON.parse(xhr.responseText);
// 接口返回nextoffset
// resp = {
// isFinish:false,
// offset:100*1024
// }
if (typeof cb === "function") {
cb.call(this, resp, chunks, query)
}
}
}
}
}
// 每片上传成功后执行
function successPerUpload(resp, chunks, query) {
if (resp.isFinish === true) {
alert("上传成功");
} else {
//未上传完毕
query.offset = resp.offset;
upload(chunks, query, successPerUpload);
}
}
// 获取文件二进制数据
function getFileBinary(file, cb) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
if (typeof cb === "function") {
// cb.call(this, this.result);
cb(this.result)
}
}
}
</script>
</body>
</html>
# ArrayBuffer
- ArrayBuffer 是最基础的二进制对象,是对固定长度的连续内存空间的引用。就是一个二进制数据通用的固定长度容器。通俗点说,就是内存上一段连续的二进制数据。
- byteLength:字节长度
const buf1 = new ArrayBuffer(16);
const buf2 = buf1.slice(4, 12);
console.log(buf2.byteLength); // 8
可以对ArrayBuffer自己不可以读写,可以slice截取部分或全部给新实例,它也可以借助提供TypeArray或DataViewblob进行读写。
相同点: Blob和ArrayBuffer都是二进制的容器;
- ArrayBuffer:ArrayBuffer更底层,就是一段纯粹的内存上的二进制数据,可以对其任何一个字节进行单独的修改,也可以根据需要以指定的形式读取指定范围的数据
- Blob:Blob就是将一段二进制数据做了一个封装,拿到的就是一个整体,可以看到它的整体属性大小、类型;可以对其分割,但不能了解到它的细节
- 联系:Blob可以接受一个ArrayBuffer作为参数生成一个Blob对象,此行为就相当于对ArrayBuffer数据做一个封装,之后就是以整体的形式展现了
应用上的区别:由于ArrayBuffer和Blob的特性,Blob作为一个整体文件,适合用于传输;而只有需要关注细节(比如要修改某一段数据时),才需要用到ArrayBuffer
# DataView
必须在对已有的ArrayBuffer读写或写入时才能创建DataView实例
- byteOffset:字节偏移量,可以看成DataView中的某种地址
const buf = new ArrayBuffer(16);
// DataView default to use the entire ArrayBuffer
const fullDataView = new DataView(buf);
console.log(fullDataView.byteOffset); // 0
console.log(fullDataView.byteLength); // 16
console.log(fullDataView.buffer === buf); // true
// Constructor takes an optional byte offset and byte length
// byteOffset=0 begins the view at the start of the buffer
// byteLength=8 restricts the view to the first 8 bytes
const firstHalfDataView = new DataView(buf, 0, 8);
console.log(firstHalfDataView.byteOffset); // 0
console.log(firstHalfDataView.byteLength); // 8
console.log(firstHalfDataView.buffer === buf); // true
console.log(firstHalfDataView.buffer.byteLength)//16
// DataView will use the remainder of the buffer unless specified
// byteOffset=8 begins the view at the 9th byte of the buffer
// byteLength default is the remainder of the buffer
const secondHalfDataView = new DataView(buf, 8);
console.log(secondHalfDataView.byteOffset); // 8
console.log(secondHalfDataView.byteLength); // 8
console.log(secondHalfDataView.buffer === buf); // true
# ElementType
DataView读写时可以指定一个ElementType.然后DataView会按规则读写转换.
const buffer = new ArrayBuffer(16); // 分配一个内存空间
const view = new DataView(buffer); // 创建 DataView 视图
view.setUint32(0, 4294967295); // 从第 0 个空间开始,以 32 位的形式写入数据
// 以 8 位的形式 “翻译” 这个内存空间,从偏移量 0 开始翻译
console.log(view.getUint8(0)); // 255
// 想以 16 位的形式 “翻译” 这个内存空间,从偏移量 0 开始读
console.log(view.getUint16(0)); // 65535
// 想以 32 位的形式 “翻译” 这个内存空间,从偏移量 0 开始读
console.log(view.getUint32(0)); // 4294967295
注意:读取超出范围的会报rangetype,写入则会尽力转换,如不能转换,会抛出异常
# 定型数组 typed array
es6新增结构。 目的:提升向原生库传输数据的效率。 是另一种形式的arraybuffer视图
const ints = new Int16Array([1, 2, 3]);
const doubleints = ints.map(x => 2*x);
console.log(doubleints instanceof Int16Array); // true
const floats = Float32Array.of(3.14, 2.718, 1.618);
console.log(floats.length); // 3
console.log(floats.buffer.byteLength); // 12
console.log(floats[2]); // 1.6180000305175781
console.log(floats.BYTES_PER_ELEMENT)//4 字节
定型数组和普通数组的大部分方法类似,但是没有合并复制和修改(unshift concat pop push shift splice);不过新增set()和subarray()
- set():从提供的数组或定型数组中把值复制到当前定型数组中指定的索引位置:
// Create an int16 array of length 8
const container = new Int16Array(8);
// Copy in typed array into first four values
// Offset default to an index of 0
container.set(Int8Array.of(1, 2, 3, 4));
console.log(container); // Int16Array(8) [1, 2, 3, 4, 0, 0, 0, 0, buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 8]
// Copy in normal array into last four values
// Offset of 4 means begin inserting at the index 4
container.set([5,6,7,8], 4);
console.log(container); // [1, 2, 3, 4, 5, 6, 7, 8, buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 8]
// An overflow will throw an error
container.set([5,6,7,8], 7);
// RangeError
- subarray:把基于从原始定型数组中复制的值返回一个新定型数组中
const source = Int16Array.of(2, 4, 6, 8);
// Copies the entire array into a new array of the same type
const fullCopy = source.subarray();
console.log(fullCopy); //Int16Array(4) [2, 4, 6, 8, buffer: ArrayBuffer(8), byteLength: 8, byteOffset: 0, length: 4]
// Copy the array from index 2 on
const halfCopy = source.subarray(2);
console.log(halfCopy); // 简写:[6, 8]
// Copy the array from index 1 up until 3
const partialCopy = source.subarray(1, 3);
console.log(partialCopy); // 简写:[4, 6]
# buffer使用场景
- 比如导出excel,下载文件,上传头像等等。 这些操作其实都是操作二进制数据。
// 伪代码示例
// Blob 上传图片
// fileHandler 为前置工作伪代码
// FileReader 加载图片,cavans 压缩图片等
const canvas = fileHandler()
// 创建 Blob 对象和 FormData
canvas.toBlob(blob => {
this.imgBlob = blob
}, 'image/jpeg')
let formdata = new FormData()
formdata.append('file', this.imgBlob, 'img.jpeg')
// 上传图片
axios({
headers: {
"Content-Type": "multipart/form-data"
},
method: "post",
url: uploadUrl,
data: formdata
})
.then(res => {
// do something
})
.catch(err => {
// do something
});
- webpack 配置:比如按行读取某个配置文件,处理 webpack 的一些工作流,和 cli 交互读取和写入文件等等。
// 伪代码示例
// 逐行读取配置文件,个性化配置
const fs = require("fs");
const readline = require("readline");
// 创建可读流
const rl = readline.createInterface({
input: fs.createReadStream("theme.less"),
});
rl.on("line", (line: string) => {
if (line.trim().startsWith("configStart")) {
// 伪代码,处理变量
themeHandlerStart()
}
if (line.trim().startsWith("configStart")) {
// 伪代码,处理变量
themeHandlerEnd()
}
});
- 后端:比如压缩和解压缩,比如加密解密,信息脱敏等等
// 伪代码示例
// 压缩文件
const { createGzip } = require("zlib");
const { pipeline } = require("stream");
const { createReadStream, createWriteStream } = require("fs");
const gzip = createGzip();
const source = createReadStream("./package.json");
const destination = createWriteStream("./package.json.gz");
pipeline(source, gzip, destination, (err) => {
if (err) {
console.error("发生错误:", err);
process.exitCode = 1;
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<input type="file" name="file" id='ac'/>
<img id="portrait" src="" width="300" height="300">
</body>
</html>
<script type="text/javascript">
let ac = document.getElementById('ac')
ac.onchange = (e)=>
{
var file = e.target.files[0];
if(window.FileReader) {
console.log(1)
var fr = new FileReader();
fr.onload = function(e) {
const img = document.getElementById("portrait")
img.src = e.target.result
setTimeout(() => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
//用于从指定的 <canvas> 元素中提取图像数据。
//这个方法返回一个 ImageData 对象,该对象包含了指定矩形区域内的像素数据。
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log(imageData)
// 使用TypedArray来操作图像数据
//imageData.data 是一个 Uint8ClampedArray,是 Canvas API 中用于表示图像数据的类型数组。
//在这个数组中,每个像素由四个连续的元素表示,分别对应红、绿、蓝和 alpha(透明度)通道。
//每个通道的值是一个 0 到 255 的整数,而不是 0 到 1 的浮点数。
const data = imageData.data; // 这是一个Uint8ClampedArray
for (let i = 0; i < data.length; i += 4) {
// 假设第一个通道为红色
data[i] = 255; // R
// data[i + 1] = 0; // G
// data[i + 2] = 0; // B
data[i + 3] = 160; // Alpha通道
}
// 将修改后的数据放回canvas ctx.putImageData() 是 HTML5 Canvas API 中的一个方法
//用于将 ImageData 对象(通常是从 ctx.getImageData() 获取的)放回 <canvas> 元素。
//这样,你可以对像素数据进行操作,然后将修改后的数据绘制回画布上。
ctx.putImageData(imageData, 0, 0);
// 可选:将canvas内容转换回图像并显示在页面上
// 您可以直接使用canvas元素或者将其转换为Blob URL
img.src = canvas.toDataURL('image/png');
//输出类似于 "data:image/png;base64,iVBORw0KGg..."
});
// 如果需要Blob URL ,它用于将画布上的图像转换为一个Blob对象
// canvas.toBlob(function(blob) {
// const itemUrl = URL.createObjectURL(blob);
// console.log(itemUrl); // 这里打印Blob URL
// // 可以使用这个URL做其他事情,比如赋值给另一个img元素的src属性
// }, 'image/png');
// // 假设我们已经创建了一个 Blob 对象
// var blob = new Blob(["Hello, world!"], { type: 'text/plain' });
// // 创建一个代表该 Blob 的 URL
// var url = URL.createObjectURL(blob);
// // 假设我们后来不再需要这个 URL 了
// //如果不再需要预览图像,你应该在某个时刻(如页面卸载时)撤销该 URL。
// 然而,在大多数情况下,浏览器会在页面卸载时自动清理这些资源。
// // 我们可以通过调用 URL.revokeObjectURL() 来释放它
URL.revokeObjectURL(url);
};
fr.readAsDataURL(file);
}
}
</script>
// 文心一言案例未验证
let inputFile = document.querySelector('input[type="file"]');
inputFile.addEventListener('change', function() {
let file = inputFile.files[0];
let reader = new FileReader();
reader.onload = function(event) {
// 读取的数据存储在event.target.result中,它是一个ArrayBuffer对象
let arrayBuffer = event.target.result;
// 将ArrayBuffer转换为Uint8Array以便于操作数据
let uint8Array = new Uint8Array(arrayBuffer);
// 现在可以使用uint8Array来处理数据了,例如打印每个字节的值:
for (let i = 0; i < uint8Array.length; i++) {
console.log(uint8Array[i]);
}
};
reader.readAsArrayBuffer(file); // 将文件读取为ArrayBuffer对象
});
# file
file就是纯粹的文件。通常,表示使用<input type="file">选择的FileList对象,或者是使用拖拽操作的DataTransfer对象。
file对象也是二进制对象,从属于Blob;也就是说file是Blob里的一个小类,Blob的属性和方法都可以用于file,而file自己也有自己特有的属性和方法。
const upload = document.getElementById('k');
console.log(k)
k.onchange=function(e){
const file = e.target.files[0];
console.log(file)
console.log(file.name)
}
- File.lastModifiedDate[只读] 文件对象最后修改的日期
- File.name[只读] 文件对象的名称
- File.size[只读]
# fileReader
前面提到Blob对象时一个只读对象,但它是一个二进制对象,如果直接读取就只能拿到一堆01数据,因此需要借助专门的工具来读取,这个工具就是fileReader。
通过fileReader可以将Blob读取为不同的格式。
Q:ArrayBuffer也需要借助工具(以dataView为例)读取数据,那和fileReader有什么区别呢?
A:ArrayBuffer的工具dataView只是简单的读取数据,最多就是将数据转为数字或字符串;
但fileReader可以看做是多了一道编码的过程,通过FileReader.readAsDataURL(blob)
就是将二进制数据读取并编码为Base64格式,FileReader.readAsText(blob)
就是将二进制数据读取并编码为字符串形式。
| - | - |
|---|---|
| readAsArrayBuffer(file) | 按字节读取文件内容,结果用ArrayBuffer对象表示 |
| readAsBinaryString(file) | 按字节读取文件内容,结果为文件的二进制串 |
readAsDataURL(file) | 读取文件内容,结果用data:url的字符串形式表示 |
| readAsText(file,encoding) | 按字符读取文件内容,结果用字符串形式表示 |
| abort() | 终止文件读取操作 |
readAsDataURL会将文件内容进行base64编码后输出
<input type="file" id="inputBox" >
<img src="" id="img">
var inputBox = document.getElementById("inputBox");
var img = document.getElementById("img");
inputBox.addEventListener("change",function(){
var reader = new FileReader();
reader.readAsDataURL(inputBox.files[0]);//发起异步请求
reader.onload = function(){
//读取完成后,将结果赋值给img的src
img.src = this.result
}
})
readAsText(file,encoding)可按指定编码方式读取文件,但读取文件的单位是字符,故对于文本文件,只要按规定的编码方式读取即可;而对于媒体文件(图片、音频、视频),其内部组成并不是按字符排列,故采用readAsText读取,会产生乱码,因此不是最理想的读取文件的方式。
| - | - |
|---|---|
| onabort | 当读取操作被中止时调用 |
| onerror | 当读取操作发生错误时调用 |
| onload | 当读取操作成功完成时调用 |
| onloadend | 当读取操作完成时调用,无论成功或失败 |
| onloadstart | 当读取操作开始时调用 |
| onprogress | 在读取数据过程中周期性调用 |
var inputBox = document.getElementById("inputBox");
var count=0;
inputBox.addEventListener("change",function(){
var reader = new FileReader();
reader.readAsText(inputBox.files[0],"utf-8");//发起异步请求
reader.onload = function(){
console.log("加载成功")
}
reader.onloadstart = function(){
console.log("开始加载")
}
reader.onloadend= function(){
console.log("加载结束")
}
reader.onprogress = function(){
count++;
console.log("加载中"+count)
}
})
每过50ms左右,就会触发一次progress事件,对于较大的文件可以利用progress实现进度条;onload事件在onloadend之前触发。
由于种种原因无法读取文件时,会触发error事件。触发error事件时,相关信息保存在FileReader对象的error属性中,这个属性将保存一个对象,此对象只有一个属性code,即错误码。1表示未找到文件,2表示安全性错误,3表示读取中断,4表示文件不可读,5表示编码错误。
如果想中断读取过程,可以调用abort方法,就会触发abort事件。
无论触发load,error,abort事件中哪一个,最终都会触发loadend事件。
# 上传图片回显
- FileReader实例,onload中的this,e.target指的内容是同一个
- e.target.result/this.result/fr.result=>fr.readAsDataURL得到URL
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<input type="file" name="file" id='ac'/>
<img id="portrait" src="" width="70" height="75">
</body>
</html>
<script type="text/javascript">
let ac = document.getElementById('ac')
ac.onchange = (e)=>
{
var file = e.target.files[0];
if(window.FileReader) {
console.log(1)
var fr = new FileReader();
fr.onload = function(e) {
document.getElementById("portrait").src = e.target.result;
console.log(e.target.result)
console.log(fr.result)
console.log(this.result)
console.log(e.target,this,fr)
};
fr.readAsDataURL(file);
}
}
</script>
- 预览文件elementui
<template>
<div class="wapper">
<el-dialog
title="导入物模型"
:visible.sync="dialogImportVisible"
width="50%"
>
<el-tabs type="border-card">
<p>上传物模型</p>
<el-upload
class="upload-demo"
action="''"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="handleSuccess"
:before-remove="beforeRemove"
accept=".txt, .json"
multiple
:limit="1"
:on-exceed="handleExceed"
:http-request="fileUpload"
:file-list="fileList"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<json-viewer
:value="fileTSL"
:expand-depth="10"
copyable
boxed
sort
/>
</el-tabs>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogImportVisible = false">取 消</el-button>
<el-button type="primary" @click="handleSave">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getMetadata, importMetadataTSL } from '@/api/IOT-product'
import JsonViewer from 'vue-json-viewer'
import { Message } from 'element-ui'
export default {
name: 'ObjectModelInfo',
components: { JsonViewer },
props: {
metadataId: {
type: String,
required: true
},
productId: {
type: String,
required: true
}
},
data() {
return {
dialogImportVisible: false,
dialogmodelTSL: false,
fileList: [],
textareaTSL: '',
fileId: '',
fileTSL: '',
submit: false
}
},
mounted() {
this.uploadfun()
},
methods: {
modelTSL() {
console.log('metadataId', this.metadataId)
const data = {
id: this.metadataId
}
console.log(data)
getMetadata(data).then(res => {
// console.log(res)
// 获取返回值中的metadata将它填充页面中即可
this.dialogmodelTSL = true
this.textareaTSL = JSON.parse(res.data)
})
},
handleRemove(file, fileList) {
console.log(file, fileList)
if (fileList.length === 0) {
this.fileId = ''
this.fileTSL = ''
this.submit = false
}
},
handlePreview(file) {
console.log(file)
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${file.name}?`)
},
uploadfun() {
return process.env.VUE_APP_BASE_API + '/openIot/iot/product/importMode'
},
fileUpload(file) {
if (!file) {
return
}
var fileReader = new FileReader()
fileReader.readAsText(file.file)
fileReader.onload = () => {
try {
this.fileTSL = JSON.parse(fileReader.result)
this.submit = true
} catch (e) {
this.$message.warning('文件上传格式错误!')
this.fileList = []
this.submit = false
}
}
// this.fileTSL = JSON.parse(formData)
},
handleSuccess(response, file, fileList) {
this.fileId = response.data
},
handleSave() {
const data = {
productId: this.productId,
metadataTSl: this.fileTSL
}
if (this.submit) {
importMetadataTSL(data).then(res => {
Message({ message: '操作成功', type: 'success', duration: 1500 })
this.dialogImportVisible = false
this.$router.go(0)
})
} else {
this.$message.warning(`请先上传附件!`)
}
}
}
}
</script>
# FormData
FormData是XMLHttpRequest Level 2添加的一个新的接口,我们可以通过JavaScript用一些键值对来模拟一系列表单控件。FormData的最大优点就是,比起普通的ajax, 使用FormData我们可以异步上传一个二进制文件,而这个二进制文件,就是我们上面讲的Blob对象