My Notes from “Hands-On JavaScript High Performance” Book
Hands-On JavaScript High Performance Justin Scherer
101 notes/highlights
Created by Ahmed Mansour Ouda – Last synced January 15, 2022
Tools for High Performance on the Web
With that piece of code inside the wheel event, we can see the stuttering happen. Since we can now see stuttering and it being a potential bottleneck for scrolling, what’s the next best option? Putting it in a setInterval, using requestAnimationFrame, or even using requestIdleCallback, with the last being the least optimal solution. October 22, 202157
This is true for pretty much every array-based function. Helper functions are great, but they are also slower than regular loops. We will go into greater detail in the next chapter, but just realize ahead of time that most of the convenience that the browser gives us is going to be slower than just writing the function in a more straightforward manner. October 22, 202161
Immutability versus Mutability – The Balance between Safety and Speed
Reading in data through nested arrays and loops can be confusing, but results in fast read times. On a dual-core processor with 8 GB of RAM, this code took 83 ms. October 26, 202178
[Example Code on page 78] This code is wonderful in terms of readability, but what are the performance characteristics of this? On a current machine, this ran in around 1 second. This is a big difference in terms of speed. Let’s see how they compare in terms of memory usage. October 26, 202178
Settled memory (what the memory goes back to after running the code) appears to be the same, settling back to around 1.2 MB. However, the peak memory for the immutable version is around 110 MB, whereas the Vanilla JavaScript version only gets to 48 MB, so a little under half the memory usage. Let’s take a look at another example and see the results that transpire. October 26, 202178
If we run these instances, we will see that the fastest will go between the basic for loop and the built-in map function. The immutable version is still eight times slower than the others. October 26, 202179
Running the same test, we get roughly an average of a tenfold slowdown with the immutable version. Now, this is not to say that the immutable version will not run faster in certain cases since we only touched on the map and list features of it, but it does bring up the point that immutability comes at a cost in terms of memory and speed when applying it to JavaScript libraries. October 26, 202179
Primitive types, in JavaScript, are anything that are not considered objects. To put it simply, numbers, strings, Booleans, null, and undefined are values. This means that if you create a new variable and assign it to the original, it will actually give it a new value October 26, 202181
let x = {}; let y = x;console.log( x === y ); y = Object.assign({}, x); console.log( x === y );
y now has a brand-new object and this means that it points to a new location in memory. While a deeper understanding of pass by value and pass by reference can be good, this should be sufficient to move on to mutable code. October 26, 202181
Writing legacy code is a hard job and most people will usually get it wrong. While we should be aiming to improve on the code base, we are also trying to match the style. It is especially difficult for developers to walk through the code and see 10 different code choices used because 10 different developers have worked on the project over its lifespan. If we are working on something that someone else has written, it is usually better to match the code style than to come up with something completely different. October 26, 202182
Side effects are conditions that occur when a function does not just return a new variable or even a reference that the variable passed in. It is when we update another variable that we do not have current scope over that this constitutes a side effect October 26, 202183
If we are trying to make sure that we don’t want anyone able to touch the internal state, then we cannot pass a reference out; we have to pass a new object. October 26, 202184
However, we have just done what we have been avoiding; we have created an immutable state system. We can add more bells and whistles to this centralized state system, such as eventing, or we can implement a coding standard that has been around for quite some time, called Resource Allocation Is Initialization (RAII). October 26, 202187
Resource allocation is initialization (RAII) The idea of RAII comes from C++, where we have no such thing as a memory manager. We encapsulate logic where we potentially want to share resources that need to be freed after their use. This makes sure that we do not have memory leaks, and that objects that are utilizing the item are doing so in a safe manner. Another name for this is scope-bound resource management (SBRM), and is also utilized in another recent language called Rust. October 26, 202188
In JavaScript, we can perform what is called lazy evaluation. Lazy evaluation means that the program does not run what it does not need to. One way of thinking about this is when someone is given a list of answers to a problem and they are told to put the correct answer to the problem. If they see that the answer was the second item that they looked at, they are not going to keep going through the rest of the answers they were given; they are going to stop at the second item. The way we use lazy evaluation in JavaScript is with generators. October 26, 202194
There are many other potential uses for generators and lazy evaluation techniques that will not be covered here, but they are available to developers who are looking for a more functional-style approach to list and map comprehensions. October 26, 202196
There is hope for JavaScript, however. A concept called trampolining can be utilized to make tail-end recursion possible by modifying the function a bit and how we call it October 26, 202197
We can see that both of these functions pass what is known as tail-end recursion and are also functions that could be written in a purely functional language. But, we will also see that these run a lot slower than simple for loops or even the built-in array methods for these types of functions October 26, 202199
Currying is the ability of a function that takes multiple arguments to actually be a series of functions that takes a single argument and returns either another function or the final value. October 26, 2021100
Bind is an interesting function. It takes the scope that we want as the first argument (what this points to) and then takes an arbitrary length of arguments to fill in for the arguments of the function we are working on. October 26, 2021101
Currying is the idea that we will use partial application, but that we are going to compose a multi-argument function with multiple nested functions inside it. October 26, 2021101
partial application, streaming/lazy evaluation, and possibly some recursion. October 26, 2021102
This could lead to quite a few problems. The following code showcases one of the issues that we could run into with the var keyword: October 26, 2021107
Another notable change to the language was the addition of arrow functions. With this, we have now gotten access to change this without having to resort to various hacks on the language October 27, 2021109
the arrow function takes the scope of the parent, October 26, 2021109
While the set takes a bit longer to create, when it comes to looking for items or even grabbing them, the set will perform nearly 100 times faster than an array. This is mostly due to the way the array has to look items up. Since an array is purely linear, it has to go through each element to check, whereas the set is a simple constant time check. October 27, 2021110
A set can be implemented in different ways depending on the engine. A set in the V8 engine is built utilizing hash dictionaries for the lookup. We will not go over the internals of this, but essentially, the lookup time is considered constant, or O(1) , for computer science folk, whereas the array lookup time is linear, or O(n) . October 27, 2021110
Finally, a map can give us performance benefits over a plain old object in the cases of large datasets and when the keys are the same types and so are the values. October 27, 2021111
have weak versions of them. The weak versions have one major limitation: the values have to be objects. This makes sense once we understand what WeakSet and WeakMap do. They weakly store the reference to the items. This means that while the items they have stored are around, we can perform the methods that these interfaces give us. Once the garbage collector decides to collect them, the references will be removed from the weak versions. We may be wondering, why would we use these? October 27, 2021111
This is what we mean when we say something either has a strong reference or a weak reference. A weak reference will allow the item to be cleaned up by the garbage collector, whereas a strong reference will not. WeakMaps are great for these types of linking in terms of data to another data source. If we want the item decoration or the link to be cleaned up when the primary object is cleaned, a WeakMap is a go-to item. October 27, 2021113
Overall, a WeakSet should be used when we need to tag an object or a reference October 27, 2021113
const a = {item1 : b}; const b = {item1 : a}; With each of these items pointing at one another, we would run into circular reference issues October 27, 2021113
The main thing is if we need to track the existence of something. If we need to do this, the WeakSet is the perfect use case for us October 27, 2021115
Metaprogramming is the technique of having code that generates code. This could be for things such as compilers or parsers. It could also be for self-changing code. It can even be for runtime evaluation of another language (interpreting) and doing something with this. While this is probably the main feature that reflection and proxies give us, it also gives us the ability to listen to events on an object. October 29, 2021116
This pattern of RAII will differ slightly from the null reference. Once we revoke a proxy, all references will no longer be able to use it. This may become an issue, but it would also give us the added benefit of failing fast, which is always a great property to have in code development. This means that when we are in development, it will throw a TypeError instead of just passing a null value. In this case, only try-catch blocks would allow this code to keep going instead of just simple null checks. Failing fast is a great way to protect ourselves in development and to catch bugs earlier. October 29, 2021117
When dealing with arguments, if we are going to mutate them at all in the function, create a copy and then mutate. Certain de-optimizations happen if we decide to mutate the arguments directly. October 29, 2021121
A lot of code still utilizes the arguments section of a function. There are even other properties of a function that we can get at such as the caller. If we are in strict mode, a lot of this behavior will break. Strict mode is a way to not allow access to certain behaviors in the JavaScript engine. A good description of this can be found at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode. In addition to this, we should no longer be using the arguments section of the function since we have plenty of helpful alternatives with the new standard. October 29, 2021124
Just remember that even though we can do something, doing something may not be the best idea. Specifically, we may be able to pass arbitrary expressions that will evaluate to something, but we should try to keep them as clean and simple as possible to make the code more readable. October 29, 2021125
typed arrays are ways of representing arbitrary bytes in the system. This allows us to work with lower-level functionality, such as encoders and decoders, or even working on the byte streams of a fetch call directly instead of having to work with converting blobs to numbers or strings. October 29, 2021126
A lot of the time, we will be utilizing Uint8Array as we need to work with the arbitrary bytes, but we can utilize views all the way up to BigInt . These are usually utilized in low-level systems such as in 3D canvas code, WebAssembly, or raw streams from the server. October 29, 2021126
Now that we have big integers, we can work with very large numbers, which can be of great use in 3D, financial, and scientific applications. Do not try to coerce BigInts back to regular numbers. There is some undefined behavior here and we may lose precision if we try to do this. The best approach is that if we need to use BigInts , stay in BigInts . October 29, 2021127
With this, we can now internationalize our system depending on where someone may be located or what language they choose at the start of our application. This will only perform conversions of the numbers to the stylings for that country code; it will not try to convert the actual values since options such as currency change within the course of the day. If we need to perform such conversions, we will need to use an API. On top of this, if we want to translate something, we will still need to have separate lookups for what we need to put in text since there is no direct translation between languages. October 29, 2021128
In pure functional programming, we have pure functions, or functions that perform an action and have no side effects (do something outside what the function is supposed to do). When we write in this way, we can create generalized functions and put them together to create a chain of simple ideas that can work on complex ideas. We also treat functions as first-class citizens in the language. This means that functions can be assigned to variables and passed to other functions. We can also compose these functions, as we have seen in previous chapters. This is one way to think about a problem October 29, 2021129
Another popular style of programming is object-oriented programming. This style states that a program can be described with a hierarchy of classes and objects that October 29, 2021129
can be built and used together to create this complex idea. This idea can be seen in most of the popular languages that are out there. We build base classes that have some general functionality or some definitions that specific versions of them need to incorporate. We inherit from this base class and add our own specific functionality and then we create these objects. Once we put all of these objects together, we can work on the complex idea that we need to. October 29, 2021129
With JavaScript, we get both of these ideas, but the OOP design in JavaScript is a bit different. We have what is known as prototypal inheritance. What this means is that there is really no idea of these abstract ideas called classes . All we have in JavaScript are objects. We inherit an object’s prototype that has methods and data on it that all objects with the same prototype share, but they are all instantiated instances. October 29, 2021130
Another way to think about this type of inheritance is to note that there are not abstract ideas in JavaScript, only concrete objects. October 29, 2021130
What all of this should showcase is the fact that JavaScript is purely made of objects. There are no abstract types such as true classes in other languages October 29, 2021131
As we can see with this example, we get some cleaner syntax while creating the same object as what we had before with the prototype version. The constructor function is the same thing as when we declared the Item as a function. We could pass in any parameters and do the setup in here. One interesting thing with classes is that we are able to create instance variables inside the class, just like when we declared them on this in the prototype example. We can also see that the declaration of d is put on the prototype. We will explore more aspects of the class syntax below, but take some time and play with both pieces of code. Understanding how JavaScript is prototype-based helps immensely when we are trying to write highly performant code. October 29, 2021132
The two static definitions were added to the newItem class and then we showcase what is available. With the function e and the static variable f , we can see that they are not included on the objects we create from newItem , but we have access to them when we access newItem directly. On top of this, we can see that the this that is inside the static function points to the class. Static members and variables are great for creating utility functions or even for creating the singleton pattern in JavaScript. October 29, 2021133
If we did not have the brackets around the declaration, we would be trying to bring in the default export. Since we have the curly brackets, it is going to look for an item called Item inside lib.js . If it finds it, then it will bring in the code that is associated with that. Now, just as we renamed the exports from the export list, we can rename the import. Let’s go ahead and change that to the following: import { Item as _item } from ‘./lib.js’; October 31, 2021138
One difference between this older API and the new querySelector and querySelectorAll is that the old API implements a collection of DOM nodes as an HTMLCollection and the newer API implements them as a NodeList . While this may not seem like a major difference, the NodeList API does give us a forEach already built into the system. Otherwise, we would have to change both of these collections to a regular array of DOM nodes October 31, 2021141
We can even start from another element so that we do not have to parse the entire DOM, like so: const hidden = document.querySelector(‘#main’).querySelectorAll(‘.hidden’); October 31, 2021142
We have seen these in previous chapters, but it is nice to touch upon them. Document fragments are reusable containers that we can create a DOM hierarchy in and attach all of those nodes at once. This leads to faster draw times and less repainting when utilized. October 31, 2021143
The shadow DOM is usually paired with templates and web components, but it can also be utilized by itself. The shadow DOM allows us to encapsulate our markup and styles for a specific section of our application. This is great if we want to have a certain styling for a piece of our page, but we do not want that to propagate to the rest of the page. October 31, 2021144
That is much easier to read and much easier to reason about. We now grab our template and get its content. We create a shadow object and append our template. We need to make sure to clone our template nodes otherwise we will share the October 31, 2021149
same reference between all of the elements that we decide to create! October 31, 2021149
As we can see, the use of the shadow DOM, along with web components and the template system in our browser, allows us to create rich elements without the need for external libraries such as Bootstrap or Foundation. We may still need these libraries to provide some base-level styling, but we should not need them to the extent we used to. The best-case scenario is that we can write all of our own components with styling and not need to utilize external libraries. But, seeing as how these systems are relatively new, if we are not able to control what our users use, we may be stuck polyfilling. October 31, 2021150
we add an event listener to the object. Since this is a plain object and not promise-based, we add a listener the old fashioned way with the addEventListener method. This means we would also clean up the event listener once it has been utilized. November 3, 2021151
callbacks can lead to quite a few problems. Instead, we have the promise. A promise takes a single argument when it is created, a function that has two parameters—resolve and reject. With these, we can either give a success back to our caller through the resolve function, or we can error out with the reject function. November 4, 2021154
We do need to be careful with the async / await system. It does actually wait, so if we put this on the main thread or do not have this wrapped in something, it can block the main thread, causing us to lock up. Also, if we have a bunch of tasks that we want to run at the same time, instead of awaiting them one at a time (making our code sequential), we can utilize Promise.all() . This allows us to put a bunch of promises together and allows them all to run asynchronously. Once they all return, we can continue execution. November 4, 2021154
One nice thing about the async / await system is that it can actually be faster than using generic promises. Many browsers have added optimizations around these specific keywords and so we should try to use them at every opportunity we have. November 4, 2021154
Streams, as stated in a previous chapter, are a way of handling chunks of data at a time. It also makes sure that we do not have to grab the entire payload at once, and instead, we can slowly build up the payload. This means that if we have to transform the data, we can do it on the fly as the blocks of data are coming in November 4, 2021156
The AbortController system allows us to stop these requests. What happens is that AbortController has a signal property. November 4, 2021159
Practical Example – A Look at Svelte and Being Vanilla
There are two types of storage that do similar things. They are LocalStorage and SessionStorage . The main difference is how long they will stay cached. LocalStorage stays until the user deletes the cache or the application developer decides to delete it. SessionStorage stays in the cache for the lifetime of the page. Once the user decides to leave the page, SessionStorage will clear out. Leaving the page means closing the tab or navigating away; it does not mean reloading the page or Chrome crashing and the user recovering the page. It is up to the designer which one to use. November 4, 2021180
Utilizing LocalStorage is quite easy. The object is held on the window in our case (if we were in a worker, it would be held on the global object). One thing to keep in mind is that when we utilize LocalStorage , it converts all values to strings, so we will need to convert complex objects November 4, 2021180
Switching Contexts – No DOM, Different Vanilla
Node.js also gives us a similar programming context that we have seen in the browser. We get an event loop that allows us to have asynchronous input and output ( I/O ). How this is achieved is through the libuv library. November 4, 2021185
type : This should either be module or commonjs . This will allow us to distinguish between legacy systems and the new ECMAScript module system. November 5, 2021190
man : This allows the man command to find the file that we wish to serve for our documentation. November 5, 2021190
engines : The versions of Node.js that we run on. November 5, 2021191
Some of the specific points that we are interested in are the prepare and install sections of the packaging life cycle. Let’s see what these sections cover: Prepare will run the script before the package is packed into a tarball and published to the remote repository. It’s a great way to run compilers and bundlers to get our package ready for deployment. Install will run the script after the package has been installed. This is great when we pull a package and want to run something such as node-gyp or something that our package may need that’s specific to the OS. November 5, 2021191
Node.js can function in this way with a mixture of two libraries. These libraries are V8, which we should already be familiar with, and libuv, which we aren’t currently familiar with. The libuv library gives us asynchronous I/O. Every OS has a different way of handling this I/O, so libuv gives us a nice C wrapper around all of these instances. November 5, 2021193
The libuv library queues up requests for I/O onto a stack of requests. Then, it farms them out to a certain amount of threads (Node.js utilizes four by default). Once the responses come back from these threads, libuv will put them on the response stack and alert V8 that the responses are ready to be consumed. Once V8 gets around to this alert, it will pull the value off and utilize it for its response to the request that we made. This is how the Node.js runtime is able to have asynchronous I/O and still maintain a single thread of execution (at least, that’s how it looks to the user). November 5, 2021193
As we saw in the DOM, streams give us the ability to control the flow of data and be able to process data in a way that creates a nonblocking system. We can see this by creating a simple stream. Let’s go ahead and utilize one of the built-in streams that comes with Node.js, readFileStream November 6, 2021194
Message Passing – Learning about the Different Types
In both the browser and Node.js, we have web workers that take the place of threads in traditional systems. While they have many of the same concepts as the threads of other languages, they are unable to share state (in our case, this is preferred). There’s a way to share state between workers. This can be done through SharedArrayBuffer . While we can utilize this to share state, we want to highlight that the event system and IPC are almost always fast enough to move state and coordinate between different pieces. Also, we don’t have to deal with concepts such as locks. November 18, 2021214
Transmission Control Protocol ( TCP ) and User Datagram Protocol ( UDP ). November 18, 2021228
The server will post new stock symbols and the amount of stock that is available. Then, it will blast the information on a known port to everyone over November 18, 2021230
UDP. The server will store all of the information related to a client’s positions. This way, there is no way for a client to be able to manipulate how many shares they may have. A client will send a buy or sell order to the server. The server will figure out whether it can handle the request. All of this traffic will be over TCP since we need to guarantee that we know the server received our message. The server will respond with an error or a success message, telling the client that their book has updated. The server will blast that a buy or sell happened for stock over the UDP channel. November 18, 2021230
Quick UDP Internet Connections ( QUIC ) was introduced by Google in 2012 November 18, 2021240
Other companies have started to implement the QUIC protocol while HTTP/3 is being developed. One notable inclusion to this list is Cloudflare. Their blogpost on implementing QUIC can be found here: https://blog.cloudflare.com/the-road-to-quic/. November 18, 2021240
Sending data between different systems, be it threads, processes, or even other computers, is what we do as developers. There are many tools we can use to do this, and we have looked at most of them. Just remember that while one option can appear to make an application simple, that doesn’t always mean it is the best choice. When it comes to breaking our systems up, we usually want to assign a specific job to a unit and use some form of IPC, such as named pipes, to communicate. If we need to move that task to another computer, we can always switch it out for TCP. November 18, 2021243
Service Workers – Caching and Making Things Faster
Service Workers – Caching and Making Things Faster January 15, 2022351
Building and Deploying a Full Web Application
With HTTP/2, some standards for bundling files have gone by the wayside. Items such as sprite sheets are no longer advisable since the HTTP/2 standard added the concept of TCP multiplexing. It can actually be faster to download multiple smaller files than one large file. For those of you who are interested, the following link explains these concepts in more detail:https://css-tricks.com/musings-on-http2-and-bundling/. November 26, 2021376
To deploy our application, we will need to deploy to our own computers. There are many services out there, such as AWS, Azure, Netlify, and so on, which will have their own ways of deploying. In our case, we are going to deploy out to Heroku November 26, 2021387
WebAssembly – A Brief Look into Native Code on the Web
On most machines, a program runs as a stack of instructions and data. This means that it pulls instructions off of the top of the stack one at a time. These instructions can be anything from loading this number into a location or adding these two numbers together. As it peels these instructions off, it discards them. December 3, 2021393
We can store various pieces of data local to this stack, or we can store data at a global level. Those local to the stack are held on exactly that—the stack. Once that stack has been exhausted, we no longer have access to those variables. The global ones are put into a location called the heap . The heap allows us to grab the data from anywhere in our system. Once the stack of our program has been exhausted, those heap objects can be left there if our program is still running. December 3, 2021394
Finally, we get a stack per function that we write. Because of this, we can treat each function as a mini-program. This means it will perform one task or a couple of tasks and then it will be exhausted, and we will go back to the stack of the function that called us. We can do two things when we exhaust this stack. The first thing we can do is go back to the stack of the function that called us with no data. Alternatively, we could give some data back (this is the return statement that we see in most languages). December 3, 2021394
Here, we can share data through these two mechanisms either by returning values from one of our subfunctions or by putting our results onto the heap so that others can access them. Putting it on the heap means that it will last for the duration of our program, but it also needs to be managed by us, whereas if we return values from the stack, it will be cleaned up as soon as the function that called us is exhausted. December 3, 2021394
The topic of computers and how programs work is quite interesting. For those of you who are interested, it may be beneficial to take a formal course at a community college. For those that like to self-learn, the following resource is of great help: https://www.nand2tetris.org/. December 3, 2021394
There is a difference between global/local variables and the stack/heap. For now, we are going to keep things simple and treat global variables as if they are on the heap and local variables as if they are on the stack. Later, our application will have a global state that is not on the heap, but it is best to try to keep the idea of the local equivalent to the stack and the global equivalent to the heap. December 3, 2021404
We talked about the stack when we talked about how programs run on a typical computer. The best way to think of this is a stack of wood. We will always pull from the top and always add to the top. This is the same way in programming. We add to the top of the stack and then we pull from those top items. As an example, let’s take the add function that we created. December 3, 2021404
A heap is just like what its name implies. We have the blob of things that can be grabbed, changed, and replaced from everywhere. Anyone can get to the heap, put items into it, and read from it. It’s like a heap of clothes – we can search through it and find the item we need, or we can just add to it at the end of the day. December 3, 2021404
The main difference between the two is that the stack will get cleaned up. Any variables that we created inside it, once the function returns, are cleaned up. On the other hand, the heap stays there. Since anyone has access to it, we have to explicitly get rid of it; otherwise, it will be there permanently. In garbage-collected environments, the best way to think of this is that our environment doesn’t know who else has items on it, so it doesn’t know what needs to be cleaned up December 3, 2021405
const bytes = new Uint32Array(memory.buffer, 0, 1); December 3, 2021406
All of our functions will have an underscore before them. This helps us and the system differentiate what may be created in the JavaScript system and what is being created in the C/C++ context. December 3, 2021413
A hamming code generator creates a piece of data that should be able to be recovered when it is transmitted between two mediums. These mediums could be anything from a computer to another computer or even a process to another process (although we should hope that data transmission between processes does not get corrupted). The data that we will be adding to accomplish this is known as hamming codes. December 3, 2021415
For those that are curious, an article on floating-point representation can be found here: https://www.cprogramming.com/tutorial/floating_point/understanding_floating_point_representation.html. December 3, 2021415
For more information on how to utilize this, go to https://github.com/kripken/sql.js/. To get the SQLite reference documentation, go to https://www.sqlite.org/lang.html. December 5, 2021425