When you get deeper into building scalable web server applications using NodeJS and JavaScript at some point in time, you’ll feel the need to handle binary data, stream, and buffer. NodeJS and JavaScript both have ways to handle binary data.
In this article, we’ll take a look at the Buffer module provided by NodeJS and the JavaScript ArrayBuffer object, understand their differences, and when they should be used in our application.
NodeJS Buffer
We know that computers store and represent data in the form of binaries i.e sets of 0s and 1s. NodeJS provides the Buffer module with the Buffer class to handle binary data. Each binary digit represents a binary bit and 8 adjacent bit represents a byte. Buffer objects in NodeJS allow the representation of a fixed-length sequence of bytes.
The Buffer class is a global type derived from Uint8Array, a TypedArray object. The Buffer class provides some extra functionalities and use cases.
JavaScript ArrayBuffer
When dealing with binary data, the most basic binary object in JavaScript is ArrayBuffer. According to MDN Web Docs, an ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer.
In simple terms, ArrayBuffer represents a fixed-length contiguous memory area. The contents of ArrayBuffer cannot be modified or accessed directly. ArrayBuffers need TypedArray objects or DataView objects to access and manipulate the data inside it.
Difference between NodeJS Buffer and JavaScript ArrayBuffer
NodeJS Buffer and JavaScript ArrayBuffer have their own methods and properties to handle binary data but there are some major differences between the two.
Environment Difference
NodeJS Buffer is designed to work on a server-side environment and is not available on the browser side. ArrayBuffer is designed to be used in a Browser environment but as it’s a JavaScript native object, it can also be accessed in server-side or non-browser NodeJS applications.
Creation and Initialization of Buffer vs ArrayBuffer
NodeJS Buffer
The most common ways of creating and initializing a NodeJS Buffer are as follows:
- alloc() method
- unSafeAlloc() method
- from() method
- fill() method
- write() method
Example: alloc(), unSafeAlloc() and from() Method
// Creates a Buffer filled with zeros of size 5
const buf1 = new Buffer.alloc(5);
// Creates a Buffer of filled with 2 of size 5
const buf2 = new Buffer.alloc(5, 2);
// Creates a Buffer of size 10 with values "abcedfgh==" and
// UTF-8 encoding
const buf22 = new Buffer.alloc(10, "abcedfgh==", "utf-8");
// Creates an uninitialized Buffer of size 5 faster than alloc
// but needs to be filled or written to
// and might contain ambigous data
const buf3 = new Buffer.allocUnsafe(5);
// Creates and initializes Buffer with 1, 2, 3, 4 from array
const buf = new Buffer.from([1, 2, 3, 4]);
Example: fill() Method
const buf1 = Buffer.allocUnsafe(5).fill("a");
const buf2 = Buffer.alloc(10);
buf2.fill("a", 1, 5, "utf-8");
console.log(buf1.toString());
console.log(buf2.toString());
Output:
aaaaa
aaaa
Example: write() Method
const buf = Buffer.alloc(10);
buf.write("abcd", 4);
console.log(buf);
Output:
JavaScript ArrayBuffer
ArrayBuffer uses the ArrayBuffer() constructor to create ArrayBuffer objects.
Example:
// Creates an ArrayBuffer with a size of 10 bytes
const buf = new ArrayBuffer(16);
console.log(buf.byteLength);
// Output: 10
Relational Difference
An instance of Buffer is also an instance of TypedArray so all methods and properties of TypedArray are available to Buffer except for some incompatibilities. It is, for this inheritance, we can say that NodeJS Buffers are just a view for looking into the underlying ArrayBuffer. We can access the ArrayBuffer using the buffer property of the NodeJS Buffer.
Example:
const buf = Buffer.from("buffer", "utf-8");
const arr8 = new Uint8Array(
buf.buffer,
buf.byteOffset,
buf.length / Uint8Array.BYTES_PER_ELEMENT
);
console.log(arr8);
Output:
Accessing and Manipulating Data of Buffer vs ArrayBuffer
Unlike NodeJS Buffer, JavaScript ArrayBuffer needs view objects which can be a TypedArray or DataViews referencing the ArrayBuffer for accessing and manipulating many number types in a binary ArrayBuffer irrespective of the hardware’s endianness.
On the other hand, NodeJS Buffer doesn’t require any views for accessing or manipulating its contents but in some methods, it does require the provision of character encoding. Secondly, as we know NodeJS Buffer is a subclass of TypedArray, we can use the Buffer also as a view for the ArrayBuffer.
Example: Data access and manipulation of Buffer
// Data access and manipulation of Buffer
const buf = Buffer.from("427566666572207673204172726179427566666572", "hex");
console.log(buf);
console.log(buf.toString());
buf[0] = 42
console.log(buf.toString());
Output:
Example: Data access and manipulation of ArrayBuffer
// Data access and manipulation of ArrayBuffer
const buf = new ArrayBuffer(8);
const view1 = new DataView(buf);
const view2 = new Uint8Array(buf); // Uint8Array is a TypedArray
view1.setUint8(0, 2)
view2[1] = 2;
console.log(view1.buffer);
console.log(view2.buffer);
Output:
API of Buffer vs ArrayBuffer
Overall, NodeJS Buffer is more flexible in terms of its API and encoding capabilities, while JavaScript ArrayBuffer is optimized for use with typed arrays and is more lightweight. NodeJS Buffer being the subclass of Uint8Array and TypedArray and having an underlying ArrayBuffer which can be accessed via the buffer property makes it feature-rich.
Transferable Property of Buffer vs ArrayBuffer
ArrayBuffers are transferable objects, i.e their data/ownership can be transferred from one context to another. Take this as a pass-by-reference concept from C/C++ except that the transferring version is no longer available once it’s transferred to the new context. NodeJS Buffer in its entirety is not a transferable object.
Performance of Buffer vs ArrayBuffer
This performance aspect mainly depends on what you are trying to achieve with either of them and what data you decide to store. However, when dealing on the server side, NodeJS Buffer can be preferred. When a NodeJS Buffer is created it’s initialized to all 0’s. Whereas, the HTML5 spec states that the initialization of typed arrays must have their values set to 0. Allocating the memory and then setting all of the memory to 0s takes more time.
On the frontend side, JavaScript engines are optimized in a way that TypedArray works fast so it would make the Application more powerful if we use TypedArray for accessing and manipulating the raw binary data in the Frontend for a better User Experience.
Conclusion
In general, if you are working with binary data in a NodeJS application, you might use NodeJS Buffer because of its rich API and incorporation of both TypedArray and ArrayBuffer. If you are working with binary data and data streams in a web browser, you should use JavaScript ArrayBuffer, as NodeJS Buffer are not native to the JavaScript Browser Engine.
In the end, when building a scalable application it would be wiser to stick to either one of them, NodeJS Buffer or the JavaScript ArrayBuffer as they don’t differ much in performance but just in certain functionalities. However, there may be situations where you need to use both, such as when building a web application that interacts extensively with a NodeJS server.
References
https://nodejs.org/api/buffer.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays