当前位置: 首页 > 科技观察

JS中的二元运算介绍

时间:2023-03-12 20:50:33 科技观察

写这篇博客的由来是小胡子哥哥《你所不知道的JavaScript数组你所不知道的JavaScript数组》的一篇文章。因为随着XHR2和现代浏览器的普及,在浏览器中处理二进制不再像过去那样混乱。随着Canvas/WebGL等新技术逐渐开始进入大众视野,一些字节数组或者16位、8位整数什么的。在node.js刚刚发布的4.0版本中,Buffer底层使用更符合JS标准的Uint8Array实现。浏览器和node.js又有点接近同一个目标了,所以对于JS中的二进制处理,我只是打算把这篇文章写成一个入门期刊,让一些不懂二进制处理的同学可以快速上手开始了。虽然在前端领域用得不多,但也可以作为饭后闲谈。JS程序中二进制数据的表达当今世界上几乎所有的计算机体系结构都使用字节作为二进制数据的基本单位(注意:不是最小单位),所以二进制在程序中往往以字节数组的形式存在。例如,在C#中,使用byte[]。标准C中没有byte类型,但是可以通过typedef将byte定义为unsignedchar的别名,效果是一样的。在JS设计之初,似乎从来没有想过要处理二进制的东西,再加上类型的极度弱化,字节的概念可以说是非常非常的模糊。如果要表达一个字节数组,好像只能用普通的数组来表达了。HTML5系统引入了很多新东西,比如XHR2,可以上传或者下载二进制内容,与之配套的东西有JS中的ArrayBuffer和TypedArray。ArrayBuffer是一个固定长度的字节序列。可以通过newArrayBuffer(length)获取一块空间,也可以使用下面介绍的方法从XHR2等渠道获取。因为内部实现不同于数组,ArrayBuffer通常是连续的内存(注意这只是经验,没有规范或文档规定),所以对于高密度访问操作在JS中比Array快很多更快(但不要将其用作Array的简单替代品)。如果你使用Chrome的Profile工具查看HeapSnapshot,你会发现ArrayBuffer会被列为一个单独的类别。也许它的内存分配和布局与Array和其他JS对象不同。ArrayBuffer不能直接访问,所以需要TypedArray。TypedArray是一组特定数据类型的Array-Like类型的总称,包括Int8Array8-bitsignedinteger,类似于C中的charUint8Array8-bitunsignedinteger,类似于C中的unsignedcharUint8ClampedArray8-bitunsignedintegerC,类似于Uint8,但在溢出处理方面不完全相同。Int16Array下面的类型就不罗嗦了。Array只是给你提供了某种类型的读写接口。用MDN的话来说,叫做Multipleviewsonthesamedata。比如我们有一个ArrayBuffer,名字叫buffer(先不考虑怎么构造这个测试数据),内容如下:0102030405060708表示它有8个字节,我们用它分别构造Uint8Array,Uint16Array,Uint32Array,那么我们可以得到varu8=newUint8Array(buffer);//长度为8varu16=newUint16Array(buffer);//长度为4varu32=newUint32Array(buffer);//长度为2,内容为[1,2,3,4,5,6,7,8][513,1027,1541,2055][67305985,134678021]这个不难理解。可以看出,如果要手动构造上面的测试数据ArrayBuffer,使用Uint8Array会很方便(嗯,其实这是我个人最常用的TypedArray)。但是,如果使用同一个ArrayBuffer构造一个有符号整数类型,可能会因为整数溢出而得到不同的结果,这在上面的例子中是没有遇到的。如果你有兴趣,你可以自己尝试一下。因此,使用TypedArray也可以用来转换有符号数和无符号数。如果你用过canvas的getImageData/putImageData,你会发现它给你的是一个Uint8ClampedArray,访问起来比JS原生的Array快很多,使得在canvas上进行高速像素操作成为可能。但是,最重要的概念是:TypedArray不直接存储任何数据,所有读写TypedArray的操作最终都会在其背后持有的ArrayBuffer上实现。ArrayBuffer是真正的原始字节,TypedArray只是一个操作窗口/操作视图(View)。如何在nodejs中获取二进制数据?常用的方法有3种,一种是通过XMLHttpRequest,另一种是通过File和Blob的一套相关接口。XHR2通过XMLHttpRequest的接口和XHR几乎一样。指定xhr.responseType='arraybuffer'后,在成功获取数据的回调中,可以通过xhr.response获取到请求结果的ArrayBuffer,然后可以按照自己的意愿构造各种TypedArray进行访问。responseType也可以有一个blob值,你可以使用xhr.response得到一个Blob对象。File和Blob对HTML5中的表单文件控件提供了更丰富的操作,可以通过inputDOM对象的.files得到一个FileList。当然,浏览器通常只提供单选。文件控件,所以这里只会有一个文件对象。此外,还可以通过拖拽、剪贴板等方式获取File或Blob。File继承了Blob,提供了name、lastModifiedDate等基本元数据,但仍然是一个深层封装,其二进制无法直接获取。Blob是Binarylargeobject的缩写。它与ArrayBuffer的区别在于,除了原始字节之外,它还提供mime类型作为元数据。但是还是不能直接读写。这时候就需要FileReader的帮助了。FileReader提供了一组方法将Blob读入更实用的类型readAsArrayBuffer()readAsBinaryString()readAsDataURL()readAsText()比如varfile=get_file_some_how();varfr=newFileReader();fr.onload=function(e){e.target.result;//读取的结果};fr.readAsDataUrl(file);//readAsArrayBuffer可以做什么?比如图片上传前的本地预览(甚至是canvas-basedediting)等等都可以实现起来。Blob的其他构造方法很多很复杂,这里就不搬文档了。二进制数据的消费什么是消费?也许最常见的方式是通过XHR2将二进制数据作为文件直接发布到服务器。这里我推荐使用FormData构造POST数据。因为在服务器端接收比较容易,有兴趣的可以找别人的例子。虽然也可以直接提交ArrayBuffer,但是此时服务端收到的POSTbody会一大群,使用起来不方便。如果要使用FormData提交ArrayBuffer,需要先构造成Blob。注意TypedArray的构造。使用newxxxxxArray(arrayBuffer)的重载进行构造时,默认会基于这个ArrayBuffer进行构造。但是在重载newxxxxArray(another_typed_array)时,进行了“拷贝构造”,这样两个TypedArray会指向不同的buffer,需要注意是否符合预期。如果需要基于同一个ArrayBuffer构造一个TypedArray,可以通过TypedArray的buffer、byteLength、byteOffset获取其后面的ArrayBuffer。Tips(坑)注意内存对齐使用ArrayBuffer构造TypedArray时,可以指定byteOffset参数,例如varbuffer=get_array_buffer_some_how();vari16=newInt16Array(buffer,10);上面的代码可以使用bufferbackward使用offset10字节作为起点构造Int16Array,但是如果你设置10为奇数,你会发现如下错误:RangeError:startoffsetofInt16Arrayshouldbeamultipleof2这是因为TypedArray有内存对齐的要求,并且不能在非对齐位置创建。原则上,Uint32Array和Int32Array要求偏移量是4字节对齐的。所以想要在未对齐的位置读写,就需要DataView的帮助了。注意字节序我们日常生活中写的程序几乎不需要关心字节序,所以这个问题并没有那么严重。知道自己的程序会有字节顺序问题的人肯定会在这里开发。我知道问题存在,但这里稍微提一下。根据MDN,TypedArray只会使用当前平台的字节顺序。比如我们现在用的台式电脑,不管是PC还是Mac,都是x86/x64,也就是little-endian。使用DataView不仅可以解决上面提到的内存对齐问题,还可以指定读写时的字节顺序。具体参数都在文档里,就不动了。使用DataViewwithTypedArray还可以检测当前平台的字节顺序:functionisLittleEndian(){varbuf=newArrayBuffer(2);varview=newDataView(buf);view.setInt16(0,256,true);//Explicit在littleendian中写入数据//此时buf中的内存布局应该是0001vari16=newInt16Array(buf);//如果以littleendian方式读取,则为256;在bigendian中是1return(i16[0]===256);}如果你写的程序需要x86/ARM/PPC等折叠架构,交换文件时需要小心处理字节顺序和网络数据包。当然,一种方法是预先规范这些地方的统一字节顺序,以防后患。但那些都是题外话。总结使用ArrayBuffer存储一段字节,使用TypedArray构建特定数值类型的访问窗口,使用DataView对不对齐或关心字节顺序的ArrayBuffer进行更精确的操作,使用XHR2、Blob、File,FileReader,FormData获取或消费ArrayBuffer的方式有很多种。另外,浏览器还提供了一系列所谓的“BinaryString”,就是一些看起来像乱码的字符串,然后提供atob/btoa来与Base64和“BinaryString”进行交互。转换,甚至FileReader都提供了readAsBinaryString方法(已弃用,goodness)。谁用这个BinaryString谁真遭罪,别问我为什么知道。。。