components

This commit is contained in:
Ilya Kantor 2019-04-02 14:01:44 +03:00
parent 304d578b54
commit 6fb4aabcba
344 changed files with 669 additions and 406 deletions

View file

@ -0,0 +1,18 @@
function concat(arrays) {
// sum of individual array lengths
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
if (!arrays.length) return null;
let result = new Uint8Array(totalLength);
// for each array - copy it over result
// next array is copied right after the previous one
let length = 0;
for(let array of arrays) {
result.set(array, length);
length += array.length;
}
return result;
}

View file

@ -0,0 +1,13 @@
function concat(arrays) {
// ...your code...
}
let chunks = [
new Uint8Array([0, 1, 2]),
new Uint8Array([3, 4, 5]),
new Uint8Array([6, 7, 8])
];
console.log(Array.from(concat(chunks))); // 0, 1, 2, 3, 4, 5, 6, 7, 8
console.log(concat(chunks).constructor.name); // Uint8Array

View file

@ -0,0 +1,31 @@
describe("concat", function() {
let chunks = [
new Uint8Array([0, 1, 2]),
new Uint8Array([3, 4, 5]),
new Uint8Array([6, 7, 8])
];
it("result has the same array type", function() {
let result = concat(chunks);
assert.equal(result.constructor, Uint8Array);
});
it("concatenates arrays", function() {
let result = concat(chunks);
assert.deepEqual(result, new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]));
});
it("returns empty array on empty input", function() {
let result = concat([]);
assert.equal(result.length, 0);
});
});

View file

