The grudge between JavaScript and binary data

Article from personal blog https://knightyun.github.io/2020/03/09/js-binary-data , reprint please state

Programming the Jianghu, bloody all the time. When it comes to binary data, how many heroes are talking and laughing, and how many of them are rising, and how many of them are high and unpredictable

Not to mention binary data. You will think of data streams like 10110110001 or 00000000 11111111 00000101. In the Wulin, it is one of the three swordsmen When JavaScript is in the Jianghu (daily development), the most likely type of fighting (processing) is the intuitive number, string or object, etc.; then what will it do (describe and process binary data) when it meets the hermit (binary) who rarely appears? Whether it's magnificent, whether it's all over, or whether it's all about turning the tide around, and listen to the decomposition in this article.

ArrayBuffer

I didn't know the hero face, but I was in this mountain. First, I want to understand the first concept. ArrayBuffer represents a primitive binary data buffer with fixed length and read-only content. If I need to write, I need to use TypedArray or DataView to implement it;

In JavaScript, the closest contact with binary data may be ArrayBuffer. The purpose of the previous discussion is to describe and operate binary data, so it is necessary to store the data in a certain place, and then operate it. Here, ArrayBuffer Buffer can be regarded as such a place; of course, the most intuitive way is to save it to a string, such as "101011011", or to an array, such as [1,0,1,0,1,1,0, 1] This is really convenient for human beings, but the efficiency of machine execution is also reduced, because after all, characters and arrays are two other basic types, and they are not specially designed for this purpose; therefore, there is an ArrayBuffer specially designed for buffering data, and an interface for accessing and operating data is provided by combining views;

grammar

When instantiating the arraybuffer constructor, only one parameter is accepted, that is, the size of the arraybuffer to be created, the unit is byte, if not specified, it defaults to 0; at the same time, it also provides an instance property byteLength (read-only), which implements the access to the byte value of the current arraybuffer;

Give an example:

var buffer = new ArrayBuffer(3);

console.log(buffer.byteLength); // 3

In addition, since ArrayBuffer is only responsible for creating such a data area and does not provide an interface for initialization and assignment, all the n bytes of data are null, that is, all are set to 0;

Method

Because the ArrayBuffer constructor itself is used to create data buffers and the data is read-only, there are only a few properties and methods provided;

.slice()

It is used to return a new buffer. The syntax is. slice(start, end), that is, take the current buffer as the parent, start from the position with index as start and end (the end position is not included), then copy and return the data of this section. Its usage is similar to Array.prototype.slice(), for example:

var buffer1 = new ArrayBuffer(5);
var buffer2 = buffer1.slice(0, 3);
var buffer3 = buffer1.slice(2);
var buffer4 = buffer1.slice(1, -1);

console.log(buffer2.byteLength); // 3
console.log(buffer3.byteLength); // 3
console.log(buffer4.byteLength); // 3

ArrayBuffer.isView()

This method is used to determine whether the provided parameter value is an ArrayBuffer view, such as TypedArray and DataView. For example:

console.log(ArrayBuffer.isView());                   // false
console.log(ArrayBuffer.isView([1, 2, 3]));          // false
console.log(ArrayBuffer.isView(new ArrayBuffer(3))); // false

console.log(ArrayBuffer.isView(new Int8Array()));                 // true
console.log(ArrayBuffer.isView(new Uint32Array(3)));              // true
console.log(ArrayBuffer.isView(new DataView(new ArrayBuffer()))); // true

Typed array

Summary

To do a good job, you must first make use of the tool. As mentioned earlier, the data buffer created by ArrayBuffer needs to be implemented by view. Typed array is such a view describing binary data buffer. This view is a class array. In addition, there is no TypedArray() constructor, which refers to a kind of array. Therefore, it has multiple implementations, i.e. multiple typed array constructor functions. It can be understood that fruit is a kind of food for apples and bananas. It is known that there is no specific food named fruit, but apples and bananas are specific;

Valid types are as follows:

Int8Array(); // 8-bit binary signed integer
Uint8Array(); // 8-bit unsigned integer (loop from another boundary when out of range)
Uint8ClampedArray(); // 8-bit unsigned integer (boundary value after out of range)
Int16Array(); // 16 bit binary signed integer
Uint16Array(); // 16 bit unsigned integer signed
Int32Array(); // 32-bit binary signed integer
Uint32Array(); // 32-bit unsigned integer
Float32Array(); // 32-bit IEEE floating point number (7 significant digits, such as 1.1234567)
Float64Array(); // 64 bit IEEE floating point number (16 significant numbers, such as 1.123... 15)
BigInt64Array(); // 64 bit binary signed integer
BigUint64Array(); // 64 bit unsigned integer

Syntax:

Ten thousand methods are inseparable from their families. Each method has its own trace to follow. Later, we will take Int8Array() as an example to illustrate. The following code shows the types of parameters that can be passed in:

