For more front-end performance optimization series click here >>
Welcome to caching, the first stop on the front-end performance tuning tour.
The performance journey begins when the browser wants to fetch remote data. However, we don’t go immediately. In computing, many performance problems are solved by adding caches, and the front end is no exception. Like many back-end services, the front-end cache is multi-level. Let’s take a look.
1. Local data store
Caching can be implemented on the business code side in conjunction with local storage.
For some requests, we can cache directly on the business code side. The cache modes include localStorage, sessionStorage, and indexedDB. Adding this to the cache discussion may be controversial, but it does provide some cache-like capabilities on the application side.
For example, if we have a list updated daily on our page, we can do a daily cache:
// When the user loads the list component of the site, the list data can be obtained by this method
async function readListData() {
const info = JSON.parse(localStorage.getItem('listInfo'));
if (isExpired(info.time, +(new Date))) {
const list = await fetchList();
localStorage.setItem('listInfo'.JSON.stringify({
time: + (new Date),
list: list
}));
return list;
}
return info.list;
}
Copy the code
LocalStorage is well known, indexedDB is probably less well known. For a quick understanding of indexedDB use, see this article [1].
From a front-end perspective, this is local storage; But from the perspective of the whole system, it is often a link in the cache chain. For special, lightweight business data, consider using local storage as a cache.
2. Memory
When you visit a page and its sub-resources, sometimes a resource is used more than once, such as ICONS. Since the resource is already stored in memory, further requests are unnecessary, and the browser memory is the nearest and fastest place to respond.
There are no specific standards for memory caching, and it has little relevance to HTTP semantics. It’s an optimization that browsers do for us, and most of the time we don’t realize it.
You can learn more about Memory caching in the Memory Cache section of this article [2].
3. Cache API
Do we start sending requests when we miss the memory cache? Not necessarily.
At this point we may also encounter caches in the Cache API, which brings us to the Service Worker. They are usually used together.
To be clear, this layer of caching does not dictate what to cache and when to cache it, it just provides the ability for clients to build a caching mechanism for requests. If you know anything about PWA or Service workers, you should know exactly what’s going on. It doesn’t matter if you don’t know, we can take a look at it briefly:
First, the Service Worker is a standalone thread that runs in the background and can be enabled in code
// index.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js').then(function () {
// Registration succeeded
});
}
Copy the code
Some Service Worker lifecycle events are then handled, of which request interception is directly related to the caching functionality mentioned here:
// sw.js
self.addEventListener('fetch'.function (e) {
// If there is a cache, return directly, otherwise fetch
e.respondWith(
caches.match(e.request).then(function (cache) {
return cache || fetch(e.request);
}).catch(function (err) {
console.log(err);
returnfetch(e.request); })); });Copy the code
The above code intercepts all network requests to see if there is any cached request content, and returns to the cache if there is, otherwise the request continues to be sent. Unlike in-memory caches, caches provided by the Cache API can be considered “permanent” and can be used the next time you close the browser or leave the page.
The Service Worker and Cache API are a very powerful combination of heap transparency and incremental support for compatibility. It is highly recommended to try it in business. Of course, the above code is much more concise, for further understanding of Service Worker and Cache API use, please see this article [3]. Google Workbox is also recommended.
4. HTTP cache
If there is no cached request information in the Service Worker, then the HTTP request phase is truly reached. This is where the HTTP caching specification, as it is known, comes in.
HTTP has a set of specifications that dictate when request information should be cached, for how long, and when information should not be cached. Caching can be implemented through the associated HTTP request headers.
HTTP caching can be roughly divided into strong caching and negotiated caching.
4.1 strong cache
In the case of strong caching, the browser does not send a request to the server, but reads the content directly from the local cache, which typically comes from hard disk. This is the “disk cache” we often see on Chrome DevTools.
The associated response headers are Expires and Cache-Control. You can set an expiration time on the Expires page. The browser compares the expiration time with the current local time to determine whether the resource has expired. Cache-control controls the expiration time by giving it a max-age. For example, max-age=300 means that the resource request will strengthen the cache for 300 seconds after the successful response.
4.2. Negotiate cache
As you might sense, strong caching is not that flexible. If I update a resource within 300 seconds, how do I notify the client? A common way to do this is through a negotiated cache.
As we know, one of the reasons for the slow remote request is the large size of the packet. The idea of negotiating a cache is to avoid unnecessary resource downloads by “asking” the server whether the resource has expired. This is often accompanied by a 304 response code in the HTTP request. Here are two ways to implement a negotiated cache:
One way to help prevent caching is for the server to return last-Modified on the first response and for the browser to attach the value as if-modified-since on subsequent requests, asking the server: Has this resource been updated Since XX point in time? The server answers yes (status code 200) or no (status code 304) based on the actual situation.
The above is to determine whether to update by time. If the update interval is too short, such as less than 1s, the accuracy of using the update time is not enough. So there’s another way to go through an identifier, ETag. The server returns ETag the first time it responds, and the browser takes its value as if-none-match on subsequent requests. The MD5 of the file is usually used as the ETag.
As a front-end engineer, you must be good at using HTTP caching. If you want to learn more about HTTP caching, read this article [4].
Each of these levels of cache matches the URI of the resource, meaning that a change in the URI will not hit the cache. As such, in our current front-end practice, we add file hashes to file names to prevent files with the same name from hitting older resources in the cache.
5. Push Cache
If, unfortunately, you miss any of these caches, then you will encounter one final Cache check, Push Cache.
Push Cache is an extension of HTTP/2’s Push functionality. In short, it used to be that an HTTP connection could only transfer one resource, but now you can request one resource and the server can “push” other resources for you — some of which you may need in the near future. For example, when you request www.sample.com, the server not only sends the page document, but also pushes key CSS stylesheets along with it. This avoids the delay that occurs when the browser receives the response and parses it to the appropriate location.
However, HTTP/2 Push Cache is a low-level network feature that differs from other caches in many ways. For example:
- When a match is made, no additional checks are made to see if the resource is expired;
- The lifetime is very short, even shorter than the memory cache (for example, as mentioned in the article, Chrome is around 5min);
- Will only be used once;
- HTTP/2 disconnection will result in cache invalidation directly;
- …
If you are interested in HTTP/2 Push, check out this article [5].
Okay, so far, we probably haven’t made a real request. This also means that there are many opportunities during the cache checking phase to nip performance issues in the back – if remote requests don’t have to be sent, why optimize load performance?
So, take a look at our application, our business, and see what performance issues can be addressed at the source.
Many times, however, only part of the problem can be solved by caching. So we’re going to continue the journey, and we’ve made a good start, haven’t we?
At present, all the content has been updated to ✨ fe-performance-Journey ✨ warehouse, and the content will be synchronized to the nuggets one after another. If you want to read the content as quickly as possible, you can go directly to the repository and browse the articles.
Like friends can star, will continue to update more performance optimization related content.
The resources
- A quick but complete guide to IndexedDB and storing data in browsers
- A Tale of Four Caches
- PWA Learning & Practice: Make your WebApp available offline
- Browser cache mechanism: strong cache, negotiated cache
- HTTP/2 push is tougher than I thought
- Caching best practices & max-age gotchas
- The Offline Cookbook (Service Worker)
- HTTP/2 ORG
- Web Caching Explained by Buying Milk at the Supermarket
- Have an in-depth understanding of the browser caching mechanism