前端操作文件和二进制数据

序言

  • 随着websocket、webAudio,Ajax2等广泛应用,前端方面只要是处理大数据 或 想提供数据处理性能,那就少不了 ArrayBuffer 对象。同时在浏览器中处理二进制数据的需求在不断增加,有时需要字节数组、8位、16位、32位整型数组,有必要对JS中处理二进制的知识做一次梳理。

应用场景

  • 操作用户选中的本地二进制文件,需要用二进制流的方式上传到服务端。
  • 保存快照 HTML 文件。JS拿到的只是HTML源码长字符串,需要生成二进制流上传到服务端。(其中涉及到 base64 和 encodeURIComponent 等编码问题)
    • 前端通过 screen-capture-js 库取得当前页面的 documentText – 长长的字符串,需要转成二进制文件流的格式,通过websocket 分多次数据包传输给后台存储。
  • 结合JS的ArrayBuffer和 Typed Array去获取及处理音频数据、XHR2上传或下载二进制内容等等

Blob 接口

  • Blob 对象表示一个不可变、原始数据的类文件对象。
  • 创建 Blob 对象的语法:
// array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体。DOMString 会被编码为 UTF-8
var aBlob = new Blob( array, {type: '', endings: 'transparent'} );

// 示例
var aBlob = new Blob(['test!'], {
	type: 'text/html'
});
  • 属性:size、type
  • 方法:
    • Blob.slice():创建子集
    • Blob.stream():返回能读取 blob 的字节流(兼容性不好)
    • Blob.text():返回一个promise且包含blob所有内容的UTF-8格式的字符串
    • Blob.arrayBuffer():返回一个promise,且包含blob所有内容的二进制格式 ArrayBuffer

File 接口

  • File API 属于 HTML5 DOM 的内容。让用户能在 web 内容中选择本地文件并读取这些文件的内容。
  • File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
  • 语法:
// bits: 一个包含ArrayBuffer,ArrayBufferView,Blob,或者 DOMString 对象的数组 — 或者任何这些对象的组合。这是 UTF-8 编码的文件内容。
// name: 文件名或文件路径
// options: { type, lastModified } MIME 类型和最后修改时间
var myFile = new File(bits, name[, options]);

// 示例
var file = new File(['foo'], 'foo.txt', {
	type: 'text/plain',
})
  • 可以处理 File 对象的 context:FileReader对象, URL.createObjectURL(), XMLHttpRequest.send(), createImageBitmap()。
  • file 对象的主要属性,都是只读:
    • name:文件名,但不包含路径信息
    • size:文件大小
    • type:文件的 MIME 类型
    • webkitRelativePath:文件 path 或 URL
  • file 接口没定义新方法,一切接口都继承自 Blob
// HTML
<input type="file" id="fileInput">

// JS
const selectFile = document.getElementById('fileInput').files[0];
  • 注意:File 对象不能简单地通过指定路径来访问本地文件,是处于安全的考虑,必须要通过用户自己手动选择的方式才可以访问到本地文件。如果要自动读取某个文件,只能通过异步请求读取服务器端的文件。

ArrayBuffer 对象

  • ES6 新增接口,ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。它是一个字节数组,通常在其他语言中称为“byte array”。
  • 你不能直接操作 ArrayBuffer 的内容,而是要通过类型数组对象或 DataView 对象来操作。它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。比如八、十六、三十二进制格式。

结合 TypedArray 一起学习

读取、预览、显示 file 对象

  • 视频文件

Streams API
流将你希望通过网络接收的资源拆分成小块,然后按位处理它。这正是浏览器在接收用于显示web页面的资源时做的事情——视频缓冲区和更多的内容可以逐渐播放,有时候随着内容的加载,你可以看到图像逐渐地显示。

  • 图片文件
  • PDF文件
  • 等等
  • 以下2种方法以预览图片文件为例
方法一:通过创建 FileReader 来读取文件信息。

示例:上传图片的预览

var img = document.createElement("img");
img.classList.add("obj");
img.file = file; // ?
preview.appendChild(img); // 假设"preview"就是用来显示内容的div

var reader = new FileReader();
reader.onload = (function(aImg) {
	return function(e) {
		// event.target.result 包含被转化为 type array 的 file
		aImg.src = e.target.result;
	};
})(img);
// readAsDataURL 方法用于将文件转换为二进制字符串
reader.readAsDataURL(file);
// 通过创建FileReader来处理异步的图片加载并把他赋给img元素。在创建一个新的 FileReader对象后,我们新建了它的onload 函数,然后调用readAsDataURL()函数开始后台读取文件。当整个图片文件的内容都被全部加载完后,它们被转换成了一个被传递到onload回调函数的data:URL。我们再执行常规操作将img元素的src属性设置为刚刚加载完毕的URL,使得图像可以显示在用户屏幕上的缩略图中。
方法二:使用 URL.createObjectURL 方法
  • 对象URL可以用于image之外的其它东西!它可以用于显示嵌入的PDF文件或任何其它浏览器能显示的资源。
  • 该静态方法接受 File 对象或者 Blob 对象为参数,会创建一个 DOMString,可直接用于 src URL。
    • file input 选择的本地文件,要显示文件缩略图的话,可使用 window.URL.createObjectURL(file) 方法,返回的是一个类似这样的url:blob:http://localhost:8080/6111b0b0-13c0-4618-a356-05d0fa62de6a,可直接赋值给 img 或 video 等标签的 src 属性值实现预览。
    • 注意:在不需要使用该对象以后要显式调用 URL.revokeObjectURL() 来释放它们。