new Int8Array(); // New in ES2017
new Int8Array(length); 
new Int8Array(typedArray); 
new Int8Array(object); 
new Int8Array(buffer [, byteOffset [, length]]); 

No parameter

The best move is that there is no move; when the constructor is instantiated, no parameters are passed in, an empty typed array is returned:

var i8 = new Int8Array();

console.log(i8); // Int8Array []
console.log(i8.length); // 0
console.log(i8.byteLength); // 0
console.log(i8.byteOffset); // 0

length

One inch long and one inch strong; pass in a number type parameter to indicate the number of elements in the declaration typed array:

var i8 = new Int8Array(3);
var _i8 = new Int8Array('3'); // The string is first converted to a number

console.log(i8); // Int8Array(3) [0, 0, 0]
console.log(_i8); // Int8Array(3) [0, 0, 0]
console.log(i8.length); // 3
console.log(i8.byteLength); // 3
console.log(i8.byteOffset); // 0

typedArray

A good move is not afraid to follow suit; when an incoming parameter is also a typed array, a copy (not a reference) of the original type array will be returned:

var i8 = new Int8Array(3);
var _i8 = new Int8Array(i8);

console.log(i8 == _i8); // false
console.log(i8 === _i8); // false
console.log(_i8); // Int8Array(3) [0, 0, 0]

object

This parameter is similar to the type array created by the TypedArray.prototype.from() method. At the same time, this method is similar to the Array.from(). That is, this object parameter is an array like object or an iterative object. For example:

// array
var i81 = new Int8Array([1, 2, 3]);
console.log(i81);
// Int8Array(3) [1, 2, 3]

// Equivalent operation
var i81 = Int8Array.from([1, 2, 3]);
// Int8Array(3) [1, 2, 3]

// Class array
var i82 = new Int8Array({
    0: 1,
    1: 2,
    2: 3,
    length: 3
});
console.log(i82);
// Int8Array(3) [1, 2, 3]

// Iteratable object
var i83 = new Int8Array(new Set([1, 2, 3]));
console.log(i83);
// Int8Array(3) [1, 2, 3]

buffer, byteOffset, length

The constructor also supports three parameters. The first buffer refers to the array buffer, an instance of ArrayBuffer, and the value of Int8Array.prototype.buffer. But yoffset refers to the offset value of the element, which means to read from the first element in the number group. The default value is 0 is the first element of the array. Length refers to the length of the element to be read after setting the offset value. The default value is the length of the entire array. For example:

var buf = new Int8Array([1,2,3,4,5]).buffer;
var i8 = new Int8Array(buf, 1, 2);

console.log(i8);
// Int8Array(2) [2, 3]

That is, based on the provided buffer, the declared typed array is read from the element whose index is 1 (the second element), and then read two elements backward; this operation is generally used to intercept the buffer data;

Type difference

Existence is reasonable; according to the previous introduction, TypedArray defines various types, such as Int8Array, Uint8Array, Int16Array, etc., which is also to adapt to different application scenarios. Next, we will roughly understand the differences between several typical typed arrays;

Is there any sign?

Taking Int8Array and Uint8Array as examples, in fact, the symbolic meaning is that the elements in an array can have symbols, that is, they can be negative numbers; therefore, the unsigned meaning is that the elements can only be non negative numbers, for example:

var i8 = new Int8Array([1, 2, 3]);
var _i8 = new Int8Array([-1, -2, -3]);
var ui8 = new Uint8Array([1, 2, 3]);
var _ui8 = new Uint8Array([-1, -2, -3]);

console.log(i8);  // Int8Array(3) [1, 2, 3]
console.log(_i8); // Int8Array(3) [-1, -2, -3]
console.log(ui8); // Uint8Array(3) [1, 2, 3]
console.log(_ui8);// Uint8Array(3) [255, 254, 253]

It can be found that the sign type initializes the negative number element, while the unsigned one will convert the negative number. The specific conversion method will be mentioned later;

Element range

For an array of types with or without symbols, in addition to the positive and negative differences in the value of the element, the value range of the element is also different. Here is a specific list:

Type Range
Int8Array -128 ~ 127
Uint8Array 0 ~ 255
Uint8ClampedArray 0 ~ 255
Int16Array -32768 ~ 32767
Uint16Array 0 ~ 65535
Int32Array -2147483648 ~ 2147483647
Uint32Array 0 ~ 4294967295
Float32Array 1.2×10-38 ~ 3.4×1038
Float64Array 5.0×10-324 ~ 1.8×10308
BigInt64Array -263 ~ 263-1
BigUint64Array 0 ~ 264-1

It can be seen that in order to take into account the same range of values of single element with or without symbol type, the upper and lower limits of their values are adjusted;

Byte digit

In the case of signed types, you can find int8array, The only difference between int16array and other arrays of different types is that the number in the middle of their constructor names is different. In fact, this number refers to the size of a single element of the typed array after instantiation, that is, how many bits, 8 is 8, that is, one byte, 16 is 2 bytes, and so on. In fact, this number also reflects the bytes_per_elementin the typed array The value of this attribute can also be seen from the name to represent the number of bytes of each element. For example:

var i8 = new Int8Array(3);
var i16 = new Int16Array(3);
var i32 = new Int32Array(3);

console.log(i8.BYTES_PER_ELEMENT); // 1
console.log(i16.BYTES_PER_ELEMENT); // 2
console.log(i32.BYTES_PER_ELEMENT); // 4

console.log(i8.length); // 3
console.log(i16.length); // 3
console.log(i32.length); // 3

console.log(i8.byteLength); // 3
console.log(i16.byteLength); // 6
console.log(i32.byteLength); // 12

In addition, byteLength refers to the total byte size of the type array. Its value is equal to the byte value of a single element multiplied by the number of elements (byteLength = bytes_per element x length);

Clamped

Crane is difficult to hide in chicken flock; the unique type array uint8clapedarray can be found in the previous list. The difference is that there is more clapped in the middle. The dictionary interpretation means "clamp, clamp". What are the specific functions? The following is explained by the code:

var i8 = new Uint8Array([1, 2, 3]);
var _i8 = new Uint8Array([-1, -2, -3]);
var _i8_ = new Uint8Array([255, 256, 257]);

var uic8 = new Uint8ClampedArray([1, 2, 3])
var _uic8 = new Uint8ClampedArray([-1, -2, -3])
var _uic8_ = new Uint8ClampedArray([255, 256, 257])

console.log(i8);    // Int8Array(3) [1, 2, 3]
console.log(_i8);   // Int8Array(3) [255, 254, 253]
console.log(_i8_);  // Uint8Array(3) [255, 0, 1]

console.log(uic8);  // Uint8ClampedArray(3) [1, 2, 3]
console.log(_uic8); // Uint8ClampedArray(3) [0, 0, 0]
console.log(_uic8_);// Uint8Array(3) [255, 255, 255]

I don't know if you can find out the clue. It can also explain the problem of converting negative values when instantiating an array of unsigned types. Through analysis, it's not difficult to find that the conversion method is similar to the cyclic value of a prime group, that is, if the incoming value exceeds one of the upper or lower limits of the value range of an element, the part that exceeds it will start from the other limit of the range to So in the above example - 1 will be converted to 255257 will be converted to 1;

For the array of type uint8clapedarray, it's almost literal. It's similar to a "clamp" operation: if it's out of range, no matter how much it's out of range, it will only be set as the corresponding boundary value, so - 1, - 2, - 3 in the above example are all converted to 0256, and 257 to 255;

Floating point number

The only two floating-point type arrays are Float32Array and Float64Array. Floating-point means that the element value can be a decimal, because all the previous ones are of int type. Let's give an example:

var f32 = new Float32Array([1.11, 2.12345678911, -3.33333333333333333333333333])
var f64 = new Float64Array([1.11, 2.12345678911, -3.33333333333333333333333333])

console.log(f32);
// Float32Array(3) [1.1100000143051147, 2.1234567165374756, -3.3333332538604736]
console.log(f64);
// Float64Array(3) [1.11, 2.12345678911, -3.3333333333333335]

From the results, we can see that each element of 32-bit floating-point array is reserved to 16 decimal places, while 64 bit array is reserved to 16 at most, so the details are not detailed;

Operational elements

In order to decide with binary data, first of all, we must choose several weapons to take advantage of. Although typed arrays have most methods of ordinary arrays, such as every, forEach, slice, etc., they also have their own unique methods, such as. set();

. set() method is used to add (all) elements of the specified array to the specified position of the current array. The accepted parameter is. set(array[, ofset]). Here, array can be a normal array or a typed array. Offset refers to the offset value, that is, from which position to write the specified array elements. For example:

var i8 = new Int8Array(6);
var i81 = new Int8Array(6);
var i82 = new Int8Array(6);
var arr = [1, 2, 3];
var arr1 = [1, 2, 3, 4, 5, 6];

i8.set(arr, 2);
console.log(i8);
// Int8Array(6) [0, 0, 1, 2, 3, 0]

i81.set(arr1, 2);
console.log(i81);
// Uncaught RangeError: offset is out of bounds

i82.set(arr, 6);
console.log(i82);
// Uncaught RangeError: offset is out of bounds

It is proved that whether the size of the copied array exceeds the original array, or the copied result exceeds the original array when the offset value is too large, an error will be reported indicating that the offset exceeds the boundary, so the calculation should be accurate when using;

Operation buffer

In the past, TypedArray was described as a view to operate the data in ArrayBuffer. Now let's see the specific operation method;

data fetch

data conversion