@ -0,0 +1,4 @@
# Concatenate typed arrays
Given an array of `Uint8Array`, write a function `concat(arrays)` that returns a concatenation of them into a single array.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,269 @@
# ArrayBuffer, binary arrays
In web-development we meet binary data mostly while dealing with files (create, upload, download). Another typical use case is image processing.
That's all possible in Javascript, and binary operations are high-performant.
Although, there's a bit of confusion, because there are many classes. To name a few:
- `ArrayBuffer`, `Uint8Array`, `DataView`, `Blob`, `File`, etc.
Binary data in Javascript is implemented in a non-standard way, compared to other languages. But when we sort things out, everything becomes fairly simple.
**The basic binary object is `ArrayBuffer` -- a reference to a fixed-length contiguos memory area.**
We create it like this:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
alert(buffer.byteLength); // 16
```
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
```warn header="`ArrayBuffer` is not an array of something"
Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in common with `Array`:
- It has a fixed length, we can't increase or decrease it.
- It takes exactly that much space in the memory.
- To access individual bytes, another "view" object is needed, not `buffer[index]`.
```
`ArrayBuffer` is a memory area. What's stored in it? It has no clue. Just a raw sequence of bytes.
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`.
For instance:
- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer".
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
So, the binary data in an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers (2 bytes each), or 4 even bigger (4 bytes each), or 2 floating-point values with high precision (8 bytes each).
![](arraybuffer-views.png)
`ArrayBuffer` is the core object, the root of everything, the raw binary data.
But if we're going to write into it, or iterate over it, basically for almost any operation we must use a view, e.g:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
*!*
let view = new Uint32Array(buffer); // treat buffer as a sequence of 32-bit integers
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 bytes per integer
*/!*
alert(view.length); // 4, it stores that many integers
alert(view.byteLength); // 16, the size in bytes
// let's write a value
view[0] = 123456;
// iterate over values
for(let num of view) {
alert(num); // 123456, then 0, 0, 0 (4 values total)
}
```
## TypedArray
The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
They are much more like regular arrays: have indexes and iterable.
A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types.
There are 5 variants of arguments:
```js
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
```
1. If an `ArrayBuffer` argument is supplied, the view is created over it. We used that syntax already.
Optionally we can provide `byteOffset` to start from (0 by default) and the `length` (till the end of the buffer by default), then the view will cover only a part of the `buffer`.
2. If an `Array`, or any array-like object is given, it creates a typed array of the same length and copies the content.
We can use it to pre-fill the array with the data:
```js run
*!*
let arr = new Uint8Array([0, 1, 2, 3]);
*/!*
alert( arr.length ); // 4
alert( arr[1] ); // 1
```
3. If another `TypedArray` is supplied, it does the same: creates a typed array of the same length and copies values. Values are converted to the new type in the process.
```js run
let arr16 = new Uint16Array([1, 1000]);
*!*
let arr8 = new Uint8Array(arr16);
*/!*
alert( arr8[0] ); // 1
alert( arr8[1] ); // 232 (tried to copy 1000, but can't fit 1000 into 8 bits)
```
4. For a numeric argument `length` -- creates the typed array to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
```js run
let arr = new Uint16Array(4); // create typed array for 4 integers
alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per integer
alert( arr.byteLength ); // 8 (size in bytes)
```
5. Without arguments, creates an zero-length typed array.
We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided).
To access the `ArrayBuffer`, there are properties:
- `arr.buffer` -- references the `ArrayBuffer`.
- `arr.byteLength` -- the length of the `ArrayBuffer`.
So, we can always move from one view to another:
```js
let arr8 = new Uint8Array([0, 1, 2, 3]);
// another view on the same data
let arr16 = new Uint16Array(arr8.buffer);
```
Here's the list of typed arrays:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
```warn header="No `int8` or similar single-valued types"
Please note, despite of the names like `Int8Array`, there's no single-value type like `int`, or `int8` in Javascript.
That's logical, as `Int8Array` is not an array of these individual values, but rather a view on `ArrayBuffer`.
```
### Out-of-bounds behavior
What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But extra bits are cut-off.
For instance, let's try to put 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 bits per value, that makes the available range from 0 to 255.
For bigger numbers, only the rightmost (less significant) 8 bits are stored, and the rest is cut off:
![](8bit-integer-256.png)
So we'll get zero.
For 257, the binary form is `100000001` (9 bits), the rightmost 8 get stored, so we'll have `1` in the array:
![](8bit-integer-257.png)
In other words, the number modulo 2<sup>8</sup> is saved.
Here's the demo:
```js run
let uint8array = new Uint8Array(16);
let num = 256;
alert(num.toString(2)); // 100000000 (binary representation)
uint8array[0] = 256;
uint8array[1] = 257;
alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
```
`Uint8ClampedArray` is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is useful for image processing.
## TypedArray methods
`TypedArray` has regular `Array` methods, with notable exceptions.
We can iterate, `map`, `slice`, `find`, `reduce` etc.
There are few things we can't do though:
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are fixed, contiguous areas of memory. All we can do is to assign a zero.
- No `concat` method.
There are two additional methods:
- `arr.set(fromArr, [offset])` copies all elements from `fromArr` to the `arr`, starting at position `offset` (0 by default).
- `arr.subarray([begin, end])` creates a new view of the same type from `begin` to `end` (exclusive). That's similar to `slice` method (that's also supported), but doesn't copy anything -- just creates a new view, to operate on the given piece of data.
These methods allow us to copy typed arrays, mix them, create new arrays from existing ones, and so on.
## DataView
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format.
- For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`.
- With `DataView` we access the data with methods like `.getUint8(i)` or `.gteUint16(i)`. We choose the format at method call time instead of the construction time.
The syntax:
```js
new DataView(buffer, [byteOffset], [byteLength])
```
- **`buffer`** -- the underlying `ArrayBuffer`. Unlike typed arrays, `DataView` doesn't create a buffer on its own. We need to have it ready.
- **`byteOffset`** -- the starting byte position of the view (by default 0).
- **`byteLength`** -- the byte length of the view (by default till the end of `buffer`).
For instance, here we extract numbers in different formats from the same buffer:
```js run
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// get 8-bit number at offset 0
alert( dataView.getUint8(0) ); // 255
// now get 16-bit number at offset 0, that's 2 bytes, both with max value
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
// get 32-bit number at offset 0
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
dataView.setUint32(0, 0); // set 4-byte number to zero
```
`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily.
## Summary
`ArrayBuffer` is the core object, a reference to the fixed-length contiguous memory area.
To do almost any operation on `ArrayBuffer`, we need a view.
- It can be a `TypedArray`:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment.
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
- Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`.
In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
There are also two additional terms:
- `ArrayBufferView` is an umbrella term for all these kinds of views.
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common teerms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
Here's a cheatsheet:
![](arraybuffer-view-buffersource.png)