var img = document.createElement("img");
img.src = window.URL.createObjectURL(files[i]);
img.height = 60;
// 当图片加载完成之后对象URL就不再需要了, 设置图片的load事件处理器来释放对象URL。
img.onload = function() {
	window.URL.revokeObjectURL(this.src);
}
li.appendChild(img);
var info = document.createElement("span");
info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
li.appendChild(info);

前端操作文件和二进制数据_第1张图片

预览 pdf 文件

  • 让PDF嵌入式地显示在iframe中(而不是作为下载的文件弹出)
// HTML
<iframe id="viewer">

// JS
const obj_url = window.URL.createObjectURL(blob);
const iframe = document.getElementById('viewer');
iframe.setAttribute('src', obj_url);
window.URL.revokeObjectURL(obj_url);

file 文件上传

FormData

  • 关于进度条的实现。
    • HXR 异步请求中,progress event 对象中有个 event.total 属性可实时反映出已上传的部分大小。
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", function(e) {
  	if(e.lengthComputable) {
	    var percentage = Math.round((e.loaded * 100) / e.total);
	}
}, false);

WebSocket 发送二进制数据

WebSocket可以通过ArrayBuffer,发送或接收二进制数据。

let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';

// Wait until socket is open
socket.addEventListener('open', function (event) {
  // Send binary data
  const typedArray = new Uint8Array(4);
  socket.send(typedArray.buffer);
});

// Receive binary data
socket.addEventListener('message', function (event) {
  const arrayBuffer = event.data;
  // ···
});

编码知识

ASCII 字符集

  • 背景:因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte)。一个字节能表示的最大的整数就是255(2^8-1=255),而ASCII编码,占用0 - 127用来表示大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码。
  • 组成:26个字母的大小写、数字、特殊符号、美式英语中的控制字符。在英语中,用128个符号编码便可以表示所有。
  • 32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
  • 大小规则:09Z

Unicode(支持多国语言)

  • Unicode码也是一种国际标准编码,采用二个字节编码,与ASCII码不兼容。在网络、Windows系统和很多大型软件中得到应用。
  • 因为ASCII编码无法表示多国语言的编码,为了统一所有文字的编码,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
  • Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。
  • Unicode 编码共有三种具体实现,分别为utf-8,utf-16,utf-32
  • 它前128个字符就是ASCII码,之后是扩展码。
  • JS 中 ASCII 码和 unicode 码之间的转换
    • ASCII 转 unicode:用 String 的 CharCodeAt() 方法,返回指定位置的字符的 unicode 编码。
    • unicode 转 ASCII:用 String 的 fromCharCode() 方法,可将 unicode 编码转为字符。

GB23129(中文编码规范)

  • 中文编码标准,由中国国家标准总局1980年发布。
  • ASCII编码只覆盖英文,如果要表示中文(10万个汉字),因为一个字节能表示的最大的整数就是255,显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
  • 由于欧洲国家使用的编码体系,可以表示最多256个符号,但不同的国家有不同的字母,亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。

base64 编码

  • 待研读

  • 注意:btoa(‘中文’) btoa方法传入中文字符会报错:

    • < VM197:1 Uncaught DOMException: Failed to execute ‘btoa’ on ‘Window’:
      The string to be encoded contains characters outside of the Latin1 range.(…)
    • 原因:btoa只能转换占一个字节宽度的字符,就是Latin1字符集(它是ASCII的超集)。而中文汉字是被编码成占两个或以上个字节的。所以btoa方法无法对中文进行操作,于是就报了上面看到的错误。

Binary strings

utf-16 编码

  • 如 DOMString 就是一种 UTF-16 字符串

注意 encodeURL 和 encodeURLComponent 编码方法的使用

  • 当单页应用里的不同路由之间想通过 URL params 的方式传递参数的话,要注意对中文等其他非英文语言做编码处理,也就是 encodeURL方法、或者 encodeURLComponent 方法。
  • 对应的解码方法分别是:decodeURL 和 decodeURLComponent。
  • 注意:当URL参数里包含特殊字符 - _ . ! ~ * ’ ( ) 的时候,要注意 encodeURLComponent 编码方法不会对这些字符作编码的,可能会在编码结果中以这些字符作分隔符时遇到坑。比如:
// FileA.js
const paramA = encodeURLComponent('xxxx.xxxxxxxx.filename.txt');
const str = `http://baidu.com?paramA=${paramA}`;

// FileB.js
// 假设已经定义好了 getParamByUrl 方法,可从 URL 获取参数的
const paramA = decodeURLComponent(getParamByUrl(url, 'paramA'));
// 此处得到的 fileSuffix 值为 xxxx.xxxxxxxx.filename.txt, 而不是期望的 filename.txt
const fileSuffix = paramA.slice('.');

参考

  • 在Web前端还可以这样实现Base64
  • 彻底弄懂 Unicode 编码
  • 在web应用程序中使用文件
  • developer.mozilla.org
  • JS中Buffer数据详解
  • Base64的编码与解码 - Web API 接口参考 _ MDN
  • ArrayBuffer - ECMAScript 6入门
  • 你不知道的 Blob

你可能感兴趣的:(技术视野)