Before using the typed array to operate the ArrayBuffer data, you need to obtain the data, that is, convert ArrayBuffer to TypedArray type. First, let's look at the methods of conversion between the two types:

ArrayBuffer to TypedArray:

var buffer = new ArrayBuffer(5); // Initialize 5-byte area first
var i8 = new Int8Array(buffer); // Then transfer the data to TypedArray

console.log(i8); // Int8Array(5) [0, 0, 0, 0, 0]

It can also be verified here that the newly created area data of ArrayBuffer has been set to 0;

Convert TypedArray to ArrayBuffer:

var i8 = new Int8Array(5);
var buffer = i8.buffer;

console.log(buffer); // ArrayBuffer(5) {}

Reading mode

In the previous lecture, there are many different implementations of typed arrays, such as Int8Array with 1 byte of symbolic elements, Int16Array with 2 bytes, etc.; according to the definition of ArrayBuffer, the buffer is created with 1 byte as the unit, so we usually use Uint8Array to read text data, because it also happens that the size of each element is 1 Bytes, of course, you can choose to use Uint16Array to read 2 bytes one by one, and so on;

Observe the specific reading mode through the code:

var data = new Uint8Array([1, 2, 3, 4])
var buffer = data.buffer;

var ui8 = new Uint8Array(buffer);
var ui16 = new Uint16Array(buffer);

console.log(ui8);  // Uint8Array(4) [1, 2, 3, 4]
console.log(ui16); // Uint16Array(2) [513, 1027]

The original data data is 4 bytes in size. The unit of Uint8Array is 1 byte, so the original data [1, 2, 3, 4] is also obtained. Because the data is small, there is no influence on whether there is symbol or not. The unit of Uint16Array is 2 bytes, so the total element length is 2 (2 = 4 / 2), but the single element 513, How are 1027 obtained? We can explore it through calculation:

First, look at the two elements 1 and 2. According to the result, they are read as 513. Then, the binary numbers of these elements are represented (the buffer is the binary data stored):

"1":   00000001
"2":   00000010
"12":  00000001 00000010

"513": 00000010 00000001
"21":  00000010 00000001

The rule is obvious. 513, the two byte data, is actually the one byte data next to 1 and 2, which are spliced together in reverse order;

Let's see if 3 and 4 are the same way to get 1027 data

"3":    00000011
"4":    00000100
"34":   00000011 00000100

"1027": 00000100 00000011
"43":   00000100 00000011

The result is as expected, so for example, Uint32Array and other type arrays that read data in multiple bytes, methods can also be analogized;

Byte order

In addition, it is worth mentioning that the above-mentioned reverse splicing method actually has a professional term called Endian. It should feel familiar for this English word. For example, in the result of executing lscpu in Linux, it will be found that it exists:

Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
Address sizes:       36 bits physical, 48 bits virtual

Byte order, or byte order ("Endian", "endianness" or "byte order"), describes how a computer organizes bytes into corresponding numbers.

This byte order can be divided into:

Little Endia: put the low data in the low position of the storage address, and put the high data in the high position address;

This order seems to be consistent with the memory address order (the low order is right, the high order is left in the reading mode), and it is also a common way, such as the Intel processor above; but for this order, people have to read it reversely (from right to left), such as the data 12 in the above example is 21 The order of reading can also be compared to this date format: "Sat 07 Mar 2020";

Big Endian: the low-level data is stored in the high-level address, and the high-level data is placed in the low-level;

This order may be more in line with human reading habits (from left to right). It is generally used in the data structure of Internet standards, which can be compared with the date format of "2020-03-07";

Data modification

The following is an attempt to modify the contents of the ArrayBuffer buffer by typing the array view:

var buffer = new ArrayBuffer(3);
var i8 = new Int8Array(buffer);

console.log(i8); // Int8Array(3) [0, 0, 0]

for (let i = 0; i < i8.length; i++) {
    i8[i] = 1;
}

var _i8 = new Int8Array(buffer); // New views verify modification succeeded

console.log(_i8); // Int8Array(3) [1, 1, 1]

Data mosaicing

Use the. set() method mentioned earlier to try to splice data into the buffer:

var buffer = new ArrayBuffer(6);
var i80 = new Int8Array(buffer);

console.log(i80); // Int8Array(6) [0, 0, 0, 0, 0, 0]

var i81 = new Int8Array([1, 2, 3]);
var i82 = new Int8Array([4, 5, 6]);

i80.set(i81);
i80.set(i82, 3);

var _i80 = new Int8Array(buffer); // Verify that the modification was successful

console.log(_i80); // Int8Array(6) [1, 2, 3, 4, 5, 6]

Note: the. concat() method of array cannot be used for element splicing here, because there is no built-in method in typed array, otherwise an error will be reported, as follows:

var arr1 = [1, 2, 3];
var arr2 = arr1.concat(4, 5, 6);

console.log(arr2); // [1, 2, 3, 4, 5, 6]