View file

@ -0,0 +1,76 @@
# TextDecoder and TextEncoder
What if the binary data is actually a string? For instance, we received a file with textual data.
The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an an actual Javascript string, given the buffer and the encoding.
We first need to create it:
```js
let decoder = new TextDecoder([label], [options]);
```
- **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported.
- **`options`** -- optional object:
- **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`.
- **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), rarely needed.
...And then decode:
```js
let str = decoder.decode([input], [options]);
```
- **`input`** -- `BufferSource` to decode.
- **`options`** -- optional object:
- **`stream`** -- true for decoding streams, when `decoder` is called repeatedly with incoming chunks of data. In that case a multi-byte character may occasionally split between chunks. This options tells `TextDecoder` to memorize "unfinished" characters and decode them when the next chunk comes.
For instance:
```js run
let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);
alert( new TextDecoder().decode(uint8Array) ); // Hello
```
```js run
let uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);
alert( new TextDecoder().decode(uint8Array) ); // 你好
```
We can decode a part of the buffer by creating a subarray view for it:
```js run
let uint8Array = new Uint8Array([0, 72, 101, 108, 108, 111, 0]);
// the string is in the middle
// create a new view over it, without copying anything
let binaryString = uint8Array.subarray(1, -1);
alert( new TextDecoder().decode(binaryString) ); // Hello
```
## TextEncoder
[TextEncoder](https://encoding.spec.whatwg.org/#interface-textencoder) does the reverse thing -- converts a string into bytes.
The syntax is:
```js run
let encoder = new TextEncoder();
```
The only encoding it supports is "utf-8".
It has two methods:
- **`encode(str)`** -- returns `Uint8Array` from a string.
- **`encodeInto(str, destination)`** -- encodes `str` into `destination` that must be `Uint8Array`.
```js run
let encoder = new TextEncoder();
let uint8Array = encoder.encode("Hello");
alert(uint8Array); // 72,101,108,108,111
```

239
4-binary/03-blob/article.md Normal file
View file

@ -0,0 +1,239 @@
# Blob
`ArrayBuffer` and views are a part of ECMA standard, a part of Javascript.
In the browser, there are additional higher-level objects, described in [File API](https://www.w3.org/TR/FileAPI/).
`Blob` consists of an optional string `type` (a MIME-type usually), plus `blobParts` -- a sequence of other `Blob` objects, strings and `BufferSources`.
![](blob.png)
Thanks to `type`, we can download/upload blobs, and it naturally becomes `Content-Type` in network requests.
The constructor syntax is:
```js
new Blob(blobParts, options);
```
- **`blobParts`** is an array of `Blob`/`BufferSource`/`String` values.
- **`options`** optional object:
- **`type`** -- blob type, usually MIME-type, e.g. `image/png`,
- **`endings`** -- whether to transform end-of-line to make the blob correspond to current OS newlines (`\r\n` or `\n`). By default `"transparent"` (do nothing), but also can be `"native"` (transform).
For example:
```js
// create Blob from a string
let blob = new Blob(["<html></html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
```
```js
// create Blob from a typed array and strings
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "hello" in binary form
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
```
We can extract blob slices with:
```js
blob.slice([byteStart], [byteEnd], [contentType]);
```
- **`byteStart`** -- the starting byte, by default 0.
- **`byteEnd`** -- the last byte (exclusive, by default till the end).
- **`contentType`** -- the `type` of the new blob, by default the same as the source.
The arguments are similar to `array.slice`, negative numbers are allowed too.
```smart header="Blobs are immutable"
We can't change data directly in a blob, but we can slice parts of blobs, create new blobs from them, mix them into a new blob and so on.
This behavior is similar to Javascript strings: we can't change a character in a string, but we can make a new corrected string.
```
## Blob as URL
A Blob can be easily used as an URL for `<a>`, `<img>` or other tags, to show its contents.
Let's start with a simple example. By clicking on a link you download a dynamically-generated blob with `hello world` contents as a file:
```html run
<!-- download attribute forces the browser to download instead of navigating -->
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
```
We can also create a link dynamically in Javascript and simulate a click by `link.click()`, then download starts authomatically.
Here's the similar "on the fly" blob creation and download code, but without HTML:
```js run
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
```
**`URL.createObjectURL` takes a blob and creates an unique URL for it, in the form `blob:<origin>/<uuid>`.**
That's what the generated url looks like:
```
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
```
The browser for each url generated by `URL.createObjectURL` stores an the url -> blob mapping internally. So such urls are short, but allow to access the blob.
A generated url is only valid while the current document is open. And it allows to reference the blob in `<img>`, `<a>`, any other object that expects an url.
There's a side-effect though. While there's an mapping for a blob, the blob itself resides in the memory. The browser can't free it.
The mapping is automatically cleared on document unload, so blobs are freed then. But if an app is long-living, then that doesn't happen soon. So if we create an URL, that blob will hang in memory, even if not needed any more.
**`URL.revokeObjectURL(url)` removes the reference from the internal mapping, thus allowing the blob to be deleted (if there are no other references), and the memory to be freed.**
In the last example, we intend the blob to be used only once, for instant downloading, so we call `URL.revokeObjectURL(link.href)` immediately.
In the previous example though, with the clickable HTML-link, we don't call `URL.revokeObjectURL(link.href)`, because that would make the blob url invalid. After the revocation, as the mapping is removed, the url doesn't work any more.
## Blob to base64
An alternative to `URL.createObjectURL` is to convert a blob into a base64-encoded string.
That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls".
A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[<mediatype>][;base64],<data>`. We can use such urls everywhere, on par with "regular" urls.
For instance, here's a smiley:
```html
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
```
The browser will decode the string and show the image: <img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
To transform a blob into base64, we'll use the built-in `FileReader` object. It can read data from Blobs in multiple formats. In the [next chapter](info:file) we'll cover it more in-depth.
Here's the demo of downloading a blob, now via base-64:
```js run
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
*!*
let reader = new FileReader();
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload
*/!*
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
```
Both ways of making an URL of a blob are usable. But usually `URL.createObjectURL(blob)` is simpler and faster.
```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url"
+ We need to revoke them if care about memory.
+ Direct access to blob, no "encoding/decoding"
- No need to revoke anything.
- Performance and memory losses on big blobs for encoding.
```
## Image to blob
We can create a blob of an image, an image part, or even make a page screenshot. That's handy to upload it somewhere.
Image operations are done via `<canvas>` element:
1. Draw an image (or its part) on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
2. Call canvas method [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) that creates a blob and runs `callback` with it when done.
In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:
```js run
// take any image
let img = document.querySelector('img');
// make <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
// copy image to it (this method allows to cut image)
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas
// toBlob is async opereation, callback is called when done
canvas.toBlob(function(blob) {
// blob ready, download it
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// delete the internal blob reference, to let the browser clear memory from it
URL.revokeObjectURL(link.href);
}, 'image/png');
```
If we prefer `async/await` instead of callbacks:
```js
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
```
For screenshotting a page, we can use a library such as <https://github.com/niklasvh/html2canvas>. What it does is just walks the page and draws it on `<canvas>`. Then we can get a blob of it the same way as above.
## From Blob to ArrayBuffer
The `Blob` constructor allows to create a blob from almost anything, including any `BufferSource`.
But if we need to perform low-level processing, we can get the lowest-level `ArrayBuffer` from it using `FileReader`:
```js
// get arrayBuffer from blob
let fileReader = new FileReader();
*!*
fileReader.readAsArrayBuffer(blob);
*/!*
fileReader.onload = function(event) {
let arrayBuffer = fileReader.result;
};
```
## Summary
While `ArrayBuffer`, `Uint8Array` and other `BufferSource` are "binary data", a [Blob](https://www.w3.org/TR/FileAPI/#dfn-Blob) represents "binary data with type".
That makes Blobs convenient for upload/download operations, that are so common in the browser.
Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch-basics) and so on, can work with `Blob` natively, as well as with other binary types.
We can easily convert betweeen `Blob` and low-level binary data types:
- We can make a Blob from a typed array using `new Blob(...)` constructor.
- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing.

BIN
4-binary/03-blob/blob.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

128
4-binary/04-file/article.md Normal file
View file

@ -0,0 +1,128 @@
# File and FileReader
A [File](https://www.w3.org/TR/FileAPI/#dfn-file) object inhereits from `Blob`, but is extended with filesystem-related capabilities.
There are two ways to obtain it.
First, there's a constructor, similar to `Blob`:
```js
new File(fileParts, fileName, [options])
```
- **`fileParts`** -- is an array of Blob/BufferSource/String value, same as `Blob`.
- **`fileName`** -- file name string.
- **`options`** -- optional object:
- **`lastModified`** -- a timestamp (integer date) of last modification.
Second, more often we get a file from `<input type="file">` or drag'n'drop or other browser interfaces. Then the file gets these from OS.
For instance:
```html run
<input type="file" onchange="showFile(this)">
<script>
function showFile(input) {
let file = input.files[0];
alert(`File name: ${file.name}`); // e.g my.png
alert(`Last modified: ${file.lastModified}`); // e.g 1552830408824
}
</script>
```
```smart
The input may select multiple files, so `input.files` is an array-like object with them. Here we have only one file, so we just take `input.files[0]`.
```
## FileReader
[FileReader](https://www.w3.org/TR/FileAPI/#dfn-filereader) is an object with the sole purpose of reading from `Blob` (and hence `File` too) objects.
It's event based, as reading from disk may take time.
The constructor:
```js
let reader = new FileReader(); // no arguments
```
The main methods:
- **`readAsArrayBuffer(blob)`** -- read the data as `ArrayBuffer`
- **`readAsText(blob, [encoding])`** -- read the data as a string (encoding is `utf-8` by default)
- **`readAsDataURL(blob)`** -- encode the data as base64 data url.
- **`abort()`** -- cancel the operation.
As the reading proceeds, there are events:
- `loadstart` -- loading started.
- `progress` -- occurs during reading.
- `load` -- no errors, reading complete.
- `abort` -- `abort()` called.
- `error` -- error has occured.
- `loadend` -- reading finished with either success or failure.
When the reading is finished, we can access the result as:
- `reader.result` is the result (if successful)
- `reader.error` is the error (if failed).
The most widely used events are for sure `load` and `error`.
Here's an example of reading a file:
```html run
<input type="file" onchange="readFile(this)">
<script>
function readFile(input) {
let file = input.files[0];
let reader = new FileReader();
reader.readAsText(file);
reader.onload = function() {
console.log(reader.result);
};
reader.onerror = function() {
console.log(reader.error);
};
}
</script>
```
```smart header="`FileReader` for blobs"
As mentioned in the chapter <info:blob>, `FileReader` works for any blobs, not just files.
So we can use it to convert a blob to another format:
- `readAsArrayBuffer(blob)` -- to `ArrayBuffer`,
- `readAsText(blob, [encoding])` -- to string (an alternative to `TextDecoder`),
- `readAsDataURL(blob)` -- to base64 data url.
```
```smart header="`FileReaderSync` is available for workers only"
For Web Workers, there also exists a synchronous variant of `FileReader`, called [FileReaderSync](https://www.w3.org/TR/FileAPI/#FileReaderSync).
Its reading methods `read*` do not generate events, but rather return a result, as regular functions do.
That's only inside a Web Worker though, because delays and hang-ups in Web Workers are less important, they do not affect the page.
```
## Summary
`File` object inherit from `Blob`.
In addition to `Blob` methods and properties, `File` objects also have `fileName` and `lastModified` properties, plus the internal ability to read from filesystem. We usually get `File` objects from user input, like `<input>` or drag'n'drop.
`FileReader` objects can read from a file or a blob, in one of three formats:
- String (`readAsText`).
- `ArrayBuffer` (`readAsArrayBuffer`).
- Data url, base-64 encoded (`readAsDataURL`).
In many cases though, we don't have to read the file contents. Just as we did with blobs, we can create a short url with `URL.createObjectURL(file)` and assign it to `<a>` or `<img>`. This way the file can be downloaded or shown up as an image, as a part of canvas etc.
And if we're going to send a `File` over a network, that's also easy, as network API like `XMLHttpRequest` or `fetch` natively accepts `File` objects.

3
4-binary/index.md Normal file
View file

@ -0,0 +1,3 @@
# Binary data, files
Working with binary data and files in Javascript.