JavaScript与二进制数据的恩怨情仇
<p>编程江湖,终日血雨腥风,论及二进制数据,又有多少豪杰谈笑风生,风生水起,水起船高,高深莫测……</p>
<p>不扯远了,想必谈到二进制数据,大家联想到的就会是 <code class="language-plaintext highlighter-rouge">1010110110001</code> 或者 <code class="language-plaintext highlighter-rouge">00000000 11111111 00000101</code> 这样的数据流;而这武林之中,号称三剑客之一的 <strong>JavaScript</strong>,在其行走江湖之际(日常开发),可能厮杀(处理)最多的类型就是直观的数字、字符串或者对象等;那么与极少露面的隐士(二进制)狭路相逢之时,它又将作何应对(描述与处理二进制数据)呢?是波澜壮阔,还是全身而退,抑或是力挽狂澜,且听本文中分解。</p>
<h1 id="arraybuffer">ArrayBuffer</h1>
<p>未曾识得英雄面,只缘身在此山中;先来了解第一个概念,<code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 表示的是一个原始二进制数据缓冲区(buffer),长度固定,并且内容是只读的;如果需要执行写操作,那么需要使用 <strong>类型化数组</strong>(<code class="language-plaintext highlighter-rouge">TypedArray</code>)或者 <strong>数据视图</strong>(<code class="language-plaintext highlighter-rouge">DataView</code>) 来实现;</p>
<p>知己知彼,方可百战百胜;在 JavaScript 中与二进制数据接触最紧密的可能就是 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 了,之前讲目的是要描述和操作二进制数据,那么就要把这些数据先存放到某个地方,然后才能对其进行操作,这里的 ArrayBuffer 缓冲区就可以被看成这么一种地方;当然,可能最直观的方式就是将其保存到字符串中,如 <code class="language-plaintext highlighter-rouge">"101011011"</code>,又或者存入数组,如 <code class="language-plaintext highlighter-rouge">[1,0,1,0,1,1,0,1]</code>,这样确实是方便人类了,但是机器执行的效率也降低了,因为毕竟字符和数组是另外两种基本类型,并且也不是专门为此设计的;所以,就出现了专门为缓冲数据设计的 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>,并通过结合视图来提供一个访问和操作数据的接口;</p>
<h2 id="语法">语法</h2>
<p>实例化 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 构造函数时,只接受一个参数,就是要创建的 Arraybuffer 的大小,单位是字节,不指定的话默认为 <strong>0</strong>;同时它也提供了一个实例属性 <code class="language-plaintext highlighter-rouge">byteLength</code>(只读),实现对当前 ArrayBuffer 字节值的访问;</p>
<p>举例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 3</span>
</code></pre></div></div>
<p>另外,由于 ArrayBuffer 只是负责创建这么一段数据区域,并没有提供初始化赋值的接口,所以这 n 字节的数据都为空,即都置 0;</p>
<h2 id="方法">方法</h2>
<p>由于 ArrayBuffer 构造函数本身是用于创建数据缓冲区,并且数据只读,所以提供的属性和方法也只有少数几个;</p>
<h3 id="slice">.slice()</h3>
<p>用于返回一个新的缓冲区,语法为 <code class="language-plaintext highlighter-rouge">.slice(start, end)</code>,即以当前缓冲区为母本,从索引为 <strong>start</strong> 的位置开始,到 <strong>end</strong> 位置结束(end 位置不包含在内),然后复制并返回这一区段的数据;其用法大致与 <code class="language-plaintext highlighter-rouge">Array.prototype.slice()</code> 类似,举例说明:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer1</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer2</span> <span class="o">=</span> <span class="nx">buffer1</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer3</span> <span class="o">=</span> <span class="nx">buffer1</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer4</span> <span class="o">=</span> <span class="nx">buffer1</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">buffer2</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">buffer3</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">buffer4</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 3</span>
</code></pre></div></div>
<h3 id="arraybufferisview">ArrayBuffer.isView()</h3>
<p>该方法用来判断所提供的参数值是否是一种 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> <strong>视图</strong>,比如类型化数组(TypedArray)和数据视图(DataView),例如:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">ArrayBuffer</span><span class="p">.</span><span class="nx">isView</span><span class="p">());</span> <span class="c1">// false</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">ArrayBuffer</span><span class="p">.</span><span class="nx">isView</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]));</span> <span class="c1">// false</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">ArrayBuffer</span><span class="p">.</span><span class="nx">isView</span><span class="p">(</span><span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">3</span><span class="p">)));</span> <span class="c1">// false</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">ArrayBuffer</span><span class="p">.</span><span class="nx">isView</span><span class="p">(</span><span class="k">new</span> <span class="nb">Int8Array</span><span class="p">()));</span> <span class="c1">// true</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">ArrayBuffer</span><span class="p">.</span><span class="nx">isView</span><span class="p">(</span><span class="k">new</span> <span class="nb">Uint32Array</span><span class="p">(</span><span class="mi">3</span><span class="p">)));</span> <span class="c1">// true</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">ArrayBuffer</span><span class="p">.</span><span class="nx">isView</span><span class="p">(</span><span class="k">new</span> <span class="nb">DataView</span><span class="p">(</span><span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">())));</span> <span class="c1">// true</span>
</code></pre></div></div>
<h1 id="类型化数组typedarray">类型化数组(TypedArray)</h1>
<h2 id="概述">概述</h2>
<p>工欲善其事必先利其器;前面提到操作 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 创建的数据缓冲区需要使用视图(view)实现,类型化数组就是这么一个描述二进制数据缓冲区(buffer)的视图(view),这个视图是一个 <strong>类数组</strong>。另外,不存在 <code class="language-plaintext highlighter-rouge">TypedArray()</code> 这个构造函数,它指的是一类数组,因此它有多种实现,即多个类型化数组构造器函数;可以姑且理解为 <em>水果</em> 之于 <em>苹果</em> 和 <em>香蕉</em>,水果指的是一类食物,都知道并不存在名为 <em>水果</em> 的一种具体食物,但是 <em>苹果</em> 和 <em>香蕉</em> 是具体存在的;</p>
<p>有效的类型如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">Int8Array</span><span class="p">();</span> <span class="c1">// 8 位二进制有符号整数</span>
<span class="nb">Uint8Array</span><span class="p">();</span> <span class="c1">// 8 位无符号整数(超出范围后从另一边界循环)</span>
<span class="nb">Uint8ClampedArray</span><span class="p">();</span> <span class="c1">// 8 位无符号整数(超出范围后为边界值)</span>
<span class="nb">Int16Array</span><span class="p">();</span> <span class="c1">// 16 位二进制有符号整数</span>
<span class="nb">Uint16Array</span><span class="p">();</span> <span class="c1">// 16 位无符号整数signed</span>
<span class="nb">Int32Array</span><span class="p">();</span> <span class="c1">// 32 位二进制有符号整数</span>
<span class="nb">Uint32Array</span><span class="p">();</span> <span class="c1">// 32 位无符号整数</span>
<span class="nb">Float32Array</span><span class="p">();</span> <span class="c1">// 32 位 IEEE 浮点数(7 位有效数字,如 1.1234567)</span>
<span class="nb">Float64Array</span><span class="p">();</span> <span class="c1">// 64 位 IEEE 浮点数(16 有效数字,如 1.123...15)</span>
<span class="nx">BigInt64Array</span><span class="p">();</span> <span class="c1">// 64 位二进制有符号整数</span>
<span class="nx">BigUint64Array</span><span class="p">();</span> <span class="c1">// 64 位无符号整数</span>
</code></pre></div></div>
<h2 id="语法-1">语法:</h2>
<p>万法不离其宗,一招一式都有迹可循;后面就都以 <code class="language-plaintext highlighter-rouge">Int8Array()</code> 为例进行说明,以下代码展示了可以传入的参数类型:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nb">Int8Array</span><span class="p">();</span> <span class="c1">// ES2017 中新增</span>
<span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">length</span><span class="p">);</span>
<span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">typedArray</span><span class="p">);</span>
<span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">object</span><span class="p">);</span>
<span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buffer</span> <span class="p">[,</span> <span class="nx">byteOffset</span> <span class="p">[,</span> <span class="nx">length</span><span class="p">]]);</span>
</code></pre></div></div>
<h3 id="无参数">无参数</h3>
<p>最好的招式是没有招式;实例化构造函数时不传入任何参数,则返回一个空的类型化数组:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array []</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// 0</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 0</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">byteOffset</span><span class="p">);</span> <span class="c1">// 0</span>
</code></pre></div></div>
<h3 id="length">length</h3>
<p>一寸长一寸强;传入一个数字类型的参数,表示申明类型化数组中元素的个数:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">_i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="dl">'</span><span class="s1">3</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 字符串会先被转换成数字</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [0, 0, 0]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [0, 0, 0]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">byteOffset</span><span class="p">);</span> <span class="c1">// 0</span>
</code></pre></div></div>
<h3 id="typedarray">typedArray</h3>
<p>好招不怕效仿;当传入的一个参数同样是一个类型化数组时,则返回一个原类型数组的拷贝(不是引用):</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">_i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span> <span class="o">==</span> <span class="nx">_i8</span><span class="p">);</span> <span class="c1">// false</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span> <span class="o">===</span> <span class="nx">_i8</span><span class="p">);</span> <span class="c1">// false</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [0, 0, 0]</span>
</code></pre></div></div>
<h3 id="object">object</h3>
<p>海纳百川有容乃大;使用该参数时类似于用 <code class="language-plaintext highlighter-rouge">TypedArray.prototype.from()</code> 方法创建的类型数组,同时该方法也和 <code class="language-plaintext highlighter-rouge">Array.from()</code> 方法类似,即这个 <code class="language-plaintext highlighter-rouge">object</code> 参数是一个类数组的对象,或者是可迭代的对象;举例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 数组</span>
<span class="kd">var</span> <span class="nx">i81</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i81</span><span class="p">);</span>
<span class="c1">// Int8Array(3) [1, 2, 3]</span>
<span class="c1">// 等价的操作</span>
<span class="kd">var</span> <span class="nx">i81</span> <span class="o">=</span> <span class="nb">Int8Array</span><span class="p">.</span><span class="k">from</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="c1">// Int8Array(3) [1, 2, 3]</span>
<span class="c1">// 类数组</span>
<span class="kd">var</span> <span class="nx">i82</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">({</span>
<span class="mi">0</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="mi">1</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="mi">2</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="na">length</span><span class="p">:</span> <span class="mi">3</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i82</span><span class="p">);</span>
<span class="c1">// Int8Array(3) [1, 2, 3]</span>
<span class="c1">// 可迭代对象</span>
<span class="kd">var</span> <span class="nx">i83</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="k">new</span> <span class="nb">Set</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]));</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i83</span><span class="p">);</span>
<span class="c1">// Int8Array(3) [1, 2, 3]</span>
</code></pre></div></div>
<h3 id="buffer-byteoffset-length">buffer, byteOffset, length</h3>
<p>众人拾柴火焰高;该构造函数也支持同时提供三个参数,第一个 <code class="language-plaintext highlighter-rouge">buffer</code> 指的是数组缓冲区,是 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 的实例,同时也是 <code class="language-plaintext highlighter-rouge">Int8Array.prototype.buffer</code> 这个属性的值;<code class="language-plaintext highlighter-rouge">butyOffset</code> 指的是元素的偏移值,表示从数组中第几个元素开始读取,默认是 0,也就是数组的第一个元素;<code class="language-plaintext highlighter-rouge">length</code> 指的是在设置了偏移值后,要读取的元素长度,默认是整个数组的长度;举例说明:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buf</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">]).</span><span class="nx">buffer</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buf</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span>
<span class="c1">// Int8Array(2) [2, 3]</span>
</code></pre></div></div>
<p>也就是让申明的类型化数组在提供的 <code class="language-plaintext highlighter-rouge">buffer</code> 的基础上,从它的索引为 <strong>1</strong> 的元素(第二个元素)开始读取,然后向后读取 <strong>2</strong> 个元素;该操作一般用于对缓冲区数据的截取;</p>
<h2 id="类型差异">类型差异</h2>
<p>存在即合理;根据前面的介绍,<code class="language-plaintext highlighter-rouge">TypedArray</code> 定义了多种类型,如 <code class="language-plaintext highlighter-rouge">Int8Array</code>, <code class="language-plaintext highlighter-rouge">Uint8Array</code>, <code class="language-plaintext highlighter-rouge">Int16Array</code> 等,这样做也是为了适应不同的应用场景,接下来大致了解一下几个典型的类型化数组之间的区别;</p>
<h3 id="有无符号">有无符号</h3>
<p>以 <code class="language-plaintext highlighter-rouge">Int8Array</code> 和 <code class="language-plaintext highlighter-rouge">Uint8Array</code> 为例,其实 <strong>有符号</strong> 的意思是数组中的元素可以存在符号,即可以是负数;因此 <strong>无符号</strong> 的意思就是元素只能是非负数,举例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">_i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">ui8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">_ui8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">]);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [1, 2, 3]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [-1, -2, -3]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">ui8</span><span class="p">);</span> <span class="c1">// Uint8Array(3) [1, 2, 3]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_ui8</span><span class="p">);</span><span class="c1">// Uint8Array(3) [255, 254, 253]</span>
</code></pre></div></div>
<p>可以发现有符号类型之处初始化负数元素,而无符号则会对负数进行转换,具体转换方式后面会提到;</p>
<h3 id="元素范围">元素范围</h3>
<p>有无符号的类型数组,除了元素的值的正负区别外,元素的取值范围也有所不同;下面是一份具体的清单:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Range</th>
</tr>
</thead>
<tbody>
<tr>
<td>Int8Array</td>
<td>-128 ~ 127</td>
</tr>
<tr>
<td>Uint8Array</td>
<td>0 ~ 255</td>
</tr>
<tr>
<td>Uint8ClampedArray</td>
<td>0 ~ 255</td>
</tr>
<tr>
<td>Int16Array</td>
<td>-32768 ~ 32767</td>
</tr>
<tr>
<td>Uint16Array</td>
<td>0 ~ 65535</td>
</tr>
<tr>
<td>Int32Array</td>
<td>-2147483648 ~ 2147483647</td>
</tr>
<tr>
<td>Uint32Array</td>
<td>0 ~ 4294967295</td>
</tr>
<tr>
<td>Float32Array</td>
<td>1.2×10-38 ~ 3.4×1038</td>
</tr>
<tr>
<td>Float64Array</td>
<td>5.0×10-324 ~ 1.8×10308</td>
</tr>
<tr>
<td>BigInt64Array</td>
<td>-263 ~ 263-1</td>
</tr>
<tr>
<td>BigUint64Array</td>
<td>0 ~ 264-1</td>
</tr>
</tbody>
</table>
<p>可以看出,为了顾及有无符号类型的单个元素取值范围区间一样,所以就调整了它们的取值上下限;</p>
<h3 id="字节位数">字节位数</h3>
<p>以有符号类型为例,可以发现有 <code class="language-plaintext highlighter-rouge">Int8Array</code>, <code class="language-plaintext highlighter-rouge">Int16Array</code> 等几个不同的类型数组,唯一的区别就是他们构造函数名字中间的数字不同,其实这个数字指的是实例化后的类型化数组的单个元素的大小,即多少位,<strong>8</strong> 就是 8 位,即一字节,<strong>16</strong> 就是 2 字节,类推;其实,这个数字也反应了类型数组中 <code class="language-plaintext highlighter-rouge">BYTES_PER_ELEMENT</code> 这个属性的值,从名字也可以看出代表的是每个元素的字节数;举例说明:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i16</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int16Array</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i32</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int32Array</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">BYTES_PER_ELEMENT</span><span class="p">);</span> <span class="c1">// 1</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i16</span><span class="p">.</span><span class="nx">BYTES_PER_ELEMENT</span><span class="p">);</span> <span class="c1">// 2</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i32</span><span class="p">.</span><span class="nx">BYTES_PER_ELEMENT</span><span class="p">);</span> <span class="c1">// 4</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i16</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i32</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i16</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 6</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i32</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 12</span>
</code></pre></div></div>
<p>另外 <code class="language-plaintext highlighter-rouge">byteLength</code> 这个属性其实指的是类型数组总的字节大小,其值等于单个元素字节值乘以元素个数(<code class="language-plaintext highlighter-rouge">byteLength = BYTES_PER_ELEMENT x length</code>);</p>
<h3 id="clamped">Clamped</h3>
<p>鹤难隐于鸡群;从前面的清单中可以找到 <code class="language-plaintext highlighter-rouge">Uint8ClampedArray</code> 这个独特的类型数组,区别就是中间多了 <code class="language-plaintext highlighter-rouge">clamped</code> 这个单词,词典解释的意思是“夹紧,箝位”,具体功能是什么,下面通过代码来解释:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">_i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">_i8_</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="mi">255</span><span class="p">,</span> <span class="mi">256</span><span class="p">,</span> <span class="mi">257</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">uic8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8ClampedArray</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
<span class="kd">var</span> <span class="nx">_uic8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8ClampedArray</span><span class="p">([</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">])</span>
<span class="kd">var</span> <span class="nx">_uic8_</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8ClampedArray</span><span class="p">([</span><span class="mi">255</span><span class="p">,</span> <span class="mi">256</span><span class="p">,</span> <span class="mi">257</span><span class="p">])</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [1, 2, 3]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [255, 254, 253]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i8_</span><span class="p">);</span> <span class="c1">// Uint8Array(3) [255, 0, 1]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">uic8</span><span class="p">);</span> <span class="c1">// Uint8ClampedArray(3) [1, 2, 3]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_uic8</span><span class="p">);</span> <span class="c1">// Uint8ClampedArray(3) [0, 0, 0]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_uic8_</span><span class="p">);</span><span class="c1">// Uint8Array(3) [255, 255, 255]</span>
</code></pre></div></div>
<p>不知诸位可否查探出端倪,这里也能解释之前说的无符号类型数组实例化时转换负值的问题;通过分析不难发现,转换方式类似于素组循环取值,就是如果传入的值超过了元素的取值范围的上限或下限之一时,那么超过的部分就会,从范围的另一个界限开始依次向后计数;所以上例中 <strong>-1</strong> 会被转换为 <strong>255</strong>,<strong>257</strong> 会被转换成 <strong>1</strong>;</p>
<p>而对于 <code class="language-plaintext highlighter-rouge">Uint8ClampedArray</code> 这个类型数组,其实差不多也是字面的意思,类似于一个 “夹住” 的操作:超出范围不会发生循环转换,无论超出多少都只会被置为对应的边界值,所以上例中 <code class="language-plaintext highlighter-rouge">-1, -2, -3</code> 都被转换为 <code class="language-plaintext highlighter-rouge">0</code>,<code class="language-plaintext highlighter-rouge">256, 257</code> 则都被转换成了 <code class="language-plaintext highlighter-rouge">255</code>;</p>
<h3 id="浮点数">浮点数</h3>
<p>论世间谁主浮沉;仅有的两个浮点类型的类型数组,<code class="language-plaintext highlighter-rouge">Float32Array</code> 和 <code class="language-plaintext highlighter-rouge">Float64Array</code>,浮点的意思就是元素值可以是小数,因为之前介绍过的都是 <code class="language-plaintext highlighter-rouge">int</code>(整数) 类型的;依然来举例说明:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">f32</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Float32Array</span><span class="p">([</span><span class="mf">1.11</span><span class="p">,</span> <span class="mf">2.12345678911</span><span class="p">,</span> <span class="o">-</span><span class="mf">3.33333333333333333333333333</span><span class="p">])</span>
<span class="kd">var</span> <span class="nx">f64</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Float64Array</span><span class="p">([</span><span class="mf">1.11</span><span class="p">,</span> <span class="mf">2.12345678911</span><span class="p">,</span> <span class="o">-</span><span class="mf">3.33333333333333333333333333</span><span class="p">])</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">f32</span><span class="p">);</span>
<span class="c1">// Float32Array(3) [1.1100000143051147, 2.1234567165374756, -3.3333332538604736]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">f64</span><span class="p">);</span>
<span class="c1">// Float64Array(3) [1.11, 2.12345678911, -3.3333333333333335]</span>
</code></pre></div></div>
<p>从结果来看 32 位浮点类型数组每个元素都保留到小数点后 16 位,而 64 位是最多保留到 16 位,具体的细节就不深究了;</p>
<h2 id="操作元素">操作元素</h2>
<p>欲与二进制数据一决高低,首先肯定是选几样趁手兵器;虽然类型化数组拥有普通数组的大部分方法,比如 <code class="language-plaintext highlighter-rouge">every</code>, <code class="language-plaintext highlighter-rouge">forEach</code>, <code class="language-plaintext highlighter-rouge">slice</code> 等等,但也有自己特有的方法值得说一下,比如 <strong>.set()</strong> 这个方法;</p>
<p><code class="language-plaintext highlighter-rouge">.set()</code> 方法用于把指定数组的(所有)元素添加到当前数组的指定位置,接受的参数为 <code class="language-plaintext highlighter-rouge">.set(array[, ofset])</code>,这里的 array 可以是普通数组或类型化数组,offset 指的是偏移值,即从哪个位置开始写入指定数组元素;举例说明:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i81</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i82</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">arr1</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">];</span>
<span class="nx">i8</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">arr</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span>
<span class="c1">// Int8Array(6) [0, 0, 1, 2, 3, 0]</span>
<span class="nx">i81</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">arr1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i81</span><span class="p">);</span>
<span class="c1">// Uncaught RangeError: offset is out of bounds</span>
<span class="nx">i82</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">arr</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i82</span><span class="p">);</span>
<span class="c1">// Uncaught RangeError: offset is out of bounds</span>
</code></pre></div></div>
<p>证明无论是拷贝的数组大小超过原数组,还是偏移值过大使得拷贝结果超过原数组,都会报错提示偏移超过边界,因此使用时需计算准确;</p>
<h2 id="操作缓冲区">操作缓冲区</h2>
<p>箭在弦上,东风将至;前面将 TypedArray 描述为操作 ArrayBuffer 中数据的视图,下面就来看一下具体的操作方法;</p>
<h3 id="数据读取">数据读取</h3>
<h4 id="数据转换">数据转换</h4>
<p>敌不动我不动;使用类型化数组操作 ArrayBuffer 的数据前,需要先获取其中的数据,也就是把 ArrayBuffer 转换为 TypedArray 类型;先来看一下这两种类型互相转换的方法:</p>
<p><strong>ArrayBuffer</strong> 转换为 <strong>TypedArray</strong>:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span> <span class="c1">// 先初始化 5 字节长的区域</span>
<span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// 再把数据传递进 TypedArray</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array(5) [0, 0, 0, 0, 0]</span>
</code></pre></div></div>
<p>这里也可以验证,ArrayBuffer 新创建的区域数据都被置 0 了;</p>
<p><strong>TypedArray</strong> 转化为 <strong>ArrayBuffer</strong>:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">i8</span><span class="p">.</span><span class="nx">buffer</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// ArrayBuffer(5) {}</span>
</code></pre></div></div>
<h4 id="读取方式">读取方式</h4>
<p>前面讲道,类型化数组有多种不同的实现,比如 1 字节有符号元素的 <code class="language-plaintext highlighter-rouge">Int8Array</code>,2 字节的 <code class="language-plaintext highlighter-rouge">Int16Array</code> 等;根据 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 的定义,缓冲区是以 <strong>1 字节</strong> 为单位进行创建的,所以我们通常读取文本类数据使用 <code class="language-plaintext highlighter-rouge">Uint8Array</code>,因为它也正好每个元素的大小为 1 字节,当然,也可以选择用 <code class="language-plaintext highlighter-rouge">Uint16Array</code> 来 2 字节地挨个读,其他类型类推;</p>
<p>通过代码来观察一下具体的读取方式:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">])</span>
<span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">buffer</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">ui8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">ui16</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint16Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">ui8</span><span class="p">);</span> <span class="c1">// Uint8Array(4) [1, 2, 3, 4]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">ui16</span><span class="p">);</span> <span class="c1">// Uint16Array(2) [513, 1027]</span>
</code></pre></div></div>
<p>原始数据 <code class="language-plaintext highlighter-rouge">data</code> 是 4 字节大小,通过 <code class="language-plaintext highlighter-rouge">Uint8Array</code> 就是以 1 字节为单位,所以得到的也是原始的数据 <code class="language-plaintext highlighter-rouge">[1, 2, 3, 4]</code>,这里由于数据小所以有无符号无影响;而通过 <code class="language-plaintext highlighter-rouge">Uint16Array</code> 则是以 2 字节为单位进行读取,所以总的元素长度为 2(2 = 4 / 2),但是其中的单个元素 <code class="language-plaintext highlighter-rouge">513</code>, <code class="language-plaintext highlighter-rouge">1027</code> 又分别是如何得到的呢?我们可以通过计算来探究一下:</p>
<p>首先看 <code class="language-plaintext highlighter-rouge">1, 2</code> 这两个元素,根据结果它们被读取成为了 <code class="language-plaintext highlighter-rouge">513</code>,那么就把这几个元素的二进制数表示出来(缓冲区就是存储的二进制数据):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"1"</span>: 00000001
<span class="s2">"2"</span>: 00000010
<span class="s2">"12"</span>: 00000001 00000010
<span class="s2">"513"</span>: 00000010 00000001
<span class="s2">"21"</span>: 00000010 00000001
</code></pre></div></div>
<p>规律显而易见了,<code class="language-plaintext highlighter-rouge">513</code> 这个 2 字节的数据,其实是把 <code class="language-plaintext highlighter-rouge">1</code> 和 <code class="language-plaintext highlighter-rouge">2</code> 这两个挨着的 1 字节的数据,以 <strong>倒序</strong> 方式拼接在一起的;</p>
<p>再来看一下 <code class="language-plaintext highlighter-rouge">3</code> 和 <code class="language-plaintext highlighter-rouge">4</code> 这两个是否也是以同样的方法得出 <code class="language-plaintext highlighter-rouge">1027</code> 这个数据的:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"3"</span>: 00000011
<span class="s2">"4"</span>: 00000100
<span class="s2">"34"</span>: 00000011 00000100
<span class="s2">"1027"</span>: 00000100 00000011
<span class="s2">"43"</span>: 00000100 00000011
</code></pre></div></div>
<p>结果不出所料,所以像 <code class="language-plaintext highlighter-rouge">Uint32Array</code> 等以多个字节读取数据的类型数组,方法也可以类推;</p>
<h4 id="字节序">字节序</h4>
<p>另外值得一提的是,上面所说的 <strong>倒序</strong> 拼接方式,其实有个专业术语,叫做 <strong>字节序(Endian)</strong>,对应这个英文单词应该会感觉似曾相识,例如 Linux 中执行 <code class="language-plaintext highlighter-rouge">lscpu</code> 得到的结果中,就会发现它的存在:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Architecture: x86_64
CPU op-mode<span class="o">(</span>s<span class="o">)</span>: 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 36 bits physical, 48 bits virtual
</code></pre></div></div>
<blockquote>
<p>字节序,或字节顺序(”Endian”、”endianness” 或 “byte-order”),描述了计算机如何组织字节,组成对应的数字。</p>
</blockquote>
<p>这个字节序可以分为:</p>
<p><strong>Little Endia(低字节序)</strong>:低位数据放入存储地址的低位,高位数据放入高位地址;</p>
<p>这种顺序就显得和内存上的存储地址顺序(阅读模式下低位在右,高位在左)保持一致,并且也是一种常见的方式,比如上面的英特尔处理器;只不过对于这种顺序人类阅读时就要反着读了(从右至左),比如上面例子中的数据 <code class="language-plaintext highlighter-rouge">12</code> 就是以 <code class="language-plaintext highlighter-rouge">21</code> 的顺序读取的,也可以类比这种日期格式:<code class="language-plaintext highlighter-rouge">"Sat 07 Mar 2020"</code>;</p>
<p><strong>Big Endian(高字节序)</strong>:低位数据存入高位地址,高位数据放入低位;</p>
<p>这种顺序可能更符合人类的阅读习惯(从左至右),它一般应用在互联网标准的数据结构中,可以类比 <code class="language-plaintext highlighter-rouge">"2020-03-07"</code> 这种日期格式;</p>
<h3 id="数据修改">数据修改</h3>
<p>下面通过类型化数组视图来尝试修改一下 ArrayBuffer 缓冲区中的内容:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [0, 0, 0]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">i8</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">i8</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">_i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// 新建个视图验证是否修改成功</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i8</span><span class="p">);</span> <span class="c1">// Int8Array(3) [1, 1, 1]</span>
</code></pre></div></div>
<h3 id="数据拼接">数据拼接</h3>
<p>用之前讲过的 <code class="language-plaintext highlighter-rouge">.set()</code> 方法来尝试将数据拼接进缓冲区:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i80</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i80</span><span class="p">);</span> <span class="c1">// Int8Array(6) [0, 0, 0, 0, 0, 0]</span>
<span class="kd">var</span> <span class="nx">i81</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">i82</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]);</span>
<span class="nx">i80</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">i81</span><span class="p">);</span>
<span class="nx">i80</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">i82</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">_i80</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// 验证是否修改成功</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_i80</span><span class="p">);</span> <span class="c1">// Int8Array(6) [1, 2, 3, 4, 5, 6]</span>
</code></pre></div></div>
<p><strong>注意</strong>:这里不能使用数组的 <code class="language-plaintext highlighter-rouge">.concat()</code> 这个方法来进行元素拼接,因为类型化数组中并没有内置这个方法,不然会报错,如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">arr1</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">arr2</span> <span class="o">=</span> <span class="nx">arr1</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">arr2</span><span class="p">);</span> <span class="c1">// [1, 2, 3, 4, 5, 6]</span>
<span class="kd">var</span> <span class="nx">i81</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">i82</span> <span class="o">=</span> <span class="nx">i81</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i82</span><span class="p">);</span>
<span class="c1">// Uncaught TypeError: i81.concat is not a function</span>
</code></pre></div></div>
<p>同样地,<code class="language-plaintext highlighter-rouge">.splice()</code> 这个可以替换元素的方法也不存在于类型化数组中;</p>
<h1 id="数据视图dataview">数据视图(DataView)</h1>
<h2 id="概述-1">概述</h2>
<p>一个好汉三个帮;<code class="language-plaintext highlighter-rouge">DataView</code> 是另外一个用于从 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 缓冲区中读写数据的视图接口,其特点就是考虑了 <em>字节序</em> 的问题,后面会讲;</p>
<p>语法为:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nb">DataView</span><span class="p">(</span><span class="nx">buffer</span> <span class="p">[,</span> <span class="nx">byteOffset</span> <span class="p">[,</span> <span class="nx">byteLength</span><span class="p">]]);</span>
</code></pre></div></div>
<p>其中 <code class="language-plaintext highlighter-rouge">buffer</code> 指传入的数据缓冲区,如 ArrayBuffer;<code class="language-plaintext highlighter-rouge">byteOffset</code> 指偏移的字节量,默认第一个字节,<code class="language-plaintext highlighter-rouge">byteLength</code> 指要传入的数据的字节长度,默认整个 buffer 的长度;并且这三个参数都可以在实例化后通过相应属性(只读)访问到;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]).</span><span class="nx">buffer</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">dv</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">DataView</span><span class="p">(</span><span class="nx">buffer</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">);</span> <span class="c1">// DataView(2) {}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span> <span class="c1">// ArrayBuffer(4) {}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">byteOffset</span><span class="p">);</span> <span class="c1">// 1</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">);</span> <span class="c1">// 2</span>
</code></pre></div></div>
<h2 id="操作数据">操作数据</h2>
<p>DataView 提供了一系列的方法用于操作缓冲区的数据,先简单预览一下:</p>
<table>
<thead>
<tr>
<th>Read</th>
<th>Write</th>
</tr>
</thead>
<tbody>
<tr>
<td>getInt8()</td>
<td>setInt8()</td>
</tr>
<tr>
<td>getUint8()</td>
<td>setUint8()</td>
</tr>
<tr>
<td>getInt16()</td>
<td>setInt16()</td>
</tr>
<tr>
<td>getUint16()</td>
<td>setUint16()</td>
</tr>
<tr>
<td>getInt32()</td>
<td>setInt32()</td>
</tr>
<tr>
<td>getUint32()</td>
<td>setUint32()</td>
</tr>
<tr>
<td>getFloat32()</td>
<td>setFloat32()</td>
</tr>
<tr>
<td>getFloat64()</td>
<td>setFloat64()</td>
</tr>
</tbody>
</table>
<h3 id="read">Read</h3>
<p>以 <code class="language-plaintext highlighter-rouge">getInt8()</code> 方法为例,可提供一个参数 <code class="language-plaintext highlighter-rouge">byteOffset</code>,表示偏移指定字节数,然后读取 1 字节(8 位)数据,默认 为 0(第一字节);而如果是 <code class="language-plaintext highlighter-rouge">getInt16()</code> 等用于获取大于 1 字节值以及浮点值的方法,还接受第二个可选参数 <code class="language-plaintext highlighter-rouge">littleEndian</code>,就是是否使用 little endian(低字节序,上文有讲)格式来读取数据,传入 <code class="language-plaintext highlighter-rouge">true</code> 就表示使用 little endian 格式,传入 <code class="language-plaintext highlighter-rouge">false</code> 或者不填,就使用 big endian(高字节序) 格式;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]).</span><span class="nx">buffer</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">dv</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">DataView</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">getInt8</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span> <span class="c1">// 2</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">getInt16</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="kc">true</span><span class="p">));</span> <span class="c1">// 513</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">getInt16</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="kc">false</span><span class="p">));</span> <span class="c1">// 258</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dv</span><span class="p">.</span><span class="nx">getInt16</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span> <span class="c1">// 258</span>
</code></pre></div></div>
<p>结果为 <code class="language-plaintext highlighter-rouge">513</code> 的这一行代码,使用的是 little endian 格式,并且 513 这个值也与之前 <code class="language-plaintext highlighter-rouge">TypedArray</code> 中关于 <code class="language-plaintext highlighter-rouge">Int16Array</code> 例子的结果一致,证明 <code class="language-plaintext highlighter-rouge">TypedArray</code> 默认使用的是 <strong>little endian</strong> 格式在操作数据缓冲区;</p>
<h3 id="write">Write</h3>
<p>以 <code class="language-plaintext highlighter-rouge">setInt8()</code> 为例,接受两个参数:<code class="language-plaintext highlighter-rouge">setInt8(byteOffset, value)</code>,第一个表示偏移字节量,用于定位,第二个则是要设置的具体值,非数字类型会报错;类似地,<code class="language-plaintext highlighter-rouge">setInt16</code> 等用于设置超过 1 字节的方法,也提供第三个可选参数 <code class="language-plaintext highlighter-rouge">littleEndian</code>,表示是否以 little endian 格式设置;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">buffer1</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">buffer2</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">ArrayBuffer</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">dv1</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">DataView</span><span class="p">(</span><span class="nx">buffer1</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">dv2</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">DataView</span><span class="p">(</span><span class="nx">buffer2</span><span class="p">);</span>
<span class="nx">dv1</span><span class="p">.</span><span class="nx">setInt8</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="nx">dv1</span><span class="p">.</span><span class="nx">setInt8</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Int8Array</span><span class="p">(</span><span class="nx">dv1</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i8</span><span class="p">);</span> <span class="c1">// Int8Array(2) [1, 2]</span>
<span class="nx">dv2</span><span class="p">.</span><span class="nx">setUint16</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">513</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nx">dv2</span><span class="p">.</span><span class="nx">setUint16</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">513</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">i16</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint16Array</span><span class="p">(</span><span class="nx">dv2</span><span class="p">.</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i16</span><span class="p">);</span> <span class="c1">// Int16Array(2) [513, 258]</span>
</code></pre></div></div>
<p>需要注意的就是,因为 <code class="language-plaintext highlighter-rouge">byteOffset</code> 这个参数的单位始终是 <strong>1 字节</strong>,所以当写入超过一字节的数据时,相应的偏移值也需要增加,就像上例所以展示的一样;</p>
<h3 id="对比">对比</h3>
<p>与前文所讲的 <code class="language-plaintext highlighter-rouge">TyptedArray</code> 视图接口相比,<code class="language-plaintext highlighter-rouge">DataView</code> 视图虽然兼容了不同平台的字节序问题,但是也没有了一些对整段数据进行修改拼接的功能,只能修改单个元素值;另外也不能用构造函数初始赋值,比如下面的情况:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]));</span>
<span class="c1">// Int8Array(3) [1, 2, 3]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">DataView</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]));</span>
<span class="c1">// Uncaught TypeError: First argument to DataView constructor must be an ArrayBuffer</span>
</code></pre></div></div>
<p>所以,需要灵活地结合二者使用,以应对复杂的场景;兄弟齐心,其力断金;</p>
<h1 id="blob">Blob</h1>
<p><code class="language-plaintext highlighter-rouge">Blob</code> 构造函数用于描述一个 blob(Binary Large OBject,二进制大对象),即保存原始数据的类文件对象,支持保存 <strong>多种类型</strong> 的数据(不像 <code class="language-plaintext highlighter-rouge">TypedArray</code>,只能使用数字类型),并且数据是只读的,不可修改;另一个基于 Blob 的构造函数 <code class="language-plaintext highlighter-rouge">File</code>,就是用来处理用户上传文件的(<code class="language-plaintext highlighter-rouge"><input type="file"></code>)数据。</p>
<p>语法:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span><span class="nx">array</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">array</code> 指的是一系列类型的数据构成的数组或者类数组,这些数据可以是字符串、ArrayBuffer、DataView、TypedArray、Blob、DOMString 等等;<code class="language-plaintext highlighter-rouge">options</code> 则是一个对象,可以包含以下两个属性:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nl">type</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span> <span class="c1">// 传入的数据的 MIMS 类型,比如 text/plain,默认为空</span>
<span class="nx">endings</span><span class="p">:</span> <span class="dl">""</span> <span class="c1">// 如何处理数据中的换行符,比如 \n 和 \r\n,因操作系统而异</span>
<span class="c1">// 值为 transparent 或者 native,默认为 transparent</span>
<span class="c1">// native 表示替换为当前系统的换行符</span>
<span class="c1">// transparent 则表示不替换,保持数据内容</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="写入数据">写入数据</h2>
<p>通过几个例子来说明:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob1</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">blob2</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">bc</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">d e</span><span class="dl">'</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">blob3</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span><span class="p">],</span> <span class="p">{</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">});</span>
<span class="kd">var</span> <span class="nx">blob4</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span><span class="k">new</span> <span class="nb">Int8Array</span><span class="p">([</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]));</span>
<span class="kd">var</span> <span class="nx">blob5</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="nx">blob2</span><span class="p">]);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob1</span><span class="p">);</span> <span class="c1">// Blob {size: 3, type: ""}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob2</span><span class="p">);</span> <span class="c1">// Blob {size: 6, type: ""}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob3</span><span class="p">);</span> <span class="c1">// Blob {size: 5, type: "text/plain"}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob4</span><span class="p">);</span> <span class="c1">// Blob {size: 3, type: ""}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob5</span><span class="p">);</span> <span class="c1">// Blob {size: 6, type: ""}</span>
</code></pre></div></div>
<p>如果参入的参数不是类数组的类型,则会报错:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob1</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span><span class="mi">123</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">blob2</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span><span class="dl">'</span><span class="s1">123</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">blob3</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">({</span><span class="na">foo</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">});</span>
<span class="kd">var</span> <span class="nx">blob4</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">blob5</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">(</span><span class="nx">blob1</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob1</span><span class="p">);</span>
<span class="c1">// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': </span>
<span class="c1">// The provided value cannot be converted to a sequence.</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob2</span><span class="p">);</span>
<span class="c1">// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': </span>
<span class="c1">// The provided value cannot be converted to a sequence.</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob3</span><span class="p">);</span>
<span class="c1">// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': </span>
<span class="c1">// The provided value cannot be converted to a sequence.</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob4</span><span class="p">);</span>
<span class="c1">// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': </span>
<span class="c1">// The provided value cannot be converted to a sequence.</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob5</span><span class="p">);</span>
<span class="c1">// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': </span>
<span class="c1">// The provided value cannot be converted to a sequence.</span>
</code></pre></div></div>
<h2 id="读取数据">读取数据</h2>
<p>写入 Blob 实例中的数据虽然不能修改,但是还是可以读取的,首先可以获取数据总的大小和类型(只读):</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">b</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">],</span> <span class="p">{</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">blob</span><span class="p">.</span><span class="nx">type</span><span class="p">);</span> <span class="c1">// text/plain</span>
</code></pre></div></div>
<p><strong>.text()</strong> 方法用于获取 Blob 中的文本数据,返回值是一个 promise 对象,包含一个 resolved 状态的文本数据,无提供的参数;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="nx">blob</span><span class="p">.</span><span class="nx">text</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="k">typeof</span> <span class="nx">data</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// 123 string</span>
</code></pre></div></div>
<p><strong>.arrayBuffer()</strong> 方法也用于获取 Blob 中的数据,并且返回一个 promise,无参数提供,只不过返回的是数据的 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>,即二进制数据缓冲区;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob1</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">blob2</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">b</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">]);</span>
<span class="nx">blob1</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span>
<span class="p">});</span>
<span class="c1">// Uint8Array(3) [49, 50, 51]</span>
<span class="nx">blob2</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span>
<span class="p">});</span>
<span class="c1">// Uint8Array(3) [97, 98, 99]</span>
</code></pre></div></div>
<p>计算以下也可以验证,类型数组中的数值确实是对应的原始数据的二进制值。</p>
<h1 id="textencoder">TextEncoder</h1>
<p>临阵磨枪,不快也光;这还是一个处于 <strong>实验阶段</strong> 的接口,当前的接口将来可能发生改变,并且目前 IE 系列浏览器都还不支持,这里只作简单介绍;</p>
<p>顾名思义,这个构造函数的作用就是负责编码文本,其实就是以指定的编码格式,将传入的文本转换成该数据对应的 <strong>类型化数组</strong>;实例化时可以提供一个参数,用于编码格式,不过目前默认并且只使用 UTF-8 格式编码,所以可以省略;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">encoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextEncoder</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">arr</span> <span class="o">=</span> <span class="nx">encoder</span><span class="p">.</span><span class="nx">encode</span><span class="p">(</span><span class="dl">'</span><span class="s1">abc</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">encoder</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span> <span class="c1">// utf-8</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">arr</span><span class="p">);</span> <span class="c1">// Uint8Array(3) [97, 98, 99]</span>
</code></pre></div></div>
<p>有编码就自然有解码,<code class="language-plaintext highlighter-rouge">TextDecode</code> 这个构造函数就与之对应,即将 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 或者 <code class="language-plaintext highlighter-rouge">ArrayBuffer View</code> 类型的数据解码为相应的文本;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">ui8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="mi">97</span><span class="p">,</span> <span class="mi">98</span><span class="p">,</span> <span class="mi">99</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">ui8</span><span class="p">.</span><span class="nx">buffer</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">decoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextDecoder</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">text1</span> <span class="o">=</span> <span class="nx">decoder</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">ui8</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">text2</span> <span class="o">=</span> <span class="nx">decoder</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">text1</span><span class="p">);</span> <span class="c1">// abc</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">text2</span><span class="p">);</span> <span class="c1">// abc</span>
</code></pre></div></div>
<p>这样,除了上面的 <code class="language-plaintext highlighter-rouge">Blob</code>,这里的 <code class="language-plaintext highlighter-rouge">TextEncoder</code> 也可以用于将文本数据保存为 JavaScript 中的二进制缓冲数据;</p>
<h1 id="处理文件数据">处理文件数据</h1>
<p>人外有人,天外有天,跨过了这二进制,便是更广阔的天地;说了一系列的关于二进制数据的保存和读写方法,也该谈谈其用武之地了;</p>
<p>要知道 JavaScript 中保存文本字符串什么的用变量就行了,缓冲区、类型数组、Blob 这些接口其实多数是用于处理文件数据相关的,因为它们有着不同的 MIME 类型,比如 <code class="language-plaintext highlighter-rouge">.jpg .mp4 .bin</code> 这些后缀的文件,JavaScript 并没有内置一些直接处理这些数据类型的接口(例如 .txt 文档就能可以处理),所以就需要以原生二进制数据的方式来保存或处理,方便用户上传、下载或预览;下面就将介绍一些文件处理相关的接口;</p>
<h2 id="file">File</h2>
<p>前面讲到,<code class="language-plaintext highlighter-rouge">File</code> 是基于 <code class="language-plaintext highlighter-rouge">Blob</code> 的,所以也就继承了它的一些方法;<code class="language-plaintext highlighter-rouge">File</code> 用于提供有关文件的信息和内容,语法如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nx">content</span><span class="p">,</span> <span class="nx">name</span><span class="p">[,</span> <span class="nx">options</span><span class="p">]);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">content</code> 指要创建的文件内容,是 <code class="language-plaintext highlighter-rouge">ArrayBuffer, View, Blob, DOMString</code> 等类型构成的 <strong>数组</strong> 或者类数组;<code class="language-plaintext highlighter-rouge">name</code> 则是文件的名称或者路径;<code class="language-plaintext highlighter-rouge">options</code> 参数可选,包含 <code class="language-plaintext highlighter-rouge">type</code> 和 <code class="language-plaintext highlighter-rouge">lastModified</code> 两个属性;</p>
<p>举例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">content</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextEncoder</span><span class="p">().</span><span class="nx">encode</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world!</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nx">content</span><span class="p">,</span> <span class="dl">'</span><span class="s1">test.txt</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// 可选,默认为空</span>
<span class="na">lastModified</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="c1">// 可选,后面是默认值</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> <span class="c1">// test.txt</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span> <span class="c1">// 12</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">type</span><span class="p">);</span> <span class="c1">// text/plain</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">lastModified</span><span class="p">);</span> <span class="c1">// 1583638485180</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">File</code> 构造函数自身并没有自带一些方法,而是继承了 <code class="language-plaintext highlighter-rouge">Blob</code> 的方法,例如:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">([</span><span class="dl">'</span><span class="s1">hello world!</span><span class="dl">'</span><span class="p">],</span> <span class="dl">'</span><span class="s1">text.txt</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// 初始化内容可以直接是字符串,只是需要放在数组中</span>
<span class="nx">file</span><span class="p">.</span><span class="nx">text</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// hello world!</span>
<span class="p">});</span>
<span class="nx">file</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">text</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextDecoder</span><span class="p">().</span><span class="nx">decode</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span> <span class="c1">// hello world!</span>
<span class="p">});</span>
</code></pre></div></div>
<p>其实,一般很少像这样用 <code class="language-plaintext highlighter-rouge">File</code> 接口来直接创建一个文件对象,多数是用在用户上传文件等情况,比如在网页中用 <code class="language-plaintext highlighter-rouge"><input type="file" /></code> 标签来上传文档,而用户点击上传后,与文件相关的信息就被包含在了这个 <code class="language-plaintext highlighter-rouge">input</code> 标签的节点引用的 <code class="language-plaintext highlighter-rouge">files</code> 属性中,这个 <code class="language-plaintext highlighter-rouge">files</code> 属性值是一个 <code class="language-plaintext highlighter-rouge">FileList</code> 接口的实例,就是包含所有上传文件的数组,其中每个元素都是一个 <code class="language-plaintext highlighter-rouge">File</code> 接口的实例;</p>
<p>通过一个简单的 demo 进行说明:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE HTML></span>
<span class="nt"><html></span>
<span class="nt"><head></head></span>
<span class="nt"><body></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"file"</span> <span class="na">class=</span><span class="s">"upload"</span> <span class="nt">/></span>
<span class="c"><!--
如果要上传多个文件,则使用:
<input type="file" class="upload" multiple />
--></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Upload"</span> <span class="na">onclick=</span><span class="s">"doUpload()"</span> <span class="nt">/></span>
<span class="nt"><script></span>
<span class="kd">var</span> <span class="nx">upload</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">.upload</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// 用户点击 Upload 按钮后执行</span>
<span class="kd">function</span> <span class="nx">doUpload</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="nx">upload</span><span class="p">.</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span>
<span class="c1">// File {name: "test.txt", lastModified: 1583634142542, lastModifiedDate: </span>
<span class="c1">// Sun Mar 08 2020 10:22:22 GMT+0800 (中国标准时间), webkitRelativePath: "", size: 12, …}</span>
<span class="p">}</span>
<span class="c1">// 也可以用这种方法获取文件对象,</span>
<span class="c1">// 这个函数中的代码会在用户完成上传操作就执行,即使没点上传按钮</span>
<span class="nx">upload</span><span class="p">.</span><span class="nx">onchange</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="c1">// 执行的操作...</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<h2 id="filereader">FileReader</h2>
<p><code class="language-plaintext highlighter-rouge">FileReader</code> 是另一个用于读取文件数据的接口,其实例化后的一些方法与 <code class="language-plaintext highlighter-rouge">Blob</code> 中的 <code class="language-plaintext highlighter-rouge">.text()</code> 和 <code class="language-plaintext highlighter-rouge">.arrayBuffer</code> 方法类似,只不过返回的不再是一个 promise 对象,而是一个基于 <strong>事件</strong> 的接口;<code class="language-plaintext highlighter-rouge">FileReader</code> 一般也用于读取用户上传文件的数据;</p>
<p>语法:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span> <span class="c1">// 实例化无须提供任何参数</span>
</code></pre></div></div>
<h3 id="事件处理">事件处理</h3>
<p>既然是以基于事件,那么就需要一系列处理不同事件的方法,列出如下:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">.onabort()</code>:该事件在读取操作被中断时触发。</li>
<li><code class="language-plaintext highlighter-rouge">.onerror()</code>:该事件在读取操作发生错误时触发。</li>
<li><code class="language-plaintext highlighter-rouge">.onload()</code>:该事件在读取操作完成时触发。</li>
<li><code class="language-plaintext highlighter-rouge">.onloadstart()</code>:该事件在读取操作开始时触发。</li>
<li><code class="language-plaintext highlighter-rouge">.onloadend()</code>:该事件在读取操作结束时(要么成功,要么失败)触发。</li>
<li><code class="language-plaintext highlighter-rouge">.onprogress()</code>:该事件在读取Blob时触发。</li>
</ul>
<p>以上事件也可以使用 <code class="language-plaintext highlighter-rouge">addEventListener()</code> 方法的相应格式来设置回调函数;</p>
<h3 id="加载状态">加载状态</h3>
<p>因为是基于事件的接口,所以 <code class="language-plaintext highlighter-rouge">FileReader</code> 提供了 <code class="language-plaintext highlighter-rouge">readyState</code> 这个属性,以不同值代表不同的数据加载状态:</p>
<ul>
<li><strong>0</strong>:数据尚未加载;</li>
<li><strong>1</strong>:数据正在加载中;</li>
<li><strong>2</strong>:数据加载完成;</li>
</ul>
<p>数据加载完成后,可以使用 <code class="language-plaintext highlighter-rouge">result</code> 这个属性来获取文件内容;</p>
<h3 id="数据加载">数据加载</h3>
<h4 id="readastext">readAsText()</h4>
<p><code class="language-plaintext highlighter-rouge">.readAsText(file[, encoding])</code> 以文本字符串的形式读取 <code class="language-plaintext highlighter-rouge">file</code> (文件对象或者 Blob)中的数据,以 <code class="language-plaintext highlighter-rouge">encoding</code> 格式进行编码(默认 utf-8);</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">([</span><span class="dl">'</span><span class="s1">abc</span><span class="dl">'</span><span class="p">],</span> <span class="dl">'</span><span class="s1">test.txt</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">onloadstart</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">loadstart state:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">readyState</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">load state:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">readyState</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">result:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">onloadend</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">loadend state:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">readyState</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">readAsText</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span>
<span class="c1">// loadstart state: 1</span>
<span class="c1">// load state: 2</span>
<span class="c1">// result: abc</span>
<span class="c1">// loadend state: 2</span>
</code></pre></div></div>
<h4 id="readasarraybuffer">readAsArrayBuffer()</h4>
<p><code class="language-plaintext highlighter-rouge">readAsArrayBuffer(file)</code> 以 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 的形式读取 <code class="language-plaintext highlighter-rouge">file</code> 中(文件或 Blob)的数据;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">b</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span>
<span class="c1">// 使用监听器触发效果相同</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">load</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">result</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">result</span><span class="p">));</span>
<span class="p">})</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">readAsArrayBuffer</span><span class="p">(</span><span class="nx">blob</span><span class="p">);</span>
<span class="c1">// ArrayBuffer(3) {}</span>
<span class="c1">// Uint8Array(3) [97, 98, 99]</span>
</code></pre></div></div>
<h4 id="readasdataurl">readAsDataURL()</h4>
<p><code class="language-plaintext highlighter-rouge">readAsDataURL(file)</code> 同样是读取 <code class="language-plaintext highlighter-rouge">file</code> 中的数据,只是将文件中的内容以 <strong>base64</strong> 编码后,放进一个 <code class="language-plaintext highlighter-rouge">DataURL</code> 中(内容可以通过 URL 链接直接访问);</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">([</span><span class="dl">'</span><span class="s1">abc</span><span class="dl">'</span><span class="p">],</span> <span class="dl">'</span><span class="s1">test.txt</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span>
<span class="p">})</span>
<span class="kd">var</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">readAsDataURL</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span>
<span class="c1">// data:text/plain;base64,YWJj</span>
</code></pre></div></div>
<p>如果将最后的输出内容粘贴复制进浏览器的地址栏,回车后就能直接看见文本内容;</p>
<h2 id="呈现数据">呈现数据</h2>
<h3 id="dataurl">DataURL</h3>
<p><strong>Data URL</strong> 指的是一种 <code class="language-plaintext highlighter-rouge">URL</code> 协议,语法格式为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:[<mediatype>][;base64],<data>
</code></pre></div></div>
<p>可以类比常见的 <code class="language-plaintext highlighter-rouge">http:</code> 协议,例如上例中的返回值:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:text/plain<span class="p">;</span><span class="nb">base64</span>,YWJj
</code></pre></div></div>
<p>具体用法如之前所述,输入到浏览器地址栏回车后会直接呈现出原内容,比如上例就是一串文本(文件类型被指定为 <code class="language-plaintext highlighter-rouge">text/plain</code>),如果类型 <code class="language-plaintext highlighter-rouge">image/png</code> 等图片格式的,则会直接显示该图片;</p>
<p>Data URL 除了可以通过浏览器地址栏访问,也可以在 HTML 文档中展示,例如使 <code class="language-plaintext highlighter-rouge"><img></code> 的 <code class="language-plaintext highlighter-rouge">src</code> 属性值等于这个 Data URL,这个标签就会展示为相应的图片,同样地,数据指定给 <code class="language-plaintext highlighter-rouge"><iframe></code> 的 <code class="language-plaintext highlighter-rouge">src</code> 属性,也可以展示图片或者文本数据,指定给 <code class="language-plaintext highlighter-rouge"><video></code> 标签的 <code class="language-plaintext highlighter-rouge">src</code> 则可以展示视频;</p>
<h3 id="objecturl">ObjectURL</h3>
<p><strong>ObjectURL</strong> 使用 <code class="language-plaintext highlighter-rouge">URL.createObjectURL()</code> 方法创建,返回结果也是一种类型的 URL,类似于上面的 Data URL,区别在于 ObjectURL 的生命周期与当前网站页面相关,例如 <strong>刷新页面</strong> 页面后不无法继续访问了;</p>
<p>例如在本地网页控制台中运行下面的代码:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">blob</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">abc</span><span class="dl">'</span><span class="p">]);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span><span class="nx">blob</span><span class="p">));</span>
</code></pre></div></div>
<p>则会输出类似下面的内容:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>blob:http://127.0.0.1:8080/4064e759-231f-466e-a6ef-778505e56d2b
</code></pre></div></div>
<p>链接临时有效,会展示数据内容刷新页面失效,不过格式基本一致;同样,ObjectURL 也可以用于设置为 <code class="language-plaintext highlighter-rouge"><img></code> 或 <code class="language-plaintext highlighter-rouge"><iframe></code> 的 <code class="language-plaintext highlighter-rouge">src</code> 属性,进行单独展示;</p>
<p>需要 <strong>注意</strong>,<code class="language-plaintext highlighter-rouge">createObjectURL()</code> 方法每次调用都会返回一个新的 ObjectURL 对象,即使数据源相同,所以如果调用量较多,可能就会内存剧增,这时需要手动回收,使用的是 <code class="language-plaintext highlighter-rouge">revokeObjectURL()</code> 这个方法,示例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span><span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="dl">'</span><span class="s1">test</span><span class="dl">'</span><span class="p">]));</span>
<span class="nx">URL</span><span class="p">.</span><span class="nx">revokeObjectURL</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span> <span class="c1">// 完成回收</span>
</code></pre></div></div>
<h3 id="文件下载">文件下载</h3>
<p>除了使用 <code class="language-plaintext highlighter-rouge"><img</code>, <code class="language-plaintext highlighter-rouge"><iframe></code> 等标签对数据进行展示,也可以将文件提供给用户下载,使用的是 <code class="language-plaintext highlighter-rouge"><a></code> 标签,把 DataURL 或者 ObjectURL 指定给它的 <code class="language-plaintext highlighter-rouge">href</code> 属性即可,另外还要指定 <code class="language-plaintext highlighter-rouge">download</code> 属性值,不然有可能会是跳转到相关页面而不是下载;</p>
<p>一个下载组件的示例:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"data:text/plain;base64,YWJj"</span> <span class="na">download=</span><span class="s">"test.txt"</span> <span class="na">type=</span><span class="s">"text/plain"</span><span class="nt">></span>Download<span class="nt"></a></span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">download</code> 属性指代下载到用户本地的文件名称,不加后缀则系统自动识别类型,同样 <code class="language-plaintext highlighter-rouge">type</code> 属性也是可选的,可用于固定下载文件类型;</p>
<h2 id="上传数据">上传数据</h2>
<p>让用户通过网页上传文件,最重要的当然就是最后的上传阶段了,即把用户选择的文件上传到服务器;下面的例子使用 <code class="language-plaintext highlighter-rouge">XMLHttpRequest()</code> 接口来实现数据的上传;</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">([</span><span class="dl">'</span><span class="s1">hello world!</span><span class="dl">'</span><span class="p">],</span> <span class="dl">'</span><span class="s1">hello.txt</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span>
<span class="p">});</span> <span class="c1">// 此处用于模拟用户上传的文件,即有具体的文件名、类型和内容</span>
<span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span>
<span class="c1">// 查看上传进度</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">upload</span><span class="p">.</span><span class="nx">onprogress</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">lengthComputable</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">进度:</span><span class="dl">'</span><span class="err">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">loaded</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">event</span><span class="p">.</span><span class="nx">total</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// 上传完成的回调</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">upload</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">upload success.</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 上传地址,参数换成实际地址</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">http://localhost/upload/upload.php</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// 服务器没有指定文件类型则自行指定</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">overrideMimeType</span><span class="p">(</span><span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// 数据读取完毕就开始上传</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">readAsText</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span>
</code></pre></div></div>
<p>另外也可以使用 form 表格来上传文件,更加直接:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">action=</span><span class="s">"upload/upload.php"</span> <span class="na">method=</span><span class="s">"post"</span> <span class="na">enctype=</span><span class="s">"multipart/form-data"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"file"</span> <span class="na">name=</span><span class="s">"upload"</span> <span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Upload"</span><span class="nt">></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>需要 <strong>注意</strong> 的是,上传文件时 <strong>必须</strong> 加上 <code class="language-plaintext highlighter-rouge">enctype="multipart/form-data"</code>,不然上传上去的只是一个文件名;</p>
<h2 id="接收数据">接收数据</h2>
<p>投我以木瓜,报之以琼琚;有时也会接收来着服务端的数据,通常就是使用 <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code> 来异步获取文本或 JSON 数据,但是它也能用于获取其他类型的数据,只不过需要手动设置 <code class="language-plaintext highlighter-rouge">responseType</code> 这个属性进行申明,该属性支持以下几个值:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">""</code>:默认值,与 text 类型相同;</li>
<li><code class="language-plaintext highlighter-rouge">"text"</code>:以文本类型响应;</li>
<li><code class="language-plaintext highlighter-rouge">"arraybuffer"</code>:以 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 二进制数据响应;</li>
<li><code class="language-plaintext highlighter-rouge">"blob"</code>:以 <code class="language-plaintext highlighter-rouge">Blob</code> 类型数据响应;</li>
<li><code class="language-plaintext highlighter-rouge">"json"</code>:响应解析为 JSON 对象;</li>
<li><code class="language-plaintext highlighter-rouge">"document"</code>:解析为 HTML 或 XML 内容;</li>
</ul>
<p>一个接收数据的实例:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">responseType</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">arraybuffer</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">xhr</span><span class="p">.</span><span class="nx">response</span><span class="p">;</span>
<span class="c1">// 可以转换为类型化数组进行数据修改</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">));</span>
<span class="p">}</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">test.png</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
</code></pre></div></div>
<hr />
<p>至此,历经几番交战,刀光剑影,战况激烈空前,难分难解,不下几十回合,能阅读至此处的诸位也都是真正的勇士,敢于面对惨淡的生活,正视淋漓的鲜血……又扯远了,俗话说,物以稀为贵,人以和为贵,JavaScript 剑客与二进制隐士此番交战,不求胜负,若这过程中的原理能被大家理解参透得透彻,也算是名留青史了;</p>
<p>恩怨自了结,情仇终消散,天下没有不散的宴席,暂且就此别过,江湖再见!</p>