var i81 = new Int8Array([1, 2, 3]);
var i82 = i81.concat(4, 5, 6);

console.log(i82);
// Uncaught TypeError: i81.concat is not a function

Similarly,. splice(), which can replace elements, does not exist in typed arrays;

Data view

Summary

DataView is another view interface for reading and writing data from the ArrayBuffer buffer, which is characterized by considering the byte order problem, which will be discussed later;

The syntax is:

new DataView(buffer [, byteOffset [, byteLength]]);

Among them, buffer refers to the incoming data buffer, such as ArrayBuffer; byteOffset refers to the byte amount of offset, the first byte by default, byteLength refers to the byte length of the incoming data, and the length of the whole buffer by default; and these three parameters can be accessed through the corresponding attribute (read-only) after instantiation;

var buffer = new Int8Array([1, 2, 3, 4]).buffer;
var dv = new DataView(buffer, 1, 2);

console.log(dv); // DataView(2) {}
console.log(dv.buffer); // ArrayBuffer(4) {}
console.log(dv.byteOffset); // 1
console.log(dv.byteLength); // 2

Operational data

DataView provides a series of methods for manipulating buffer data. First, a simple preview is given:

Read Write
getInt8() setInt8()
getUint8() setUint8()
getInt16() setInt16()
getUint16() setUint16()
getInt32() setInt32()
getUint32() setUint32()
getFloat32() setFloat32()
getFloat64() setFloat64()

Read

Taking getInt8() method as an example, we can provide a parameter byteOffset, which means to offset the specified number of bytes, and then read 1 byte (8 bits) of data, the default is 0 (the first byte); if getInt16() and other methods are used to obtain the value greater than 1 byte and floating-point value, we also receive the second optional parameter littleEndian, which is whether to use little or not Endian (low byte order, as mentioned above) format is used to read data. If true is passed in, it means little endian format is used. If false is passed in or not filled in, big endian format is used;

var buffer = new Int8Array([1, 2, 3, 4]).buffer;
var dv = new DataView(buffer);

console.log(dv.getInt8(1)); // 2
console.log(dv.getInt16(0, true)); // 513
console.log(dv.getInt16(0, false)); // 258
console.log(dv.getInt16(0)); // 258

The result is 513, which uses little endian format, and the value of 513 is also consistent with the result of Int16Array example in TypedArray. It is proved that little endian format is used in operation data buffer by default in TypedArray;

Write

For example, setInt8() accepts two parameters: setInt8(byteOffset, value). The first represents the offset byte amount, which is used for positioning, the second is the specific value to be set, and the non numeric type will report an error. Similarly, setInt16 and other methods for setting more than 1 byte also provide the third optional parameter little endian, which indicates whether to set in the format of little endian;

var buffer1 = new ArrayBuffer(2);
var buffer2 = new ArrayBuffer(4);
var dv1 = new DataView(buffer1);
var dv2 = new DataView(buffer2);

dv1.setInt8(0, 1);
dv1.setInt8(1, 2);
var i8 = new Int8Array(dv1.buffer);
console.log(i8); // Int8Array(2) [1, 2]

dv2.setUint16(0, 513, true);
dv2.setUint16(2, 513);
var i16 = new Uint16Array(dv2.buffer);
console.log(i16); // Int16Array(2) [513, 258]

It should be noted that since the unit of byteOffset is always 1 byte, when more than one byte of data is written, the corresponding offset value also needs to be increased, as shown in the above example;

Contrast

Compared with the typedarray view interface mentioned earlier, although DataView is compatible with byte order problems of different platforms, it does not have some functions of modifying and splicing the whole data, and can only modify the value of a single element; in addition, it cannot use the constructor for initial assignment, such as the following situations:

console.log(new Int8Array([1, 2, 3]));
// Int8Array(3) [1, 2, 3]

console.log(new DataView([1, 2, 3]));
// Uncaught TypeError: First argument to DataView constructor must be an ArrayBuffer

Therefore, we need to use them flexibly to deal with complex scenes;

Blob

Blob constructor is used to describe a blob (Binary Large OBject), that is, a class file object to save the original data. It supports saving multiple types of data (unlike TypedArray, only numeric types can be used), and the data is read-only and cannot be modified. Another blob based constructor File is the (< input type = "file" >) data used to process files uploaded by users.

Syntax:

new Blob(array, options);

Array refers to an array or class array composed of a series of types of data, which can be string, ArrayBuffer, DataView, TypedArray, Blob, DOMString, etc.; options is an object, which can contain the following two properties:

{
    type: "", // The MIMS type of the incoming data, such as text/plain, is empty by default
    endings: "" // How to handle line breaks in data, such as \ N and \ r\n, varies by operating system
                // The value is transparent or native, and the default value is transparent
                // native means replace with the line feed of the current system
                // transparent means not to replace and keep the data content
}

Write data

It is illustrated by several examples:

var blob1 = new Blob([1, 2, 3]);
var blob2 = new Blob(['a', 'bc', 'd e']);
var blob3 = new Blob(['hello'], {type: 'text/plain'});
var blob4 = new Blob(new Int8Array([4, 5, 6]));
var blob5 = new Blob([blob2]);

console.log(blob1); // Blob {size: 3, type: ""}
console.log(blob2); // Blob {size: 6, type: ""}
console.log(blob3); // Blob {size: 5, type: "text/plain"}
console.log(blob4); // Blob {size: 3, type: ""}
console.log(blob5); // Blob {size: 6, type: ""}

If the parameter is not the type of class array, an error will be reported:

var blob1 = new Blob(123);
var blob2 = new Blob('123');
var blob3 = new Blob({foo: 'bar'});
var blob4 = new Blob(true);
var blob5 = new Blob(blob1);

console.log(blob1);
// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': 
// The provided value cannot be converted to a sequence.
console.log(blob2);
// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': 
// The provided value cannot be converted to a sequence.
console.log(blob3);
// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': 
// The provided value cannot be converted to a sequence.
console.log(blob4);
// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': 
// The provided value cannot be converted to a sequence.
console.log(blob5);
// VM3497:1 Uncaught TypeError: Failed to construct 'Blob': 
// The provided value cannot be converted to a sequence.

Read data

Although the data written to the Blob instance cannot be modified, it can be read. First, you can get the total size and type of data (read-only):

var blob = new Blob(['a', 'b', 'c'], {type: 'text/plain'});

console.log(blob.size); // 3
console.log(blob.type); // text/plain

. text() method is used to get the text data in Blob. The return value is a promise object, which contains the text data in a resolved state. There is no parameter provided;

var blob = new Blob([1, 2, 3]);

blob.text().then(data => {
    console.log(data, typeof data);
});
// 123 string

. arrayBuffer() method is also used to obtain data in Blob, and returns a promise without parameters, but only the ArrayBuffer of data, i.e. binary data buffer;

var blob1 = new Blob([1, 2, 3]);
var blob2 = new Blob(['a', 'b', 'c']);

blob1.arrayBuffer().then(data => {
    console.log(new Uint8Array(data));
});
// Uint8Array(3) [49, 50, 51]

blob2.arrayBuffer().then(data => {
    console.log(new Uint8Array(data));
});
// Uint8Array(3) [97, 98, 99]

It can also be verified that the value in the type array is the binary value of the corresponding original data.

TextEncoder

This is still an interface in the experimental stage, the current interface may change in the future, and the current IE series browsers do not support it, here is just a brief introduction;

As the name implies, the function of this constructor is to encode the text. In fact, it is to convert the incoming text into the typed array corresponding to the data in the specified encoding format. When instantiating, it can provide a parameter for encoding format, but at present, it is only encoded in UTF-8 format by default, so it can be omitted;

var encoder = new TextEncoder();
var arr = encoder.encode('abc');

console.log(encoder.encoding); // utf-8
console.log(arr); // Uint8Array(3) [97, 98, 99]

If there is encoding, there will be decoding. TextDecode is the constructor corresponding to it, that is, the data of ArrayBuffer or ArrayBuffer View type will be decoded into the corresponding text;

var ui8 = new Uint8Array([97, 98, 99]);
var buffer = ui8.buffer;
var decoder = new TextDecoder();

var text1 = decoder.decode(ui8);
var text2 = decoder.decode(buffer);

console.log(text1); // abc
console.log(text2); // abc

In this way, in addition to the Blob above, the TextEncoder here can also be used to save text data as binary buffer data in JavaScript;

Processing file data

There are people outside, there are days outside, across this binary, it is a broader world; said a series of binary data storage and read-write methods, it is time to talk about its application;

You need to know what variables are used to save text strings in JavaScript. In fact, buffer, type array and Blob are mostly used to deal with file data. Because they have different MIME types, such as.Jpg.Mp4.Bin files with suffixes, JavaScript does not have built-in interfaces that directly deal with these data types (such as.txt) Documents can be processed), so they need to be saved or processed in the way of native binary data, which is convenient for users to upload, download or preview;

File

As mentioned earlier, file is based on Blob, so it also inherits some of its methods. File is used to provide information and content about files. The syntax is as follows:

new File(content, name[, options]);

Content refers to the content of the file to be created, which is an array or class array composed of ArrayBuffer, View, Blob, DOMString and other types; name refers to the name or path of the file; options parameter is optional, including two attributes: type and lastModified;

Give an example:

var content = new TextEncoder().encode('hello world!');
var file = new File(content, 'test.txt', {
    type: 'text/plain', // Optional, blank by default
    lastModified: Date.now() // Optional, followed by default
});

console.log(file.name); // test.txt
console.log(file.size); // 12
console.log(file.type); // text/plain
console.log(file.lastModified); // 1583638485180

The File constructor does not bring some methods, but inherits the Blob's methods, for example:

var file = new File(['hello world!'], 'text.txt'); 
// The initialization content can be a string directly, but it needs to be put in an array

file.text().then(data => {
    console.log(data); // hello world!
});

file.arrayBuffer().then(data => {
    var text = new TextDecoder().decode(data);
    console.log(text); // hello world!
});

In fact, File interface is rarely used to directly create a File object, most of which are used for users to upload files, such as using < input type = "File" / > tag to upload documents in web pages. After users click upload, the information related to the File is included in the files attribute referenced by the node of the input tag, such as files The attribute value is an instance of the FileList interface, that is, an array containing all uploaded files, in which each element is an instance of the File interface;

This is illustrated by a simple demo:

<!DOCTYPE HTML>
<html>
    <head></head>
    <body>
        <input type="file" class="upload" />
        <!--
        //If you want to upload multiple files, use:
        <input type="file" class="upload" multiple />
        -->
        <input type="submit" value="Upload" onclick="doUpload()" />
        
        <script>
            var upload = document.querySelector('.upload');
            
            // Execute after the user clicks the Upload button
            function doUpload() {
                var file = upload.files[0];
                
                console.log(file);
                // File {name: "test.txt", lastModified: 1583634142542, lastModifiedDate: 
                // Sun Mar 08 2020 10:22:22 GMT+0800 (China standard time), webkitRelativePath: "", size: 12 }
            }
            
            // You can also get file objects in this way,
            // The code in this function will be executed after the user completes the upload operation, even if the upload button is not clicked
            upload.onchange = function(el) {
                var file = el.files[0];
                // Actions performed
            }
        </script>
    </body>
</html>

FileReader

FileReader is another interface for reading file data. In fact, some of the instantiated methods are similar to the. text() and. arrayBuffer methods in Blob, except that the returned method is no longer a promise object, but an event based interface; FileReader is also generally used to read the data uploaded by users;

Syntax:

new FileReader(); // Instantiation does not require any parameters

event processing

Since it is based on events, a series of methods for handling different events are needed, as follows:

  • . onabort(): this event is triggered when the read operation is interrupted.
  • . onerror(): this event is triggered when an error occurs in a read operation.
  • . onload(): this event is triggered when the read operation completes.
  • . onloadstart(): this event is triggered at the start of the read operation.
  • . onloaded(): this event is triggered at the end of the read operation (either successful or failed).
  • . onprogress(): this event is triggered when the Blob is read.

The above events can also use the corresponding format of the addEventListener() method to set the callback function;

Loading state

Because it is an event based interface, FileReader provides the readyState property, which represents different data loading states with different values:

  • 0: data has not been loaded;
  • 1: Data is loading;
  • 2: Data loading completed;

After the data is loaded, you can use the result property to get the file content;

Data loading

readAsText()

. readAsText(file[, encoding]) reads the data in file (file object or Blob) in the form of text string, and encodes it in encoding format (default utf-8);

var file = new File(['abc'], 'test.txt');
var reader = new FileReader();

reader.onloadstart = event => {
    console.log('loadstart state:', event.target.readyState);
}
reader.onload = event => {
    console.log('load state:', event.target.readyState);
    console.log('result:', event.target.result);
}
reader.onloadend = event => {
    console.log('loadend state:', event.target.readyState);
}

reader.readAsText(file);
// loadstart state: 1
// load state: 2
// result: abc
// loadend state: 2

readAsArrayBuffer()

readAsArrayBuffer(file) reads the data in file (file or Blob) in the form of ArrayBuffer;

var blob = new Blob(['a', 'b', 'c']);
var reader = new FileReader();

// The trigger effect is the same when using the listener
reader.addEventListener('load', event => {
    console.log(event.target.result);
    console.log(new Uint8Array(event.target.result));
})

reader.readAsArrayBuffer(blob);
// ArrayBuffer(3) {}
// Uint8Array(3) [97, 98, 99]

readAsDataURL()

Readas data URL (file) is also used to read the data in the file. Only after the content in the file is encoded with base64, it is put into a data URL (the content can be directly accessed through the URL link);

var file = new File(['abc'], 'test.txt', {
    type: 'text/plain'
})
var reader = new FileReader();

reader.onload = event => {
    console.log(event.target.result);
}

reader.readAsDataURL(file);
// data:text/plain;base64,YWJj

If you paste and copy the last output content into the address bar of the browser, you can directly see the text content after entering;

Presentation data

DataURL

Data URL refers to a URL protocol. The syntax format is:

data:[<mediatype>][;base64],<data>

You can analogy the common http: protocol, such as the return value in the above example:

data:text/plain;base64,YWJj

For specific usage, as mentioned before, the original content will be displayed directly after entering the browser address bar. For example, the above example is a string of text (the file type is specified as text/plain). If the type is image/png and other image formats, the image will be displayed directly;

The Data URL can be accessed through the browser address bar or displayed in HTML documents. For example, if the src attribute value of < img > is equal to the Data URL, the tag will be displayed as the corresponding picture. Similarly, the data can be assigned to the src attribute of < iframe >, the image or text data can be displayed, and the src assigned to the < video > tag can be displayed as the video;

ObjectURL

ObjectURL is created by using the URL.createObjectURL() method, and the return result is also a type of URL, similar to the Data URL above. The difference is that the life cycle of ObjectURL is related to the current website page. For example, after refreshing the page page, you can no longer access it;

For example, run the following code in the local web console:

var blob = new Blob(['abc']);

console.log(URL.createObjectURL(blob));

The output is similar to the following:

blob:http://127.0.0.1:8080/4064e759-231f-466e-a6ef-778505e56d2b

The link is temporarily valid, and the refresh page of the exhibition data content fails, but the format is basically the same; similarly, the ObjectURL can also be used to set the src attribute of < img > or < iframe > for separate display;

It should be noted that the createObjectURL() method returns a new ObjectURL object every time it is called. Even if the data source is the same, if there are many calls, the memory may increase dramatically. At this time, it needs to be recycled manually. The revokeObjectURL() method is used, as shown in the following example:

var url = URL.createObjectURL(new Blob(['test']));

URL.revokeObjectURL(url); // Complete recovery

File download

In addition to using tags such as < img, < iframe > to display data, you can also provide files for users to download. Using the < a > tag, you can assign the data URL or object URL to its href attribute, and specify the download attribute value. Otherwise, you may jump to the relevant page instead of downloading;

An example of a download component:

<a href="data:text/plain;base64,YWJj" download="test.txt" type="text/plain">Download</a>

The download attribute refers to the name of the file downloaded to the user's local location. Without a suffix, the system automatically identifies the type. The same type attribute is optional and can be used to fix the download file type;

Upload data

Let users upload files through web pages, the most important of course is the last upload stage, that is, upload the files selected by users to the server; the following example uses the XMLHttpRequest() interface to upload data;

var file = new File(['hello world!'], 'hello.txt', {
    type: 'text/plain'
}); // This is used to simulate the files uploaded by users, i.e. there are specific file names, types and contents
var xhr = new XMLHttpRequest();
var reader = new FileReader();

// View upload progress
xhr.upload.onprogress = event => {
    if (event.lengthComputable) {
        console.log('Speed of progress:', event.loaded + '/' + event.total);
    }
}
// Upload completed callback
xhr.upload.onload = event => {
    console.log('upload success.');
}
// Upload address, change parameter to actual address
xhr.open('POST', 'http://localhost/upload/upload.php');
// The server does not specify the file type
xhr.overrideMimeType('text/plain');

reader.onload = event => {
    // Upload after reading data
    xhr.send(event.target.result);
}
reader.readAsText(file);

In addition, you can also use form table to upload files, which is more direct:

<form action="upload/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="upload" >
    <input type="submit" value="Upload">
</form>

It should be noted that enctype = "multipart / form data" must be added when uploading files, otherwise, only a file name will be uploaded;

receive data

Papaya is sent to me and reported as qiongju. Sometimes it will receive data from the server. Usually, XMLHttpRequest is used to get text or JSON data asynchronously, but it can also be used to get other types of data. Just manually set the responseType property to declare. The property supports the following values:

  • "": default value, same as text type;
  • "Text": respond with text type;
  • "ArrayBuffer": respond with ArrayBuffer binary data;
  • "Blob": respond with blob type data;
  • "JSON": the response resolves to a JSON object;
  • "document": parsing to HTML or XML content;

An example of receiving data:

var xhr = new XMLHttpRequest();

xhr.responseType = 'arraybuffer';
xhr.onload = () => {
    var buffer = xhr.response;
    // Can be converted to typed array for data modification
    console.log(new Uint8Array(buffer));
}
xhr.open('GET', 'test.png');
xhr.send();

So far, after several battles, the situation is fierce and unprecedented. It's hard to tell. In the next few dozens of rounds, all the people who can read here are real warriors. They dare to face the bleak life and face the bloody blood It goes further. As the saying goes, rare things are the most precious, and harmony is the most precious. JavaScript swordsmen and binary hermits are fighting for no victory or defeat. If the principles in this process can be understood and penetrated thoroughly by everyone, it is also a famous history;

The gratitude and resentment will end, the love and hatred will end. There is no banquet that will not end. A person's road is also called the Jianghu. Let's leave now. Goodbye in the Jianghu!

Technical article push Sharing of practical software for mobile phones and computers
58 original articles published, 174 praised, 230000 visitors+
Private letter follow

Tags: Attribute Javascript encoding JSON

Posted on Mon, 09 Mar 2020 04:44:13 -0400 by jrforrester