<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Frontend Almanac</title><subtitle>Author’s blog by Roman Maksimov. Web developer since 2006. Frontend expert, researcher and mentor.</subtitle><author><name>Frontend Almanac</name></author><id>https://teletype.in/atom/frontend_almanac</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/frontend_almanac?offset=0"></link><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/frontend_almanac?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-09T10:05:23.500Z</updated><entry><id>frontend_almanac:react-lanes</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/react-lanes?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>React Lanes</title><published>2025-03-24T10:14:06.008Z</published><updated>2025-03-24T10:14:06.008Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/55/15/551560bd-5fd8-4030-a635-85bf39d38665.png"></media:thumbnail><category term="react" label="React ⚛️"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/99/38/9938514f-984e-48f6-b0e5-80bda3f1c4eb.png&quot;&gt;React is one of the most popular libraries for creating user interfaces. However, when working with large amounts of data or complex calculations, developers often face performance issues. In this article, we'll explore the concept of React Lanes — a mechanism that allows prioritizing rendering tasks and making the interface more responsive even when performing heavy operations.</summary><content type="html">
  &lt;figure id=&quot;sjba&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/99/38/9938514f-984e-48f6-b0e5-80bda3f1c4eb.png&quot; width=&quot;1216&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WbX6&quot;&gt;React is one of the most popular libraries for creating user interfaces. However, when working with large amounts of data or complex calculations, developers often face performance issues. In this article, we&amp;#x27;ll explore the concept of React Lanes — a mechanism that allows prioritizing rendering tasks and making the interface more responsive even when performing heavy operations.&lt;/p&gt;
  &lt;h2 id=&quot;KfB1&quot;&gt;What are React Lanes?&lt;/h2&gt;
  &lt;p id=&quot;mrEf&quot;&gt;Let&amp;#x27;s consider a typical task for React. Let it be a trivial search through text elements. For this, we&amp;#x27;ll take a regular input field and a list of elements that are filtered depending on the value entered in this field.&lt;/p&gt;
  &lt;pre id=&quot;Gyhe&quot; data-lang=&quot;jsx&quot;&gt;const SearchList = ({ items, filter }) =&amp;gt; {
  // Heavy filtering operation (simulation)
  const filteredItems = items.filter((item) =&amp;gt;
    item.toLowerCase().includes(filter.toLowerCase()),
  );

  return (
    &amp;lt;ul&amp;gt;
      {filteredItems.map((item, i) =&amp;gt; (
        &amp;lt;li key={i}&amp;gt;{item}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};

const App = () =&amp;gt; {
  const [inputValue, setInputValue] = useState(&amp;quot;&amp;quot;);

  // Generate large list for demonstration
  const bigList = Array(10_000)
    .fill(null)
    .map((_, i) =&amp;gt; &amp;#x60;Item ${i + 1}&amp;#x60;);

  const handleChange = (e) =&amp;gt; {
    setInputValue(e.target.value);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        type=&amp;quot;text&amp;quot;
        value={inputValue}
        onChange={handleChange}
        placeholder=&amp;quot;Search...&amp;quot;
      /&amp;gt;

      &amp;lt;SearchList items={bigList} filter={inputValue} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/pre&gt;
  &lt;p id=&quot;KNlH&quot;&gt;For clarity, let the list consist of a large number of elements. This will obviously provoke a heavy array filtering operation. You can try the resulting application below.&lt;/p&gt;
  &lt;figure id=&quot;hcV5&quot; class=&quot;m_column&quot;&gt;
    &lt;iframe src=&quot;https://codepen.io/rmaksimov/embed/dPybOBV?default-tab=js,result&quot;&gt;&lt;/iframe&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;589q&quot;&gt;You can experiment with the number of elements depending on the power of your device and the environment in which the application is running. But one way or another, if you quickly enter values into the search string, you can notice some lag when there are many elements in the selection.&lt;/p&gt;
  &lt;p id=&quot;YyRA&quot;&gt;This happens because each value change leads to a re-render of the &lt;code&gt;SearchList&lt;/code&gt; component, which in turn must render the required number of child elements.&lt;/p&gt;
  &lt;h2 id=&quot;OPKs&quot;&gt;Synchronous updates&lt;/h2&gt;
  &lt;p id=&quot;wz9U&quot;&gt;The example above demonstrates typical synchronous work on updating the component tree. I wrote in detail about the update process in the article &lt;a href=&quot;https://blog.frontend-almanac.com/JqtGelofzm1&quot; target=&quot;_blank&quot;&gt;Detailed React. Reconciliation, Renderers, Fiber, Virtual Tree&lt;/a&gt;. The &lt;strong&gt;Fiber&lt;/strong&gt; engine tries to make all necessary changes at once. In other words, the &lt;code&gt;render&lt;/code&gt; phase will not complete until the reconciler has gone through all the scheduled synchronous changes.&lt;/p&gt;
  &lt;p id=&quot;JSYj&quot;&gt;Specifically in our example, each change in the text field causes reconciliation of &lt;code&gt;SearchList&lt;/code&gt;, which in turn schedules renders of child components in the same phase. And until all child nodes are processed, &lt;strong&gt;the main thread will be blocked&lt;/strong&gt; by the engine&amp;#x27;s work. If there are too many components to output, the blocking time becomes significant and manifests as a delay in response to user input.&lt;/p&gt;
  &lt;figure id=&quot;A7ZM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d1/35/d135e2a3-13c9-44b8-93e0-059cc35d3cb9.png&quot; width=&quot;2176&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;thkJ&quot;&gt;In the screenshot above, the character &amp;quot;0&amp;quot; was entered into the text field, which gives 2620 child elements. Reconciliation of such a number of elements took &lt;code&gt;253ms&lt;/code&gt;, considering that delays of more than 100ms are noticeable to the human eye, and the estimated time of a browser frame is 16.6ms (at a refresh rate of 60 fps).&lt;/p&gt;
  &lt;figure id=&quot;RxIi&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c0/31/c03136ad-7ab1-42f9-ae84-aa37a16b2ab6.png&quot; width=&quot;2178&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KIoz&quot;&gt;When adding another &amp;quot;0&amp;quot;, 181 elements are displayed. Reconciliation already took &lt;code&gt;146ms&lt;/code&gt;. However, this is still a lot.&lt;/p&gt;
  &lt;figure id=&quot;HHLx&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9c/70/9c703da6-73d6-4259-96ed-0e3fc321a9e2.png&quot; width=&quot;2174&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;lNnm&quot;&gt;Let&amp;#x27;s continue entering zeros. The next &amp;quot;0&amp;quot; will give only 10 elements, and here we already see an acceptable duration of &lt;code&gt;10.6ms&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;QZbl&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bd/d5/bdd5d1dd-65df-4087-85ef-5d55d3def487.png&quot; width=&quot;2174&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ESZV&quot;&gt;The fourth zero will leave only a single element, and the task duration was only &lt;code&gt;2ms&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;RKCI&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c6/33/c633a128-d1a0-4d99-993d-3ad431b9d477.png&quot; width=&quot;2174&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KRfX&quot;&gt;Which is not much more than with the value &amp;quot;00000&amp;quot;, where there are no elements to display.&lt;/p&gt;
  &lt;p id=&quot;vz4k&quot;&gt;Now let&amp;#x27;s do the reverse procedure and delete one character at a time from the text field.&lt;/p&gt;
  &lt;figure id=&quot;rsvS&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c7/89/c78934eb-dd54-41fa-a06e-715f385d50fe.png&quot; width=&quot;2176&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;8T8e&quot;&gt;Value &amp;quot;0000&amp;quot;. Duration &lt;code&gt;0.9ms&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;d8B4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3b/dd/3bdde961-dabf-4a18-bad6-b1cc768d3a1a.png&quot; width=&quot;2176&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;aNx9&quot;&gt;Value &amp;quot;000&amp;quot;. Duration &lt;code&gt;1.2ms&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;zoIy&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2a/77/2a770fd5-c31e-4396-887f-aa21d4697530.png&quot; width=&quot;2178&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;XC0J&quot;&gt;Value &amp;quot;00&amp;quot;. Duration &lt;code&gt;12.8ms&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;Lrp2&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/73/e6/73e65c4b-db71-4a06-945f-78bb4f3a34d5.png&quot; width=&quot;2176&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Wd5c&quot;&gt;Value &amp;quot;0&amp;quot;. Duration &lt;code&gt;178ms&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;YBLw&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a5/ae/a5ae2266-bbb7-44c2-bc22-5079951af9bb.png&quot; width=&quot;2178&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hYgo&quot;&gt;And finally, an empty string. Duration &lt;code&gt;699ms&lt;/code&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;pbEk&quot;&gt;Task Priorities&lt;/h2&gt;
  &lt;p id=&quot;3sdl&quot;&gt;The problem is obvious. Blocking operations have been a problem that needed to be addressed for quite some time. As a solution, one could suggest splitting heavy operations into threads and executing them in parallel. But a JavaScript task cannot be executed in multiple threads.&lt;/p&gt;
  &lt;h3 id=&quot;C9GJ&quot;&gt;ReactPriorityLevel&lt;/h3&gt;
  &lt;p id=&quot;ZtDB&quot;&gt;The first attempts to solve the problem were made in 2016. In version 15.2.1, the concept of &lt;code&gt;ReactPriorityLevel&lt;/code&gt; was first introduced. Since tasks cannot be divided into parallel threads, they can at least be arranged in order of importance. The essence of &lt;code&gt;ReactPriorityLevel&lt;/code&gt; is to assign a priority flag to each tree update.&lt;/p&gt;
  &lt;p id=&quot;voUY&quot;&gt;Initially, there were 5 such priorities, from most important to less important:&lt;/p&gt;
  &lt;ul id=&quot;y8bi&quot;&gt;
    &lt;li id=&quot;SFX3&quot;&gt;&lt;strong&gt;SynchronousPriority&lt;/strong&gt; - for controlled input updates and synchronous operations.&lt;/li&gt;
    &lt;li id=&quot;MRBx&quot;&gt;&lt;strong&gt;AnimationPriority&lt;/strong&gt; - animations must complete the calculation of the next step in the current frame.&lt;/li&gt;
    &lt;li id=&quot;6AV6&quot;&gt;&lt;strong&gt;HighPriority&lt;/strong&gt; - updates that should be executed as quickly as possible to maintain optimal response&lt;/li&gt;
    &lt;li id=&quot;Nv5J&quot;&gt;&lt;strong&gt;LowPriority&lt;/strong&gt; - for requests and receiving responses from stores&lt;/li&gt;
    &lt;li id=&quot;tR50&quot;&gt;&lt;strong&gt;OffscreenPriority&lt;/strong&gt; - for elements that were hidden on the page but become visible again&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;dkDR&quot;&gt;Expiration Times&lt;/h3&gt;
  &lt;p id=&quot;Pf5d&quot;&gt;In general, the concept of priorities gave a certain result. But not all problems were solved. &lt;code&gt;ReactPriorityLevel&lt;/code&gt; divided tasks into groups, but within a group, tasks still have the same priority. For example, there may well be several animated elements on a page. At the end of 2017, React 16.1 was introduced, which instead of priority flags suggested setting a time by which the task should be processed. The more important the task, the smaller &lt;code&gt;expirationTime&lt;/code&gt; it had and, accordingly, was taken into work earlier than the others.&lt;/p&gt;
  &lt;h3 id=&quot;koK5&quot;&gt;Lanes&lt;/h3&gt;
  &lt;p id=&quot;8OuX&quot;&gt;The Expiration Times concept turned out to be quite workable. React 16 lived with it for the next 3 years. The essence of the model was that having priorities A &amp;gt; B &amp;gt; C, you cannot take B into work without executing A. Similarly, you cannot work on C without taking A and B into work.&lt;/p&gt;
  &lt;p id=&quot;m4MG&quot;&gt;This approach worked well until &lt;code&gt;Suspense&lt;/code&gt; appeared. Prioritization works as long as all tasks are performed linearly. When a deferred task (such as &lt;code&gt;Suspense&lt;/code&gt;) intervenes in the process, a situation arises where a deferred task with high priority blocks less priority main ones.&lt;/p&gt;
  &lt;p id=&quot;2zzE&quot;&gt;In terms of expressing a group of several priorities at once, the Expiration Times model is quite limited. To understand whether to include a task in the scope of work in the current iteration, it is enough to compare the relative priorities:&lt;/p&gt;
  &lt;pre id=&quot;0kTH&quot; data-lang=&quot;javascript&quot;&gt;const isTaskIncludedInBatch = priorityOfTask &amp;gt;= priorityOfBatch;&lt;/pre&gt;
  &lt;p id=&quot;YJ9g&quot;&gt;To solve the Suspense problem, one could use a &lt;code&gt;Set&lt;/code&gt; with priorities. But this would be very costly in terms of performance and would negate all the benefits of prioritization.&lt;/p&gt;
  &lt;p id=&quot;35N5&quot;&gt;As a compromise, one could introduce priority ranges approximately as follows:&lt;/p&gt;
  &lt;pre id=&quot;796c&quot; data-lang=&quot;javascript&quot;&gt;const isTaskIncludedInBatch = taskPriority &amp;lt;= highestPriorityInRange &amp;amp;&amp;amp; taskPriority &amp;gt;= lowestPriorityInRange;&lt;/pre&gt;
  &lt;p id=&quot;7UMu&quot;&gt;But even if we turn a blind eye to the fact that in this case it will be necessary to keep two fields, this approach still does not solve all the problems. For example, how to remove a task in the middle of the range? Some solution can certainly be found. But be that as it may, any manipulation of ranges will inevitably affect other groups, and their maintenance and stability will turn into a real nightmare.&lt;/p&gt;
  &lt;p id=&quot;0ETk&quot;&gt;To avoid all these problems, the React developers decided to separate the two concepts of prioritization and grouping. And they proposed to express groups of tasks by relative numbers representing a bit mask.&lt;/p&gt;
  &lt;pre id=&quot;rDUk&quot; data-lang=&quot;javascript&quot;&gt;const isTaskIncludedInBatch = (task &amp;amp; batchOfTasks) !== 0;&lt;/pre&gt;
  &lt;p id=&quot;6mT5&quot;&gt;The type of bit mask representing a task is called &lt;strong&gt;Lane&lt;/strong&gt;. And the bit mask representing a group of tasks is &lt;strong&gt;Lanes&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;fxaQ&quot;&gt;How Lanes Work&lt;/h2&gt;
  &lt;p id=&quot;TbtN&quot;&gt;In the official release &lt;code&gt;Lanes&lt;/code&gt; first appeared in October 2020 with version React 17.0.0. This was a fairly large refactoring that significantly affected the work of &lt;code&gt;Fiber&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;XW1w&quot;&gt;At the moment, the latest stable version of React is v19.0.0. Most of the work Lanes is encapsulated in the &lt;code&gt;ReactFiberLane&lt;/code&gt; reconciler module.&lt;/p&gt;
  &lt;p id=&quot;qaOE&quot;&gt;In fact, &lt;code&gt;Lane&lt;/code&gt; is a 32-bit number where each bit indicates the task&amp;#x27;s belonging to a certain lane.&lt;/p&gt;
  &lt;pre id=&quot;U73b&quot; data-lang=&quot;flow&quot;&gt;/packages/react-reconciler/src/ReactFiberLane.js#L39
export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncHydrationLane: Lane = /*               */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000010;
export const SyncLaneIndex: number = 1;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000001000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000100000;

export const SyncUpdateLanes: Lane =
SyncLane | InputContinuousLane | DefaultLane;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000001000000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111110000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000001000000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000001000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;

const NonIdleLanes: Lanes = /*                          */ 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0010000000000000000000000000000;

export const OffscreenLane: Lane = /*                   */ 0b0100000000000000000000000000000;
export const DeferredLane: Lane = /*                    */ 0b1000000000000000000000000000000;&lt;/pre&gt;
  &lt;p id=&quot;IpPE&quot;&gt;Conditionally, all lanes can be divided into &lt;strong&gt;synchronous&lt;/strong&gt; and &lt;strong&gt;deferred&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;ozvr&quot;&gt;Synchronous Lanes&lt;/h2&gt;
  &lt;p id=&quot;U6p9&quot;&gt;Synchronous lanes include &lt;code&gt;SyncLane&lt;/code&gt;, &lt;code&gt;InputContinuousLane&lt;/code&gt; and &lt;code&gt;DefaultLane&lt;/code&gt;. Tasks placed in these lanes have the highest priority and are executed synchronously in the current phase of the reconciler.&lt;/p&gt;
  &lt;p id=&quot;fNVQ&quot;&gt;From the names, we can guess that different types of tasks may have different priorities even in the synchronous phase. The most important ones are:&lt;/p&gt;
  &lt;ul id=&quot;9njV&quot;&gt;
    &lt;li id=&quot;6VzH&quot;&gt;&lt;strong&gt;Root unmounting&lt;/strong&gt;, which leads to immediate clearing of the React state and initiates an asynchronous removal of the tree elements&lt;/li&gt;
    &lt;li id=&quot;UKXY&quot;&gt;&lt;strong&gt;Hot Reload&lt;/strong&gt;, since JS modules must be immediately replaced with new ones&lt;/li&gt;
    &lt;li id=&quot;aFug&quot;&gt;Any &lt;strong&gt;overrides in Dev Tools&lt;/strong&gt; must always happen first&lt;/li&gt;
    &lt;li id=&quot;CX7r&quot;&gt;The &lt;code&gt;useSyncExternalStore&lt;/code&gt; hook synchronously reads data from external storage, this must happen at the very beginning of the reconciliation&lt;/li&gt;
    &lt;li id=&quot;BOQq&quot;&gt;The &lt;code&gt;useOptimistic&lt;/code&gt; hook guarantees synchronous optimistic state update&lt;/li&gt;
    &lt;li id=&quot;1G77&quot;&gt;In the current version of React, there is no official component &lt;code&gt;&amp;lt;Activity /&amp;gt;&lt;/code&gt; (formerly known as &lt;code&gt;&amp;lt;Offscreen /&amp;gt;&lt;/code&gt;), but it is present in the engine and may someday become available. This component has three modes: &amp;quot;hidden&amp;quot;, &amp;quot;visible&amp;quot; and &amp;quot;manual&amp;quot;. Unlike the first two, the manual mode assumes that the developer himself is responsible for hiding/showing the component and manually calls &lt;code&gt;Activity.attach()&lt;/code&gt; and &lt;code&gt;Activity.detach()&lt;/code&gt;. These two methods are also executed synchronously so that React can immediately queue work for mounting/unmounting the component.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;J85P&quot;&gt;All the above processes happen in the &lt;code&gt;SyncLane&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;3Jdi&quot;&gt;Event Priorities&lt;/h3&gt;
  &lt;p id=&quot;B96i&quot;&gt;The essential dispatcher of reactions in React is events. More precisely, synthetic events. And among all potential events, there are more important and less important ones. Therefore, React separates them all into three conditional groups:&lt;/p&gt;
  &lt;ul id=&quot;21bE&quot;&gt;
    &lt;li id=&quot;r0HX&quot;&gt;&lt;strong&gt;Discrete&lt;/strong&gt; - events triggered directly by the user (for example, &lt;code&gt;MouseEvent&lt;/code&gt; or &lt;code&gt;KeyboardEvent&lt;/code&gt;) and at the same time all events in the sequence are intentional, for example &amp;quot;click&amp;quot;. Such events are assigned priority &lt;code&gt;DiscreteEventPriority&lt;/code&gt; and they will be executed in &lt;strong&gt;SyncLane&lt;/strong&gt;. These events can interrupt background tasks, but cannot be grouped over time (each event happens here and now).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;dWcJ&quot; data-lang=&quot;jsx&quot;&gt;import { useState } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

function App() {
  const [count, setCount] = useState(0);
  const handleClick = () =&amp;gt; {
    setCount(count + 1);
  };
  return &amp;lt;button onClick={handleClick}&amp;gt;Update state in the SyncLane&amp;lt;/button&amp;gt;;
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;p id=&quot;OgZU&quot;&gt;At the moment, the list of discrete events looks like this:&lt;/p&gt;
  &lt;pre id=&quot;LXLQ&quot;&gt;beforetoggle
cancel
click
close
contextmenu
copy
cut
auxclick
dblclick
dragend
dragstart
drop
focusin
focusout
input
invalid
keydown
keypress
keyup
mousedown
mouseup
paste
pause
play
pointercancel
pointerdown
pointerup
ratechange
reset
resize
seeked
submit
toggle
touchcancel
touchend
touchstart
volumechange
change
selectionchange
textInput
compositionstart
compositionend
compositionupdate
beforeblur
afterblur
beforeinput
blur
fullscreenchange
focus
hashchange
popstate
select
selectstart&lt;/pre&gt;
  &lt;ul id=&quot;m9MN&quot;&gt;
    &lt;li id=&quot;hyAV&quot;&gt;&lt;strong&gt;Continuous&lt;/strong&gt; - events triggered directly by the user, but the user cannot distinguish individual events in the sequence (for example, &lt;code&gt;mouseover&lt;/code&gt;). Such events are assigned priority &lt;code&gt;ContinuousEventPriority&lt;/code&gt; and they are executed in &lt;strong&gt;InputContinuousLane&lt;/strong&gt;. These events can interrupt background tasks and can be grouped over time.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;jIRu&quot; data-lang=&quot;jsx&quot;&gt;import { useState } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

function App() {
  const [count, setCount] = useState(0);
  const handleMouseOver = () =&amp;gt; setCount(count + 1);

  return (
    &amp;lt;div onMouseOver={handleMouseOver}&amp;gt;
      Update state in the InputContinuousLane
    &amp;lt;/div&amp;gt;
  );
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;p id=&quot;G0re&quot;&gt;The list of continuous events:&lt;/p&gt;
  &lt;pre id=&quot;SLTD&quot;&gt;drag
dragenter
dragexit
dragleave
dragover
mousemove
mouseout
mouseover
pointermove
pointerout
pointerover
scroll
touchmove
wheel
mouseenter
mouseleave
pointerenter
pointerleave&lt;/pre&gt;
  &lt;ul id=&quot;bmHn&quot;&gt;
    &lt;li id=&quot;BVmy&quot;&gt;&lt;strong&gt;Others&lt;/strong&gt; - events that do not fall into the first two groups (except &amp;quot;message&amp;quot;, their priority is determined dynamically depending on the current priority of the scheduler). Such events are assigned priority &lt;code&gt;DefaultEventPriority&lt;/code&gt; and they are executed in &lt;strong&gt;DefaultLane&lt;/strong&gt; accordingly. The absence of an event is also considered &lt;code&gt;DefaultEventPriority&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;D5ZL&quot; data-lang=&quot;jsx&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));

root.render(
  &amp;lt;div&amp;gt;
    No events emitted. Common rendering uses the DefaultLane
  &amp;lt;/div&amp;gt;
);&lt;/pre&gt;
  &lt;h2 id=&quot;ZrMw&quot;&gt;Deferred Lanes&lt;/h2&gt;
  &lt;p id=&quot;32gg&quot;&gt;As you might guess from the name, they can be executed asynchronously with a delay. They have &lt;strong&gt;lower priority&lt;/strong&gt; compared to synchronous lanes and are executed in the background after all synchronous lanes have been processed. Tasks executed in deferred lanes are considered &lt;strong&gt;non-blocking&lt;/strong&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;wSbn&quot;&gt;TransitionLane&lt;/h3&gt;
  &lt;p id=&quot;Dqoz&quot;&gt;The most obvious way to put a deferred task is through &lt;code&gt;startTransition&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;dNLY&quot; data-lang=&quot;jsx&quot;&gt;import { startTransition, useEffect, useState } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

function App() {
  const [, setHigh] = useState(0);
  const [, setLow] = useState(0);

  useEffect(() =&amp;gt; {
    startTransition(() =&amp;gt; {
      // 2. TransitionLane will be proceeded in a background after a sync lane
      setLow((prevLow) =&amp;gt; prevLow + 1);
    });

    // 1. DefaultLane will be done first
    setHigh((prevHigh) =&amp;gt; prevHigh + 1);
  }, []);

  return null;
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;figure id=&quot;l6Ww&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ab/b8/abb8d987-10c2-4de7-bfc0-5eca2daa0316.png&quot; width=&quot;2014&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;yooW&quot;&gt;Tasks placed in &lt;code&gt;TransitionLane&lt;/code&gt; can &lt;strong&gt;parallelize&lt;/strong&gt;. For this, there are 15 lanes &lt;code&gt;TransitionLane1..15&lt;/code&gt;. Unlike synchronous lanes, their effect is more effective to combine into packages than to parallelize.&lt;/p&gt;
  &lt;pre id=&quot;eUTh&quot; data-lang=&quot;jsx&quot;&gt;import { startTransition, useEffect, useState } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

function App() {
  const [, setHigh] = useState(0);
  const [, setLow] = useState(0);

  useEffect(() =&amp;gt; {
    // TransitionLane1
    startTransition(() =&amp;gt; {
      setLow((prevLow) =&amp;gt; prevLow + 1);
    });

    // TransitionLane2
    setTimeout(() =&amp;gt; {
      startTransition(() =&amp;gt; {
        setLow((prevLow) =&amp;gt; prevLow + 1);
      });
    }, 0);

    // TransitionLane3
    setTimeout(() =&amp;gt; {
      startTransition(() =&amp;gt; {
        setLow((prevLow) =&amp;gt; prevLow + 1);
      });
    }, 0);
    
    // DefaultLane
    setHigh((prevHigh) =&amp;gt; prevHigh + 1);
  }, []);

  return null;
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;figure id=&quot;7hfV&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/32/9e/329e7afd-49b8-43bd-8503-f4c3bc8df137.png&quot; width=&quot;2016&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;QEY3&quot;&gt;RetryLane&lt;/h3&gt;
  &lt;p id=&quot;q0mc&quot;&gt;Another way to perform a deferred task is &lt;code&gt;Suspense&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;6QWd&quot; data-lang=&quot;jsx&quot;&gt;import { Suspense, use } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

const cache = new Map();

function fetchData() {
  if (!cache.has(&amp;quot;data&amp;quot;)) {
    cache.set(&amp;quot;data&amp;quot;, getData());
  }
  return cache.get(&amp;quot;data&amp;quot;);
}

async function getData() {
  // Add a fake delay to make waiting noticeable.
  await new Promise((resolve) =&amp;gt; {
    setTimeout(resolve, 1_000);
  });

  return [&amp;quot;item1&amp;quot;, &amp;quot;item2&amp;quot;, &amp;quot;item3&amp;quot;];
}

function AsyncComponent() {
  const items = use(fetchData());

  return (
    &amp;lt;ul&amp;gt;
      {items.map((item) =&amp;gt; (
        &amp;lt;li key={item}&amp;gt;{item}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;Retry lanes&amp;lt;/h1&amp;gt;
      &amp;lt;Suspense fallback=&amp;quot;Loading...&amp;quot;&amp;gt;
        &amp;lt;AsyncComponent /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;figure id=&quot;1Am2&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3e/16/3e16474b-93c8-49eb-8b29-a0864f9a3442.png&quot; width=&quot;2192&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;yz0i&quot;&gt;In simple words, &lt;code&gt;Suspense&lt;/code&gt; first renders the fallback component. Then, when one or more promises inside the Suspended component resolve, Fiber will try to re-render, putting the task in &lt;code&gt;RetryLane&lt;/code&gt;. Like &lt;code&gt;TransitionLane&lt;/code&gt;, &lt;code&gt;RetryLane&lt;/code&gt; can parallelize and has four lanes &lt;code&gt;RetryLane1..4&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;oc5C&quot; data-lang=&quot;jsx&quot;&gt;function AsyncComponent() {
  const items = use(fetchData());
  const meta = use(fetchMeta());

  return (
    &amp;lt;ul&amp;gt;
      {items.map((item) =&amp;gt; (
        &amp;lt;li key={item}&amp;gt;{item}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;Retry lanes&amp;lt;/h1&amp;gt;
      &amp;lt;Suspense fallback=&amp;quot;Loading...&amp;quot;&amp;gt;
        &amp;lt;AsyncComponent /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;figure id=&quot;sJVe&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a6/69/a66984da-47e0-493d-bcb5-f99682fa7390.png&quot; width=&quot;2194&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CpKm&quot;&gt;The same work happens with components &lt;code&gt;SuspenseList&lt;/code&gt; and &lt;code&gt;Activity&lt;/code&gt;. But they are not included in the latest, today&amp;#x27;s React release. So it is not worth discussing them in detail now.&lt;/p&gt;
  &lt;h3 id=&quot;bc5g&quot;&gt;IdleLane&lt;/h3&gt;
  &lt;p id=&quot;uKyj&quot;&gt;The next by priority lane is &lt;code&gt;IdleLane&lt;/code&gt;. The idea of &lt;code&gt;IdleLane&lt;/code&gt; is to perform some tasks only when the engine is idle and not busy with other, more important tasks. This can be compared to the Idle period in the browser scheduler (I wrote more about this in the article &lt;a href=&quot;https://blog.frontend-almanac.com/chromium-rendering&quot; target=&quot;_blank&quot;&gt;Chromium. Web page rendering using Blink, CC and scheduler&lt;/a&gt;), but at the Fiber engine and React scheduler level.&lt;/p&gt;
  &lt;p id=&quot;J6GU&quot;&gt;At the moment, there is no React DOM API that could cause an update with idle priority, nor native DOM events with idle priority. Therefore, for now, this lane can only be used through internal methods of the engine, not available in the prod build of React.&lt;/p&gt;
  &lt;h3 id=&quot;K625&quot;&gt;OffscreenLane&lt;/h3&gt;
  &lt;p id=&quot;Uymn&quot;&gt;Just above, when I was talking about synchronous lanes, I mentioned a so-called component &lt;code&gt;Offscreen&lt;/code&gt;, which is now called &lt;code&gt;Activity&lt;/code&gt;. Its idea is to determine whether a component is currently in the visible part of the page, or outside its boundaries. If the component is not visible on the screen, then the tasks it has are of low priority. Therefore, for such tasks, the lane &lt;code&gt;OffscreenLane&lt;/code&gt; was allocated.&lt;/p&gt;
  &lt;p id=&quot;q1xN&quot;&gt;Unfortunately, this is still another component that has not yet entered the current React release. But it has all the chances to enter one of the next ones. We hope to see it soon.&lt;/p&gt;
  &lt;h3 id=&quot;Ztlv&quot;&gt;DeferredLane&lt;/h3&gt;
  &lt;p id=&quot;MNc9&quot;&gt;It&amp;#x27;s not hard to guess that the lane &lt;code&gt;DeferredLane&lt;/code&gt; is intended for the &lt;code&gt;useDeferredValue&lt;/code&gt; hook. However, it&amp;#x27;s not as simple as it might seem.&lt;/p&gt;
  &lt;pre id=&quot;BMRK&quot; data-lang=&quot;jsx&quot;&gt;import { useDeferredValue } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

function App() {
  const text = useDeferredValue(&amp;quot;Final&amp;quot;);

  return &amp;lt;div&amp;gt;{text}&amp;lt;/div&amp;gt;;
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;figure id=&quot;dauJ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/69/d5/69d5d3ba-99e9-4b59-912b-f6217fcf894c.png&quot; width=&quot;2192&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7QLM&quot;&gt;If the hook does not have a second argument - &lt;code&gt;initialValue&lt;/code&gt;, it has nothing to compare the new value with when mounting and it will be executed synchronously in &lt;code&gt;DefaultLane&lt;/code&gt;. However, if the hook has a previous value with which it can compare the current one, for example when the hook is mounted and updated again or if an &lt;code&gt;initialValue&lt;/code&gt; is specified, we will see the following picture.&lt;/p&gt;
  &lt;pre id=&quot;vte6&quot; data-lang=&quot;jsx&quot;&gt;import { useDeferredValue } from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

function App() {
  const text = useDeferredValue(&amp;quot;Final&amp;quot;, &amp;quot;Initial&amp;quot;);

  return &amp;lt;div&amp;gt;{text}&amp;lt;/div&amp;gt;;
}

const root = createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(&amp;lt;App /&amp;gt;);&lt;/pre&gt;
  &lt;figure id=&quot;y041&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/78/b5/78b50eff-b2ea-4246-b361-ab8264fe9d41.png&quot; width=&quot;2194&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Rj3L&quot;&gt;The work was actually done in &lt;code&gt;TransitionLane&lt;/code&gt;. If we had tried to execute the hook in the &lt;code&gt;Offscreen&lt;/code&gt; component, we would have seen that it was executed in &lt;code&gt;OffscreenLane&lt;/code&gt;. The reason is that &lt;code&gt;DeferredLane&lt;/code&gt; is a technical intermediate lane. It is only for logical separation of deferred tasks from the others. This task does not have its own execution priority and is always mixed with other lanes depending on the situation.&lt;/p&gt;
  &lt;h2 id=&quot;r4vX&quot;&gt;Non-blocking Rendering&lt;/h2&gt;
  &lt;p id=&quot;6TYl&quot;&gt;Let&amp;#x27;s now return to our original example and try to get rid of the interface blocking heavy operations.&lt;/p&gt;
  &lt;pre id=&quot;5HjS&quot; data-lang=&quot;jsx&quot;&gt;const SearchList = ({ items, filter }) =&amp;gt; {
  // Heavy filtering operation (simulation)
  const filteredItems = items.filter((item) =&amp;gt;
    item.toLowerCase().includes(filter.toLowerCase()),
  );

  return (
    &amp;lt;ul&amp;gt;
      {filteredItems.map((item, i) =&amp;gt; (
        &amp;lt;li key={i}&amp;gt;{item}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};

const App = () =&amp;gt; {
  const [inputValue, setInputValue] = useState(&amp;quot;&amp;quot;);
  const deferredValue = useDeferredValue(inputValue);

  // Generate large list for demonstration
  const bigList = Array(10_000)
    .fill(null)
    .map((_, i) =&amp;gt; &amp;#x60;Item ${i + 1}&amp;#x60;);

  const handleChange = (e) =&amp;gt; {
    setInputValue(e.target.value);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        type=&amp;quot;text&amp;quot;
        value={inputValue}
        onChange={handleChange}
        placeholder=&amp;quot;Search...&amp;quot;
      /&amp;gt;

      &amp;lt;SearchList items={bigList} filter={deferredValue} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/pre&gt;
  &lt;figure id=&quot;yNBT&quot; class=&quot;m_column&quot;&gt;
    &lt;iframe src=&quot;https://codepen.io/rmaksimov/embed/zxYOoyr?default-tab=js,result&quot;&gt;&lt;/iframe&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;Ibr7&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cb/ce/cbce822d-4eeb-4ef4-97f3-2729b2a43f4a.png&quot; width=&quot;2194&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;a2zF&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d4/50/d450f271-1a21-4773-b940-00bf3c7c75ff.png&quot; width=&quot;2196&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;HPVJ&quot;&gt;All we did in &lt;code&gt;SearchList&lt;/code&gt; was to pass &lt;code&gt;deferredValue&lt;/code&gt; instead of &lt;code&gt;inputValue&lt;/code&gt;. This allowed us to put the task of calculating the array of elements in the deferred &lt;code&gt;TransitionLane&lt;/code&gt; and unlock synchronous updates of the input element.&lt;/p&gt;
  &lt;p id=&quot;L5Js&quot;&gt;Of course, no miracles from React are expected. &lt;strong&gt;Heavy operation remains heavy&lt;/strong&gt;. And React, being just a JavaScript library, is still executed in the main browser thread. The Lanes system is just a way to prioritize tasks. More important tasks are done first, less important ones are postponed. However, if a heavy deferred task is taken into work, it will still block the main thread.&lt;/p&gt;
  &lt;h2 id=&quot;NE4a&quot;&gt;Practical Recommendations for Using Lanes&lt;/h2&gt;
  &lt;p id=&quot;jUGv&quot;&gt;Understanding the Lanes system in React can significantly help in optimizing application performance. Here are some practical recommendations:&lt;/p&gt;
  &lt;ol id=&quot;QeR3&quot;&gt;
    &lt;li id=&quot;e5p2&quot;&gt;&lt;strong&gt;Use useDeferredValue for heavy rendering operations&lt;/strong&gt;. As we saw in the example with search, deferred value allows you to maintain responsiveness of the interface even when working with large amounts of data.&lt;/li&gt;
    &lt;li id=&quot;sLgd&quot;&gt;&lt;strong&gt;Apply startTransition for non-priority UI updates&lt;/strong&gt;. When you need to perform an update that does not require immediate reaction (for example, switching tabs or loading additional content), wrap it in &lt;code&gt;startTransition&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;gRu6&quot;&gt;&lt;strong&gt;Separate synchronous and asynchronous parts of your application&lt;/strong&gt;. Elements that require immediate reaction (input fields, buttons) should be updated synchronously, and heavy calculations and displaying large lists should be postponed.&lt;/li&gt;
    &lt;li id=&quot;RpXi&quot;&gt;&lt;strong&gt;Use Suspense for asynchronous data loading&lt;/strong&gt;. This will allow React to automatically manage task priorities when loading data from the server.&lt;/li&gt;
    &lt;li id=&quot;xja7&quot;&gt;&lt;strong&gt;Remember about component nesting&lt;/strong&gt;. The more deeply nested the component, the more time it may take to update. Try to move heavy components closer to the root of the tree.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2 id=&quot;Qhq2&quot;&gt;Future React Lanes&lt;/h2&gt;
  &lt;p id=&quot;VoyT&quot;&gt;The Lanes system continues to evolve. In future versions of React, we can expect:&lt;/p&gt;
  &lt;ul id=&quot;odyz&quot;&gt;
    &lt;li id=&quot;VH0V&quot;&gt;Official API for the &lt;code&gt;Activity&lt;/code&gt; component, which will allow more flexible management of rendering priorities depending on component visibility.&lt;/li&gt;
    &lt;li id=&quot;OdYX&quot;&gt;Possible integration of Web Workers for performing heavy calculations in separate threads.&lt;/li&gt;
    &lt;li id=&quot;UDvI&quot;&gt;Further development of the API for more fine-grained task priority tuning.&lt;/li&gt;
    &lt;li id=&quot;m2fR&quot;&gt;Improvement of integration with profiling tools for more visual display of system priority work.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;XHrh&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;3Edt&quot;&gt;The Lanes system in React is a powerful tool for optimizing user experience. It does not make your application faster in absolute terms, but it allows more reasonable distribution of computing resources, giving priority to what is important for the user right now.&lt;/p&gt;
  &lt;p id=&quot;73ox&quot;&gt;Proper use of &lt;code&gt;useDeferredValue&lt;/code&gt;, &lt;code&gt;startTransition&lt;/code&gt; and &lt;code&gt;Suspense&lt;/code&gt; can significantly improve the perceived performance of your application, making it more responsive even when performing complex operations. This is especially important for modern web applications that often work with large amounts of data and complex interfaces.&lt;/p&gt;
  &lt;p id=&quot;n5JE&quot;&gt;In the end, understanding the internal structure of React and the principles of system priority work allows developers to create more efficient and user-friendly applications, which is the key goal of any frontend development.&lt;/p&gt;
  &lt;p id=&quot;sFQG&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;7vk5&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;hjS9&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/react-lanes&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/react-lanes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:style-setproperty-vs-setattribute</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/style-setproperty-vs-setattribute?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>style.setProperty vs setAttribute</title><published>2024-11-18T07:59:06.498Z</published><updated>2024-11-19T11:57:14.484Z</updated><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e5/22/e522a213-9a7d-45e7-83a3-384906f209a9.png&quot;&gt;Recently, I encountered an interesting question: which is faster, element.style.setProperty(property, value) or element.setAttribute('style', 'property: value')? At first glance, the answer seems obvious. Logic suggests that setProperty should store the value directly in the CSSOM, while setAttribute first sets the style attribute, which is then parsed into the CSSOM. Therefore, setProperty should be faster. But is it really that straightforward? Let's delve into it.</summary><content type="html">
  &lt;p id=&quot;MnWX&quot;&gt;Recently, I encountered an interesting question: which is faster, &lt;code&gt;element.style.setProperty(property, value)&lt;/code&gt; or &lt;code&gt;element.setAttribute(&amp;#x27;style&amp;#x27;, &amp;#x27;property: value&amp;#x27;)&lt;/code&gt;? At first glance, the answer seems obvious. Logic suggests that &lt;code&gt;setProperty&lt;/code&gt; should store the value directly in the CSSOM, while &lt;code&gt;setAttribute&lt;/code&gt; first sets the &lt;code&gt;style&lt;/code&gt; attribute, which is then parsed into the CSSOM. Therefore, &lt;code&gt;setProperty&lt;/code&gt; should be faster. But is it really that straightforward? Let&amp;#x27;s delve into it.&lt;/p&gt;
  &lt;p id=&quot;rq9p&quot;&gt;To begin, let&amp;#x27;s refresh some foundational concepts. We know that styles are described using the CSS language. After receiving a string representation of styles in CSS, the browser parses it and constructs a CSSOM object. The interface of this object is defined by the specification &lt;a href=&quot;https://www.w3.org/TR/cssom-1&quot; target=&quot;_blank&quot;&gt;https://www.w3.org/TR/cssom-1&lt;/a&gt;. It adheres to the principles of cascading and inheritance outlined in &lt;a href=&quot;https://www.w3.org/TR/css-cascade-4&quot; target=&quot;_blank&quot;&gt;https://www.w3.org/TR/css-cascade-4&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;UFDg&quot;&gt;From the specifications mentioned above, we know that the fundamental unit of CSS is a &lt;code&gt;property&lt;/code&gt;. Each property is assigned a value that is specific to that property. If a value is not explicitly specified, it is inherited from a parent style, or, if there is no parent, it will be set to the &lt;a href=&quot;https://drafts.csswg.org/css-cascade-4/#initial-value&quot; target=&quot;_blank&quot;&gt;initial value&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;8QeF&quot;&gt;The set of properties for an element is organized into &lt;a href=&quot;https://www.w3.org/TR/cssom-1/#css-rules&quot; target=&quot;_blank&quot;&gt;CSSRule&lt;/a&gt;. There are different types of rules, with the most common type being the &lt;code&gt;CSSStyleRule&lt;/code&gt;, which defines the properties of an element. This type of rule begins with a valid selector followed by curly braces containing a set of properties and values, formatted as &lt;code&gt;&amp;lt;selector&amp;gt;: { ... }&lt;/code&gt;. Other types of rules exist as well, such as the &lt;code&gt;CSSFontFaceRule&lt;/code&gt;, which describes the parameters for an imported font using &lt;code&gt;@font-face { ... }&lt;/code&gt;, the &lt;code&gt;CSSMediaRule&lt;/code&gt;, represented as &lt;code&gt;@media { ... }&lt;/code&gt;, and others. You can find a complete list in the specification at &lt;a href=&quot;https://www.w3.org/TR/cssom-1/#css-rules&quot; target=&quot;_blank&quot;&gt;https://www.w3.org/TR/cssom-1/#css-rules&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;JcRx&quot;&gt;These rules are compiled into what is known as a &lt;a href=&quot;https://www.w3.org/TR/cssom-1/#css-style-sheets&quot; target=&quot;_blank&quot;&gt;CSSStyleSheet&lt;/a&gt;, which serves as an abstract representation of the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag. In turn, style sheets are aggregated into a collection and associated with the document.&lt;/p&gt;
  &lt;p id=&quot;7sUq&quot;&gt;All of these levels of abstraction constitute the &lt;strong&gt;CSS Object Model (CSSOM)&lt;/strong&gt;. While the specification primarily describes the syntax of the model, in practice, it also includes the direct implementation of the CSS API used by browsers. Functions such as computed style, color encoding methods, mathematical operations, and many others are also part of CSSOM.&lt;/p&gt;
  &lt;h2 id=&quot;VOI4&quot;&gt;Setting Properties in Blink&lt;/h2&gt;
  &lt;p id=&quot;W04J&quot;&gt;Having covered the theory, let&amp;#x27;s take a closer look under the hood of the browser, specifically at the Blink rendering engine used by browsers like Chromium, Opera, WebView, and others. Blink is part of the Chromium repository and is not supplied independently. Therefore, we will be conducting our experiments using Chromium (version &lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/&quot; target=&quot;_blank&quot;&gt;132.0.6812.1&lt;/a&gt; as of the time this article was written).&lt;/p&gt;
  &lt;h3 id=&quot;gv2b&quot;&gt;style.setProperty&lt;/h3&gt;
  &lt;p id=&quot;2DdA&quot;&gt;Let&amp;#x27;s begin with the method &lt;code&gt;style.setProperty&lt;/code&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;yJNE&quot;&gt;element.style.setProperty(&amp;quot;background-color&amp;quot;, &amp;quot;red&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;eSCp&quot;&gt;To understand what happens under the hood of Blink, it&amp;#x27;s essential to take a closer look at the inner workings of Blink itself.&lt;/p&gt;
  &lt;p id=&quot;tf2V&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#130&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#130&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;bmVs&quot;&gt;void AbstractPropertySetCSSStyleDeclaration::setProperty(
    const ExecutionContext* execution_context,
    const String&amp;amp; property_name,
    const String&amp;amp; value,
    const String&amp;amp; priority,
    ExceptionState&amp;amp; exception_state) {
  CSSPropertyID property_id =
      UnresolvedCSSPropertyID(execution_context, property_name);
  if (!IsValidCSSPropertyID(property_id) || !IsPropertyValid(property_id)) {
    return;
  }
  
  bool important = EqualIgnoringASCIICase(priority, &amp;quot;important&amp;quot;);
  if (!important &amp;amp;&amp;amp; !priority.empty()) {
    return;
  }
  
  const SecureContextMode mode = execution_context
                                     ? execution_context-&amp;gt;GetSecureContextMode()
                                     : SecureContextMode::kInsecureContext;
  SetPropertyInternal(property_id, property_name, value, important, mode,
                      exception_state);
}&lt;/pre&gt;
  &lt;p id=&quot;B5ie&quot;&gt;First, a basic validity check is performed on the property that has been passed in. Then, the style&amp;#x27;s priority is evaluated, and an internal method &lt;code&gt;SetPropertyInternal&lt;/code&gt; is invoked.&lt;/p&gt;
  &lt;p id=&quot;LqzZ&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#236&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#236&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;KRGG&quot;&gt;void AbstractPropertySetCSSStyleDeclaration::SetPropertyInternal(
    CSSPropertyID unresolved_property,
    const String&amp;amp; custom_property_name,
    StringView value,
    bool important,
    SecureContextMode secure_context_mode,
    ExceptionState&amp;amp;) {
  StyleAttributeMutationScope mutation_scope(this);
  WillMutate();
  
  MutableCSSPropertyValueSet::SetResult result;
  if (unresolved_property == CSSPropertyID::kVariable) {
    AtomicString atomic_name(custom_property_name);
    
    bool is_animation_tainted = IsKeyframeStyle();
    result = PropertySet().ParseAndSetCustomProperty(
        atomic_name, value, important, secure_context_mode, ContextStyleSheet(),
        is_animation_tainted);
  } else {
    result = PropertySet().ParseAndSetProperty(unresolved_property, value,
                                               important, secure_context_mode,
                                               ContextStyleSheet());
  }
  
  if (result == MutableCSSPropertyValueSet::kParseError ||
      result == MutableCSSPropertyValueSet::kUnchanged) {
    DidMutate(kNoChanges);
    return;
  }
  
  CSSPropertyID property_id = ResolveCSSPropertyID(unresolved_property);
  
  if (result == MutableCSSPropertyValueSet::kModifiedExisting &amp;amp;&amp;amp;
      CSSProperty::Get(property_id).SupportsIncrementalStyle()) {
    DidMutate(kIndependentPropertyChanged);
  } else {
    DidMutate(kPropertyChanged);
  }
  
  mutation_scope.EnqueueMutationRecord();
}&lt;/pre&gt;
  &lt;p id=&quot;MBJo&quot;&gt;Within this internal method, a mutation process is initiated, the string value is parsed, and this value is assigned to the specified property. Following this, the changes are reflected in the CSSOM. Overall, the process is straightforward.&lt;/p&gt;
  &lt;h3 id=&quot;KqS6&quot;&gt;setAttribute&lt;/h3&gt;
  &lt;p id=&quot;QznG&quot;&gt;Now, Let&amp;#x27;s Examine &lt;code&gt;setAttribute.&lt;/code&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;grJ7&quot;&gt;element.setAttribute(&amp;quot;style&amp;quot;, &amp;quot;background-color: red&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;E5fK&quot;&gt;This operation goes beyond the CSSOM and directly affects elements and their attributes, resulting in modifications to the DOM. Within the DOM, there is a class called &lt;a href=&quot;https://dom.spec.whatwg.org/#interface-attr&quot; target=&quot;_blank&quot;&gt;Attr&lt;/a&gt; that describes a single attribute of an element. There are several ways to set the value of an attribute.&lt;/p&gt;
  &lt;p id=&quot;X7C3&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/dom/attr.cc#73&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/dom/attr.cc#73&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;mgeB&quot;&gt;void Attr::setValue(const AtomicString&amp;amp; value,
                    ExceptionState&amp;amp; exception_state) {
  // Element::setAttribute will remove the attribute if value is null.
  DCHECK(!value.IsNull());
  if (element_) {
    element_-&amp;gt;SetAttributeWithValidation(GetQualifiedName(), value,
                                         exception_state);
  } else {
    standalone_value_or_attached_local_name_ = value;
  }
}&lt;/pre&gt;
  &lt;p id=&quot;XLrc&quot;&gt;This method allows us to assign a value to an existing attribute. In other words, we can do something like this.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;HSMu&quot;&gt;element.style = &amp;quot;background-color: red&amp;quot;;&lt;/pre&gt;
  &lt;p id=&quot;Qh7W&quot;&gt;This will lead to the invocation of the method &lt;code&gt;SetAttributeWithValidation&lt;/code&gt;, which we will discuss shortly.&lt;/p&gt;
  &lt;p id=&quot;7d6d&quot;&gt;However, in our original case, we are not directly accessing the &lt;code&gt;style&lt;/code&gt; attribute; instead, we are calling the &lt;code&gt;setAttribute&lt;/code&gt;method from the &lt;a href=&quot;https://dom.spec.whatwg.org/#interface-element&quot; target=&quot;_blank&quot;&gt;Element&lt;/a&gt; class.&lt;/p&gt;
  &lt;p id=&quot;VTKa&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/dom/element.h#282&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/dom/element.h#282&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;SD8w&quot;&gt;void setAttribute(const QualifiedName&amp;amp; name, const AtomicString&amp;amp; value) {
  SetAttributeWithoutValidation(name, value);
}&lt;/pre&gt;
  &lt;p id=&quot;AWkj&quot;&gt;This method, in turn, calls another method: &lt;code&gt;SetAttributeWithoutValidation&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;9TRD&quot;&gt;So, what are the methods &lt;code&gt;SetAttributeWithoutValidation&lt;/code&gt; and &lt;code&gt;SetAttributeWithValidation&lt;/code&gt;?&lt;/p&gt;
  &lt;p id=&quot;q3Dh&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/dom/element.cc#10314&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/dom/element.cc#10314&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;uJRi&quot;&gt;void Element::SetAttributeWithoutValidation(const QualifiedName&amp;amp; name,
                                            const AtomicString&amp;amp; value) {
  SynchronizeAttribute(name);
  SetAttributeInternal(FindAttributeIndex(name), name, value,
                       AttributeModificationReason::kDirectly);
}

void Element::SetAttributeWithValidation(const QualifiedName&amp;amp; name,
                                         const AtomicString&amp;amp; value,
                                         ExceptionState&amp;amp; exception_state) {
  SynchronizeAttribute(name);
  
  AtomicString trusted_value(TrustedTypesCheckFor(
      ExpectedTrustedTypeForAttribute(name), value, GetExecutionContext(),
      &amp;quot;Element&amp;quot;, &amp;quot;setAttribute&amp;quot;, exception_state));
  if (exception_state.HadException()) {
    return;
  }
  
  SetAttributeInternal(FindAttributeIndex(name), name, trusted_value,
                       AttributeModificationReason::kDirectly);
}&lt;/pre&gt;
  &lt;p id=&quot;8Gsp&quot;&gt;As we can see, both methods essentially perform the same function. They first synchronize the current value of the attribute to account for any pending mutations. After that, they proceed to set the new value. The only difference is that in the second case, the new value is validated against the specified attribute before it is set. In other words, the following is not allowed.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;wm9x&quot;&gt;element.style = &amp;quot;invalid-prop: red&amp;quot;
// &amp;lt;div style&amp;gt;&amp;lt;/div&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;lBaN&quot;&gt;Attempting to assign an invalid style to the &lt;code&gt;style&lt;/code&gt; attribute will result in the attribute&amp;#x27;s value being removed.&lt;/p&gt;
  &lt;p id=&quot;P7FT&quot;&gt;However, there is nothing stopping us from doing this instead.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Ie62&quot;&gt;element.setAttribute(&amp;quot;style&amp;quot;, &amp;quot;invalid-prop: red&amp;quot;)
// &amp;lt;div style=&amp;quot;invalid-prop: red&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;F2LA&quot;&gt;In this case, the &lt;code&gt;style&lt;/code&gt; will be set regardless of what we specified as the value.&lt;/p&gt;
  &lt;p id=&quot;JGHa&quot;&gt;This seems quite strange. Why not validate attribute values all the time? The answer is quite simple. The &lt;code&gt;setAttribute&lt;/code&gt; method acts as a sort of backdoor for attributes. It is designed to operate not only with built-in attributes but also with arbitrary ones, such as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*&quot; target=&quot;_blank&quot;&gt;data-*&lt;/a&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;t5tu&quot;&gt;element.setAttribute(&amp;quot;data-ship-id&amp;quot;, &amp;quot;324&amp;quot;);
element.setAttribute(&amp;quot;data-weapons&amp;quot;, &amp;quot;laserI laserII&amp;quot;);
element.setAttribute(&amp;quot;data-shields&amp;quot;, &amp;quot;72%&amp;quot;);
element.setAttribute(&amp;quot;data-x&amp;quot;, &amp;quot;414354&amp;quot;);
element.setAttribute(&amp;quot;data-y&amp;quot;, &amp;quot;85160&amp;quot;);
element.setAttribute(&amp;quot;data-z&amp;quot;, &amp;quot;31940&amp;quot;);
element.setAttribute(&amp;quot;onclick&amp;quot;, &amp;quot;spaceships[this.dataset.shipId].blasted()&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;ziqF&quot;&gt;The &lt;code&gt;setAttribute&lt;/code&gt; method operates at the DOM element level, and its primary function is to assign a value to an attribute without any validations. A detailed algorithm is described in the &lt;a href=&quot;https://dom.spec.whatwg.org/#dom-element-setattribute&quot; target=&quot;_blank&quot;&gt;DOM standard&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;MxPF&quot;&gt;&lt;strong&gt;Which is Faster?&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;UbPm&quot;&gt;Based on the above information, we can speculate that &lt;code&gt;style.setProperty&lt;/code&gt; should be faster since, unlike &lt;code&gt;setAttribute&lt;/code&gt;, the engine doesn’t need to search for a reference to the attribute object in the attribute table and can directly proceed to set the value. On the other hand, the overhead of validating the value itself could be significant.&lt;/p&gt;
  &lt;h3 id=&quot;qd6f&quot;&gt;&lt;strong&gt;Test 1: One Property&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;qvIg&quot;&gt;To determine which method is actually faster, we will conduct an experiment. For this purpose, we will need an HTML page with a test element and a couple of buttons.&lt;/p&gt;
  &lt;pre data-lang=&quot;html&quot; id=&quot;8l6k&quot;&gt;&amp;lt;div id=&amp;quot;test-element&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;button onclick=&amp;quot;handleSetProperty()&amp;quot;&amp;gt;style.setProperty&amp;lt;/button&amp;gt;
&amp;lt;button onclick=&amp;quot;handleSetAttribute()&amp;quot;&amp;gt;setAttribute&amp;lt;/button&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;jNDw&quot;&gt;Additionally, a simple JavaScript script will be required.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;wLgB&quot;&gt;const N = 100;

function sleep(ms) {
  return new Promise((resolve) =&amp;gt; setTimeout(resolve, ms));
}

function createElement() {
  const el = document.getElementById(&amp;quot;test-element&amp;quot;);
  
  const newEl = document.createElement(&amp;quot;div&amp;quot;);
  newEl.setAttribute(&amp;quot;id&amp;quot;, &amp;quot;test-element&amp;quot;);
  newEl.setAttribute(&amp;quot;width&amp;quot;, 100);
  newEl.setAttribute(&amp;quot;height&amp;quot;, 100);
  
  el.replaceWith(newEl);
  
  return newEl;
}

async function handleSetProperty() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.style.setProperty(&amp;quot;background-color&amp;quot;, &amp;quot;red&amp;quot;);
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}

async function handleSetAttribute() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.setAttribute(&amp;quot;style&amp;quot;, &amp;quot;background-color: red&amp;quot;);
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}&lt;/pre&gt;
  &lt;p id=&quot;QNVr&quot;&gt;This script, upon clicking one of the buttons, will initiate a loop of 100 iterations that sets the value of the style for a test element. To ensure the integrity of the experiment, we will create a new test element during each iteration and insert pauses after creating the element and after setting the value. This approach aims to eliminate browser optimizations and guarantee the rendering of the element after each iteration. We will take measurements separately, following a hard refresh of the page.&lt;/p&gt;
  &lt;p id=&quot;ofPr&quot;&gt;After collecting the measurements from the 100 iterations, we will calculate the simple average to obtain the final result.&lt;/p&gt;
  &lt;pre id=&quot;yPUU&quot;&gt;avgSetProperty     0.07400000274181366
avgSetAttribute    0.08999999761581420&lt;/pre&gt;
  &lt;p id=&quot;BBza&quot;&gt;In this experiment, &lt;code&gt;style.setProperty&lt;/code&gt; clearly takes the lead. However, we only attempted to set a single CSS property.&lt;/p&gt;
  &lt;h3 id=&quot;z3IZ&quot;&gt;&lt;strong&gt;Test 2: Two Properties&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;BsIm&quot;&gt;For objectivity, we will repeat the experiment with two properties simultaneously. To facilitate this, we will make slight adjustments to the test functions.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;kYiE&quot;&gt;async function handleSetProperty() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.style.setProperty(&amp;quot;background-color&amp;quot;, &amp;quot;red&amp;quot;);
    el.style.setProperty(&amp;quot;border&amp;quot;, &amp;quot;1px solid blue&amp;quot;);
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}

async function handleSetAttribute() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.setAttribute(&amp;quot;style&amp;quot;, &amp;quot;background-color: red; border: 1px solid blue;&amp;quot;);
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}&lt;/pre&gt;
  &lt;p id=&quot;HHlS&quot;&gt;Upon running the loops again, we obtained the following result.&lt;/p&gt;
  &lt;pre id=&quot;THDK&quot;&gt;avgSetProperty     0.10900000214576722
avgSetAttribute    0.10399999618530273&lt;/pre&gt;
  &lt;p id=&quot;8pLY&quot;&gt;The situation has changed. The average speed of both methods has approximately equalized. The &lt;code&gt;setAttribute&lt;/code&gt; method is actually a little faster; however, this difference can be attributed to potential measurement inaccuracies. The reason for this is not even validation, as it is still insignificant at this point. In this case, another process comes into play: the mutation of the &lt;a href=&quot;https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface&quot; target=&quot;_blank&quot;&gt;CSSStyleDeclaration&lt;/a&gt; object. This interface describes the structure of a set of styles, such as the body of the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag. A similar object also stores the style for an individual element. I previously provided a code snippet for the method that sets a property value in &lt;code&gt;CSSStyleDeclaration&lt;/code&gt;. Let&amp;#x27;s take another look at it.&lt;/p&gt;
  &lt;p id=&quot;kuUL&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#236&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#236&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;FJq3&quot;&gt;void AbstractPropertySetCSSStyleDeclaration::SetPropertyInternal(
    CSSPropertyID unresolved_property,
    const String&amp;amp; custom_property_name,
    StringView value,
    bool important,
    SecureContextMode secure_context_mode,
    ExceptionState&amp;amp;) {
  StyleAttributeMutationScope mutation_scope(this);
  WillMutate();

  MutableCSSPropertyValueSet::SetResult result;
  if (unresolved_property == CSSPropertyID::kVariable) {
    AtomicString atomic_name(custom_property_name);

    bool is_animation_tainted = IsKeyframeStyle();
    result = PropertySet().ParseAndSetCustomProperty(
        atomic_name, value, important, secure_context_mode, ContextStyleSheet(),
        is_animation_tainted);
  } else {
    result = PropertySet().ParseAndSetProperty(unresolved_property, value,
                                               important, secure_context_mode,
                                               ContextStyleSheet());
  }

  if (result == MutableCSSPropertyValueSet::kParseError ||
      result == MutableCSSPropertyValueSet::kUnchanged) {
    DidMutate(kNoChanges);
    return;
  }

  CSSPropertyID property_id = ResolveCSSPropertyID(unresolved_property);

  if (result == MutableCSSPropertyValueSet::kModifiedExisting &amp;amp;&amp;amp;
      CSSProperty::Get(property_id).SupportsIncrementalStyle()) {
    DidMutate(kIndependentPropertyChanged);
  } else {
    DidMutate(kPropertyChanged);
  }

  mutation_scope.EnqueueMutationRecord();
}&lt;/pre&gt;
  &lt;p id=&quot;vaWg&quot;&gt;First, the object is locked. Then the property value is parsed and saved. After this, the lock is released.&lt;/p&gt;
  &lt;p id=&quot;OUIT&quot;&gt;Here is how setting the style as a whole looks when using a text string.&lt;/p&gt;
  &lt;p id=&quot;V7py&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#54&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.cc#54&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;06CM&quot;&gt;void AbstractPropertySetCSSStyleDeclaration::setCSSText(
    const ExecutionContext* execution_context,
    const String&amp;amp; text,
    ExceptionState&amp;amp;) {
  StyleAttributeMutationScope mutation_scope(this);
  WillMutate();

  const SecureContextMode mode = execution_context
                                     ? execution_context-&amp;gt;GetSecureContextMode()
                                     : SecureContextMode::kInsecureContext;
  PropertySet().ParseDeclarationList(text, mode, ContextStyleSheet());

  DidMutate(kPropertyChanged);

  mutation_scope.EnqueueMutationRecord();
}&lt;/pre&gt;
  &lt;p id=&quot;zMbm&quot;&gt;The same object is locked. Next, the entire text string is parsed and broken down into properties. The values are then saved, and the object is unlocked.&lt;/p&gt;
  &lt;p id=&quot;sjXi&quot;&gt;Thus, by calling &lt;code&gt;style.setProperty&lt;/code&gt; twice, we initiate the lock-parse-unlock process two times. In contrast, &lt;code&gt;setAttribute&lt;/code&gt;can parse the entire style in a single operation.&lt;/p&gt;
  &lt;p id=&quot;M1Qv&quot;&gt;I must emphasize one particular point regarding style parsing optimization.&lt;/p&gt;
  &lt;p id=&quot;1xV0&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/132.0.6812.1/third_party/blink/renderer/core/css/parser/css_parser_impl.cc#255&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/css/parser/css_parser_impl.cc#255&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;UiLz&quot;&gt; static ImmutableCSSPropertyValueSet* CreateCSSPropertyValueSet(
    HeapVector&amp;lt;CSSPropertyValue, 64&amp;gt;&amp;amp; parsed_properties,
    CSSParserMode mode,
    const Document* document) {
  if (mode != kHTMLQuirksMode &amp;amp;&amp;amp;
      (parsed_properties.size() &amp;lt; 2 ||
       (parsed_properties.size() == 2 &amp;amp;&amp;amp;
        parsed_properties[0].Id() != parsed_properties[1].Id()))) {
    // Fast path for the situations where we can trivially detect that there can
    // be no collision between properties, and don&amp;#x27;t need to reorder, make
    // bitsets, or similar.
    ImmutableCSSPropertyValueSet* result =
        ImmutableCSSPropertyValueSet::Create(parsed_properties, mode);
    parsed_properties.clear();
    return result;
  }
  
  ...
}&lt;/pre&gt;
  &lt;p id=&quot;4dcH&quot;&gt;The parser, after processing a string, produces an array of properties. However, this array may contain duplicate properties. To create a set of unique properties, it is necessary to iterate over the array again and gather the unique ones. However, if the original array contains only two properties, checking for uniqueness can be done simply with an &lt;code&gt;if&lt;/code&gt; statement by comparing them by their IDs. It may seem trivial, but it&amp;#x27;s quite satisfying.&lt;/p&gt;
  &lt;h3 id=&quot;gWqc&quot;&gt;&lt;strong&gt;Test 3: Multiple Properties&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;JVKj&quot;&gt;Let’s conduct another test. This time, we will use a larger number of properties — say, seven. Why seven specifically? This number is chosen for a reason, which I will explain shortly.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;XkdU&quot;&gt;async function handleSetProperty() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.style.setProperty(&amp;quot;background-color&amp;quot;, &amp;quot;red&amp;quot;);
    el.style.setProperty(&amp;quot;border&amp;quot;, &amp;quot;1px solid blue&amp;quot;);
    el.style.setProperty(&amp;quot;position&amp;quot;, &amp;quot;relative&amp;quot;);
    el.style.setProperty(&amp;quot;display&amp;quot;, &amp;quot;flex&amp;quot;);
    el.style.setProperty(&amp;quot;align-items&amp;quot;, &amp;quot;center&amp;quot;);
    el.style.setProperty(&amp;quot;text-align&amp;quot;, &amp;quot;center&amp;quot;);
    el.style.setProperty(&amp;quot;text-transform&amp;quot;, &amp;quot;uppercase&amp;quot;);
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}

async function handleSetAttribute() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.setAttribute(
      &amp;quot;style&amp;quot;,
      &amp;quot;background-color: red; border: 1px solid blue; position: relative; display: flex; align-items: center; text-align: center; text-transform: uppercase;&amp;quot;,
    );
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}

&lt;/pre&gt;
  &lt;p id=&quot;BVGF&quot;&gt;The results of this test are quite logical.&lt;/p&gt;
  &lt;pre id=&quot;9czy&quot;&gt;avgSetProperty     0.17899999499320984
avgSetAttribute    0.12500000596046448&lt;/pre&gt;
  &lt;p id=&quot;aZkD&quot;&gt;This time, &lt;code&gt;setAttribute&lt;/code&gt; achieved a clear victory in speed for the reasons mentioned above.&lt;/p&gt;
  &lt;h3 id=&quot;0f92&quot;&gt;&lt;strong&gt;Test 4: Multiple Numeric Values&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;Gdby&quot;&gt;You may have noticed that in the previous test, all properties had string values. The reason for this is that numeric values coming from JavaScript do not require additional parsing from strings. This allows for further optimization and some savings in processing.&lt;/p&gt;
  &lt;p id=&quot;ixVO&quot;&gt;Unfortunately, there are not many properties that can handle numeric values in this way. Most of the properties we are familiar with that can accept numeric values are designed to work with various units of measurement, perform different mathematical operations, etc. As a result, it is necessary to convert their numeric values into other formats in any case. Therefore, developers have limited the set of properties that can accept numeric values. Currently, there are only seven such properties: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/opacity&quot; target=&quot;_blank&quot;&gt;opacity&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/fill-opacity&quot; target=&quot;_blank&quot;&gt;fill-opacity&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/flood-opacity&quot; target=&quot;_blank&quot;&gt;flood-opacity&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/stop-opacity&quot; target=&quot;_blank&quot;&gt;stop-opacity&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-opacity&quot; target=&quot;_blank&quot;&gt;stroke-opacity&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/shape-image-threshold&quot; target=&quot;_blank&quot;&gt;shape-image-threshold&lt;/a&gt;, and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/box-flex&quot; target=&quot;_blank&quot;&gt;-webkit-box-flex&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;DQcv&quot;&gt;Let’s repeat our previous experiment, but this time focusing solely on numeric properties. This is precisely why I selected only seven properties last time, allowing us to effectively compare the results.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;ROHo&quot;&gt;async function handleSetProperty() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.style.setProperty(&amp;quot;opacity&amp;quot;, 0.5);
    el.style.setProperty(&amp;quot;fill-opacity&amp;quot;, 0.5);
    el.style.setProperty(&amp;quot;flood-opacity&amp;quot;, 0.5);
    el.style.setProperty(&amp;quot;stop-opacity&amp;quot;, 0.5);
    el.style.setProperty(&amp;quot;stroke-opacity&amp;quot;, 0.5);
    el.style.setProperty(&amp;quot;shape-image-threshold&amp;quot;, 0.5);
    el.style.setProperty(&amp;quot;-webkit-box-flex&amp;quot;, 1);

    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}

async function handleSetAttribute() {
  const durations = [];

  for await (const i of new Array(N).fill(true).map((_, index) =&amp;gt; index)) {
    const el = createElement();

    await sleep(300);

    const startTime = performance.now();
    el.setAttribute(
      &amp;quot;style&amp;quot;,
      &amp;quot;opacity: 0.5; fill-opacity: 0.5; flood-opacity: 0.5; stop-opacity: 0.5; stroke-opacity: 0.5; shape-image-threshold: 0.5; -webkit-box-flex: 1;&amp;quot;,
    );
    const duration = performance.now() - startTime;

    durations.push(duration);

    await sleep(300);

    console.log(i);
  }

  console.log(durations);
}&lt;/pre&gt;
  &lt;p id=&quot;wVmJ&quot;&gt;And now for the results.&lt;/p&gt;
  &lt;pre id=&quot;nv0M&quot;&gt;avgSetProperty     0.17099999666213989
avgSetAttribute    0.09600000023841858&lt;/pre&gt;
  &lt;p id=&quot;q9ap&quot;&gt;The &lt;code&gt;setAttribute&lt;/code&gt; method remains faster. Ultimately, the overhead of converting a string to a number does not compare to the performance cost of locking, unlocking, and writing a value. Nevertheless, in both cases, the results turned out to be slightly better than in Test 3.&lt;/p&gt;
  &lt;h2 id=&quot;LXBc&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;3ENl&quot;&gt;Let’s summarize everything discussed above.&lt;/p&gt;
  &lt;ol id=&quot;5TNt&quot;&gt;
    &lt;li id=&quot;uRMr&quot;&gt;The &lt;code&gt;style.setProperty&lt;/code&gt; method demonstrates better performance when setting a single property. However, when two or more properties are set simultaneously, &lt;code&gt;setAttribute&lt;/code&gt; is faster.&lt;/li&gt;
    &lt;li id=&quot;PozB&quot;&gt;The &lt;code&gt;setAttribute&lt;/code&gt; method does not validate values, which can be both an advantage and a disadvantage. If it is important for us to ensure that only valid values are set, we can use the setter &lt;code&gt;element.style = &amp;quot;&amp;lt;style&amp;gt;&amp;quot;&lt;/code&gt;. However, if we need to set a custom attribute on an element or an intentionally incorrect value, &lt;code&gt;setAttribute&lt;/code&gt; is indispensable.&lt;/li&gt;
    &lt;li id=&quot;eSJe&quot;&gt;There are seven properties that are allowed to accept numeric values from JavaScript scripts. Setting the values of these properties will be slightly faster due to the elimination of the string-to-number conversion operation.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;N4Dl&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;cELV&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;hjS9&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/style-setproperty-vs-setattribute&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/style-setproperty-vs-setattribute&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:v8-strings</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/v8-strings?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>V8. Working with Strings. Expanding Vocabulary</title><published>2024-08-20T07:43:01.667Z</published><updated>2024-08-20T07:43:01.667Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d3/b3/d3b3fc23-12ac-4cdf-bc22-ade0076d9c8a.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/7e/2e/7e2e28f6-c0b8-4bcb-93a4-085b41075a9d.png&quot;&gt;What is a string? What types of strings exist? How are they stored inside the engine? Let's examine all the details and features in depth.</summary><content type="html">
  &lt;h2 id=&quot;zzYJ&quot;&gt;What is a String&lt;/h2&gt;
  &lt;p id=&quot;u1vk&quot;&gt;To better understand what happens under the hood of V8, it is useful to recall some theory first.&lt;/p&gt;
  &lt;p id=&quot;poWm&quot;&gt;The ECMA-262 specification states:&lt;/p&gt;
  &lt;blockquote id=&quot;pi4d&quot;&gt;The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values (“elements”) up to a maximum length of 2**53 - 1 elements.&lt;/blockquote&gt;
  &lt;p id=&quot;THJs&quot;&gt;The String type is a set of all ordered sequences of zero or more 16-bit unsigned integers (“elements”) with a maximum length of &lt;code&gt;2**53 - 1&lt;/code&gt; elements.&lt;/p&gt;
  &lt;p id=&quot;xXWH&quot;&gt;In practice, it is necessary to store additional information along with strings in machine memory to be able to determine the end of the string in the general heap. There are two approaches for this purpose. The first is an array of characters: a structure that represents a sequence of elements and a separate field for the length of this sequence. The second is the null-terminator method, where a special character indicating the end of the string is placed at the end of the sequence of elements. Depending on the system, the terminator character can be the byte &lt;code&gt;0xFF&lt;/code&gt; or, for example, an ASCII code character. However, the byte &lt;code&gt;0x00&lt;/code&gt; is the most commonly used. Strings with this terminating element are referred to as &lt;a href=&quot;https://en.wikipedia.org/wiki/Null-terminated_string&quot; target=&quot;_blank&quot;&gt;null-terminated strings&lt;/a&gt;. Both methods have their own advantages and disadvantages, which is why in the modern world, both methods are often combined, and strings in memory can simultaneously be null-terminated and store their length. This approach is used, for instance, in the C++ language starting with version 11.&lt;/p&gt;
  &lt;p id=&quot;oqQa&quot;&gt;In addition to the general definition of a string, the ECMA-262 specification has several other requirements alongside the other data types in the JavaScript language. I wrote in detail about types and their representation within the V8 engine in my article &amp;quot;Deep JS: In Memory of Types and Data.&amp;quot; In that article, we learned that all types have their own hidden class, which stores all necessary attributes, service information, and encapsulates the logic of working with that type. It is obvious that for working with strings, the standard C++ library &lt;strong&gt;std::string&lt;/strong&gt; is insufficient, and they are transformed into an internal class &lt;strong&gt;v8::String&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;JD8T&quot;&gt;Types of Strings&lt;/h2&gt;
  &lt;p id=&quot;fNpU&quot;&gt;To meet all the specification requirements and due to various optimizations, strings within V8 are divided into different types.&lt;/p&gt;
  &lt;h3 id=&quot;jxeT&quot;&gt;One-byte / Two-byte&lt;/h3&gt;
  &lt;p id=&quot;UsyR&quot;&gt;Despite the specification directly defining all strings as sequences of 16-bit elements, this is quite wasteful from an optimization standpoint. After all, not all characters require 2 bytes for representation. The standard ASCII table contains 128 elements, including Arabic numerals, Latin alphabet letters in uppercase and lowercase without diacritical marks, basic punctuation, mathematical symbols, and control characters. This entire table implies coding with only 8 bits, and the set of characters is widely used in practice. Therefore, the V8 developers decided to encode strings that contain only 8-bit characters separately from the standard 16-bit ones. This allows for significant memory savings.&lt;/p&gt;
  &lt;h3 id=&quot;Kf8s&quot;&gt;Internalized / Externalized Strings&lt;/h3&gt;
  &lt;p id=&quot;vNtq&quot;&gt;Besides bit-depth, strings differ in their storage location. After being converted into an internal structure, the string gets placed into the so-called &lt;strong&gt;StringTable&lt;/strong&gt;, which we will discuss a bit later. For now, note that there are three such tables. The StringTable itself stores strings within a single &lt;strong&gt;Isolate&lt;/strong&gt; (essentially, within a single browser tab). Additionally, the engine allows strings to be stored outside the heap and even outside the engine itself, in external storage. Pointers to such strings are placed in a separate table called the &lt;strong&gt;ExternalStringTable&lt;/strong&gt;. Furthermore, there is another table — the &lt;strong&gt;StringForwardingTable&lt;/strong&gt; — for the purposes of garbage collection. Yes, strings, like objects, participate in garbage collection, and since they are stored in separate tables, they require specific mechanisms for this process. We will discuss this in detail a bit later.&lt;/p&gt;
  &lt;h3 id=&quot;pMdB&quot;&gt;Strings by purpose&lt;/h3&gt;
  &lt;p id=&quot;nbXT&quot;&gt;The JavaScript language is quite flexible. During code execution, strings can be transformed multiple times and participate in related processes. For better performance, several functional types are allocated to them. These include: &lt;strong&gt;SeqString&lt;/strong&gt;, &lt;strong&gt;ConsString&lt;/strong&gt;, &lt;strong&gt;ThinString&lt;/strong&gt;, and &lt;strong&gt;SlicedString&lt;/strong&gt; and &lt;strong&gt;ExternalString&lt;/strong&gt;. We will discuss each type in detail later. For now, let&amp;#x27;s summarize everything mentioned above.&lt;/p&gt;
  &lt;figure id=&quot;tBTi&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7e/2e/7e2e28f6-c0b8-4bcb-93a4-085b41075a9d.png&quot; width=&quot;1534&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;5Lh8&quot;&gt;This is the general classification of string types in V8. Besides the main types, there are also several derived types. For example, SeqString and ExternalString can be placed in a common shared heap, where their types may be &lt;code&gt;SharedSeq&amp;lt;one|two&amp;gt;ByteString&lt;/code&gt; and &lt;code&gt;SharedExternal&amp;lt;one|two&amp;gt;ByteString&lt;/code&gt;, respectively. Additionally, ExternalString can be uncachable and have additional types like &lt;code&gt;UncachableExternal&amp;lt;one|two&amp;gt;ByteString&lt;/code&gt; and even &lt;code&gt;SharedUncachableExternal&amp;lt;one|two&amp;gt;ByteString&lt;/code&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;Y7Or&quot;&gt;AST&lt;/h2&gt;
  &lt;p id=&quot;h0kb&quot;&gt;Before a string takes its final form in the depths of V8, the engine must first read and interpret it. This is done using the same mechanism that interprets all other syntactic units of the language. Specifically, this involves constructing an abstract syntax tree (AST), which I discussed in the article &lt;a href=&quot;https://blog.frontend-almanac.com/4q2JxpUOpAt&quot; target=&quot;_blank&quot;&gt;Deep JS. Scopes of darkness or where variables live&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;qFFe&quot;&gt;Upon receiving a string node through a string literal or some other means, V8 creates an instance of the &lt;strong&gt;AstRawString&lt;/strong&gt; class. If the string is a concatenation of two or more other strings, it creates an &lt;strong&gt;AstConsString&lt;/strong&gt;. These classes are designed to store strings outside V8&amp;#x27;s heap, in the so-called &lt;strong&gt;AstValueFactory&lt;/strong&gt;. Later, these strings will be assigned a type and will be internalized, i.e., moved into V8&amp;#x27;s heap.&lt;/p&gt;
  &lt;h2 id=&quot;5HOS&quot;&gt;The length of the string&lt;/h2&gt;
  &lt;p id=&quot;zeTi&quot;&gt;The size of each string in memory depends on its length. The longer the string, the more memory it occupies. But are there any limitations on the maximum string size imposed by the engine? The specification explicitly states that a string cannot exceed &lt;code&gt;2**53 - 1&lt;/code&gt; elements. However, in practice, this number can be different. After all, a maximum one-byte string would weigh in at &lt;code&gt;8,192 TB (8 PB)&lt;/code&gt;, while a two-byte string would correspondingly be &lt;code&gt;4,096 TB (4 PB)&lt;/code&gt;. Clearly, no personal computer has that much RAM, which is why JavaScript engines have their own limits that are significantly stricter than the requirements of the specification. Specifically in V8 (the version of V8 at the time of writing this article is &lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325&quot; target=&quot;_blank&quot;&gt;12.8.325&lt;/a&gt;), the maximum string length depends on the system architecture.&lt;/p&gt;
  &lt;p id=&quot;TaZ8&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325/include/v8-primitive.h#126&quot; target=&quot;_blank&quot;&gt;/include/v8-primitive.h#126&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;mgqW&quot;&gt;static constexpr int kMaxLength =
    internal::kApiSystemPointerSize == 4 ? (1 &amp;lt;&amp;lt; 28) - 16 : (1 &amp;lt;&amp;lt; 29) - 24;&lt;/pre&gt;
  &lt;p id=&quot;x58F&quot;&gt;For 32-bit systems, this number is &lt;strong&gt;2**28 - 16&lt;/strong&gt;, and for 64-bit systems, it&amp;#x27;s slightly higher: &lt;strong&gt;2**29 - 24&lt;/strong&gt;. These limitations prevent a one-byte string from occupying more than 256 MB in 32-bit systems, and a one-byte string from occupying more than 512 MB. In 64-bit systems, the maximum values are no more than 512 MB for one-byte strings and 1024 MB for two-byte strings. If a string exceeds the length limit, the engine will return an &lt;code&gt;Invalid string length&lt;/code&gt; error.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;J62e&quot;&gt;const length = (1 &amp;lt;&amp;lt; 29) - 24;
const longString = &amp;#x27;&amp;quot;&amp;#x27; + new Array(length - 2).join(&amp;#x27;x&amp;#x27;);

String.prototype.link(longString); // RangeError: Invalid string length&lt;/pre&gt;
  &lt;p id=&quot;gzkh&quot;&gt;To be objective, it is also important to note that the maximum heap size plays an indirect role, as it is not a constant and can be adjusted by the system or engine configuration.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;LHo2&quot;&gt;// d8 --max-heap-size=128

const length = (1 &amp;lt;&amp;lt; 27) + 24; // 129 MB
const longString = &amp;#x27;&amp;quot;&amp;#x27; + new Array(length - 2).join(&amp;#x27;x&amp;#x27;);

String.prototype.link(longString);

// Fatal JavaScript out of memory: Reached heap limit&lt;/pre&gt;
  &lt;h2 id=&quot;9oto&quot;&gt;StringTable&lt;/h2&gt;
  &lt;p id=&quot;DnuZ&quot;&gt;During the execution of a JavaScript program, it can manipulate a large number of both strings and variables that reference them. As we have seen, each string can consume a significant amount of memory. Moreover, strings within a JS program can be cloned, modified, concatenated, and transformed in various ways multiple times. As a result, duplicates of string values can end up in memory. Storing multiple copies of the same sequence of characters would be extremely wasteful. Therefore, in the world of system programming, the practice of storing string values in a separate structure that ensures there is no duplication of values, and that all string variables reference this structure, has long been actively applied. In Java and .NET, for example, such a structure is called a &amp;quot;StringPool&amp;quot;, and in JavaScript, it is called a &lt;strong&gt;StringTable&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;Z8UP&quot;&gt;The essence of this is that once a string is included in the AST, a hash key is generated for it based on its type and value. The generated key is saved in the string object, and it is then checked against a special table of strings located in the heap. If such a key exists in the table, the corresponding JS variable will receive a reference to the existing string object. Otherwise, a new string object will be created, and the key will be placed in the table. This process is called internalization.&lt;/p&gt;
  &lt;h2 id=&quot;5cGK&quot;&gt;InternalizedString&lt;/h2&gt;
  &lt;p id=&quot;Ss0h&quot;&gt;Just above, I mentioned that in the internal representation of V8, there are many types of strings. Internalized strings, in their most basic form, receive the type &lt;strong&gt;InternalizedString&lt;/strong&gt;, which indicates that the string is located in the StringTable.&lt;/p&gt;
  &lt;p id=&quot;SPpW&quot;&gt;As usual, let&amp;#x27;s take a look at the structure of the string inside the engine by compiling V8 in debug mode and running it with the flag &lt;code&gt;--allow-natives-syntax&lt;/code&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;3beD&quot;&gt;d8&amp;gt; %DebugPrint(&amp;quot;FrontendAlmanac&amp;quot;)

DebugPrint: 0x28db000d9b99: [String] in OldSpace: #FrontendAlmanac
0x28db000003d5: [Map] in ReadOnlySpace
 - map: 0x28db000004c5 &amp;lt;MetaMap (0x28db0000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: INTERNALIZED_ONE_BYTE_STRING_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x28db00000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x28db00000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x28db0000007d &amp;lt;null&amp;gt;
 - constructor: 0x28db0000007d &amp;lt;null&amp;gt;
 - dependent code: 0x28db000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;JI6W&quot;&gt;The first thing we see here is the type &lt;code&gt;INTERNALIZED_ONE_BYTE_STRING_TYPE&lt;/code&gt;, which means that the string is one-byte and placed in the StringTable. Let&amp;#x27;s try to create another string literal with the same value.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;M8Au&quot;&gt;d8&amp;gt; const string2 = &amp;quot;FrontendAlmanac&amp;quot;;
d8&amp;gt; %DebugPrint(string2);

DebugPrint: 0x28db000d9b99: [String] in OldSpace: #FrontendAlmanac
0x28db000003d5: [Map] in ReadOnlySpace
 - map: 0x28db000004c5 &amp;lt;MetaMap (0x28db0000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: INTERNALIZED_ONE_BYTE_STRING_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x28db00000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x28db00000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x28db0000007d &amp;lt;null&amp;gt;
 - constructor: 0x28db0000007d &amp;lt;null&amp;gt;
 - dependent code: 0x28db000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;uVxq&quot;&gt;Notice that the constant &lt;code&gt;string2&lt;/code&gt; refers to the exact same string instance at the address &lt;code&gt;0x28db000d9b99&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;Kcfb&quot;&gt;We can observe a similar situation in the Chrome browser.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Kte0&quot;&gt;// Замкнем значения в функции
function V8Snapshot() {
  this.string1 = &amp;quot;FrontendAlmanac&amp;quot;;
  this.string2 = &amp;quot;FrontendAlmanac&amp;quot;;
}

const v8Snapshot = new V8Snapshot();&lt;/pre&gt;
  &lt;figure id=&quot;IcWk&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/ba/03/ba03e791-195e-40e2-bb3a-d99f82443d85.png&quot; width=&quot;1882&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q9KU&quot;&gt;Both V8Snapshot properties refer to the same address &lt;code&gt;@61559&lt;/code&gt;. The contents of the entire StringTable can be seen right there in the snapshot in the &lt;code&gt;(string)&lt;/code&gt; object.&lt;/p&gt;
  &lt;figure id=&quot;kDiA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/36/27/36271ec0-630a-41f1-89ad-827a065a0f1a.png&quot; width=&quot;1884&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;TuKQ&quot;&gt;Here, we can find our string and see which variables refer to it.&lt;/p&gt;
  &lt;p id=&quot;s65e&quot;&gt;You have probably noticed that in addition to our string, there are over 4k other values in the table. The thing is, V8 also stores its internal strings here, including keywords, different names of system methods and functions, and even texts of JavaScript scripts.&lt;/p&gt;
  &lt;figure id=&quot;Ikft&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/99/5c/995c6dac-af5a-4b10-836c-910940d29073.png&quot; width=&quot;1886&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;fF4R&quot;&gt;SeqString&lt;/h2&gt;
  &lt;p id=&quot;Jit8&quot;&gt;You have probably noticed that in addition to our string, there are over 4k other values in the table. The thing is, V8 also stores its internal strings here, including keywords, different names of system methods and functions, and even texts of JavaScript scripts.&lt;/p&gt;
  &lt;p id=&quot;sral&quot;&gt;The concept of &lt;strong&gt;InternalizedString&lt;/strong&gt; appeared relatively recently, in 2018, as part of the TurboFan optimizing compiler. Before this, plain strings had the type &lt;strong&gt;SeqString&lt;/strong&gt;. Technically, InternalizedString differs from SeqString in its internal structure. Specifically, the classes &lt;strong&gt;SeqOneByteString&lt;/strong&gt; and &lt;strong&gt;SeqTwoByteString&lt;/strong&gt; contain a pointer to a character array &lt;code&gt;chars&lt;/code&gt; and a number of methods that can interact with it. The implementation of the SeqString class looks literally as follows:&lt;/p&gt;
  &lt;p id=&quot;YaV7&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325/src/objects/string.h#733&quot; target=&quot;_blank&quot;&gt;/src/objects/string.h#733&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;gft1&quot;&gt;// The SeqString abstract class captures sequential string values.
class SeqString : public String {
 public:
  // Truncate the string in-place if possible and return the result.
  // In case of new_length == 0, the empty string is returned without
  // truncating the original string.
  V8_WARN_UNUSED_RESULT static Handle&amp;lt;String&amp;gt; Truncate(Isolate* isolate,
                                                       Handle&amp;lt;SeqString&amp;gt; string,
                                                       int new_length);
                                                       
  struct DataAndPaddingSizes {
    const int data_size;
    const int padding_size;
    bool operator==(const DataAndPaddingSizes&amp;amp; other) const {
      return data_size == other.data_size &amp;amp;&amp;amp; padding_size == other.padding_size;
    }
  };
  DataAndPaddingSizes GetDataAndPaddingSizes() const;
  
  // Zero out only the padding bytes of this string.
  void ClearPadding();
  
  EXPORT_DECL_VERIFIER(SeqString)
};&lt;/pre&gt;
  &lt;p id=&quot;j3ih&quot;&gt;One of its descendants, SeqOneByteString, looks like this:&lt;/p&gt;
  &lt;p id=&quot;VwAy&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325/src/objects/string.h#763&quot; target=&quot;_blank&quot;&gt;/src/objects/string.h#763&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;B4PP&quot;&gt;// The OneByteString class captures sequential one-byte string objects.
// Each character in the OneByteString is an one-byte character.
V8_OBJECT class SeqOneByteString : public SeqString {
 public:
  static const bool kHasOneByteEncoding = true;
  using Char = uint8_t;
  
  V8_INLINE static constexpr int32_t DataSizeFor(int32_t length);
  V8_INLINE static constexpr int32_t SizeFor(int32_t length);
  
  // Dispatched behavior. The non SharedStringAccessGuardIfNeeded method is also
  // defined for convenience and it will check that the access guard is not
  // needed.
  inline uint8_t Get(int index) const;
  inline uint8_t Get(int index,
                     const SharedStringAccessGuardIfNeeded&amp;amp; access_guard) const;
  inline void SeqOneByteStringSet(int index, uint16_t value);
  inline void SeqOneByteStringSetChars(int index, const uint8_t* string,
                                       int length);
                                       
  // Get the address of the characters in this string.
  inline Address GetCharsAddress() const;
  
  // Get a pointer to the characters of the string. May only be called when a
  // SharedStringAccessGuard is not needed (i.e. on the main thread or on
  // read-only strings).
  inline uint8_t* GetChars(const DisallowGarbageCollection&amp;amp; no_gc);
  
  // Get a pointer to the characters of the string.
  inline uint8_t* GetChars(const DisallowGarbageCollection&amp;amp; no_gc,
                           const SharedStringAccessGuardIfNeeded&amp;amp; access_guard);
                           
  DataAndPaddingSizes GetDataAndPaddingSizes() const;
  
  // Initializes padding bytes. Potentially zeros tail of the payload too!
  inline void clear_padding_destructively(int length);
  
  // Maximal memory usage for a single sequential one-byte string.
  static const int kMaxCharsSize = kMaxLength;
  
  inline int AllocatedSize() const;
  
  // A SeqOneByteString have different maps depending on whether it is shared.
  static inline bool IsCompatibleMap(Tagged&amp;lt;Map&amp;gt; map, ReadOnlyRoots roots);
  
  class BodyDescriptor;
  
 private:
  friend struct OffsetsForDebug;
  friend class CodeStubAssembler;
  friend class ToDirectStringAssembler;
  friend class IntlBuiltinsAssembler;
  friend class StringBuiltinsAssembler;
  friend class StringFromCharCodeAssembler;
  friend class maglev::MaglevAssembler;
  friend class compiler::AccessBuilder;
  friend class TorqueGeneratedSeqOneByteStringAsserts;
  
  FLEXIBLE_ARRAY_MEMBER(Char, chars);
} V8_OBJECT_END;&lt;/pre&gt;
  &lt;p id=&quot;hV1Z&quot;&gt;For comparison, the InternalizedString class looks like this:&lt;/p&gt;
  &lt;p id=&quot;KzPa&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325/src/objects/string.h#758&quot; target=&quot;_blank&quot;&gt;/src/objects/string.h#758&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;HpaN&quot;&gt;V8_OBJECT class InternalizedString : public String{

  // TODO(neis): Possibly move some stuff from String here.
} V8_OBJECT_END;&lt;/pre&gt;
  &lt;p id=&quot;SaO4&quot;&gt;As you can see, there is no implementation here at all, as the concept of InternalizedString was introduced merely for the sake of terminology. In fact, all the logic related to memory allocation, encoding, decoding, comparing, and modifying strings is handled in the base class &lt;code&gt;String&lt;/code&gt;. Characters are stored not as an array but directly as a sequence of 32-bit or 64-bit character codes in memory. The class itself only has a system pointer to the beginning of the corresponding memory area. This structure is referred to as &amp;quot;FlatContent,&amp;quot; and the strings are thus called flat strings.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;OZQ8&quot;&gt;V8_OBJECT class String : public Name {
  ...
 private:
  union {
    const uint8_t* onebyte_start;
    const base::uc16* twobyte_start;
  };
  ...
}&lt;/pre&gt;
  &lt;p id=&quot;4loA&quot;&gt;So, what is SeqString in practice?&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;6Fzk&quot;&gt;d8&amp;gt; const seqString = [
  &amp;quot;F&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;o&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;d&amp;quot;,
  &amp;quot;A&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;m&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;
].join(&amp;quot;&amp;quot;);
d8&amp;gt; 
d8&amp;gt; %DebugPrint(seqString);

DebugPrint: 0x2353001c94e1: [String]: &amp;quot;FrontendAlmanac&amp;quot;
0x235300000105: [Map] in ReadOnlySpace
 - map: 0x2353000004c5 &amp;lt;MetaMap (0x23530000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: SEQ_ONE_BYTE_STRING_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x235300000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x235300000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x23530000007d &amp;lt;null&amp;gt;
 - constructor: 0x23530000007d &amp;lt;null&amp;gt;
 - dependent code: 0x2353000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;pGBi&quot;&gt;In the example above, the string was created by combining elements of an array. Since the array had to be created regardless during this operation, and the resulting string cannot be checked in the StringTable until it has been combined, the variable is assigned the type SeqString.&lt;/p&gt;
  &lt;p id=&quot;uqlj&quot;&gt;Let’s modify the previous example a bit.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;6AYo&quot;&gt;function V8Snapshot() {
  this.string1 = &amp;quot;FrontendAlmanac&amp;quot;;
  this.string2 = [
    &amp;quot;F&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;o&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;d&amp;quot;,
    &amp;quot;A&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;m&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;
  ].join(&amp;quot;&amp;quot;);
}

const v8Snapshot = new V8Snapshot();&lt;/pre&gt;
  &lt;p id=&quot;gAx1&quot;&gt;How will the string table look in this case?&lt;/p&gt;
  &lt;figure id=&quot;Udrq&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b8/66/b8666fe6-3912-4ffa-9147-56f505dbbe10.png&quot; width=&quot;1880&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;YNqy&quot;&gt;Since &lt;code&gt;string1&lt;/code&gt; and &lt;code&gt;string2&lt;/code&gt; are two different objects with different types of strings, each of them has its own address. Moreover, each of them will have its own unique hash key. Therefore, in the table, we can see two identical values with different keys. In other words, there are duplicate strings. Generally, all such duplicates can be seen by applying the &lt;code&gt;Duplicated strings&lt;/code&gt; filter in the snapshot.&lt;/p&gt;
  &lt;p id=&quot;BbIc&quot;&gt;Furthermore, if we attempt to create several SeqString objects with the same value, their duplicates also will not appear in the table.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Zsvu&quot;&gt;function V8Snapshot() {
  this.string1 = &amp;quot;FrontendAlmanac&amp;quot;;
  this.string2 = [
    &amp;quot;F&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;o&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;d&amp;quot;,
    &amp;quot;A&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;m&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;
  ].join(&amp;quot;&amp;quot;);
  this.string3 = [
    &amp;quot;F&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;o&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;d&amp;quot;,
    &amp;quot;A&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;m&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;c&amp;quot;
  ].join(&amp;quot;&amp;quot;);
}

const v8Snapshot = new V8Snapshot();&lt;/pre&gt;
  &lt;figure id=&quot;ZNJO&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fb/7c/fb7c2f92-d5ce-4651-ab51-2a8690380d0d.png&quot; width=&quot;1960&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;4sGZ&quot;&gt;ConsString&lt;/h2&gt;
  &lt;p id=&quot;A7fB&quot;&gt;Let&amp;#x27;s consider another example.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;hSf2&quot;&gt;d8&amp;gt; const consString = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;;
d8&amp;gt; %DebugPrint(consString);

DebugPrint: 0xf0001c952d: [String]: c&amp;quot;FrontendAlmanac&amp;quot;
0xf000000155: [Map] in ReadOnlySpace
 - map: 0x00f0000004c5 &amp;lt;MetaMap (0x00f00000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: CONS_ONE_BYTE_STRING_TYPE
 - instance size: 20
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - non-extensible
 - back pointer: 0x00f000000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x00f000000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x00f00000007d &amp;lt;null&amp;gt;
 - constructor: 0x00f00000007d &amp;lt;null&amp;gt;
 - dependent code: 0x00f0000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;lzgN&quot;&gt;The variable &lt;code&gt;consString&lt;/code&gt; is formed by concatenating two string literals. Such strings are given the type ConsString.&lt;/p&gt;
  &lt;p id=&quot;JKZP&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325/src/objects/string.h#916&quot; target=&quot;_blank&quot;&gt;/src/objects/string.h#916&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;EqSy&quot;&gt;V8_OBJECT class ConsString : public String {
 public:
  inline Tagged&amp;lt;String&amp;gt; first() const;
  inline void set_first(Tagged&amp;lt;String&amp;gt; value,
                        WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
  
  inline Tagged&amp;lt;String&amp;gt; second() const;
  inline void set_second(Tagged&amp;lt;String&amp;gt; value,
                         WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
  
  // Doesn&amp;#x27;t check that the result is a string, even in debug mode.  This is
  // useful during GC where the mark bits confuse the checks.
  inline Tagged&amp;lt;Object&amp;gt; unchecked_first() const;
  
  // Doesn&amp;#x27;t check that the result is a string, even in debug mode.  This is
  // useful during GC where the mark bits confuse the checks.
  inline Tagged&amp;lt;Object&amp;gt; unchecked_second() const;
  
  V8_INLINE bool IsFlat() const;
  
  // Dispatched behavior.
  V8_EXPORT_PRIVATE uint16_t
  Get(int index, const SharedStringAccessGuardIfNeeded&amp;amp; access_guard) const;
  
  // Minimum length for a cons string.
  static const int kMinLength = 13;
  
  DECL_VERIFIER(ConsString)
  
 private:
  friend struct ObjectTraits&amp;lt;ConsString&amp;gt;;
  friend struct OffsetsForDebug;
  friend class V8HeapExplorer;
  friend class CodeStubAssembler;
  friend class ToDirectStringAssembler;
  friend class StringBuiltinsAssembler;
  friend class maglev::MaglevAssembler;
  friend class compiler::AccessBuilder;
  friend class TorqueGeneratedConsStringAsserts;
  
  friend Tagged&amp;lt;String&amp;gt; String::GetUnderlying() const;
  
  TaggedMember&amp;lt;String&amp;gt; first_;
  TaggedMember&amp;lt;String&amp;gt; second_;
} V8_OBJECT_END;&lt;/pre&gt;
  &lt;p id=&quot;2sPW&quot;&gt;The ConsString class has two pointers to other strings, which can in turn be of any type, including ConsString. As a result, this type can form a binary tree of ConsString nodes, where the leaves are not ConsString. The final value of such a string is obtained by concatenating the leaves of the tree from left to right, from the deepest node to the first one.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;yj6r&quot;&gt;function V8Snapshot() {
  this.string1 = &amp;quot;FrontendAlmanac&amp;quot;;
  this.string2 = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;;
  this.string3 = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;;
}

const v8Snapshot = new V8Snapshot();&lt;/pre&gt;
  &lt;figure id=&quot;TOwo&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/24/26/24260f8c-f9df-44f7-8e1b-cbccc9fd9f52.png&quot; width=&quot;1954&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OWLw&quot;&gt;Just like with SeqString, each concatenated string has its own instance of the class and, accordingly, its own hash key. These strings can also be duplicated in the string table. However, in the table, we can also find the internalized leaves of this concatenation.&lt;/p&gt;
  &lt;figure id=&quot;4gUf&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/58/3e/583ef330-0bac-411a-866c-83d69ab784f6.png&quot; width=&quot;1954&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;1m1V&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e7/7d/e77d28a9-f6c8-4ae4-ac24-6c4c9a815ba6.png&quot; width=&quot;1956&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;W7ze&quot;&gt;It is important to make a disclaimer. Not every concatenation operation results in the creation of a ConsString.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;2LPY&quot;&gt;d8&amp;gt; const string = &amp;quot;a&amp;quot; + &amp;quot;b&amp;quot;;
d8&amp;gt; %DebugPrint(string);

DebugPrint: 0x1d48001c9ec5: [String]: &amp;quot;ab&amp;quot;
0x1d4800000105: [Map] in ReadOnlySpace
 - map: 0x1d48000004c5 &amp;lt;MetaMap (0x1d480000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: SEQ_ONE_BYTE_STRING_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x1d4800000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x1d4800000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x1d480000007d &amp;lt;null&amp;gt;
 - constructor: 0x1d480000007d &amp;lt;null&amp;gt;
 - dependent code: 0x1d48000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;4cx7&quot;&gt;In the example above, the string is represented by the type SeqString. The fact is that the procedures for reading and writing to the ConsString structure are not free. While the structure itself is considered efficient in terms of memory optimization, all the advantages become evident only with relatively long strings. In the case of short strings, the overhead of maintaining a binary tree nullifies these advantages. Therefore, the V8 developers empirically determined a critical string length below which the ConsString structure is inefficient. This number is &lt;strong&gt;13&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;U0By&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/heads/12.8.325/src/objects/string.h#940&quot; target=&quot;_blank&quot;&gt;/src/objects/string.h#940&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;TlPV&quot;&gt;// Minimum length for a cons string.
static const int kMinLength = 13;&lt;/pre&gt;
  &lt;h2 id=&quot;TdGQ&quot;&gt;SlicedString&lt;/h2&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;XhmM&quot;&gt;d8&amp;gt; const parentString = &amp;quot; FrontendAlmanac FrontendAlmanac &amp;quot;;
d8&amp;gt; const slicedString1 = parentString.substring(1, 16);
d8&amp;gt; const slicedString2 = parentString.slice(1, 16);
d8&amp;gt; const slicedString3 = parentString.trim();
d8&amp;gt; 
d8&amp;gt; %DebugPrint(slicedString1);

DebugPrint: 0x312b001c9509: [String]: &amp;quot;FrontendAlmanac&amp;quot;
0x312b000001a5: [Map] in ReadOnlySpace
 - map: 0x312b000004c5 &amp;lt;MetaMap (0x312b0000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: SLICED_ONE_BYTE_STRING_TYPE
 - instance size: 20
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x312b00000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x312b00000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x312b0000007d &amp;lt;null&amp;gt;
 - constructor: 0x312b0000007d &amp;lt;null&amp;gt;
 - dependent code: 0x312b000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;QmWg&quot;&gt;This type is assigned to strings formed as a result of being a substring of another string. Such operations include &lt;code&gt;substring()&lt;/code&gt;, &lt;code&gt;slice()&lt;/code&gt; and trimming methods &lt;code&gt;trim()&lt;/code&gt;, &lt;code&gt;trimStart()&lt;/code&gt;, and &lt;code&gt;trimEnd()&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;eKEb&quot;&gt;The SlicedString class stores only a pointer to the parent string, along with the offset and length of the sequence within the parent string. This significantly reduces memory usage and improves performance when working with such strings. However, the same rule applies here as with ConsString: the length of the substring must not be less than 13 characters. Otherwise, this optimization loses its value, as SlicedString requires unpacking the parent string and adding the offset to the starting address of the sequence.&lt;/p&gt;
  &lt;p id=&quot;8iqy&quot;&gt;Another important feature is that SlicedString cannot be nested.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;bx8m&quot;&gt;function V8Snapshot() {
  this.parentString = &amp;quot; FrontendAlmanac FrontendAlmanac &amp;quot;;
  this.slicedString1 = this.parentString.trim();
  this.slicedString2 = this.slicedString1.substring(0, 15);
}

const v8Snapshot = new V8Snapshot();&lt;/pre&gt;
  &lt;p id=&quot;1YNn&quot;&gt;In the example above, we attempt to create a SlicedString from another SlicedString. However, &lt;code&gt;slicedString1&lt;/code&gt; cannot be the parent string because it is itself a SlicedString.&lt;/p&gt;
  &lt;figure id=&quot;VRhD&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/03/b2/03b20f48-82d1-4def-9f06-4b84def16aab.png&quot; width=&quot;2024&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hQ0t&quot;&gt;Therefore, the parent string for both properties will be &amp;quot;parentString&amp;quot;.&lt;/p&gt;
  &lt;h2 id=&quot;55w5&quot;&gt;ThinString&lt;/h2&gt;
  &lt;p id=&quot;c9nu&quot;&gt;There are instances when it is necessary to internalize a string, but for some reason, it cannot be done immediately. In such cases, a new string object is created and internalized, while the original string is converted into a &lt;strong&gt;ThinString&lt;/strong&gt; type, which essentially serves as a reference to its internalized version. ThinString is very similar to ConsString, but it only contains a single reference.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;TJh2&quot;&gt;d8&amp;gt; const string = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;; // create ConsString
d8&amp;gt; const obj = {};
d8&amp;gt; 
d8&amp;gt; obj[string]; // convert to ThinString
d8&amp;gt; 
d8&amp;gt; %DebugPrint(string);

DebugPrint: 0x335f001c947d: [String]: &amp;gt;&amp;quot;FrontendAlmanac&amp;quot;
0x335f00000425: [Map] in ReadOnlySpace
 - map: 0x335f000004c5 &amp;lt;MetaMap (0x335f0000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: THIN_ONE_BYTE_STRING_TYPE
 - instance size: 16
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x335f00000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x335f00000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x335f0000007d &amp;lt;null&amp;gt;
 - constructor: 0x335f0000007d &amp;lt;null&amp;gt;
 - dependent code: 0x335f000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;qsNC&quot;&gt;In the example above, we first create a ConsString. We then use this string as a key for an object. To find the key in the object&amp;#x27;s properties array, the string needs to be flat; however, at this point, it is represented as a binary tree with two nodes. In this situation, the engine is forced to compute a flat value from the ConsString, create a new string object, intern it, and convert the original string into a ThinString. This particular case was mentioned in an article about &lt;a href=&quot;https://habr.com/ru/articles/449368/&quot; target=&quot;_blank&quot;&gt;string cleansing&lt;/a&gt; on Habr, although it did not explain why this occurs.&lt;/p&gt;
  &lt;figure id=&quot;WnO5&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/71/b9/71b97002-be26-4474-b272-98fdc3ee2110.png&quot; width=&quot;2056&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;yzP1&quot;&gt;Now, let&amp;#x27;s take a look at DevTools. We won&amp;#x27;t be able to see the ThinString here, but we can observe that the property &lt;code&gt;string&lt;/code&gt; is represented as &lt;strong&gt;InternalizedString&lt;/strong&gt;. This is because ThinString, I reiterate, is merely a reference to the internalized version of the string.&lt;/p&gt;
  &lt;h2 id=&quot;ibXA&quot;&gt;ExternalString&lt;/h2&gt;
  &lt;p id=&quot;nOSB&quot;&gt;Another type of string is the &lt;strong&gt;ExternalString&lt;/strong&gt;. The V8 engine provides the capability to create and store strings outside the engine itself. To facilitate this, the ExternalString type and the corresponding API were introduced. References to these strings are stored in a separate table called the ExternalStringTable in the heap. Typically, such strings are created by the engine consumer for their specific needs. For instance, browsers might store external resources in this way. Additionally, the consumer can fully control the lifecycle of these resources, with one caveat: the consumer must ensure that such strings are not deallocated while the ExternalString object is alive in the V8 heap.&lt;/p&gt;
  &lt;figure id=&quot;9QHu&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7d/5b/7d5b18ec-7d86-4269-b9fc-d45942684eec.png&quot; width=&quot;2052&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;lSKV&quot;&gt;The screenshot above shows one of these strings created by the browser. However, we can also create our own. To do this, we can use the internal API method &lt;strong&gt;externalizeString&lt;/strong&gt; (V8 must be started with the &lt;code&gt;--allow-natives-syntax&lt;/code&gt; flag).&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;1xbE&quot;&gt;//&amp;gt; d8 --allow-natives-syntax --expose-externalize-string
d8&amp;gt; const string = &amp;quot;FrontendAlmanac&amp;quot;;
d8&amp;gt; 
d8&amp;gt; externalizeString(string);
d8&amp;gt; 
d8&amp;gt; %DebugPrint(string);

DebugPrint: 0x7d6000da08d: [String] in OldSpace: #FrontendAlmanac
0x7d600000335: [Map] in ReadOnlySpace
 - map: 0x07d6000004c5 &amp;lt;MetaMap (0x07d60000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE
 - instance size: 20
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x07d600000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x07d600000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x07d60000007d &amp;lt;null&amp;gt;
 - constructor: 0x07d60000007d &amp;lt;null&amp;gt;
 - dependent code: 0x07d6000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;h2 id=&quot;uxNy&quot;&gt;Garbage Collection&lt;/h2&gt;
  &lt;p id=&quot;9zGE&quot;&gt;Strings, like any other variables, participate in the garbage collection process. I&amp;#x27;ve covered this topic in detail in my article &lt;a href=&quot;https://blog.frontend-almanac.com/v8-garbage-collection&quot; target=&quot;_blank&quot;&gt;Garbage Collection in V8&lt;/a&gt;, so I won&amp;#x27;t go into it here. More interestingly, the StringTable itself also participates in this process. To be precise, any transformations and deletions of strings in the StringTable occur during Full GC. For this, a temporary table called the StringForwardingTable is used, into which only relevant strings are placed during garbage collection. After this, the reference to the StringTable is updated to point to the new table.&lt;/p&gt;
  &lt;h2 id=&quot;WAZa&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;k1IY&quot;&gt;So, we have familiarized ourselves with the organization of strings within the V8 engine. We have learned more about the string table and the different types of strings.&lt;/p&gt;
  &lt;p id=&quot;xPMT&quot;&gt;Here are some key points and conclusions.&lt;/p&gt;
  &lt;ul id=&quot;2yNB&quot;&gt;
    &lt;li id=&quot;FmCh&quot;&gt;Strings can be one-byte or two-byte. Two-byte strings require approximately twice as much memory, as each character in such a string is encoded with two bytes, regardless of whether it is an ASCII character or not. Therefore, if you have a choice of which string to use, a one-byte string is preferable in most cases.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;GV87&quot;&gt;const myMap = {
  &amp;quot;Frontend Almanac&amp;quot;: undefined, // one-byte key,
  &amp;quot;Frontend Альманах&amp;quot;: undefined, // two-byte key
}&lt;/pre&gt;
  &lt;ul id=&quot;kSsd&quot;&gt;
    &lt;li id=&quot;2OuG&quot;&gt;Despite the specified number &lt;code&gt;2**53 - 1&lt;/code&gt; in the specification, the actual length of strings on real systems is much lower. On 32-bit systems, V8 allows storing strings no longer than &lt;code&gt;2**28 - 16&lt;/code&gt; characters, whereas on 64-bit systems, it is no more than &lt;code&gt;2**29 - 24&lt;/code&gt; characters.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;ul id=&quot;Efao&quot;&gt;
    &lt;li id=&quot;cYrY&quot;&gt;Seq, Cons, and Sliced strings can be duplicated in the string table.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Ps3N&quot;&gt;const string1 = &amp;quot;FrontendAlmanac&amp;quot;; // creates internalized string
const string2 = &amp;quot;FrontendAlmanac&amp;quot;; // doesn&amp;#x27;t create new string
const string3 = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;; // creates duplicated string&lt;/pre&gt;
  &lt;ul id=&quot;3bSb&quot;&gt;
    &lt;li id=&quot;mOIZ&quot;&gt;SlicedStrings cannot be nested. They always refer to the original internalized string. The parent string remains alive as long as at least one of its child SlicedStrings exists.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;F2cv&quot;&gt;let parentString = &amp;quot;FrontendAlmanac&amp;quot;; // parent string
let slicedString1 = parentString.slice(1); // refers to the parentString
let slicedString2 = slicedString1.slice(1); // also refers to the parentString
let slicedString3 = slicedString2.slice(1); // and this one too

slicedString1 = undefind;
slicedString2 = undefind;
// parentString still not garbage collected
// as the slicedString3 is stil alive&lt;/pre&gt;
  &lt;ul id=&quot;Bbj0&quot;&gt;
    &lt;li id=&quot;p6q2&quot;&gt;If a string needs to be internalized but cannot be done &amp;quot;in place&amp;quot; — for example, if it is a Cons or Sliced string — the engine will calculate the flat value, create a new internalized string object, and convert the reference to this internalized version.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;jymP&quot;&gt;const string = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;; // ConsString

const obj = {};
obj[string]; // ThinString

// string is not ConsString anymore, now it&amp;#x27;s ThinString and refers to
// a flat internalized valued in the StringTable&lt;/pre&gt;
  &lt;ul id=&quot;VQuH&quot;&gt;
    &lt;li id=&quot;UmOk&quot;&gt;The previous example can be used to eliminate duplicate strings in the StringTable, as suggested in the article about &lt;a href=&quot;https://habr.com/ru/articles/449368/&quot; target=&quot;_blank&quot;&gt;string cleansing&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;jPHR&quot;&gt;const string1 = &amp;quot;FrontendAlmanac&amp;quot;;
const string2 = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;; // creates duplicate
const string3 = &amp;quot;Frontend&amp;quot; + &amp;quot;Almanac&amp;quot;; // creates duplicate

const obj = {};
obj[string2]; // ThinSting
obj[string3]; // ThinString

// Now the string2 and string3 are not ConsString anymoer and they refer
// to their internalized version, in this case to the string1,
// which is already in the StringTable.&lt;/pre&gt;
  &lt;p id=&quot;LmHc&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Ntxn&quot;&gt;I have described only a few cases of working with strings. The article&amp;#x27;s format is not sufficient to cover all possible features and special cases. Therefore, I invite everyone to share their experiences and ask questions in the comments on my Telegram channel.&lt;/p&gt;
  &lt;p id=&quot;OX8A&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;taBp&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;hjS9&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/v8-strings&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/v8-strings&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:react-memoization</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/react-memoization?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>React. Nodes update and memoization</title><published>2024-06-12T11:59:03.630Z</published><updated>2024-11-18T19:25:40.762Z</updated><category term="react" label="React ⚛️"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/10/e1/10e18547-c53e-49d1-9ae9-995ed13d4888.png&quot;&gt;In this article, we will delve &quot;under the hood&quot; of the React engine and see how node updates occur. At the same time, we will also explore the basic principles of memoization and its application in different types of components.</summary><content type="html">
  &lt;p id=&quot;Yw7o&quot;&gt;During the development of modern web applications, performance often becomes one of the key aspects that concern both developers and users. Users expect lightning-fast responsiveness, and developers strive to create applications that work quickly and efficiently.&lt;/p&gt;
  &lt;p id=&quot;wiNR&quot;&gt;One powerful tool that enables high performance in React applications is memoization. Memoization helps significantly reduce the amount of computations and, consequently, interface updates, which positively impacts the overall speed and responsiveness of the application.&lt;/p&gt;
  &lt;p id=&quot;WDmR&quot;&gt;In this article, we will delve &amp;quot;under the hood&amp;quot; of the React engine and see how node updates occur. At the same time, we will also explore the basic principles of memoization and its application in different types of components.&lt;/p&gt;
  &lt;h2 id=&quot;oM0i&quot;&gt;What is memoization?&lt;/h2&gt;
  &lt;p id=&quot;RWXZ&quot;&gt;Let&amp;#x27;s start with the theory. Formally, memoization can be defined as an optimization technique used to increase the performance of programs by storing the results of function calls and returning the saved result for subsequent calls with the same arguments.&lt;/p&gt;
  &lt;p id=&quot;eEvR&quot;&gt;In the context of React, memoization is especially useful for preventing unnecessary redraws and improving the performance of applications. In other words, it helps avoid unnecessary updates of components, making the application more reactive and efficient. Another equally important property of memoization is reducing the number of expensive computations. Instead of performing them on every component render, you can &amp;quot;remember the result&amp;quot; and recalculate it only in case of changes in the corresponding input parameters.&lt;/p&gt;
  &lt;h2 id=&quot;1Win&quot;&gt;Where exactly is the memoized result stored?&lt;/h2&gt;
  &lt;p id=&quot;T5M8&quot;&gt;To answer this question, we need to introduce the concept of React Fiber. I extensively described this structure in the article &lt;a href=&quot;https://blog.frontend-almanac.com/JqtGelofzm1&quot; target=&quot;_blank&quot;&gt;In-Depth React: Reconciliation, Renders, Fiber, Virtual Tree&lt;/a&gt;. In short, Fiber is an abstract entity in the React engine that describes any processed React node, whether it&amp;#x27;s a component, hook, or host root. Fiber has properties like &lt;code&gt;pendingProps&lt;/code&gt;, &lt;code&gt;memoizedProps&lt;/code&gt;, and &lt;code&gt;memoizedState&lt;/code&gt;, among others. It is in these properties that the memoized result is stored. How exactly? Let&amp;#x27;s find out.&lt;/p&gt;
  &lt;h2 id=&quot;FznF&quot;&gt;Memoized Components&lt;/h2&gt;
  &lt;p id=&quot;ue9Z&quot;&gt;Although the principles of memoization in React are the same for all entities, the implementation details may vary depending on the type of Fiber. To understand the process, we need to go through each type separately. Let&amp;#x27;s start with components, which are the &amp;quot;oldest&amp;quot; structure in React.&lt;/p&gt;
  &lt;p id=&quot;YdoR&quot;&gt;Since the early versions of React, we have been used to distinguishing components into class and functional components. In reality, there are many more component types inside the engine. The component type corresponds to the Fiber type and is specified in the &lt;code&gt;Fiber.tag&lt;/code&gt; property. &lt;a href=&quot;https://blog.frontend-almanac.com/JqtGelofzm1&quot; target=&quot;_blank&quot;&gt;In the mentioned article&lt;/a&gt;, I provided a full list of types for React version 18.2, which included a total of 28 types. In version 18.3.1, the list has slightly changed, now totaling 26 types.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;Udzn&quot;&gt;export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
export const TracingMarkerComponent = 25;&lt;/pre&gt;
  &lt;p id=&quot;I8C3&quot;&gt;To understand the memoization processes, we only need three items from this list:&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;YauZ&quot;&gt;export const FunctionComponent = 0;
export const ClassComponent = 1;
export const MemoComponent = 14;&lt;/pre&gt;
  &lt;h2 id=&quot;8RtS&quot;&gt;When does memoization occur?&lt;/h2&gt;
  &lt;p id=&quot;DIEy&quot;&gt;Before delving into the Fiber types directly, it is worth understanding at which specific moment the memoization process is triggered. &lt;a href=&quot;https://blog.frontend-almanac.com/JqtGelofzm1&quot; target=&quot;_blank&quot;&gt;In the previous article&lt;/a&gt;, I outlined four processing phases for a node. The first phase, &lt;strong&gt;begin&lt;/strong&gt;, determines the Fiber type that will initiate the necessary lifecycle. Nodes that have not yet been mounted have types like &lt;code&gt;IndeterminateComponent&lt;/code&gt;, &lt;code&gt;LazyComponent&lt;/code&gt;, or &lt;code&gt;IncompleteClassComponent&lt;/code&gt;. For such Fibers, the mounting process will be initiated. In other cases, the reconciler will attempt to update the existing node. To be more specific, the decision tree is processed by the reconciler&amp;#x27;s &lt;code&gt;beginWork&lt;/code&gt; function.&lt;/p&gt;
  &lt;p id=&quot;oIxO&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3699&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3699&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;59X8&quot;&gt;function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  ...
  
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        renderLanes,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, renderLanes);
    case HostPortal:
      return updatePortalComponent(current, workInProgress, renderLanes);
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderLanes,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderLanes);
    case Mode:
      return updateMode(current, workInProgress, renderLanes);
    case Profiler:
      return updateProfiler(current, workInProgress, renderLanes);
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderLanes);
    case ContextConsumer:
      return updateContextConsumer(current, workInProgress, renderLanes);
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      // Resolve outer props first, then resolve inner props.
      let resolvedProps = resolveDefaultProps(type, unresolvedProps);
      resolvedProps = resolveDefaultProps(type.type, resolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderLanes,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        renderLanes,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case SuspenseListComponent: {
      return updateSuspenseListComponent(current, workInProgress, renderLanes);
    }
    case ScopeComponent: {
      if (enableScopeAPI) {
        return updateScopeComponent(current, workInProgress, renderLanes);
      }
      break;
    }
    case OffscreenComponent: {
      return updateOffscreenComponent(current, workInProgress, renderLanes);
    }
    case LegacyHiddenComponent: {
      if (enableLegacyHidden) {
        return updateLegacyHiddenComponent(
          current,
          workInProgress,
          renderLanes,
        );
      }
      break;
    }
    case CacheComponent: {
      if (enableCache) {
        return updateCacheComponent(current, workInProgress, renderLanes);
      }
      break;
    }
    case TracingMarkerComponent: {
      if (enableTransitionTracing) {
        return updateTracingMarkerComponent(
          current,
          workInProgress,
          renderLanes,
        );
      }
      break;
    }
  }
  
  ...
}&lt;/pre&gt;
  &lt;p id=&quot;ZvXO&quot;&gt;From the memoization perspective, whether a component is mounted or updated, there is not a significant difference. Ultimately, the corresponding component update methods will be called regardless. The only distinction lies in whether the previous property values, state, or dependencies are passed into these methods. I will leave the process of transforming mountable types into updatable ones for future publications and suggest moving directly to the specific node update methods.&lt;/p&gt;
  &lt;h2 id=&quot;9bcU&quot;&gt;ClassComponent&lt;/h2&gt;
  &lt;p id=&quot;c2m2&quot;&gt;The name indicates that this type is assigned to class components. Just in case, let&amp;#x27;s recall how a class component is created.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;S5r4&quot;&gt;class MyComponent extends React.Component&amp;lt;{ prop1: string }&amp;gt; {
  render() {
    return (
      &amp;lt;div&amp;gt;
        {this.props.prop1}
      &amp;lt;/div&amp;gt;
    );
  }
}&lt;/pre&gt;
  &lt;p id=&quot;0dMt&quot;&gt;In the provided &lt;code&gt;beginWork&lt;/code&gt;, the update of class components begins as follows.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;d5s2&quot;&gt;const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
  workInProgress.elementType === Component
    ? unresolvedProps
    : resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
  current,
  workInProgress,
  Component,
  resolvedProps,
  renderLanes,
);&lt;/pre&gt;
  &lt;p id=&quot;O757&quot;&gt;The first thing that catches the eye here is the constant &lt;code&gt;resolvedProps&lt;/code&gt;. Despite the 5 lines of code, this constant simply takes the Fiber.pendingProps property, which is nothing but an object with the new component properties. To make it clear, I will also provide the code of the &lt;code&gt;resolveDefaultProps&lt;/code&gt; function.&lt;/p&gt;
  &lt;p id=&quot;3DsC&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberLazyComponent.new.js#L12&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberLazyComponent.new.js#L12&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;Z4lG&quot;&gt;export function resolveDefaultProps(Component: any, baseProps: Object): Object {
  if (Component &amp;amp;&amp;amp; Component.defaultProps) {
    // Resolve default props. Taken from ReactElement
    const props = assign({}, baseProps);
    const defaultProps = Component.defaultProps;
    for (const propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
    return props;
  }
  return baseProps;
}&lt;/pre&gt;
  &lt;p id=&quot;5189&quot;&gt;In more human terms, this function simply assigns default values to properties if they were specified when creating the component.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;MsRT&quot;&gt;class MyComponent extends React.Component&amp;lt;{ color?: string }&amp;gt; {
  static defaultProps = {
    color: &amp;#x27;blue&amp;#x27;,
  };

  render() {
    return (
      &amp;lt;div&amp;gt;
        {this.props.color}
      &amp;lt;/div&amp;gt;
    );
  }
}

&amp;lt;MyComponent /&amp;gt;              // &amp;lt;div&amp;gt;blue&amp;lt;/div&amp;gt;
&amp;lt;MyComponent color=&amp;quot;red&amp;quot; /&amp;gt;  // &amp;lt;div&amp;gt;red&amp;lt;/div&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;77qY&quot;&gt;The update mechanism itself is moved to the &lt;code&gt;updateClassComponent&lt;/code&gt; function. I will provide its code in a shortened form, as part of the functionality deals with processing the context provider and some service operations for dev mode.&lt;/p&gt;
  &lt;p id=&quot;oeaf&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L1062&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L1062&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;7uFO&quot;&gt;function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  ...
  const instance = workInProgress.stateNode;
  let shouldUpdate;
  if (instance === null) {
    resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);
    
    // In the initial pass we might need to construct the instance.
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  } else if (current === null) {
    // In a resume, we&amp;#x27;ll already have an instance we can reuse.
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderLanes,
    );
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderLanes,
    );
  }
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes,
  );
  ...
  return nextUnitOfWork;
}&lt;/pre&gt;
  &lt;p id=&quot;sbV8&quot;&gt;The first two &amp;quot;if&amp;quot; statements here are responsible for the process of mounting a new component. As mentioned earlier, we will not delve into these processes separately because ultimately the cycle will come to the update function where all the memoization work will take place. Specifically, it happens in the &lt;code&gt;updateClassInstance&lt;/code&gt; function, which I will present in its entirety.&lt;/p&gt;
  &lt;p id=&quot;pBZq&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L1123&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L1123&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;khEe&quot;&gt;// Invokes the update life-cycles and returns false if it shouldn&amp;#x27;t rerender.
function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderLanes: Lanes,
): boolean {
  const instance = workInProgress.stateNode;
  
  cloneUpdateQueue(current, workInProgress);
  
  const unresolvedOldProps = workInProgress.memoizedProps;
  const oldProps =
    workInProgress.type === workInProgress.elementType
      ? unresolvedOldProps
      : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
  instance.props = oldProps;
  const unresolvedNewProps = workInProgress.pendingProps;
  
  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext = emptyContextObject;
  if (typeof contextType === &amp;#x27;object&amp;#x27; &amp;amp;&amp;amp; contextType !== null) {
    nextContext = readContext(contextType);
  } else if (!disableLegacyContext) {
    const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }
  
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === &amp;#x27;function&amp;#x27; ||
    typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;;
    
  // Note: During these life-cycles, instance.props/instance.state are what
  // ever the previously attempted to render - not the &amp;quot;current&amp;quot;. However,
  // during componentDidUpdate we pass the &amp;quot;current&amp;quot; props.
  
  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  if (
    !hasNewLifecycles &amp;amp;&amp;amp;
    (typeof instance.UNSAFE_componentWillReceiveProps === &amp;#x27;function&amp;#x27; ||
      typeof instance.componentWillReceiveProps === &amp;#x27;function&amp;#x27;)
  ) {
    if (
      unresolvedOldProps !== unresolvedNewProps ||
      oldContext !== nextContext
    ) {
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext,
      );
    }
  }
  
  resetHasForceUpdateBeforeProcessing();
  
  const oldState = workInProgress.memoizedState;
  let newState = (instance.state = oldState);
  processUpdateQueue(workInProgress, newProps, instance, renderLanes);
  newState = workInProgress.memoizedState;
  
  if (
    unresolvedOldProps === unresolvedNewProps &amp;amp;&amp;amp;
    oldState === newState &amp;amp;&amp;amp;
    !hasContextChanged() &amp;amp;&amp;amp;
    !checkHasForceUpdateAfterProcessing() &amp;amp;&amp;amp;
    !(
      enableLazyContextPropagation &amp;amp;&amp;amp;
      current !== null &amp;amp;&amp;amp;
      current.dependencies !== null &amp;amp;&amp;amp;
      checkIfContextChanged(current.dependencies)
    )
  ) {
    // If an update was already in progress, we should schedule an Update
    // effect even though we&amp;#x27;re bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === &amp;#x27;function&amp;#x27;) {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;) {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Snapshot;
      }
    }
    return false;
  }
  
  if (typeof getDerivedStateFromProps === &amp;#x27;function&amp;#x27;) {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    newState = workInProgress.memoizedState;
  }
  
  const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    ) ||
    // TODO: In some cases, we&amp;#x27;ll end up checking if context has changed twice,
    // both before and after &amp;#x60;shouldComponentUpdate&amp;#x60; has been called. Not ideal,
    // but I&amp;#x27;m loath to refactor this function. This only happens for memoized
    // components so it&amp;#x27;s not that common.
    (enableLazyContextPropagation &amp;amp;&amp;amp;
      current !== null &amp;amp;&amp;amp;
      current.dependencies !== null &amp;amp;&amp;amp;
      checkIfContextChanged(current.dependencies));
      
  if (shouldUpdate) {
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    if (
      !hasNewLifecycles &amp;amp;&amp;amp;
      (typeof instance.UNSAFE_componentWillUpdate === &amp;#x27;function&amp;#x27; ||
        typeof instance.componentWillUpdate === &amp;#x27;function&amp;#x27;)
    ) {
      if (typeof instance.componentWillUpdate === &amp;#x27;function&amp;#x27;) {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === &amp;#x27;function&amp;#x27;) {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
    }
    if (typeof instance.componentDidUpdate === &amp;#x27;function&amp;#x27;) {
      workInProgress.flags |= Update;
    }
    if (typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;) {
      workInProgress.flags |= Snapshot;
    }
  } else {
    // If an update was already in progress, we should schedule an Update
    // effect even though we&amp;#x27;re bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === &amp;#x27;function&amp;#x27;) {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;) {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Snapshot;
      }
    }
    
    // If shouldComponentUpdate returned false, we should still update the
    // memoized props/state to indicate that this work can be reused.
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }
  
  // Update the existing instance&amp;#x27;s state, props, and context pointers even
  // if shouldComponentUpdate returns false.
  instance.props = newProps;
  instance.state = newState;
  instance.context = nextContext;
  
  return shouldUpdate;
}&lt;/pre&gt;
  &lt;p id=&quot;SPMq&quot;&gt;It may look quite cumbersome, but that&amp;#x27;s how it is. This is where all the lifecycle magic of class components happens. Let&amp;#x27;s go through it step by step.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;0bL2&quot;&gt;const unresolvedOldProps = workInProgress.memoizedProps;
const oldProps =
  workInProgress.type === workInProgress.elementType
    ? unresolvedOldProps
    : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
instance.props = oldProps;
const unresolvedNewProps = workInProgress.pendingProps;&lt;/pre&gt;
  &lt;p id=&quot;Zlrj&quot;&gt;We have already seen what this is about. The essence of this code is to obtain two constants: &lt;code&gt;unresolvedOldProps&lt;/code&gt;, which is immediately assigned to &lt;code&gt;this.props&lt;/code&gt; of the component instance, and &lt;code&gt;unresolvedNewProps&lt;/code&gt;. These constants will be involved in lifecycle methods and memoization, among other things.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;EvxD&quot;&gt;const oldContext = instance.context;
const contextType = ctor.contextType;
let nextContext = emptyContextObject;
if (typeof contextType === &amp;#x27;object&amp;#x27; &amp;amp;&amp;amp; contextType !== null) {
  nextContext = readContext(contextType);
} else if (!disableLegacyContext) {
  const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
  nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
} &lt;/pre&gt;
  &lt;p id=&quot;Phvp&quot;&gt;The next block is responsible for resolving the attached context of the component. We won&amp;#x27;t delve into this part in detail here, but variables like &lt;code&gt;oldContext&lt;/code&gt; and &lt;code&gt;nextContext&lt;/code&gt; participate in lifecycle methods alongside &lt;code&gt;unresolvedOldProps&lt;/code&gt; and &lt;code&gt;unresolvedNewProps&lt;/code&gt;, so it is worth noting them.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;9VFy&quot;&gt;const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
  typeof getDerivedStateFromProps === &amp;#x27;function&amp;#x27; ||
  typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;;
  
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the &amp;quot;current&amp;quot;. However,
// during componentDidUpdate we pass the &amp;quot;current&amp;quot; props.

// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
  !hasNewLifecycles &amp;amp;&amp;amp;
  (typeof instance.UNSAFE_componentWillReceiveProps === &amp;#x27;function&amp;#x27; ||
    typeof instance.componentWillReceiveProps === &amp;#x27;function&amp;#x27;)
) {
  if (
    unresolvedOldProps !== unresolvedNewProps ||
    oldContext !== nextContext
  ) {
    callComponentWillReceiveProps(
      workInProgress,
      instance,
      newProps,
      nextContext,
    );
  }
}&lt;/pre&gt;
  &lt;p id=&quot;xwcP&quot;&gt;Another block that I cannot overlook is the one before proceeding to the next stages of the component&amp;#x27;s lifecycle. It is necessary to first execute the &lt;a href=&quot;https://react.dev/reference/react/Component#componentwillreceiveprops&quot; target=&quot;_blank&quot;&gt;componentWillReceiveProps(nextProps)&lt;/a&gt; method if it has been defined in the component. An interesting point here is that this method will &lt;strong&gt;only&lt;/strong&gt; be called if the component does not use the new API, i.e., if it does not have the methods &lt;a href=&quot;https://react.dev/reference/react/Component#static-getderivedstatefromprops&quot; target=&quot;_blank&quot;&gt;getDerivedStateFromProps(props, state)&lt;/a&gt; and &lt;a href=&quot;https://react.dev/reference/react/Component#getsnapshotbeforeupdate&quot; target=&quot;_blank&quot;&gt;getSnapshotBeforeUpdate(prevProps, prevState)&lt;/a&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;ONtj&quot;&gt;const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;&lt;/pre&gt;
  &lt;p id=&quot;SOq3&quot;&gt;Lastly, the fourth block to consider is the variables &lt;code&gt;oldState&lt;/code&gt; and &lt;code&gt;newState&lt;/code&gt;, which also play a role in the component&amp;#x27;s lifecycle. Let&amp;#x27;s dive a bit deeper into this. While oldState is straightforward and retrieved from &lt;code&gt;Fiber.memoizedState&lt;/code&gt;, &lt;code&gt;newState&lt;/code&gt; requires further explanation.&lt;/p&gt;
  &lt;p id=&quot;g6Ha&quot;&gt;By default, newState is assigned the value of the current &lt;code&gt;oldState&lt;/code&gt;. Next, the &lt;code&gt;updateQueue&lt;/code&gt; loop is executed, triggered by the call to &lt;code&gt;processUpdateQueue&lt;/code&gt;. I won&amp;#x27;t provide the code for this function here as it is quite extensive and variable, which would only distract us from the article&amp;#x27;s main topic. The key point is to understand that this function eventually generates a new State and writes it to &lt;code&gt;workInProgress.memoizedState&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;Rr2i&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js#L458&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js#L458&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;0T9r&quot;&gt;export function processUpdateQueue&amp;lt;State&amp;gt;(
  ...
  // These values may change as we process the queue.
  if (firstBaseUpdate !== null) {
    ...
    workInProgress.memoizedState = newState;
  }
}&lt;/pre&gt;
  &lt;p id=&quot;asis&quot;&gt;After that, we obtain our variable &lt;code&gt;newState&lt;/code&gt; in our update function.&lt;/p&gt;
  &lt;p id=&quot;IOsO&quot;&gt;This concludes the gathering of essential information about the component, and the actual updating process begins.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;dy4L&quot;&gt;if (
  unresolvedOldProps === unresolvedNewProps &amp;amp;&amp;amp;
  oldState === newState &amp;amp;&amp;amp;
  !hasContextChanged() &amp;amp;&amp;amp;
  !checkHasForceUpdateAfterProcessing() &amp;amp;&amp;amp;
  !(
    enableLazyContextPropagation &amp;amp;&amp;amp;
    current !== null &amp;amp;&amp;amp;
    current.dependencies !== null &amp;amp;&amp;amp;
    checkIfContextChanged(current.dependencies)
  )
) {
  // If an update was already in progress, we should schedule an Update
  // effect even though we&amp;#x27;re bailing out, so that cWU/cDU are called.
  if (typeof instance.componentDidUpdate === &amp;#x27;function&amp;#x27;) {
    if (
      unresolvedOldProps !== current.memoizedProps ||
      oldState !== current.memoizedState
    ) {
      workInProgress.flags |= Update;
    }
  }
  if (typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;) {
    if (
      unresolvedOldProps !== current.memoizedProps ||
      oldState !== current.memoizedState
    ) {
      workInProgress.flags |= Snapshot;
    }
  }
  return false;
}&lt;/pre&gt;
  &lt;p id=&quot;2WmS&quot;&gt;No, this is not an update yet, as it may have initially appeared. Before proceeding with the update, the reconciler excludes unnecessary processing for optimization purposes. In this case, if the references to the component&amp;#x27;s properties and its state have not changed and &lt;a href=&quot;https://react.dev/reference/react/Component#forceupdate&quot; target=&quot;_blank&quot;&gt;forceUpdate()&lt;/a&gt; has not been called, there is no point in continuing the process, and the entire function stops.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;BFqm&quot;&gt;if (typeof getDerivedStateFromProps === &amp;#x27;function&amp;#x27;) {
  applyDerivedStateFromProps(
    workInProgress,
    ctor,
    getDerivedStateFromProps,
    newProps,
  );
  newState = workInProgress.memoizedState;
}&lt;/pre&gt;
  &lt;p id=&quot;OSz1&quot;&gt;Now, if something in the component&amp;#x27;s state has indeed changed, we can move on to its update. The first step is to call &lt;a href=&quot;https://react.dev/reference/react/Component#static-getderivedstatefromprops&quot; target=&quot;_blank&quot;&gt;getDerivedStateFromProps(props, state)&lt;/a&gt; because it modifies the component&amp;#x27;s state and, consequently, the newState variable in the update function.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;8kgL&quot;&gt;const shouldUpdate =
  checkHasForceUpdateAfterProcessing() ||
  checkShouldComponentUpdate(
    workInProgress,
    ctor,
    oldProps,
    newProps,
    oldState,
    newState,
    nextContext,
  ) ||
  // TODO: In some cases, we&amp;#x27;ll end up checking if context has changed twice,
  // both before and after &amp;#x60;shouldComponentUpdate&amp;#x60; has been called. Not ideal,
  // but I&amp;#x27;m loath to refactor this function. This only happens for memoized
  // components so it&amp;#x27;s not that common.
  (enableLazyContextPropagation &amp;amp;&amp;amp;
    current !== null &amp;amp;&amp;amp;
    current.dependencies !== null &amp;amp;&amp;amp;
    checkIfContextChanged(current.dependencies));&lt;/pre&gt;
  &lt;p id=&quot;84Lu&quot;&gt;Finally, all constants and variables have been gathered, and now a decision can be made regarding the further progression through the lifecycle, specifically, whether the component will be re-rendered. A component should only be re-rendered in two cases: if the values of properties, state, or context differ from the previous ones, or if &lt;a href=&quot;https://react.dev/reference/react/Component#forceupdate&quot; target=&quot;_blank&quot;&gt;forceUpdate()&lt;/a&gt; has been called. The last one is determined by the &lt;code&gt;checkHasForceUpdateAfterProcessing()&lt;/code&gt; function, while the comparison of values occurs within the &lt;code&gt;checkShouldComponentUpdate()&lt;/code&gt; function. Let&amp;#x27;s delve into this function in more detail.&lt;/p&gt;
  &lt;p id=&quot;t5yy&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L305&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L305&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;KjSY&quot;&gt;function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  const instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === &amp;#x27;function&amp;#x27;) {
    let shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    ...
    return shouldUpdate;
  }
  
  if (ctor.prototype &amp;amp;&amp;amp; ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }
  
  return true;
}&lt;/pre&gt;
  &lt;p id=&quot;9nl5&quot;&gt;If we remove the auxiliary dev-mode code from the function, it surprisingly turns out to be quite simple. The function encompasses just three scenarios:&lt;/p&gt;
  &lt;ol id=&quot;8qZ7&quot;&gt;
    &lt;li id=&quot;Q6YX&quot;&gt;The component has a &lt;a href=&quot;https://react.dev/reference/react/Component#shouldcomponentupdate&quot; target=&quot;_blank&quot;&gt;shouldComponentUpdate()&lt;/a&gt; method. In this case, the decision of whether the component should be re-rendered is entirely left to the developer.&lt;/li&gt;
    &lt;li id=&quot;nQ5s&quot;&gt;n the case of a &lt;code&gt;PureComponent&lt;/code&gt;, a shallow comparison of properties and state is conducted (we will discuss this in more detail shortly).&lt;/li&gt;
    &lt;li id=&quot;PbUl&quot;&gt;In all other cases, the component will be re-rendered anyway.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;dhuC&quot;&gt;if (shouldUpdate) {
  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  if (
    !hasNewLifecycles &amp;amp;&amp;amp;
    (typeof instance.UNSAFE_componentWillUpdate === &amp;#x27;function&amp;#x27; ||
      typeof instance.componentWillUpdate === &amp;#x27;function&amp;#x27;)
  ) {
    if (typeof instance.componentWillUpdate === &amp;#x27;function&amp;#x27;) {
      instance.componentWillUpdate(newProps, newState, nextContext);
    }
    if (typeof instance.UNSAFE_componentWillUpdate === &amp;#x27;function&amp;#x27;) {
      instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
    }
  }
  if (typeof instance.componentDidUpdate === &amp;#x27;function&amp;#x27;) {
    workInProgress.flags |= Update;
  }
  if (typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;) {
    workInProgress.flags |= Snapshot;
  }
} else {
  // If an update was already in progress, we should schedule an Update
  // effect even though we&amp;#x27;re bailing out, so that cWU/cDU are called.
  if (typeof instance.componentDidUpdate === &amp;#x27;function&amp;#x27;) {
    if (
      unresolvedOldProps !== current.memoizedProps ||
      oldState !== current.memoizedState
    ) {
      workInProgress.flags |= Update;
    }
  }
  if (typeof instance.getSnapshotBeforeUpdate === &amp;#x27;function&amp;#x27;) {
    if (
      unresolvedOldProps !== current.memoizedProps ||
      oldState !== current.memoizedState
    ) {
      workInProgress.flags |= Snapshot;
    }
  }
  
  // If shouldComponentUpdate returned false, we should still update the
  // memoized props/state to indicate that this work can be reused.
  workInProgress.memoizedProps = newProps;
  workInProgress.memoizedState = newState;
}&lt;/pre&gt;
  &lt;p id=&quot;H6pm&quot;&gt;The final, well, almost final stage is the execution of the remaining lifecycle methods. In the event that the component needs to be re-rendered, the following methods will be executed:&lt;/p&gt;
  &lt;ol id=&quot;dg91&quot;&gt;
    &lt;li id=&quot;KL2a&quot;&gt;&lt;code&gt;componentWillUpdate&lt;/code&gt; (only if the new API is not used)&lt;/li&gt;
    &lt;li id=&quot;6kB0&quot;&gt;&lt;code&gt;UNSAFE_componentWillUpdate&lt;/code&gt; (only if the new API is not used)&lt;/li&gt;
    &lt;li id=&quot;sdwC&quot;&gt;&lt;code&gt;componentDidUpdate&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;xNw8&quot;&gt;&lt;code&gt;getSnapshotBeforeUpdate&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;8D4c&quot;&gt;Otherwise, these methods will not be called, only the necessary utility flags will be set, and the properties and state will be stored in memoizedProps and memoizedState, respectively.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;TA3W&quot;&gt;// Update the existing instance&amp;#x27;s state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;

return shouldUpdate;&lt;/pre&gt;
  &lt;p id=&quot;dggw&quot;&gt;And finally, the last stage. The new properties, state, and context values are assigned to the class instance, after which the function completes.&lt;/p&gt;
  &lt;h2 id=&quot;hmSV&quot;&gt;Shallow Comparison&lt;/h2&gt;
  &lt;p id=&quot;yTYx&quot;&gt;The concept of shallow comparison has already been mentioned above. Let&amp;#x27;s delve into it in more detail. We talked about how this comparison occurs when implementing a PureComponent. Just to recap, let&amp;#x27;s remember what a PureComponent is. Let&amp;#x27;s use the example mentioned earlier and make slight modifications to it.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;yZN3&quot;&gt;class MyComponent extends React.PureComponent&amp;lt;{ color?: string }&amp;gt; {
  static defaultProps = {
    color: &amp;#x27;blue&amp;#x27;,
  };
  
  render() {
    return (
      &amp;lt;div&amp;gt;
        {this.props.color}
      &amp;lt;/div&amp;gt;
    );
  }
}&lt;/pre&gt;
  &lt;p id=&quot;tMS6&quot;&gt;As you can see, the only difference is in which parent class we inherit our class component from (in the original version, we inherited from &lt;code&gt;React.Component&lt;/code&gt;). It is in such a component that shallow comparison will be applied.&lt;/p&gt;
  &lt;p id=&quot;YnNX&quot;&gt;So, what exactly is shallow comparison? Perhaps the best answer to this question would be the code of the &lt;code&gt;shallowEqual&lt;/code&gt; function itself, which we have seen before.&lt;/p&gt;
  &lt;p id=&quot;D7gU&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/shared/shallowEqual.js#L13&quot; target=&quot;_blank&quot;&gt;/packages/shared/shallowEqual.js#L13&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;RINv&quot;&gt;/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }
  
  if (
    typeof objA !== &amp;#x27;object&amp;#x27; ||
    objA === null ||
    typeof objB !== &amp;#x27;object&amp;#x27; ||
    objB === null
  ) {
    return false;
  }
  
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  
  if (keysA.length !== keysB.length) {
    return false;
  }
  
  // Test for A&amp;#x27;s keys different from B.
  for (let i = 0; i &amp;lt; keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }
  
  return true;
}&lt;/pre&gt;
  &lt;p id=&quot;nR8I&quot;&gt;This utility function consists of two main parts:&lt;/p&gt;
  &lt;ol id=&quot;6DRv&quot;&gt;
    &lt;li id=&quot;KI4x&quot;&gt;Comparing variables &lt;code&gt;objA&lt;/code&gt; and &lt;code&gt;objB&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;ICz1&quot;&gt;If the variables are equal or have equal references to the objects they represent, a comparison is made for each property of these objects.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;rvos&quot;&gt;The actual comparison is performed by another utility called &lt;code&gt;is()&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;GTZC&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/shared/objectIs.js#L10&quot; target=&quot;_blank&quot;&gt;/packages/shared/objectIs.js#L10&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;LqhJ&quot;&gt;/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x: any, y: any) {
  return (
    (x === y &amp;amp;&amp;amp; (x !== 0 || 1 / x === 1 / y)) || (x !== x &amp;amp;&amp;amp; y !== y) // eslint-disable-line no-self-compare
  );
}

const objectIs: (x: any, y: any) =&amp;gt; boolean =
  typeof Object.is === &amp;#x27;function&amp;#x27; ? Object.is : is;&lt;/pre&gt;
  &lt;p id=&quot;njFl&quot;&gt;This utility is nothing but a function from the Web API - &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is&quot; target=&quot;_blank&quot;&gt;Object.is()&lt;/a&gt;. If the browser does not support this Web API method, a built-in polyfill is used.&lt;/p&gt;
  &lt;p id=&quot;m9qD&quot;&gt;In sort, two values are the same if one of the following holds:&lt;/p&gt;
  &lt;ul id=&quot;emGY&quot;&gt;
    &lt;li id=&quot;NjNO&quot;&gt;both &lt;code&gt;undefined&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;7ZSl&quot;&gt;both &lt;code&gt;null&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;8c4s&quot;&gt;both &lt;code&gt;true&lt;/code&gt; or both &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;X3yJ&quot;&gt;both strings of the same length with the same characters in the same order&lt;/li&gt;
    &lt;li id=&quot;gqmj&quot;&gt;both the same object (meaning both values reference the same object in memory)&lt;/li&gt;
    &lt;li id=&quot;NjNN&quot;&gt;both &lt;code&gt;BigInts&lt;/code&gt; with the same numeric value&lt;/li&gt;
    &lt;li id=&quot;pEFA&quot;&gt;both &lt;code&gt;symbols&lt;/code&gt; that reference the same symbol value&lt;/li&gt;
    &lt;li id=&quot;gmXb&quot;&gt;both numbers and&lt;/li&gt;
    &lt;ul id=&quot;Ezue&quot;&gt;
      &lt;li id=&quot;6eTV&quot;&gt;both &lt;code&gt;+0&lt;/code&gt;&lt;/li&gt;
      &lt;li id=&quot;3HD8&quot;&gt;both &lt;code&gt;-0&lt;/code&gt;&lt;/li&gt;
      &lt;li id=&quot;TG3J&quot;&gt;both &lt;code&gt;NaN&lt;/code&gt;&lt;/li&gt;
      &lt;li id=&quot;HB7i&quot;&gt;or both non-zero, not &lt;code&gt;NaN&lt;/code&gt;, and have the same value&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;GMPM&quot;&gt;FunctionComponent&lt;/h2&gt;
  &lt;p id=&quot;SAJh&quot;&gt;Let&amp;#x27;s switch to functional components now that we&amp;#x27;ve covered class components.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;fLwJ&quot;&gt;const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
  workInProgress.elementType === Component
    ? unresolvedProps
    : resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
  current,
  workInProgress,
  Component,
  resolvedProps,
  renderLanes,
);&lt;/pre&gt;
  &lt;p id=&quot;kKTg&quot;&gt;At the very beginning of the begin phase of updating a functional component, there is no difference compared to updating a class component, except for the update function itself.&lt;/p&gt;
  &lt;p id=&quot;5Hwf&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L965&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L965&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;ALbD&quot;&gt;function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
  ...
  
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );
  hasId = checkDidRenderIdHook();

  ...
    
  if (current !== null &amp;amp;&amp;amp; !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
  
  if (getIsHydrating() &amp;amp;&amp;amp; hasId) {
    pushMaterializedTreeId(workInProgress);
  }
  
  ...
  
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}&lt;/pre&gt;
  &lt;p id=&quot;Zu4v&quot;&gt;Specifically, what matters here is the launch of a separate flow called &lt;code&gt;renderWithHooks&lt;/code&gt;, which is characteristic of functional components. Let&amp;#x27;s take a look at what&amp;#x27;s going on there.&lt;/p&gt;
  &lt;p id=&quot;gVrr&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L374&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberHooks.new.js#L374&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;REyj&quot;&gt;export function renderWithHooks&amp;lt;Props, SecondArg&amp;gt;(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) =&amp;gt; any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;
  
  ...
  
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;
  
  // The following should have already been reset
  // currentHook = null;
  // workInProgressHook = null;
  
  // didScheduleRenderPhaseUpdate = false;
  // localIdCounter = 0;
  
  // TODO Warn if no hooks are used at all during mount, then some are used during update.
  // Currently we will identify the update render as a mount because memoizedState === null.
  // This is tricky because it&amp;#x27;s valid for certain types of components (e.g. React.lazy)
  
  // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
  // Non-stateful hooks (e.g. context) don&amp;#x27;t get added to memoizedState,
  // so memoizedState would be null during updates and mounts.
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;
      
  let children = Component(props, secondArg);
  
  // Check if there was a render phase update
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    // Keep rendering in a loop for as long as render phase updates continue to
    // be scheduled. Use a counter to prevent infinite loops.
    let numberOfReRenders: number = 0;
    do {
      didScheduleRenderPhaseUpdateDuringThisPass = false;
      localIdCounter = 0;
      
      if (numberOfReRenders &amp;gt;= RE_RENDER_LIMIT) {
        throw new Error(
          &amp;#x27;Too many re-renders. React limits the number of renders to prevent &amp;#x27; +
            &amp;#x27;an infinite loop.&amp;#x27;,
        );
      }
      
      numberOfReRenders += 1;
      
      // Start over from the beginning of the list
      currentHook = null;
      workInProgressHook = null;
      
      workInProgress.updateQueue = null;
      
      ReactCurrentDispatcher.current = __DEV__
        ? HooksDispatcherOnRerenderInDEV
        : HooksDispatcherOnRerender;
        
      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  }
  
  // We can assume the previous dispatcher is always this one, since we set it
  // at the beginning of the render phase and there&amp;#x27;s no re-entrance.
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
  
  // This check uses currentHook so that it works the same in DEV and prod bundles.
  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
  const didRenderTooFewHooks =
    currentHook !== null &amp;amp;&amp;amp; currentHook.next !== null;
    
  renderLanes = NoLanes;
  currentlyRenderingFiber = (null: any);
  
  currentHook = null;
  workInProgressHook = null;
  
  didScheduleRenderPhaseUpdate = false;
  // This is reset by checkDidRenderIdHook
  // localIdCounter = 0;
  
  if (didRenderTooFewHooks) {
    throw new Error(
      &amp;#x27;Rendered fewer hooks than expected. This may be caused by an accidental &amp;#x27; +
        &amp;#x27;early return statement.&amp;#x27;,
    );
  }
  
  if (enableLazyContextPropagation) {
    if (current !== null) {
      if (!checkIfWorkInProgressReceivedUpdate()) {
        // If there were no changes to props or state, we need to check if there
        // was a context change. We didn&amp;#x27;t already do this because there&amp;#x27;s no
        // 1:1 correspondence between dependencies and hooks. Although, because
        // there almost always is in the common case (&amp;#x60;readContext&amp;#x60; is an
        // internal API), we could compare in there. OTOH, we only hit this case
        // if everything else bails out, so on the whole it might be better to
        // keep the comparison out of the common path.
        const currentDependencies = current.dependencies;
        if (
          currentDependencies !== null &amp;amp;&amp;amp;
          checkIfContextChanged(currentDependencies)
        ) {
          markWorkInProgressReceivedUpdate();
        }
      }
    }
  }
  return children;
}&lt;/pre&gt;
  &lt;p id=&quot;xIg2&quot;&gt;The function may seem big and complex, but essentially, it does only two things. First, it defines the hook dispatcher, which will be responsible for the further execution of these hooks. Formally, in React, there are only 4 such dispatchers:&lt;/p&gt;
  &lt;ul id=&quot;LaKt&quot;&gt;
    &lt;li id=&quot;KATx&quot;&gt;&lt;code&gt;ContextOnlyDispatcher&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;nSzt&quot;&gt;&lt;code&gt;HooksDispatcherOnMount&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;5mIY&quot;&gt;&lt;code&gt;HooksDispatcherOnUpdate&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;QGEw&quot;&gt;&lt;code&gt;HooksDispatcherOnRender&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;fW4U&quot;&gt;In reality, the &lt;code&gt;ContextOnlyDispatcher&lt;/code&gt; does not implement anything; it serves as a kind of default dispatcher and remains in place until the engine identifies a more relevant dispatcher for the current component.&lt;/p&gt;
  &lt;p id=&quot;rh6l&quot;&gt;Among the remaining three dispatchers, &lt;code&gt;HooksDispatcherOnUpdate&lt;/code&gt; and &lt;code&gt;HooksDispatcherOnRender&lt;/code&gt; practically do not differ, both leading to the same implementations of update hooks (&lt;code&gt;updateMemo&lt;/code&gt;, &lt;code&gt;updateCallback&lt;/code&gt;, etc.). The separation of these two different dispatchers is purely logical, in case React developers ever need to create different versions of hooks for different phases. As for &lt;code&gt;HooksDispatcherOnMount&lt;/code&gt;, it leads to separate implementations of mount hooks (&lt;code&gt;mountMemo&lt;/code&gt;, &lt;code&gt;mountCallback&lt;/code&gt;, etc.).&lt;/p&gt;
  &lt;p id=&quot;lhH5&quot;&gt;We will discuss hooks and their providers a little later. For now, let&amp;#x27;s just note that each hook has two versions: &lt;strong&gt;mount&lt;/strong&gt; and &lt;strong&gt;update&lt;/strong&gt;. At this stage, it is important to determine which version of the hooks the engine will execute. This is done as follows.&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;oEWd&quot;&gt;ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;&lt;/pre&gt;
  &lt;p id=&quot;Mu73&quot;&gt;Where &lt;code&gt;current&lt;/code&gt; is a reference to &lt;code&gt;Fiber.alternate&lt;/code&gt;. If it is equal to &lt;code&gt;null&lt;/code&gt;, it means the component has not been created yet. Similarly, if &lt;code&gt;Fiber.alternate.memoizedState&lt;/code&gt; is empty, then the hooks have not been executed in this component yet. In both cases, the &lt;code&gt;HooksDispatcherOnMount&lt;/code&gt; dispatcher will be applied. Otherwise, it will be &lt;code&gt;HooksDispatcherOnUpdate&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;iPsd&quot;&gt;The second thing the function does is create a component using &lt;code&gt;let children = Component(props, secondArg)&lt;/code&gt; and repeats this process in a loop until all scheduled updates are completed in the current phase. And yes, this is where the &lt;code&gt;Too many re-renders. React limits the number of renders to prevent an infinite loop.&lt;/code&gt; exception is thrown if the number of updates exceeds &lt;strong&gt;25&lt;/strong&gt; iterations.&lt;/p&gt;
  &lt;h2 id=&quot;rkIk&quot;&gt;HooksDispatcherOnMount&lt;/h2&gt;
  &lt;p id=&quot;A0yM&quot;&gt;The dispatcher code looks like this&lt;/p&gt;
  &lt;p id=&quot;iTsd&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L2427&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberHooks.new.js#L2427&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;4lLB&quot;&gt;const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useSyncExternalStore: mountSyncExternalStore,
  useId: mountId,
  
  unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
  (HooksDispatcherOnMount: Dispatcher).getCacheSignal = getCacheSignal;
  (HooksDispatcherOnMount: Dispatcher).getCacheForType = getCacheForType;
  (HooksDispatcherOnMount: Dispatcher).useCacheRefresh = mountRefresh;
}&lt;/pre&gt;
  &lt;p id=&quot;2Aoq&quot;&gt;We will not consider all mount functions of hooks, it is more important to understand the essence of these functions. Let&amp;#x27;s see this on the example of &lt;code&gt;mountMemo&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;udI4&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L1899&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberHooks.new.js#L1899&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;XUHr&quot;&gt;function mountMemo&amp;lt;T&amp;gt;(
  nextCreate: () =&amp;gt; T,
  deps: Array&amp;lt;mixed&amp;gt; | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}&lt;/pre&gt;
  &lt;p id=&quot;P9WS&quot;&gt;As you can see, the hook code is embarrassingly simple. First, a reference to Fiber is defined. Then, the value is computed, which occurs in the custom callback &lt;code&gt;nextCreate&lt;/code&gt;. Next, the computed value and the array of dependencies are saved in &lt;code&gt;Fiber.memoizedState&lt;/code&gt;. Here it is, memoization!&lt;/p&gt;
  &lt;h2 id=&quot;C94Q&quot;&gt;HooksDispatcherOnUpdate&lt;/h2&gt;
  &lt;p id=&quot;KBTw&quot;&gt;Now let&amp;#x27;s peek into the &lt;code&gt;HooksDispatcherOnUpdate&lt;/code&gt; dispatcher.&lt;/p&gt;
  &lt;p id=&quot;yGHP&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L2454&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberHooks.new.js#L2454&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;0vCd&quot;&gt;const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,
  
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useInsertionEffect: updateInsertionEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useSyncExternalStore: updateSyncExternalStore,
  useId: updateId,
  
  unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
  (HooksDispatcherOnUpdate: Dispatcher).getCacheSignal = getCacheSignal;
  (HooksDispatcherOnUpdate: Dispatcher).getCacheForType = getCacheForType;
  (HooksDispatcherOnUpdate: Dispatcher).useCacheRefresh = updateRefresh;
}&lt;/pre&gt;
  &lt;p id=&quot;zHOO&quot;&gt;As I mentioned earlier, this dispatcher differs from the previous one in that it leads to the update versions of the same hooks. Since in the previous example we looked at &lt;code&gt;mountMemo&lt;/code&gt;, this time let&amp;#x27;s look at &lt;code&gt;updateMemo&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;PmOH&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L1910&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberHooks.new.js#L1910&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;7Ybt&quot;&gt;function updateMemo&amp;lt;T&amp;gt;(
  nextCreate: () =&amp;gt; T,
  deps: Array&amp;lt;mixed&amp;gt; | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    // Assume these are defined. If they&amp;#x27;re not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      const prevDeps: Array&amp;lt;mixed&amp;gt; | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}&lt;/pre&gt;
  &lt;p id=&quot;OyyU&quot;&gt;Here we already have the memory effect that was initiated during the mounting phase. Now, before starting the computations, the hook will take the previous dependencies from &lt;code&gt;Fiber.memoizedState[1]&lt;/code&gt; and compare them with the current ones using the function &lt;code&gt;areHookInputsEqual(nextDeps, prevDeps)&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;ywXz&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberHooks.new.js#L327&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberHooks.new.js#L327&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;B8SU&quot;&gt;function areHookInputsEqual(
  nextDeps: Array&amp;lt;mixed&amp;gt;,
  prevDeps: Array&amp;lt;mixed&amp;gt; | null,
) {
  ...
  
  if (prevDeps === null) {
    if (__DEV__) {
      console.error(
        &amp;#x27;%s received a final argument during this render, but not during &amp;#x27; +
          &amp;#x27;the previous render. Even though the final argument is optional, &amp;#x27; +
          &amp;#x27;its type cannot change between renders.&amp;#x27;,
        currentHookNameInDev,
      );
    }
    return false;
  }
  
  ...
  
  for (let i = 0; i &amp;lt; prevDeps.length &amp;amp;&amp;amp; i &amp;lt; nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  return true;
}&lt;/pre&gt;
  &lt;p id=&quot;iAIn&quot;&gt;Which, in turn, performs a shallow comparison of dependencies using the same &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is&quot; target=&quot;_blank&quot;&gt;Object.is&lt;/a&gt; function.&lt;/p&gt;
  &lt;h2 id=&quot;yizd&quot;&gt;MemoComponent&lt;/h2&gt;
  &lt;p id=&quot;iGSr&quot;&gt;It&amp;#x27;s time to talk about the third type of node that interests us - &lt;code&gt;MemoComponent&lt;/code&gt;. Such a node can be created using &lt;a href=&quot;https://react.dev/reference/react/memo&quot; target=&quot;_blank&quot;&gt;React.memo&lt;/a&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Hrlj&quot;&gt;const MyComponent = React.memo&amp;lt;{ color?: string }&amp;gt;(({ color }) =&amp;gt; {
  return &amp;lt;div&amp;gt;{color}&amp;lt;/div&amp;gt;;
});&lt;/pre&gt;
  &lt;p id=&quot;1uwH&quot;&gt;This type of component will only rerender if the properties passed to it have changed. It looks like this:&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;HvJr&quot;&gt;const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);

resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
  current,
  workInProgress,
  type,
  resolvedProps,
  renderLanes,
);&lt;/pre&gt;
  &lt;p id=&quot;jRmL&quot;&gt;The update function for these components is called &lt;code&gt;updateMemoComponent&lt;/code&gt;. The function is quite long, I will only provide the part of it that we need within the scope of this article.&lt;/p&gt;
  &lt;p id=&quot;qNxT&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v18.3.1/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L452&quot; target=&quot;_blank&quot;&gt;/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L452&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;flow&quot; id=&quot;Kiiz&quot;&gt;function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
): null | Fiber {
  ...
  
  const currentChild = ((current.child: any): Fiber); // This is always exactly one child
  const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
    current,
    renderLanes,
  );
  if (!hasScheduledUpdateOrContext) {
    // This will be the props with resolved defaultProps,
    // unlike current.memoizedProps which will be the unresolved ones.
    const prevProps = currentChild.memoizedProps;
    // Default to shallow comparison
    let compare = Component.compare;
    compare = compare !== null ? compare : shallowEqual;
    if (compare(prevProps, nextProps) &amp;amp;&amp;amp; current.ref === workInProgress.ref) {
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  }
  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork;
  const newChild = createWorkInProgress(currentChild, nextProps);
  newChild.ref = workInProgress.ref;
  newChild.return = workInProgress;
  workInProgress.child = newChild;
  return newChild;
}&lt;/pre&gt;
  &lt;p id=&quot;pbuH&quot;&gt;The essence of this code boils down to calling the comparison function &lt;code&gt;compare(prevProps, nextProps)&lt;/code&gt;, where &lt;code&gt;compare&lt;/code&gt; can be either a custom function (the second argument of &lt;a href=&quot;https://react.dev/reference/react/memo&quot; target=&quot;_blank&quot;&gt;React.memo&lt;/a&gt;) or the default &lt;code&gt;shallowEqual&lt;/code&gt;, which we have already seen earlier.&lt;/p&gt;
  &lt;h2 id=&quot;7J1z&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;x5ok&quot;&gt;In this article, we took a peek under the hood of the React engine and examined the mechanisms of mounting and updating nodes. One of the most important mechanisms in React involved in these processes is memoization. We discussed it using examples of three types of Fibers: &lt;code&gt;ClassComponent&lt;/code&gt;, &lt;code&gt;FunctionComponent&lt;/code&gt;, and &lt;code&gt;MemoComponent&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;ikfZ&quot;&gt;Now that we have a better understanding of these processes, it&amp;#x27;s time to draw some conclusions.&lt;/p&gt;
  &lt;ul id=&quot;MYBn&quot;&gt;
    &lt;li id=&quot;xJb5&quot;&gt;Whether it&amp;#x27;s a class, function, or a hook&amp;#x27;s dependencies, React uses the same approach as a comparison function - the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is&quot; target=&quot;_blank&quot;&gt;Object.is()&lt;/a&gt; Web API method or its polyfill. &lt;/li&gt;
    &lt;li id=&quot;IfP5&quot;&gt;Each hook has two implementation versions, &lt;strong&gt;mount&lt;/strong&gt; and &lt;strong&gt;update&lt;/strong&gt;. The update version is usually more complex because it needs to compare dependencies. Developers cannot influence the specific version of the hook used by the engine. The mount version only runs during the first component mount.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;ul id=&quot;B4AC&quot;&gt;
    &lt;li id=&quot;ret8&quot;&gt;Since React compares each dependency separately when processing a hook, it is not advisable to add unnecessary dependencies. Additionally, dependencies are stored in memory, so having a large number of them can impact resource consumption. In other words,&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;LELr&quot;&gt;const value = useMemo(() =&amp;gt; {
  return a + b
}, [a, b]);&lt;/pre&gt;
  &lt;p id=&quot;9NgY&quot;&gt;this code will be slightly more performant than the following&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;qh5Y&quot;&gt;const value = useMemo(() =&amp;gt; {
  return a + b
}, [a, b, c]);&lt;/pre&gt;
  &lt;p id=&quot;zDOb&quot;&gt;Similarly,&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;OOan&quot;&gt;const SOME_CONST = &amp;quot;some value&amp;quot;;

const MyComponent = ({ a, b }) =&amp;gt; {
  // this hook is more performant
  const value1 = useMemo(() =&amp;gt; {
    return a + SOME_CONST
  }, [a]);

  // than this one
  const value2 = useMemo(() =&amp;gt; {
    return b + SOME_CONST
  }, [b, SOME_CONST]);  

  return null;
}&lt;/pre&gt;
  &lt;ul id=&quot;aWx0&quot;&gt;
    &lt;li id=&quot;WvnY&quot;&gt;For the same reason, it is essential to adequately assess the need for memoization in general. For example,&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;BxL7&quot;&gt;const isEqual = useMemo(() =&amp;gt; {
  return a === b
}, [a, b]);&lt;/pre&gt;
  &lt;p id=&quot;edgm&quot;&gt;It won&amp;#x27;t provide any performance boost. On the contrary, the engine will have to compare &lt;code&gt;prevDeps.a === currDeps.a&lt;/code&gt; and &lt;code&gt;prevDeps.b === currDeps.b&lt;/code&gt; each time. Moreover, an additional function will be introduced in the code, which will consume its own resources.&lt;/p&gt;
  &lt;ul id=&quot;J63f&quot;&gt;
    &lt;li id=&quot;RODn&quot;&gt;The previous point applies to &lt;a href=&quot;https://react.dev/reference/react/memo&quot; target=&quot;_blank&quot;&gt;React.memo&lt;/a&gt; as well. Developers should understand whether the effect of memoization will outweigh the overhead of maintaining it.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;VqMo&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;ioIU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;taBp&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/react-memoization&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/react-memoization&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:js-optimisation-ic</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/js-optimisation-ic?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>JavaScript Optimisation. Inline Caches</title><published>2024-04-23T17:50:26.635Z</published><updated>2024-04-25T18:54:50.422Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/56/9f/569f1d89-14c1-4ac5-9af6-bd03b1fe9b35.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/2d/a4/2da4e55d-16a5-429a-8cff-57f83fd6d27b.png&quot;&gt;I think it is not a secret for anyone that all popular JavaScript engines have a similar code execution pipeline. It looks something like this. The interpreter quickly compiles JavaScript code into bytecode &amp;quot;on the fly&amp;quot;. The resulting bytecode starts executing and is processed in parallel by the optimizer. The optimizer needs time for this processing, but in the end, highly optimized code can be obtained, which will work much faster. In the V8 engine, Ignition acts as the interpreter, and Turbofan acts as the optimizer. In the Chakra engine, which is used in Edge, instead of one optimizer, there are two - SimpleJIT and FullJIT. In JSC (Safari), there are three Baseline, DFG, and FTL optimizers. The specific implementation may...</summary><content type="html">
  &lt;p id=&quot;l91T&quot;&gt;I think it is not a secret for anyone that all popular JavaScript engines have a similar code execution pipeline. It looks something like this. The interpreter quickly compiles JavaScript code into bytecode &amp;quot;on the fly&amp;quot;. The resulting bytecode starts executing and is processed in parallel by the optimizer. The optimizer needs time for this processing, but in the end, highly optimized code can be obtained, which will work much faster. In the V8 engine, Ignition acts as the interpreter, and Turbofan acts as the optimizer. In the Chakra engine, which is used in Edge, instead of one optimizer, there are two - SimpleJIT and FullJIT. In JSC (Safari), there are three Baseline, DFG, and FTL optimizers. The specific implementation may vary in different engines, but the basic scheme is generally the same.&lt;/p&gt;
  &lt;p id=&quot;3Vi4&quot;&gt;Today, we will talk about one of the many optimization mechanisms called &lt;strong&gt;Inline Caches&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;nuKp&quot;&gt;So, let&amp;#x27;s take a very ordinary function and try to understand how it will work at runtime.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;t7fI&quot;&gt;function getX(obj) {
  return obj.x;
}&lt;/pre&gt;
  &lt;p id=&quot;SUjc&quot;&gt;Our function is rather primitive. It is a simple getter that takes an object as an argument and returns the value of the &lt;code&gt;x&lt;/code&gt; property of that object. From the perspective of a JavaScript developer, the function looks absolutely atomic. However, let&amp;#x27;s dive under the hood of the engine and see what it represents in its compiled form.&lt;/p&gt;
  &lt;p id=&quot;4bat&quot;&gt;For experiments, as usual, we will use the most popular JS engine V8 (as of the time of writing this article, version &lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/tags/12.6.72&quot; target=&quot;_blank&quot;&gt;12.6.72&lt;/a&gt;). For full debugging, in addition to the body of the function itself, we need to call it with a real argument. Also, let&amp;#x27;s add output of information about the object, which will be needed a bit later.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;6DaJ&quot;&gt;function getX(obj) {
  %DebugPrint(obj);
  return obj.x;
}

getX({ x: 1 });&lt;/pre&gt;
  &lt;p id=&quot;o9sk&quot;&gt;I would like to remind that &lt;code&gt;%DebugPrint&lt;/code&gt; is a built-in function in V8. In order to use it in the code, you need to run the d8 runtime with the &lt;code&gt;--allow-natives-syntax&lt;/code&gt; flag. Additionally, let&amp;#x27;s print the bytecode of the executable script to the console (using the &lt;code&gt;--print-bytecode&lt;/code&gt; flag).&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;x1hh&quot;&gt;%&amp;gt; d8 --print-bytecode --allow-natives-syntax index.js 
...
// байткод функции getX
[generated bytecode for function: getX (0x0b75000d9c29 &amp;lt;SharedFunctionInfo getX&amp;gt;)]
Bytecode length: 10
Parameter count 2
Register count 0
Frame size 0
         0x28c500002194 @    0 : 65 af 01 03 01    CallRuntime [DebugPrint], a0-a0
         0x28c500002199 @    5 : 2d 03 00 00       GetNamedProperty a0, [0], [0]
         0x28c50000219d @    9 : ab                Return
Constant pool (size = 1)
0xb75000d9dcd: [FixedArray] in OldSpace
 - map: 0x0b7500000565 &amp;lt;Map(FIXED_ARRAY_TYPE)&amp;gt;
 - length: 1
           0: 0x0b7500002b91 &amp;lt;String[1]: #x&amp;gt;
Handler Table (size = 0)
Source Position Table (size = 0)
// Далее информация о переданном объекте
DebugPrint: 0xb75001c9475: [JS_OBJECT_TYPE]
 - map: 0x0b75000d9d81 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x0b75000c4b11 &amp;lt;Object map = 0xb75000c414d&amp;gt;
 - elements: 0x0b75000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x0b75000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {
    0xb7500002b91: [String] in ReadOnlySpace: #x: 1 (const data field 0), location: in-object
 }
0xb75000d9d81: [Map] in OldSpace
 - map: 0x0b75000c3c29 &amp;lt;MetaMap (0x0b75000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 16
 - inobject properties: 1
 - unused property fields: 0
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x0b75000d9d59 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x0b7500000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #1: 0x0b75001c9485 &amp;lt;DescriptorArray[1]&amp;gt;
 - prototype: 0x0b75000c4b11 &amp;lt;Object map = 0xb75000c414d&amp;gt;
 - constructor: 0x0b75000c4655 &amp;lt;JSFunction Object (sfi = 0xb7500335385)&amp;gt;
 - dependent code: 0x0b75000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;7PAZ&quot;&gt;So, essentially, the compiled bytecode of the &lt;code&gt;getX&lt;/code&gt; function looks as follows.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;5Oex&quot;&gt;0x28c500002194 @    0 : 65 af 01 03 01    CallRuntime [DebugPrint], a0-a0
0x28c500002199 @    5 : 2d 03 00 00       GetNamedProperty a0, [0], [0]
0x28c50000219d @    9 : ab                Return&lt;/pre&gt;
  &lt;p id=&quot;j63Q&quot;&gt;The first line is a call to the &lt;code&gt;%DebugPrint&lt;/code&gt; function. It is purely auxiliary and does not affect the executable code; we can safely omit it. The next instruction is &lt;code&gt;GetNamedProperty&lt;/code&gt;. Its purpose is to retrieve the value of the specified property from the passed object. The instruction takes three parameters as input. The first one is a reference to the object. In our case, the reference to the object is stored in &lt;code&gt;a0&lt;/code&gt;, the first argument of the &lt;code&gt;getX&lt;/code&gt; function. The second and third parameters are the address of the hidden class and the &lt;code&gt;offset&lt;/code&gt; of the property in the descriptor array.&lt;/p&gt;
  &lt;h2 id=&quot;KGhU&quot;&gt;Object&amp;#x27;s Shape&lt;/h2&gt;
  &lt;p id=&quot;t2nC&quot;&gt;In the article &lt;a href=&quot;https://blog.frontend-almanac.com/js-object-structure&quot; target=&quot;_blank&quot;&gt;Object Structure in JavaScript Engines&lt;/a&gt;, I explained in detail what hidden classes, descriptor arrays, and how objects in JavaScript are structured. I won’t retell the whole article but will highlight only the essential facts. Every object has one or several hidden classes. Hidden classes are an internal engine mechanism inaccessible to JavaScript developers, at least without using special built-in engine methods. A hidden class represents the so-called shape of an object and all the necessary information about the object&amp;#x27;s properties. The object itself only stores property values and a reference to the hidden class. If two objects have the same shape, they will have a reference to the same hidden class.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;YOyu&quot;&gt;const obj1 = { x: 1 }
const obj2 = { x: 2 }

//          {}     &amp;lt;- empty map
//          |
//        { x }    &amp;lt;- common map
//       /    \
// &amp;lt;obj1&amp;gt;      &amp;lt;obj2&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;N0FJ&quot;&gt;As properties are added to the object, its hidden class tree grows.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;tw7A&quot;&gt;const obj1 = {}
obj1.x = 1;
obj1.y = 2;
obj1.z = 3;

// {} -&amp;gt; { x } -&amp;gt; { x, y } -&amp;gt; { x, y, z } -&amp;gt; &amp;lt;obj1&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;nNzq&quot;&gt;Let&amp;#x27;s go back to the function we started with. Let&amp;#x27;s assume we passed it an object of the following type.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;JZ0t&quot;&gt;function getX(obj) {
  return obj.x;
}

getX({ y: 1, x: 2, z: 3 });&lt;/pre&gt;
  &lt;p id=&quot;DcPD&quot;&gt;In order to access the value of property &lt;code&gt;x&lt;/code&gt;, the interpreter needs to know: a) the address of the object&amp;#x27;s last hidden class; b) the offset of this property in the descriptors array.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;uDuX&quot;&gt;d8&amp;gt; const obj = { y: 1, x: 2, z: 3};
d8&amp;gt;
d8&amp;gt; %DebugPrint(obj);
DebugPrint: 0x2034001c9435: [JS_OBJECT_TYPE]
 - map: 0x2034000d9cf9 &amp;lt;Map[24](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x2034000c4b11 &amp;lt;Object map = 0x2034000c414d&amp;gt;
 - elements: 0x2034000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x2034000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {
    0x203400002ba1: [String] in ReadOnlySpace: #y: 1 (const data field 0), location: in-object
    0x203400002b91: [String] in ReadOnlySpace: #x: 2 (const data field 1), location: in-object
    0x203400002bb1: [String] in ReadOnlySpace: #z: 3 (const data field 2), location: in-object
 }
0x2034000d9cf9: [Map] in OldSpace
 - map: 0x2034000c3c29 &amp;lt;MetaMap (0x2034000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 24
 - inobject properties: 3
 - unused property fields: 0
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x2034000d9cd1 &amp;lt;Map[24](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x203400000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #3: 0x2034001c9491 &amp;lt;DescriptorArray[3]&amp;gt;
 - prototype: 0x2034000c4b11 &amp;lt;Object map = 0x2034000c414d&amp;gt;
 - constructor: 0x2034000c4655 &amp;lt;JSFunction Object (sfi = 0x203400335385)&amp;gt;
 - dependent code: 0x2034000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;LqTi&quot;&gt;In the example above, the hidden class is located at the address &lt;code&gt;0x2034000d9cd1&lt;/code&gt; (back pointer).&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;lonx&quot;&gt;d8&amp;gt; %DebugPrintPtr(0x2034000d9cd1)
DebugPrint: 0x2034000d9cd1: [Map] in OldSpace
 - map: 0x2034000c3c29 &amp;lt;MetaMap (0x2034000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 24
 - inobject properties: 3
 - unused property fields: 1
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x2034000d9c89 &amp;lt;Map[24](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x203400000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors #2: 0x2034001c9491 &amp;lt;DescriptorArray[3]&amp;gt;
 - transitions #1: 0x2034000d9cf9 &amp;lt;Map[24](HOLEY_ELEMENTS)&amp;gt;
     0x203400002bb1: [String] in ReadOnlySpace: #z: (transition to (const data field, attrs: [WEC]) @ Any) -&amp;gt; 0x2034000d9cf9 &amp;lt;Map[24](HOLEY_ELEMENTS)&amp;gt;
 - prototype: 0x2034000c4b11 &amp;lt;Object map = 0x2034000c414d&amp;gt;
 - constructor: 0x2034000c4655 &amp;lt;JSFunction Object (sfi = 0x203400335385)&amp;gt;
 - dependent code: 0x2034000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0
0x2034000c3c29: [MetaMap] in OldSpace
 - map: 0x2034000c3c29 &amp;lt;MetaMap (0x2034000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: MAP_TYPE
 - instance size: 40
 - native_context: 0x2034000c3c79 &amp;lt;NativeContext[285]&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;QiAs&quot;&gt;From there, we can access the descriptors array at the address &lt;code&gt;0x2034001c9491&lt;/code&gt; (instance descriptors).&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;wAyy&quot;&gt;d8&amp;gt; %DebugPrintPtr(0x2034001c9491)
DebugPrint: 0x2034001c9491: [DescriptorArray]
 - map: 0x20340000062d &amp;lt;Map(DESCRIPTOR_ARRAY_TYPE)&amp;gt;
 - enum_cache: 3
   - keys: 0x2034000daaa9 &amp;lt;FixedArray[3]&amp;gt;
   - indices: 0x2034000daabd &amp;lt;FixedArray[3]&amp;gt;
 - nof slack descriptors: 0
 - nof descriptors: 3
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0x203400002ba1: [String] in ReadOnlySpace: #y (const data field 0:s, p: 2, attrs: [WEC]) @ Any
  [1]: 0x203400002b91: [String] in ReadOnlySpace: #x (const data field 1:s, p: 1, attrs: [WEC]) @ Any
  [2]: 0x203400002bb1: [String] in ReadOnlySpace: #z (const data field 2:s, p: 0, attrs: [WEC]) @ Any
0x20340000062d: [Map] in ReadOnlySpace
 - map: 0x2034000004c5 &amp;lt;MetaMap (0x20340000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: DESCRIPTOR_ARRAY_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x203400000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x203400000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x20340000007d &amp;lt;null&amp;gt;
 - constructor: 0x20340000007d &amp;lt;null&amp;gt;
 - dependent code: 0x2034000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;Wzb4&quot;&gt;The interpreter can then locate the desired property by its name and retrieve the &lt;code&gt;offset&lt;/code&gt;, which in this case is &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;HaIV&quot;&gt;Thus, the interpreter&amp;#x27;s path will look something like this.&lt;/p&gt;
  &lt;figure id=&quot;aTUL&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2d/a4/2da4e55d-16a5-429a-8cff-57f83fd6d27b.png&quot; width=&quot;1520&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;BNJF&quot;&gt;Our case is quite straightforward. Here, the values of the properties are stored inside the object itself, which makes access to them pretty fast. However, the determination of the property&amp;#x27;s position in the array of descriptors will still be proportional to the number of properties themselves. Furthermore, in the article &lt;a href=&quot;https://blog.frontend-almanac.com/js-object-structure&quot; target=&quot;_blank&quot;&gt;Object Structure in JavaScript Engines&lt;/a&gt;, I mentioned that values are not always stored in such a &amp;quot;fast&amp;quot; manner. In some cases, the storage type may be changed to a slower &amp;quot;dictionary.&amp;quot;&lt;/p&gt;
  &lt;h2 id=&quot;kqds&quot;&gt;Inline Cache&lt;/h2&gt;
  &lt;p id=&quot;Kf2U&quot;&gt;Of course, in isolated cases for a JS engine, this does not pose significant difficulties, and the time it takes to access property values is hardly noticeable to the naked eye. But what if our case is not isolated? Let&amp;#x27;s say we need to execute a function hundreds or thousands of times in a loop? The total time of operation becomes critically important here. Given that the function actually performs the same operations, JavaScript authors have proposed optimizing the property search process to avoid executing it every time. All that is required for this is to store the object&amp;#x27;s hidden class address and the &amp;quot;offset&amp;quot; of the desired property.&lt;/p&gt;
  &lt;p id=&quot;sF5v&quot;&gt;Let&amp;#x27;s go back to the beginning of the article and the &lt;code&gt;GetNamedProperty&lt;/code&gt; instruction, which has three parameters. We have already determined that the first parameter is a reference to the object. The second parameter is the hidden class address. The third parameter is the found &amp;quot;offset&amp;quot; of the property. Once these parameters are defined, the function can memorize them and not perform the search procedure again during the next execution. This caching is called &lt;strong&gt;Inline Cache (IC)&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;cMEw&quot;&gt;TurboFan&lt;/h2&gt;
  &lt;p id=&quot;RMNg&quot;&gt;However, it is worth considering that parameter memoization also requires memory and some CPU time. This makes the mechanism efficient only during intensive function execution (so-called &lt;strong&gt;hot&lt;/strong&gt; function). The intensity of function execution depends on the quantity and frequency of its calls. The optimizer is responsible for assessing this intensity. In the case of V8, TurboFan acts as the optimizer. First of all, the optimizer constructs a graph from the AST and bytecode and sends it to the &amp;quot;inlining&amp;quot; phase, where metrics for calls, loads, and cache storage are gathered. Next, if a function is suitable for inline caching, it is queued for optimization. Once the queue is full, the optimizer must identify the hottest function and perform caching on it. Then move on to the next one and so on. By the way, unlike its predecessor Crankshaft, which took functions one by one starting from the first. In general, this topic is quite extensive and deserves a separate article; there is no point in discussing all the details and peculiarities of TurboFan&amp;#x27;s heuristics now. It&amp;#x27;s better to move on to examples.&lt;/p&gt;
  &lt;h2 id=&quot;Tn1Y&quot;&gt;IC Types&lt;/h2&gt;
  &lt;p id=&quot;CQ2q&quot;&gt;To analyze the IC operation, you need to enable logging in the runtime environment. For d8, you can do this by specifying the &lt;code&gt;--log-ic&lt;/code&gt; flag.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;PFWM&quot;&gt;%&amp;gt; d8 --log-ic

function getX(obj) {
  return obj.x;
}

for (let i = 0; i &amp;lt; 10; i++) {
  getX({ x: i });
}&lt;/pre&gt;
  &lt;p id=&quot;csYj&quot;&gt;As I have mentioned before, TurboFan starts to cache object properties only in &amp;quot;hot&amp;quot; functions. To make our function &amp;quot;hot,&amp;quot; we need to run it multiple times in a loop. In my simple script in the d8 environment, it required a minimum of 10 iterations. In practice, in different conditions and with other functions present, this number may vary and is likely to be higher. The resulting log can now be analyzed using the &lt;strong&gt;System Analyzer&lt;/strong&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;SFmM&quot;&gt;Grouped by type: 1#
&amp;gt;100.00%	1	LoadIC
Grouped by category: 1#
&amp;gt;100.00%	1	Load
Grouped by functionName: 1#
&amp;gt;100.00%	1	~getX
Grouped by script: 1#
&amp;gt;100.00%	1	Script(3): index.js
Grouped by sourcePosition: 1#
&amp;gt;100.00%	1	index.js:3:14
Grouped by code: 1#
&amp;gt;100.00%	1	Code(JS~)
Grouped by state: 1#
&amp;gt;100.00%	1	0 → 1
Grouped by key: 1#
&amp;gt;100.00%	1	x
Grouped by map: 1#
&amp;gt;100.00%	1	Map(0x3cd2000d9e61)
Grouped by reason: 1#
&amp;gt;100.00%	1	&lt;/pre&gt;
  &lt;p id=&quot;dbl5&quot;&gt;In the IC List, we see the notation of the type &lt;code&gt;LoadIC&lt;/code&gt;, which indicates accessing an object property from the cache. Here we have the function itself &lt;code&gt;functionName: ~getX&lt;/code&gt;, the address of the hidden class &lt;code&gt;Map(0x3cd2000d9e61)&lt;/code&gt;, and the property name &lt;code&gt;key: x&lt;/code&gt;. However, the most interesting part is &lt;code&gt;state: 0 -&amp;gt; 1&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;TYyE&quot;&gt;There are several types of IC. The complete list looks as follows:&lt;/p&gt;
  &lt;p id=&quot;Qi78&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/tags/12.6.72/src/common/globals.h#1481&quot; target=&quot;_blank&quot;&gt;/src/common/globals.h#1481&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;7f7M&quot;&gt;// State for inline cache call sites. Aliased as IC::State.
enum class InlineCacheState {
  // No feedback will be collected.
  NO_FEEDBACK,
  // Has never been executed.
  UNINITIALIZED,
  // Has been executed and only one receiver type has been seen.
  MONOMORPHIC,
  // Check failed due to prototype (or map deprecation).
  RECOMPUTE_HANDLER,
  // Multiple receiver types have been seen.
  POLYMORPHIC,
  // Many DOM receiver types have been seen for the same accessor.
  MEGADOM,
  // Many receiver types have been seen.
  MEGAMORPHIC,
  // A generic handler is installed and no extra typefeedback is recorded.
  GENERIC,
};&lt;/pre&gt;
  &lt;p id=&quot;ac4S&quot;&gt;In the system analyzer, these types are denoted by symbols&lt;/p&gt;
  &lt;pre id=&quot;W3bj&quot;&gt;0: UNINITIALIZED
X: NO_FEEDBACK
1: MONOMORPHIC
^: RECOMPUTE_HANDLER
P: POLYMORPHIC
N: MEGAMORPHIC
D: MEGADOM
G: GENERIC&lt;/pre&gt;
  &lt;p id=&quot;jdQG&quot;&gt;For example, the type &lt;code&gt;X (NO_FEEDBACK)&lt;/code&gt; indicates that the optimizer has not yet gathered sufficient statistics to optimize the function. In our case, seeing &lt;code&gt;state: 0 -&amp;gt; 1&lt;/code&gt; means that the function has transitioned to the state of &amp;quot;monomorphic IC&amp;quot;.&lt;/p&gt;
  &lt;h3 id=&quot;ceDI&quot;&gt;Monomorphic&lt;/h3&gt;
  &lt;p id=&quot;F114&quot;&gt;As mentioned before, in practice, the same function can take an object as an argument. The shape of this object in a specific function call may differ from the previous one, which complicates the optimizer&amp;#x27;s task. The least trouble arises when we pass only objects of the same shape to the function, as in our last example.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;98KI&quot;&gt;function getX(obj) {
  return obj.x; // Monomorphic IC
}

for (let i = 0; i &amp;lt; 10; i++) {
  getX({ x: i }); // all objects have the same shape
}&lt;/pre&gt;
  &lt;p id=&quot;bTx7&quot;&gt;In such cases, the optimizer simply needs to remember the address of the hidden class and the &lt;code&gt;offset&lt;/code&gt; property. This type of IC is called &lt;strong&gt;monomorphic&lt;/strong&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;DCVd&quot;&gt;Polymorphic&lt;/h3&gt;
  &lt;p id=&quot;X9ub&quot;&gt;Now, let&amp;#x27;s try to add a function call with object of a different shape.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;xlxG&quot;&gt;function getX(obj) {
  return obj.x; // Polymorphic IC
}

for (let i = 0; i &amp;lt; 10; i++) {
  getX({ x: i, y: 0 });
  getX({ y: 0, x: i });
}&lt;/pre&gt;
  &lt;p id=&quot;Ts5f&quot;&gt;In the &amp;quot;IC List&amp;quot; now, we can see:&lt;/p&gt;
  &lt;pre id=&quot;7nMJ&quot;&gt;50.00%        1    0 → 1
50.00%        1    1 → P&lt;/pre&gt;
  &lt;p id=&quot;l8Ot&quot;&gt;A function in runtime can receive multiple different forms of an object. Access to property &lt;code&gt;x&lt;/code&gt; will vary for each form. Each form has its own hidden class address and its own offset of this property. In this case, the optimizer allocates two slots for each form of the object, where it saves the necessary parameters.&lt;/p&gt;
  &lt;p id=&quot;Iqou&quot;&gt;Such structure can be conceptually represented as an array of parameter sets.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;C5xt&quot;&gt;[
  [Map1, offset1],
  [Map2, offset2],
  ...
]&lt;/pre&gt;
  &lt;p id=&quot;nAtv&quot;&gt;This type of IC is called &lt;strong&gt;polymorphic&lt;/strong&gt;. Polymorphic IC has a limit on the number of permissible forms.&lt;/p&gt;
  &lt;p id=&quot;V4Hb&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8.git/+/refs/tags/12.6.72/src/wasm/wasm-constants.h#192&quot; target=&quot;_blank&quot;&gt;/src/wasm/wasm-constants.h#192&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;T3Wv&quot;&gt;// Maximum number of call targets tracked per call.
constexpr int kMaxPolymorphism = 4;&lt;/pre&gt;
  &lt;p id=&quot;wqoc&quot;&gt;By default, a polymorphic type can have from 2 to 4 forms per function. However, this parameter can be adjusted with the flag &lt;code&gt;--max-valid-polymorphic-map-count&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;it95&quot;&gt;Megamorphic&lt;/h3&gt;
  &lt;p id=&quot;FP9J&quot;&gt;If the number of object forms exceeds what Polymorphic can handle, the type changes to &lt;strong&gt;megamorphic&lt;/strong&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;84vG&quot;&gt;function getX(obj) {
  return obj.x; // Megamorphic IC
}

for (let i = 0; i &amp;lt; 10; i++) {
  getX({ x: i });
  getX({ prop1: 0, x: i });
  getX({ prop1: 0, prop2: 1, x: i });
  getX({ prop1: 0, prop2: 1, prop3: 2, x: i });
  getX({ prop1: 0, prop2: 1, prop3: 2, prop4: 3, x: i });
}&lt;/pre&gt;
  &lt;p id=&quot;g3oM&quot;&gt;The result of the IC List:&lt;/p&gt;
  &lt;pre id=&quot;0nEF&quot;&gt;40.00%        2    P → P
20.00%        1    0 → 1
20.00%        1    1 → P
20.00%        1    P → N&lt;/pre&gt;
  &lt;p id=&quot;eQCc&quot;&gt;In this case, the search for the required set of parameters negates the saving of CPU time and, therefore, does not make sense. That is why the optimizer simply caches the &lt;code&gt;MegamorphicSymbol&lt;/code&gt; symbol. For subsequent function calls, this will mean that the cached parameters are not available here and will have to be taken in the usual way. There is also no point in further optimizing the function and collecting its metrics.&lt;/p&gt;
  &lt;h2 id=&quot;O04T&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;wVRb&quot;&gt;You have probably noticed that there is an additional type called &lt;code&gt;MEGADOM&lt;/code&gt; in the IC types list. This type is used for caching the DOM tree nodes. The thing is, the inline caching mechanism is not limited to functions and objects only. It is actively used in many other places, including outside V8. Covering all the information about caching at once is physically impossible. Since today we are talking about JavaScript objects, we will not discuss the MegaDom type in this article.&lt;/p&gt;
  &lt;p id=&quot;lXqo&quot;&gt;Let&amp;#x27;s conduct a couple of tests and see how effective Turbofan optimization works in V8. The experiment will be conducted in the &lt;strong&gt;Node.js&lt;/strong&gt; environment (the latest stable version at the time of writing this article is &lt;code&gt;v20.12.2&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;eiTF&quot;&gt;Experimental code:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;HjR6&quot;&gt;const N = 1000;

//===

function getXMonomorphic(obj) {
  let sum = 0;

  for (let i = 0; i &amp;lt; N; i++) {
    sum += obj.x;
  }

  return sum;
}

console.time(&amp;#x27;Monomorphic&amp;#x27;);

for (let i = 0; i &amp;lt; N; i++) {
  getXMonomorphic({ x: i });
  getXMonomorphic({ x: i });
  getXMonomorphic({ x: i });
  getXMonomorphic({ x: i });
  getXMonomorphic({ x: i });
}

console.timeLog(&amp;#x27;Monomorphic&amp;#x27;);

//===

function getXPolymorphic(obj) {
  let sum = 0;

  for (let i = 0; i &amp;lt; N; i++) {
    sum += obj.x;
  }

  return sum;
}

console.time(&amp;#x27;Polymorphic&amp;#x27;);

for (let i = 0; i &amp;lt; N; i++) {
  getXPolymorphic({ x: i, y: 0 });
  getXPolymorphic({ y: 0, x: i });
  getXPolymorphic({ x: i, y: 0 });
  getXPolymorphic({ y: 0, x: i });
  getXPolymorphic({ x: i, y: 0 });
}

console.timeEnd(&amp;#x27;Polymorphic&amp;#x27;);

//===

function getXMegamorphic(obj) {
  let sum = 0;

  for (let i = 0; i &amp;lt; N; i++) {
    sum += obj.x;
  }

  return sum;
}

//===

console.time(&amp;#x27;Megamorphic&amp;#x27;);

for (let i = 0; i &amp;lt; N; i++) {
  getXMegamorphic({ x: i });
  getXMegamorphic({ prop1: 0, x: i });
  getXMegamorphic({ prop1: 0, prop2: 1, x: i });
  getXMegamorphic({ prop1: 0, prop2: 1, prop3: 2, x: i });
  getXMegamorphic({ prop1: 0, prop2: 1, prop3: 2, prop4: 3, x: i });
}

console.timeLog(&amp;#x27;Megamorphic&amp;#x27;);&lt;/pre&gt;
  &lt;p id=&quot;TqGa&quot;&gt;To begin with, let&amp;#x27;s run the script with optimization turned off and observe the &amp;quot;clean&amp;quot; performance of the functions.&lt;/p&gt;
  &lt;pre id=&quot;RXjI&quot; data-lang=&quot;cpp&quot;&gt;%&amp;gt; node --no-opt test.js
Monomorphic: 68.55ms
Polymorphic: 69.939ms
Megamorphic: 85.045ms&lt;/pre&gt;
  &lt;p id=&quot;ZGa5&quot;&gt;For the sake of the experiment&amp;#x27;s integrity, I repeated the tests several times. The performance between monomorphic and polymorphic objects is approximately the same. Polymorphic objects can sometimes even be faster. This is not so much related to the workings of V8 itself as it is to system resources. However, the speed of megamorphic objects is slightly lower due to the formation of a hidden class tree at this stage, making access to the properties of these objects inherently more complex than in the first two cases.&lt;/p&gt;
  &lt;p id=&quot;yGGT&quot;&gt;Now let&amp;#x27;s run the same script with optimization enabled:&lt;/p&gt;
  &lt;pre id=&quot;YZLC&quot; data-lang=&quot;cpp&quot;&gt;%&amp;gt; node test.js
Monomorphic: 9.313ms
Polymorphic: 9.673ms
Megamorphic: 29.104ms&lt;/pre&gt;
  &lt;p id=&quot;qHTX&quot;&gt;The speed of monomorphic and polymorphic functions has increased by about 7 times. Similar to the previous case, the difference between these two types is insignificant, and in repeated tests, polymorphic functions are sometimes even faster. However, the speed of the megamorphic function has increased by only 3 times. Generally, based on the theory described above, megamorphic functions were not supposed to show an increase in speed through optimization. However, it is not that simple. Firstly, they do have a side effect from optimization since such functions are excluded from the process of further metric collection. This is a small but still an advantage over the other types. Secondly, the optimization of JS work is not limited to inline caching of object property access. There are several other types of optimizations that we did not consider in this article.&lt;/p&gt;
  &lt;p id=&quot;p7ZP&quot;&gt;Moreover, in 2023, Google released the &lt;code&gt;Chromium M117&lt;/code&gt; version, which included the new optimizer &lt;strong&gt;Maglev&lt;/strong&gt;. It was integrated as a middleware between Ignition (interpreter) and Turbofan (optimizer). Now the optimization process has acquired a three-tiers architecture and looks like &lt;code&gt;Ignition -&amp;gt; Maglev -&amp;gt; Turbofan&lt;/code&gt;. Maglev contributes to function optimization, particularly working with function arguments. We will discuss this in more detail another time. For now, we can conclude that megamorphic functions are approximately twice as slow as monomorphic and polymorphic ones.&lt;/p&gt;
  &lt;p id=&quot;Wwwy&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;4FEw&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ioIU&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/js-optimisation-ic&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/js-optimisation-ic&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:js-object-structure</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/js-object-structure?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>Object Structure in JavaScript Engines</title><published>2024-03-31T20:02:57.218Z</published><updated>2024-04-23T16:55:16.840Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/fe/bb/febbf129-152f-49f0-9296-31bd9fb29dc1.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/23/86/23864a98-b722-4c0c-9e46-b91d39e0d667.png&quot;&gt;From a developer's perspective, objects in JavaScript are quite flexible and understandable. We can add, remove, and modify object properties on our own. However, few people think about how objects are stored in memory and processed by JS engines. Can a developer's actions, directly or indirectly, impact performance and memory consumption? Let's try to delve into all of this in this article.</summary><content type="html">
  &lt;p id=&quot;SHRk&quot;&gt;From a developer&amp;#x27;s perspective, objects in JavaScript are quite flexible and understandable. We can add, remove, and modify object properties on our own. However, few people think about how objects are stored in memory and processed by JS engines. Can a developer&amp;#x27;s actions, directly or indirectly, impact performance and memory consumption? Let&amp;#x27;s try to delve into all of this in this article.&lt;/p&gt;
  &lt;h2 id=&quot;5BMc&quot;&gt;Object and its properties&lt;/h2&gt;
  &lt;p id=&quot;DYa8&quot;&gt;Before delving into the internal structures of the object, let&amp;#x27;s quickly review the basics and recall what an object actually is. The ECMA-262 specification in section &lt;a href=&quot;https://262.ecma-international.org/#sec-object-type&quot; target=&quot;_blank&quot;&gt;6.1.7 The Object Type&lt;/a&gt; defines an object rather primitively, simply as a set of properties. Object properties are represented as a &amp;quot;key-value&amp;quot; structure, where the key is the property name, and the value is a set of attributes. All object properties can be conventionally divided into two types: &lt;strong&gt;data properties&lt;/strong&gt; and &lt;strong&gt;accessor properties&lt;/strong&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;lZqy&quot;&gt;Data properties&lt;/h3&gt;
  &lt;p id=&quot;nXUx&quot;&gt;Properties which have the following attributes:&lt;/p&gt;
  &lt;ul id=&quot;bZeI&quot;&gt;
    &lt;li id=&quot;BVW9&quot;&gt;&lt;code&gt;[[Value]]&lt;/code&gt; - the value of the property&lt;/li&gt;
    &lt;li id=&quot;Cpvg&quot;&gt;&lt;code&gt;[[Writable]]&lt;/code&gt; - &lt;em&gt;boolean&lt;/em&gt;, by default set to false - if false, the [[Value]] cannot be changed&lt;/li&gt;
    &lt;li id=&quot;53RV&quot;&gt;&lt;code&gt;[[Enumerable]]&lt;/code&gt; - &lt;em&gt;boolean&lt;/em&gt;, by default set to false - if true, the property can be iterated through using &amp;quot;for-in&amp;quot;&lt;/li&gt;
    &lt;li id=&quot;HCSO&quot;&gt;&lt;code&gt;[[Configurable]]&lt;/code&gt; - &lt;em&gt;boolean&lt;/em&gt;, by default set to false - if false, the property cannot be deleted, its type cannot be changed from Data property to Accessor property (or vice versa), no attributes except for &lt;code&gt;[[Value]]&lt;/code&gt; and setting &lt;code&gt;[[Writable]]&lt;/code&gt; can be set to false&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Aywz&quot;&gt;Accessor properties&lt;/h3&gt;
  &lt;p id=&quot;7eob&quot;&gt;Properties which have the following attributes:&lt;/p&gt;
  &lt;ul id=&quot;NQ4j&quot;&gt;
    &lt;li id=&quot;pAjL&quot;&gt;&lt;code&gt;[[Get]]&lt;/code&gt; - a function that returns the value of the object&lt;/li&gt;
    &lt;li id=&quot;Fezz&quot;&gt;&lt;code&gt;[[Set]]&lt;/code&gt; - a function called when an attempt to assign a value to the property is made&lt;/li&gt;
    &lt;li id=&quot;TU7c&quot;&gt;&lt;code&gt;[[Enumerable]]&lt;/code&gt; - identical to Data property&lt;/li&gt;
    &lt;li id=&quot;feOA&quot;&gt;&lt;code&gt;[[Configurable]]&lt;/code&gt; - identical to Data property.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;XUQI&quot;&gt;Hidden Classes&lt;/h2&gt;
  &lt;p id=&quot;iA3u&quot;&gt;According to the specification, in addition to the values themselves, each object property should store information about its attributes.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;3hlv&quot;&gt;const obj1 = { a: 1, b: 2 };&lt;/pre&gt;
  &lt;p id=&quot;BPU6&quot;&gt;The simple object mentioned above, in the context of a JavaScript engine, should look something like this.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;DbUB&quot;&gt;{
  a: {
    [[Value]]: 1,
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  },
  b: {
    [[Value]]: 2,
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}&lt;/pre&gt;
  &lt;p id=&quot;ZR2a&quot;&gt;Now, let&amp;#x27;s imagine that we have two objects with similar structures.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;tj3m&quot;&gt;const obj1 = { a: 1, b: 2 };
const obj2 = { a: 3, b: 4 };&lt;/pre&gt;
  &lt;p id=&quot;LMVF&quot;&gt;Based on the above, we need to store information about each of the four properties of these two objects. It sounds somewhat wasteful in terms of memory consumption. Furthermore, it is evident that the configuration of these properties is the same, except for the property name and its &lt;code&gt;[[Value]]&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;l4Lu&quot;&gt;The popular JS engines solve this problem using so-called &lt;strong&gt;hidden classes&lt;/strong&gt;. This concept is often encountered in various publications and documentation. However, it intersects somewhat with the concept of JavaScript classes, so engine developers have adopted their own definitions. For example, in V8, hidden classes are referred to as &lt;strong&gt;Maps&lt;/strong&gt; (which also intersects with the concept of JavaScript Maps). In the Chakra engine used in the Internet Explorer browser, the term &lt;strong&gt;Types&lt;/strong&gt; is applied. Safari developers, in their JavaScriptCore engine, use the notion of &lt;strong&gt;Structures&lt;/strong&gt;. In the SpiderMonkey engine for Mozilla, hidden classes are called &lt;strong&gt;Shapes&lt;/strong&gt;. Actually, this term is quite popular and often appears in publications because it is unique and can hardly be confused with anything else in JavaScript.&lt;/p&gt;
  &lt;p id=&quot;4buK&quot;&gt;In general, there are many interesting publications about hidden classes in the network. In particular, I recommend taking a look at &lt;a href=&quot;https://mathiasbynens.be/notes/shapes-ics&quot; target=&quot;_blank&quot;&gt;Mathias Bynens&amp;#x27; post&lt;/a&gt;, one of the developers of V8 and Chrome DevTools.&lt;/p&gt;
  &lt;p id=&quot;IfLw&quot;&gt;So, the essence of hidden classes lies in extracting meta-information and object properties into separate, reusable objects and binding such a class to the real object by reference.&lt;/p&gt;
  &lt;p id=&quot;oZV4&quot;&gt;In this concept, the example above can be represented as follows. Later, we will see how real Maps look in the V8 engine, but for now, I will illustrate it in a hypothetical way.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;74nq&quot;&gt;Map1 {
  a: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  },
  b: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

ob1 {
  map: Map1,
  values: { a: 1, a: 2 }
}

ob2 {
  map: Map1,
  values: { a: 3, a: 4 }
}&lt;/pre&gt;
  &lt;h2 id=&quot;wtZP&quot;&gt;Hidden Classes Inheritance&lt;/h2&gt;
  &lt;p id=&quot;7VVC&quot;&gt;The concept of hidden classes looks good in the case of objects with the same shape. However, what to do if the second object has a different structure? In the following example, the two objects are not structurally identical to each other, but have an intersection.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;DLdX&quot;&gt;const obj1 = { a: 1, b: 2 };
const obj2 = { a: 3, b: 4, c: 5 };&lt;/pre&gt;
  &lt;p id=&quot;usJi&quot;&gt;According to the logic described above, two classes with different shapes should appear in memory. However, this leads back to the issue of attribute duplication. To avoid this, it is accepted to inherit hidden classes from each other.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;ymDW&quot;&gt;Map1 {
  a: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  },
  b: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

Map2 {
  back_pointer: Map1,
  с: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

ob1 {
  map: Map1,
  values: { a: 1, b: 2 }
}

ob2 {
  map: Map2,
  values: { a: 3, b: 4, c: 5 }
}&lt;/pre&gt;
  &lt;p id=&quot;YYrh&quot;&gt;Here we see that the class &lt;code&gt;Map2&lt;/code&gt; describes only one property and a reference to an object with a more &amp;quot;specific&amp;quot; shape.&lt;/p&gt;
  &lt;p id=&quot;TsCU&quot;&gt;It is also worth mentioning that the shape of the hidden class is influenced not only by the set of properties but also by their order. In other words, the following objects will have different shapes of hidden classes.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Sn56&quot;&gt;Map1 {
  a: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  },
  b: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

Map2 {
  b: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  },
  a: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

ob1 {
  map: Map1,
  values: { a: 1, b: 2 }
}

ob2 {
  map: Map2,
  values: { b: 3, a: 4 }
}&lt;/pre&gt;
  &lt;p id=&quot;PMKd&quot;&gt;If we change the shape of the object after initialization, this also leads to the creation of a new hidden subclass.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;2zxF&quot;&gt;const ob1 = { a: 1, b: 2 };
obj1.c = 3;

const obj2 = { a: 4, b: 5, c: 6 };&lt;/pre&gt;
  &lt;p id=&quot;EWRm&quot;&gt;This example results in the following structure of hidden classes.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;rB34&quot;&gt;Map1 {
  a: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  },
  b: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

Map2 {
  back_pointer: Map1,
  с: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

Map3 {
  back_pointer: Map1,
  с: {
    [[Writable]]: true,
    [[Enumerable]]: true,
    [[Configurable]]: true,
  }
}

ob1 {
  map: Map2,
  values: { a: 1, b: 2, c: 3 }
}

ob2 {
  map: Map3,
  values: { a: 4, b: 5, c: 6 }
}&lt;/pre&gt;
  &lt;h2 id=&quot;TAZ8&quot;&gt;Hidden Classes in Practice&lt;/h2&gt;
  &lt;p id=&quot;DGAB&quot;&gt;A bit earlier, I referred to &lt;a href=&quot;https://mathiasbynens.be/notes/shapes-ics&quot; target=&quot;_blank&quot;&gt;Mathias Binnen&amp;#x27;s post&lt;/a&gt; on object shapes. However, many years have passed since then. For the sake of the experiment&amp;#x27;s purity, I decided to check the practical situation in the real V8 engine.&lt;/p&gt;
  &lt;p id=&quot;1AZK&quot;&gt;Let&amp;#x27;s conduct an experiment using the example provided in Mathias&amp;#x27;s article.&lt;/p&gt;
  &lt;figure id=&quot;QO2C&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fe/89/fe898845-11e5-48cb-b38d-1530a90d65d2.png&quot; width=&quot;3542&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;YfIT&quot;&gt;To do this, we will need the V8&amp;#x27;s built-in internal method - &lt;code&gt;%DebugPrint&lt;/code&gt;. Just a reminder, in order to use the engine&amp;#x27;s built-in methods, it needs to be launched with the &lt;code&gt;--allow-natives-syntax&lt;/code&gt; flag. To see detailed information about JS objects, the engine must be compiled in &lt;code&gt;debug&lt;/code&gt; mode.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;7AVl&quot;&gt;d8&amp;gt; const a = {};
d8&amp;gt; a.x = 6;
d8&amp;gt; const b = { x: 6 };
d8&amp;gt;
d8&amp;gt; %DebugPrint(a);
DebugPrint: 0x1d47001c9425: [JS_OBJECT_TYPE]
 - map: 0x1d47000da9a9 &amp;lt;Map[28](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x1d47000c4b11 &amp;lt;Object map = 0x1d47000c414d&amp;gt;
 - elements: 0x1d47000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x1d47000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {
    0x1d4700002b91: [String] in ReadOnlySpace: #x: 6 (const data field 0), location: in-object
 }
0x1d47000da9a9: [Map] in OldSpace
 - map: 0x1d47000c3c29 &amp;lt;MetaMap (0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 3
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x1d47000c4945 &amp;lt;Map[28](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x1d47000da9f1 &amp;lt;Cell value= 0&amp;gt;
 - instance descriptors (own) #1: 0x1d47001cb111 &amp;lt;DescriptorArray[1]&amp;gt;
 - prototype: 0x1d47000c4b11 &amp;lt;Object map = 0x1d47000c414d&amp;gt;
 - constructor: 0x1d47000c4655 &amp;lt;JSFunction Object (sfi = 0x1d4700335385)&amp;gt;
 - dependent code: 0x1d47000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;v33f&quot;&gt;We see an object &lt;code&gt;a&lt;/code&gt; located at the address &lt;code&gt;0x1d47001c9425&lt;/code&gt;. The object is associated with a hidden class at the address &lt;code&gt;0x1d47000da9a9&lt;/code&gt;. Inside the object itself, the value &lt;code&gt;#x: 6&lt;/code&gt; is stored. The property attributes are located in the associated hidden class under the field &lt;code&gt;instance descriptors&lt;/code&gt;. Just in case, let&amp;#x27;s take a look at the array of descriptors at the specified address.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;voK7&quot;&gt;d8&amp;gt; %DebugPrintPtr(0x1d47001cb111)
DebugPrint: 0x1d47001cb111: [DescriptorArray]
 - map: 0x1d470000062d &amp;lt;Map(DESCRIPTOR_ARRAY_TYPE)&amp;gt;
 - enum_cache: 1
   - keys: 0x1d47000dacad &amp;lt;FixedArray[1]&amp;gt;
   - indices: 0x1d47000dacb9 &amp;lt;FixedArray[1]&amp;gt;
 - nof slack descriptors: 0
 - nof descriptors: 1
 - raw gc state: mc epoch 0, marked 0, delta 0
  [0]: 0x1d4700002b91: [String] in ReadOnlySpace: #x (const data field 0:s, p: 0, attrs: [WEC]) @ Any
0x1d470000062d: [Map] in ReadOnlySpace
 - map: 0x1d47000004c5 &amp;lt;MetaMap (0x1d470000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: DESCRIPTOR_ARRAY_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - non-extensible
 - back pointer: 0x1d4700000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x1d4700000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x1d470000007d &amp;lt;null&amp;gt;
 - constructor: 0x1d470000007d &amp;lt;null&amp;gt;
 - dependent code: 0x1d47000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

32190781763857&lt;/pre&gt;
  &lt;p id=&quot;oUvY&quot;&gt;The array of descriptors contains an element &lt;code&gt;#x&lt;/code&gt;, which holds all the necessary information about the object property.&lt;/p&gt;
  &lt;p id=&quot;Xw6k&quot;&gt;Now let&amp;#x27;s take a look at the &lt;code&gt;back pointer&lt;/code&gt; link with the address &lt;code&gt;0x1d47000c4945&lt;/code&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Nmf1&quot;&gt;d8&amp;gt; %DebugPrintPtr(0x1d47000c4945)
DebugPrint: 0x1d47000c4945: [Map] in OldSpace
 - map: 0x1d47000c3c29 &amp;lt;MetaMap (0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 4
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x1d4700000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0x1d4700000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #0: 0x1d4700000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - transitions #1: 0x1d47000da9d1 &amp;lt;TransitionArray[6]&amp;gt;Transition array #1:
     0x1d4700002b91: [String] in ReadOnlySpace: #x: (transition to (const data field, attrs: [WEC]) @ Any) -&amp;gt; 0x1d47000da9a9 &amp;lt;Map[28](HOLEY_ELEMENTS)&amp;gt;

 - prototype: 0x1d47000c4b11 &amp;lt;Object map = 0x1d47000c414d&amp;gt;
 - constructor: 0x1d47000c4655 &amp;lt;JSFunction Object (sfi = 0x1d4700335385)&amp;gt;
 - dependent code: 0x1d47000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0
0x1d47000c3c29: [MetaMap] in OldSpace
 - map: 0x1d47000c3c29 &amp;lt;MetaMap (0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: MAP_TYPE
 - instance size: 40
 - native_context: 0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;

32190780688709&lt;/pre&gt;
  &lt;p id=&quot;dlKd&quot;&gt;This hidden class represents an empty object. Its array of descriptors is empty, and the &lt;code&gt;back pointer&lt;/code&gt; reference is not defined.&lt;/p&gt;
  &lt;p id=&quot;m2lF&quot;&gt;Now let&amp;#x27;s take a look at object &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;4SQA&quot;&gt;d8&amp;gt; %DebugPrint(b)    
DebugPrint: 0x1d47001cb169: [JS_OBJECT_TYPE]
 - map: 0x1d47000dab39 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x1d47000c4b11 &amp;lt;Object map = 0x1d47000c414d&amp;gt;
 - elements: 0x1d47000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x1d47000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {
    0x1d4700002b91: [String] in ReadOnlySpace: #x: 6 (const data field 0), location: in-object
 }
0x1d47000dab39: [Map] in OldSpace
 - map: 0x1d47000c3c29 &amp;lt;MetaMap (0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 16
 - inobject properties: 1
 - unused property fields: 0
 - elements kind: HOLEY_ELEMENTS
 - enum length: 1
 - stable_map
 - back pointer: 0x1d47000dab11 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x1d4700000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #1: 0x1d47001cb179 &amp;lt;DescriptorArray[1]&amp;gt;
 - prototype: 0x1d47000c4b11 &amp;lt;Object map = 0x1d47000c414d&amp;gt;
 - constructor: 0x1d47000c4655 &amp;lt;JSFunction Object (sfi = 0x1d4700335385)&amp;gt;
 - dependent code: 0x1d47000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

{x: 6}&lt;/pre&gt;
  &lt;p id=&quot;j8ya&quot;&gt;The value of the property is also stored within the object itself, while the attributes of the property are stored in an array of descriptors of the hidden class. However, I would like to point out that the &lt;code&gt;back pointer&lt;/code&gt; reference here is not empty either, although it should not be present in the parent class diagram provided. Let&amp;#x27;s take a look at the class through this reference.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;UaEw&quot;&gt;d8&amp;gt; %DebugPrintPtr(0x1d47000dab11)
DebugPrint: 0x1d47000dab11: [Map] in OldSpace
 - map: 0x1d47000c3c29 &amp;lt;MetaMap (0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 16
 - inobject properties: 1
 - unused property fields: 1
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x1d4700000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0x1d4700000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #0: 0x1d4700000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - transitions #1: 0x1d47000dab39 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt;
     0x1d4700002b91: [String] in ReadOnlySpace: #x: (transition to (const data field, attrs: [WEC]) @ Any) -&amp;gt; 0x1d47000dab39 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt;
 - prototype: 0x1d47000c4b11 &amp;lt;Object map = 0x1d47000c414d&amp;gt;
 - constructor: 0x1d47000c4655 &amp;lt;JSFunction Object (sfi = 0x1d4700335385)&amp;gt;
 - dependent code: 0x1d47000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0
0x1d47000c3c29: [MetaMap] in OldSpace
 - map: 0x1d47000c3c29 &amp;lt;MetaMap (0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: MAP_TYPE
 - instance size: 40
 - native_context: 0x1d47000c3c79 &amp;lt;NativeContext[285]&amp;gt;

32190780779281&lt;/pre&gt;
  &lt;p id=&quot;KpcH&quot;&gt;The class looks exactly like the hidden class of an empty object above, but with a different address. This means that it is, in fact, a duplicate of the previous class. Therefore, the actual structure of this example looks as follows.&lt;/p&gt;
  &lt;figure id=&quot;vLTL&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f8/02/f802be5a-7123-4ebd-9732-22e1179bcfff.png&quot; width=&quot;3564&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;17ad&quot;&gt;This is the first deviation from the theory. To understand the need for another hidden class for an empty object, we will need an object with multiple properties. Let&amp;#x27;s assume that the original object initially has several properties. It will not be very convenient to explore such an object through a command line, so let&amp;#x27;s use Chrome DevTools. For convenience, we will enclose the object within the function context.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;uq3E&quot;&gt;function V8Snapshot() {
  this.obj1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 };
}

const v8Snapshot1 = new V8Snapshot();&lt;/pre&gt;
  &lt;figure id=&quot;ZvoM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c4/01/c4013aee-927c-4f07-8715-3498c1c058f6.png&quot; width=&quot;2122&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RliO&quot;&gt;The memory snapshot shows 6 inherited classes for this object, which equals the number of object properties. This is the second deviation from the theory, according to which it was assumed that the object initially has a single hidden class, the shape of which contains the properties with which it was initialized. The reason for this lies in the fact that in practice we operate not with a single object, but with several, perhaps even tens, hundreds, or thousands. In such circumstances, searching for and restructuring class trees can be quite expensive. So we have come to another concept of JS engines.&lt;/p&gt;
  &lt;h2 id=&quot;gVIL&quot;&gt;Transitions&lt;/h2&gt;
  &lt;p id=&quot;ZKXQ&quot;&gt;Let&amp;#x27;s add another object with a similar shape to the example above.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;mnfK&quot;&gt;function V8Snapshot() {
  this.obj1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 };
  this.obj2 = { a: 1, b: 2, d: 3, c: 4, e: 5, f: 6 };
}

const v8Snapshot1 = new V8Snapshot();&lt;/pre&gt;
  &lt;p id=&quot;aSuf&quot;&gt;At first glance, the shape of the second object is very similar, but the properties &lt;code&gt;c&lt;/code&gt; and &lt;code&gt;d&lt;/code&gt; have a different order.&lt;/p&gt;
  &lt;figure id=&quot;tB6P&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e4/79/e47932db-ae93-4b9f-bbc8-dec81a0fe24b.png&quot; width=&quot;2124&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;XoKe&quot;&gt;In the descriptor arrays, these properties will have different indexes. The class with the address &lt;code&gt;@101187&lt;/code&gt; has two descendants.&lt;/p&gt;
  &lt;figure id=&quot;0MWR&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/08/bf/08bf461b-dd6c-491d-9066-ea5b36ef3880.png&quot; width=&quot;2126&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;sRHZ&quot;&gt;For better clarity, let&amp;#x27;s run the script log through the V8 System Analyzer.&lt;/p&gt;
  &lt;figure id=&quot;ggGw&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/38/29/38299252-eed7-430d-a940-dd4cad02d959.png&quot; width=&quot;1730&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4az2&quot;&gt;It is clearly visible here that the original form &lt;code&gt;{ a, b, c, d, e, f }&lt;/code&gt; extends at the point &lt;code&gt;c&lt;/code&gt;. However, the interpreter does not recognize this until it starts initializing the second object. In order to create a new class tree, the engine would have to search for a class in the heap that matches the form, break it down into parts, create new classes, and reassign them to all created objects. To avoid this, the developers of V8 decided to divide the class into a set of minimal forms right away, starting with an empty class during the first object initialization.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;GuCY&quot;&gt;{}
{ a }
{ a, b }
{ a, b, c }
{ a, b, c, d }
{ a, b, c, d, e }
{ a, b, c, d, e, f }&lt;/pre&gt;
  &lt;p id=&quot;fHfK&quot;&gt;The process of creating a new hidden class with the addition or modification of any property is called a &lt;strong&gt;transition&lt;/strong&gt;. In our case, the first object will have 6 transitions initially (+a, +b, +c, etc.).&lt;/p&gt;
  &lt;p id=&quot;SARC&quot;&gt;This approach allows for the following: a) easily find a suitable initial form for the new object, b) there is no need to rebuild anything, just create a new class with a reference to the appropriate minimal form.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;XFqR&quot;&gt;              {}
              { a }
              { a, b }

{ a, b, c }            { a, b, d }
{ a, b, c, d }         { a, b, d, c }
{ a, b, c, d, e }      { a, b, d, c, e }
{ a, b, c, d, e, f }   { a, b, d, c, e, f }&lt;/pre&gt;
  &lt;h2 id=&quot;nHd6&quot;&gt;In-object and  External Properties.&lt;/h2&gt;
  &lt;p id=&quot;g5KL&quot;&gt;Let&amp;#x27;s consider the following example:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;8nIq&quot;&gt;d8&amp;gt; const obj1 = { a: 1 };
d8&amp;gt; obj1.b = 2;
d8&amp;gt;
d8&amp;gt; %DebugPrint(obj1);
DebugPrint: 0x2387001c942d: [JS_OBJECT_TYPE]
 - map: 0x2387000dabb1 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x2387000c4b11 &amp;lt;Object map = 0x2387000c414d&amp;gt;
 - elements: 0x2387000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x2387001cb521 &amp;lt;PropertyArray[3]&amp;gt;
 - All own properties (excluding elements): {
    0x238700002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x238700002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: properties[0]
 }
0x2387000dabb1: [Map] in OldSpace
 - map: 0x2387000c3c29 &amp;lt;MetaMap (0x2387000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 16
 - inobject properties: 1
 - unused property fields: 2
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x2387000d9ca1 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x2387000dabd9 &amp;lt;Cell value= 0&amp;gt;
 - instance descriptors (own) #2: 0x2387001cb4f9 &amp;lt;DescriptorArray[2]&amp;gt;
 - prototype: 0x2387000c4b11 &amp;lt;Object map = 0x2387000c414d&amp;gt;
 - constructor: 0x2387000c4655 &amp;lt;JSFunction Object (sfi = 0x238700335385)&amp;gt;
 - dependent code: 0x2387000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0
 
 {a: 1, b: 2}&lt;/pre&gt;
  &lt;p id=&quot;Phg3&quot;&gt;Upon closer inspection of the set of values of this object, we can see that the property &lt;code&gt;a&lt;/code&gt; is marked as &lt;code&gt;in-object&lt;/code&gt;, while the property &lt;code&gt;b&lt;/code&gt; is marked as an element of the &lt;code&gt;properties&lt;/code&gt; array.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Fr7S&quot;&gt;- All own properties (excluding elements): {
    ... #a: 1 (const data field 0), location: in-object
    ... #b: 2 (const data field 1), location: properties[0]
 }&lt;/pre&gt;
  &lt;p id=&quot;Vksd&quot;&gt;This example demonstrates that some properties are stored directly inside the object itself (&amp;quot;in-object&amp;quot;), while others are stored in an external storage of properties. This is related to the fact that according to the &lt;a href=&quot;https://262.ecma-international.org/#sec-object-type&quot; target=&quot;_blank&quot;&gt;ECMA-262&lt;/a&gt; specification, JavaScript objects do not have a fixed size. By adding or removing properties from an object, its size changes. This raises the question: how much memory should be allocated for the object? Furthermore, how can we expand the already allocated memory for the object? Developers of V8 addressed these issues as follows.&lt;/p&gt;
  &lt;h3 id=&quot;P3Hb&quot;&gt;In-object Properties&lt;/h3&gt;
  &lt;p id=&quot;5N3j&quot;&gt;At the moment of the object&amp;#x27;s primary initialization, the object literal has already been parsed, and the AST tree contains information about the properties indicated at the initialization moment. This set of properties is placed directly inside the object, allowing them to be accessed quickly and with minimal overhead. These properties are referred to as &lt;strong&gt;in-object&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;qjmZ&quot;&gt;Let&amp;#x27;s take another look at the class of an empty object.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;NgGe&quot;&gt;d8&amp;gt; const obj1 = {}
d8&amp;gt;
d8&amp;gt; %DebugPrint(obj1);
DebugPrint: 0x2d56001c9ed1: [JS_OBJECT_TYPE]
 - map: 0x2d56000c4945 &amp;lt;Map[28](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x2d56000c4b11 &amp;lt;Object map = 0x2d56000c414d&amp;gt;
 - elements: 0x2d56000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x2d56000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {}
0x2d56000c4945: [Map] in OldSpace
 - map: 0x2d56000c3c29 &amp;lt;MetaMap (0x2d56000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 4
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - back pointer: 0x2d5600000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0x2d5600000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #0: 0x2d5600000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x2d56000c4b11 &amp;lt;Object map = 0x2d56000c414d&amp;gt;
 - constructor: 0x2d56000c4655 &amp;lt;JSFunction Object (sfi = 0x2d5600335385)&amp;gt;
 - dependent code: 0x2d56000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;hjAA&quot;&gt;I would like to draw your attention to the parameter &lt;code&gt;inobject properties&lt;/code&gt;. Here it is set to 4, even though the object does not have any properties yet. The thing is, empty objects by default have several slots for &lt;code&gt;in-object&lt;/code&gt; properties. In V8, the number of such slots is 4.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;vi38&quot;&gt;d8&amp;gt; obj1.a = 1;
d8&amp;gt; obj1.b = 2;
d8&amp;gt; obj1.c = 3;
d8&amp;gt; obj1.d = 4;
d8&amp;gt; obj1.e = 5;
d8&amp;gt; obj1.f = 6;
d8&amp;gt;
d8&amp;gt; %DebugPrint(obj1);
DebugPrint: 0x2d56001c9ed1: [JS_OBJECT_TYPE]
 - map: 0x2d56000db291 &amp;lt;Map[28](HOLEY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x2d56000c4b11 &amp;lt;Object map = 0x2d56000c414d&amp;gt;
 - elements: 0x2d56000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x2d56001cc1a9 &amp;lt;PropertyArray[3]&amp;gt;
 - All own properties (excluding elements): {
    0x2d5600002a21: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x2d5600002a31: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object
    0x2d5600002a41: [String] in ReadOnlySpace: #c: 3 (const data field 2), location: in-object
    0x2d5600002a51: [String] in ReadOnlySpace: #d: 4 (const data field 3), location: in-object
    0x2d5600002a61: [String] in ReadOnlySpace: #e: 5 (const data field 4), location: properties[0]
    0x2d5600002a71: [String] in ReadOnlySpace: #f: 6 (const data field 5), location: properties[1]
 }
0x2d56000db291: [Map] in OldSpace
 - map: 0x2d56000c3c29 &amp;lt;MetaMap (0x2d56000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 28
 - inobject properties: 4
 - unused property fields: 1
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x2d56000db169 &amp;lt;Map[28](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x2d56000dace9 &amp;lt;Cell value= 0&amp;gt;
 - instance descriptors (own) #6: 0x2d56001cc1f5 &amp;lt;DescriptorArray[6]&amp;gt;
 - prototype: 0x2d56000c4b11 &amp;lt;Object map = 0x2d56000c414d&amp;gt;
 - constructor: 0x2d56000c4655 &amp;lt;JSFunction Object (sfi = 0x2d5600335385)&amp;gt;
 - dependent code: 0x2d56000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0&lt;/pre&gt;
  &lt;p id=&quot;ijUZ&quot;&gt;This means that the first 4 properties added to an empty object will be placed in these slots as &lt;code&gt;in-object &lt;/code&gt;properties.&lt;/p&gt;
  &lt;h3 id=&quot;yhT4&quot;&gt;External Properties&lt;/h3&gt;
  &lt;p id=&quot;Hhfp&quot;&gt;Properties that were added after initialization can no longer be placed inside the object since memory for the object is already allocated. To avoid wasting resources on reallocating the entire object, the engine places such properties in an external storage, in this case, in an external array of properties, a reference to which already exists inside the object. These properties are called &lt;strong&gt;external&lt;/strong&gt; or &lt;strong&gt;normal&lt;/strong&gt; (this exact term can often be found in publications by V8 developers). Access to such properties is slightly slower, as it requires resolving the reference to the storage and obtaining the property by index. However, this is much more efficient than reallocating the entire object.&lt;/p&gt;
  &lt;h3 id=&quot;hy6t&quot;&gt;Fast and Slow Properties&lt;/h3&gt;
  &lt;p id=&quot;oLAV&quot;&gt;The external property from the example above, as we have just discussed, is stored in an external property array directly linked to our object. The data format in this array is identical to the format of internal properties. In other words, only property values are stored there, while metadata about them is placed in the descriptors array, which also contains information about internal properties. Essentially, external properties differ from internal ones only in the location where they are stored. Both can be considered fast properties in a broad sense. However, I would like to remind you that JavaScript is a dynamic and flexible programming language. A developer has the ability to add, remove, and modify object properties as desired. Active changes to the set of properties can lead to significant processor time costs. To optimize this process, V8 supports the so-called &amp;quot;slow&amp;quot; properties. The essence of slow properties lies in using a different type of external storage. Instead of an array of values, properties are placed in a separate dictionary object together with all their attributes. Access to both the values and attributes of such properties is done by their name, which serves as the key to the dictionary.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;x4qe&quot;&gt;d8&amp;gt; delete obj1.a;
d8&amp;gt;
d8&amp;gt; %DebugPrint(obj1)
DebugPrint: 0x2387001c942d: [JS_OBJECT_TYPE]
 - map: 0x2387000d6071 &amp;lt;Map[12](HOLEY_ELEMENTS)&amp;gt; [DictionaryProperties]
 - prototype: 0x2387000c4b11 &amp;lt;Object map = 0x2387000c414d&amp;gt;
 - elements: 0x2387000006cd &amp;lt;FixedArray[0]&amp;gt; [HOLEY_ELEMENTS]
 - properties: 0x2387001cc1d9 &amp;lt;NameDictionary[30]&amp;gt;
 - All own properties (excluding elements): {
   b: 2 (data, dict_index: 2, attrs: [WEC])
 }
0x2387000d6071: [Map] in OldSpace
 - map: 0x2387000c3c29 &amp;lt;MetaMap (0x2387000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_OBJECT_TYPE
 - instance size: 12
 - inobject properties: 0
 - unused property fields: 0
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - dictionary_map
 - may_have_interesting_properties
 - back pointer: 0x238700000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0x238700000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #0: 0x238700000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x2387000c4b11 &amp;lt;Object map = 0x2387000c414d&amp;gt;
 - constructor: 0x2387000c4655 &amp;lt;JSFunction Object (sfi = 0x238700335385)&amp;gt;
 - dependent code: 0x2387000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

{b: 2}&lt;/pre&gt;
  &lt;p id=&quot;GG69&quot;&gt;We have deleted the property &lt;code&gt;obj1.a&lt;/code&gt;. Despite the fact that the property was internal, we completely changed the shape of the hidden class. To be precise, we have shrunk it, which is different from the typical shape extension. This means that the tree of shpaes has become shorter; hence, the descriptors and value arrays must also be reconstructed. All these operations require certain time resources. In order to avoid this, the engine changes the way object properties are stored to a slower method using an object dictionary. In this example, the dictionary (&lt;code&gt;NameDictionary&lt;/code&gt;) is located at address &lt;code&gt;0x2387001cc1d9&lt;/code&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Reeo&quot;&gt;d8&amp;gt; %DebugPrintPtr(0x2387001cc1d9)
DebugPrint: 0x2387001cc1d9: [NameDictionary]
 - FixedArray length: 30
 - elements: 1
 - deleted: 1
 - capacity: 8
 - elements: {
              7: b -&amp;gt; 2 (data, dict_index: 2, attrs: [WEC])
 }
0x238700000ba1: [Map] in ReadOnlySpace
 - map: 0x2387000004c5 &amp;lt;MetaMap (0x23870000007d &amp;lt;null&amp;gt;)&amp;gt;
 - type: NAME_DICTIONARY_TYPE
 - instance size: variable
 - elements kind: HOLEY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x238700000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0
 - instance descriptors (own) #0: 0x238700000701 &amp;lt;DescriptorArray[0]&amp;gt;
 - prototype: 0x23870000007d &amp;lt;null&amp;gt;
 - constructor: 0x23870000007d &amp;lt;null&amp;gt;
 - dependent code: 0x2387000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

39062729441753&lt;/pre&gt;
  &lt;h2 id=&quot;wz6e&quot;&gt;Arrays&lt;/h2&gt;
  &lt;p id=&quot;kM1l&quot;&gt;According to the &lt;a href=&quot;https://262.ecma-international.org/#sec-array-objects&quot; target=&quot;_blank&quot;&gt;23.1 Array Objects&lt;/a&gt; section of the specification, an array is an object whose keys are integers from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;2**32 - 2&lt;/code&gt;. On the one hand, it seems that from the perspective of hidden classes, an array is no different from a regular object. However, in practice, arrays can be quite large. What if there are thousands of elements in an array? Will a separate hidden class be created for each element? Let&amp;#x27;s see what the hidden class of an array actually looks like.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;mU44&quot;&gt;d8&amp;gt; arr = [];
d8&amp;gt; arr[0] = 1;
d8&amp;gt; arr[1] = 2;
d8&amp;gt;
d8&amp;gt; %DebugPrint(arr); 
DebugPrint: 0x24001c9421: [JSArray]
 - map: 0x0024000ce6b1 &amp;lt;Map[16](PACKED_SMI_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x0024000ce925 &amp;lt;JSArray[0]&amp;gt;
 - elements: 0x0024001cb125 &amp;lt;FixedArray[17]&amp;gt; [PACKED_SMI_ELEMENTS]
 - length: 2
 - properties: 0x0024000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {
    0x2400000d41: [String] in ReadOnlySpace: #length: 0x00240030f6f9 &amp;lt;AccessorInfo name= 0x002400000d41 &amp;lt;String[6]: #length&amp;gt;, data= 0x002400000061 &amp;lt;undefined&amp;gt;&amp;gt; (const accessor descriptor), location: descriptor
 }
 - elements: 0x0024001cb125 &amp;lt;FixedArray[17]&amp;gt; {
           0: 1
           1: 2
        2-16: 0x0024000006e9 &amp;lt;the_hole_value&amp;gt;
 }
0x24000ce6b1: [Map] in OldSpace
 - map: 0x0024000c3c29 &amp;lt;MetaMap (0x0024000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - unused property fields: 0
 - elements kind: PACKED_SMI_ELEMENTS
 - enum length: invalid
 - back pointer: 0x002400000061 &amp;lt;undefined&amp;gt;
 - prototype_validity cell: 0x002400000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors #1: 0x0024000cef3d &amp;lt;DescriptorArray[1]&amp;gt;
 - transitions #1: 0x0024000cef59 &amp;lt;TransitionArray[4]&amp;gt;Transition array #1:
     0x002400000e05 &amp;lt;Symbol: (elements_transition_symbol)&amp;gt;: (transition to HOLEY_SMI_ELEMENTS) -&amp;gt; 0x0024000cef71 &amp;lt;Map[16](HOLEY_SMI_ELEMENTS)&amp;gt;

 - prototype: 0x0024000ce925 &amp;lt;JSArray[0]&amp;gt;
 - constructor: 0x0024000ce61d &amp;lt;JSFunction Array (sfi = 0x2400335da5)&amp;gt;
 - dependent code: 0x0024000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

[1, 2]&lt;/pre&gt;
  &lt;p id=&quot;k4fu&quot;&gt;As we can see, in the hidden class of this object, the &lt;code&gt;back pointer&lt;/code&gt; reference is empty, indicating the absence of a parent class, even though we have added two elements. The thing is, the hidden class of any array always has a uniform shape of &lt;code&gt;JS_ARRAY_TYPE&lt;/code&gt;. This is a special hidden class that only has one property in its descriptors - &lt;code&gt;length&lt;/code&gt;. The array elements, on the other hand, are arranged inside the object in a &lt;code&gt;FixedArray&lt;/code&gt; structure. In reality, hidden array classes can still be inherited, as the elements themselves can have different data types, and keys, depending on the number, can be stored in different ways for optimization of access to them. In this article, I will not delve into all the possible transitions within arrays in detail, as this is a topic for a separate article. However, it is worth noting that various non-standard manipulations with array keys can lead to the creation of a class tree for all or some of the elements.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;7yF3&quot;&gt;d8&amp;gt; const arr = [];
d8&amp;gt; arr[-1] = 1;
d8&amp;gt; arr[2**32 - 1] = 2;
d8&amp;gt;
d8&amp;gt; %DebugPrint(arr)
DebugPrint: 0xe0b001c98c9: [JSArray]
 - map: 0x0e0b000dacc1 &amp;lt;Map[16](PACKED_SMI_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x0e0b000ce925 &amp;lt;JSArray[0]&amp;gt;
 - elements: 0x0e0b000006cd &amp;lt;FixedArray[0]&amp;gt; [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0x0e0b001cb5f1 &amp;lt;PropertyArray[3]&amp;gt;
 - All own properties (excluding elements): {
    0xe0b00000d41: [String] in ReadOnlySpace: #length: 0x0e0b0030f6f9 &amp;lt;AccessorInfo name= 0x0e0b00000d41 &amp;lt;String[6]: #length&amp;gt;, data= 0x0e0b00000061 &amp;lt;undefined&amp;gt;&amp;gt; (const accessor descriptor), location: descriptor
    0xe0b000dab35: [String] in OldSpace: #-1: 1 (const data field 0), location: properties[0]
    0xe0b000daca9: [String] in OldSpace: #4294967295: 2 (const data field 1), location: properties[1]
 }
0xe0b000dacc1: [Map] in OldSpace
 - map: 0x0e0b000c3c29 &amp;lt;MetaMap (0x0e0b000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - unused property fields: 1
 - elements kind: PACKED_SMI_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x0e0b000dab45 &amp;lt;Map[16](PACKED_SMI_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x0e0b000dab95 &amp;lt;Cell value= 0&amp;gt;
 - instance descriptors (own) #3: 0x0e0b001cb651 &amp;lt;DescriptorArray[3]&amp;gt;
 - prototype: 0x0e0b000ce925 &amp;lt;JSArray[0]&amp;gt;
 - constructor: 0x0e0b000ce61d &amp;lt;JSFunction Array (sfi = 0xe0b00335da5)&amp;gt;
 - dependent code: 0x0e0b000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

[]&lt;/pre&gt;
  &lt;p id=&quot;dteD&quot;&gt;In the example above, both elements &lt;code&gt;-1&lt;/code&gt; and &lt;code&gt;2**32 - 1&lt;/code&gt; are not within the range of possible array indexes &lt;code&gt;[0 .. 2**32 - 2]&lt;/code&gt; and were declared as regular object properties with corresponding shapes and hidden class tree generation.&lt;/p&gt;
  &lt;p id=&quot;SpO0&quot;&gt;Another exceptional situation may occur when attempting to change the index attributes. For elements to be stored in a fast store, all indexes must have the same configuration. Trying to change the attributes of any of the indexes will not result in the creation of a separate property, but will lead to a change in the storage type to slow, in which not only values but also attributes of each index will be stored. Essentially, the same rule is applied here as with slow object properties.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;1eqN&quot;&gt;d8&amp;gt; const arr = [1];
d8&amp;gt; Object.defineProperty(arr, &amp;#x27;0&amp;#x27;, { value: 2, writable:  false });      
d8&amp;gt; arr.push(3);
d8&amp;gt;
d8&amp;gt; %DebugPrint(arr);
DebugPrint: 0x29ee001c9425: [JSArray]
 - map: 0x29ee000dad05 &amp;lt;Map[16](DICTIONARY_ELEMENTS)&amp;gt; [FastProperties]
 - prototype: 0x29ee000ce925 &amp;lt;JSArray[0]&amp;gt;
 - elements: 0x29ee001cb391 &amp;lt;NumberDictionary[16]&amp;gt; [DICTIONARY_ELEMENTS]
 - length: 2
 - properties: 0x29ee000006cd &amp;lt;FixedArray[0]&amp;gt;
 - All own properties (excluding elements): {
    0x29ee00000d41: [String] in ReadOnlySpace: #length: 0x29ee0030f6f9 &amp;lt;AccessorInfo name= 0x29ee00000d41 &amp;lt;String[6]: #length&amp;gt;, data= 0x29ee00000061 &amp;lt;undefined&amp;gt;&amp;gt; (const accessor descriptor), location: descriptor
 }
 - elements: 0x29ee001cb391 &amp;lt;NumberDictionary[16]&amp;gt; {
   - requires_slow_elements
   0: 2 (data, dict_index: 0, attrs: [_EC])
   1: 3 (data, dict_index: 0, attrs: [WEC])
 }
0x29ee000dad05: [Map] in OldSpace
 - map: 0x29ee000c3c29 &amp;lt;MetaMap (0x29ee000c3c79 &amp;lt;NativeContext[285]&amp;gt;)&amp;gt;
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - unused property fields: 0
 - elements kind: DICTIONARY_ELEMENTS
 - enum length: invalid
 - stable_map
 - back pointer: 0x29ee000cf071 &amp;lt;Map[16](HOLEY_ELEMENTS)&amp;gt;
 - prototype_validity cell: 0x29ee00000a31 &amp;lt;Cell value= 1&amp;gt;
 - instance descriptors (own) #1: 0x29ee000cef3d &amp;lt;DescriptorArray[1]&amp;gt;
 - prototype: 0x29ee000ce925 &amp;lt;JSArray[0]&amp;gt;
 - constructor: 0x29ee000ce61d &amp;lt;JSFunction Array (sfi = 0x29ee00335da5)&amp;gt;
 - dependent code: 0x29ee000006dd &amp;lt;Other heap object (WEAK_ARRAY_LIST_TYPE)&amp;gt;
 - construction counter: 0

[2, 3]&lt;/pre&gt;
  &lt;h2 id=&quot;PoZs&quot;&gt;Conclusion&lt;/h2&gt;
  &lt;p id=&quot;SgnO&quot;&gt;In this article, we have delved deeper into the methods of storing object properties, concepts of hidden classes, object shapes, object descriptors, internal and external properties, as well as fast and slow methods of storing them. Let us now briefly recap the main terms and conclusions.&lt;/p&gt;
  &lt;ul id=&quot;4OzD&quot;&gt;
    &lt;li id=&quot;umrV&quot;&gt;Every object in JavaScript has its main internal class and a hidden class that describes its shape.&lt;/li&gt;
    &lt;li id=&quot;TCd0&quot;&gt;Hidden classes inherit from each other and are organized into class trees. The shape of an object &lt;code&gt;{ a: 1 }&lt;/code&gt; will be the parent for the shape of an object &lt;code&gt;{ a: 1, b: 2 }&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;M6MU&quot;&gt;The order of properties matters. Objects &lt;code&gt;{ a: 1, b: 2 }&lt;/code&gt; and &lt;code&gt;{ b: 2, a: 1 }&lt;/code&gt; will have two different shapes.&lt;/li&gt;
    &lt;li id=&quot;5VrI&quot;&gt;A subclass holds a reference to the superclass and information about what has changed (&lt;strong&gt;transition&lt;/strong&gt;).&lt;/li&gt;
    &lt;li id=&quot;gkEk&quot;&gt;In the class tree of each object, the number of levels is not less than the number of properties in the object.&lt;/li&gt;
    &lt;li id=&quot;R1x4&quot;&gt;The fastest properties of an object will be those declared at initialization. In the following example, access to the property &lt;code&gt;obj1.a&lt;/code&gt; will be faster than to &lt;code&gt;obj2.a&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;L9Au&quot;&gt;const obj1 = { a: undefined };
obj1.a = 1; // &amp;lt;- &amp;quot;a&amp;quot; - in-object property

const obj2 = {};
obj2.a = 1; // &amp;lt;- &amp;quot;a&amp;quot; - external property&lt;/pre&gt;
  &lt;ul id=&quot;v6Ak&quot;&gt;
    &lt;li id=&quot;pL1y&quot;&gt;Atypical changes in the object&amp;#x27;s structure, such as property removal, can lead to a change in the storage type of properties to a slower one. In the following example, &lt;code&gt;obj1&lt;/code&gt; will change its type to &lt;code&gt;NamedDictionary&lt;/code&gt;, and accessing its properties will be significantly slower than accessing the properties of &lt;code&gt;obj2&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;S52D&quot;&gt;const obj1 = { a: 1, b: 2 };
delete obj1.a; // cheanges a storage type to NameDictionary 

const obj2 = { a: 1, b: 2 };
obj2.a = undefined; // a storage type is not changed&lt;/pre&gt;
  &lt;ul id=&quot;r1uV&quot;&gt;
    &lt;li id=&quot;JzuS&quot;&gt;If an object has external properties but the internal ones are less 4, such an object can be slightly optimized, as an empty object by default has several slots for &lt;code&gt;in-object&lt;/code&gt; properties.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;xNix&quot;&gt;const obj1 = { a: 1 };
obj1.b = 2;
obj1.c = 3;
obj1.d = 4;
obj1.e = 5;
obj1.f = 6;

%DebugPrint(obj1);
...
- All own properties (excluding elements): {
    ...#a: 1 (const data field 0), location: in-object
    ...#b: 2 (const data field 1), location: properties[0]
    ...#c: 3 (const data field 2), location: properties[1]
    ...#d: 4 (const data field 3), location: properties[2]
    ...#e: 5 (const data field 4), location: properties[3]
    ...#f: 6 (const data field 5), location: properties[4]
 }

const obj2 = Object.fromEntries(Object.entries(obj1));

%DebugPrint(obj2);
...
 - All own properties (excluding elements): {
    ...#a: 1 (const data field 0), location: in-object
    ...#b: 2 (const data field 1), location: in-object
    ...#c: 3 (const data field 2), location: in-object
    ...#d: 4 (const data field 3), location: in-object
    ...#e: 5 (const data field 4), location: properties[0]
    ...#f: 6 (const data field 5), location: properties[1]
 }&lt;/pre&gt;
  &lt;ul id=&quot;m8BJ&quot;&gt;
    &lt;li id=&quot;Dcd4&quot;&gt;An array is a regular class whose structure looks like &lt;code&gt;{ length: [W__] }&lt;/code&gt;. The elements of the array are stored in special structures, and references to these structures are placed inside the object. Adding or removing elements from the array does not lead to an increase in the class tree.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;AytZ&quot;&gt;const arr = [];
arr[0] = 1; // new element of the array doesn&amp;#x27;t extends the shapes tree

const obj = {};
obj1[0] = 1; // each new property extends the shapes tree&lt;/pre&gt;
  &lt;ul id=&quot;j9Ja&quot;&gt;
    &lt;li id=&quot;x6Dq&quot;&gt;The use of atypical keys in an array, such as non-numeric keys or keys outside the range &lt;code&gt;[0 .. 2**32 - 2]&lt;/code&gt;, leads to the creation of new shapes in the class tree.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;1lQf&quot;&gt;const arr = [];
arr[-1] = 1;
arr[2**32 - 1] = 2;
// Leads to the shapes tree generation
// { length } =&amp;gt; { length, [-1] } =&amp;gt; { length, [-1], [2**32 - 1] }&lt;/pre&gt;
  &lt;ul id=&quot;EKOI&quot;&gt;
    &lt;li id=&quot;ioLk&quot;&gt;Attempting to modify an array element&amp;#x27;s attribute will result in a switch to a slower storage type.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;CIkr&quot;&gt;const arr = [1, 2, 3];
// { elements: {
//   #0: 1,
//   #1: 2,
//   #2: 3
// }}

Object.defineProperty(arr, &amp;#x27;0&amp;#x27;, { writable: false };
// { elements: {
//   #0: { value: 1, attrs: [_EC] }, 
//   #1: { value: 2, attrs: [WEC] },
//   #2: { value: 3, attrs: [WEC] }
// }}&lt;/pre&gt;
  &lt;p id=&quot;7pWR&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;rwuY&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4FEw&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/js-objects-structure&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-a&lt;/a&gt;&lt;a href=&quot;https://blog.frontend-almanac.ru/js-object-structure&quot; target=&quot;_blank&quot;&gt;lmanac.ru/js-object-structure&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:react-state-management</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/react-state-management?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>State management in React applications</title><published>2024-03-19T19:09:17.606Z</published><updated>2024-03-19T19:09:17.606Z</updated><summary type="html">The issue of state management in React applications has always been very relevant. React itself, when it first came out, did not offer any comprehensive approaches to state management and left this task to the developers. The technical tools available were the classic component properties and the internal state of components. The standard basic concept was extremely primitive and involved storing data in the component's state. Thus, global data could be stored in the state of the top component, while more specific data could be stored in the states of lower components. The only built-in way to exchange data between components was to use component properties.</summary><content type="html">
  &lt;p id=&quot;Vzeo&quot;&gt;The issue of state management in React applications has always been very relevant. React itself, when it first came out, did not offer any comprehensive approaches to state management and left this task to the developers. The technical tools available were the classic component properties and the internal state of components. The standard basic concept was extremely primitive and involved storing data in the component&amp;#x27;s state. Thus, global data could be stored in the state of the top component, while more specific data could be stored in the states of lower components. The only built-in way to exchange data between components was to use component properties.&lt;/p&gt;
  &lt;h2 id=&quot;bhWm&quot;&gt;Prop drilling&lt;/h2&gt;
  &lt;p id=&quot;qZtD&quot;&gt;The typical approach looks something like this:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Mikt&quot;&gt;class App extends React.Component {
  constructor(props) {
    super(props);

    // initialize the state with default values
    this.state = {
      a: &amp;#x27;&amp;#x27;,
      b: 0,
    };
  }

  render() {
    // pass data from the state to a child component
    return &amp;lt;Child a={this.state.a} b={this.state.b} /&amp;gt;;
  }
}

class Child extends React.Component&amp;lt;any, any&amp;gt; {
  // the child component receives data through the reference this.props
  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.a}&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.b}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById(&amp;#x27;root&amp;#x27;));&lt;/pre&gt;
  &lt;p id=&quot;apuo&quot;&gt;In the example above, the data is stored in the state of the parent component and passed to the child component through props. The child component may also have its own internal state.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;QrDD&quot;&gt;class Child extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      c: 2,
    };
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.a}&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.b}&amp;lt;/div&amp;gt;
        &amp;lt;Child2 c={this.state.c} /&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

class Child2 extends React.Component {
  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.c}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}&lt;/pre&gt;
  &lt;p id=&quot;J0Q1&quot;&gt;In this case, changing the component&amp;#x27;s state is only possible by calling the method &lt;code&gt;this.setState&lt;/code&gt; inside the component, meaning the child component can update the parent-level data only if it has the corresponding method passed to it through the same props.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;FTbM&quot;&gt;class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      a: &amp;#x27;&amp;#x27;,
      b: 0,
    };
  }

  render() {
    return (
      &amp;lt;Child
        a={this.state.a}
        b={this.state.b}
        setB={(b) =&amp;gt; {
          this.setState({ b });
        }}
      /&amp;gt;
    );
  }
}

class Child extends React.Component&amp;lt;any, any&amp;gt; {
  constructor(props) {
    super(props);

    this.state = {
      c: 2,
    };
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.a}&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.b}&amp;lt;/div&amp;gt;
        &amp;lt;Child2
          b={this.state.b}
          c={this.state.c}
          setB={this.props.setB}
        /&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

class Child2 extends React.Component {
  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;{this.props.c}&amp;lt;/div&amp;gt;

        &amp;lt;button
          onClick={() =&amp;gt; {
            this.props.setB(this.props.b + 1);
          }}
        &amp;gt;
          Increment
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}&lt;/pre&gt;
  &lt;p id=&quot;xInu&quot;&gt;At this stage, the problem of this approach becomes apparent. In real applications with a large number of components, this scheme leads to what is known as &lt;strong&gt;prop drilling&lt;/strong&gt;, which is the mass passing of references down the component tree and back up. This inevitably leads to:&lt;/p&gt;
  &lt;p id=&quot;cvzC&quot;&gt;a) Data update race, where two components, unaware of each other, try to update the same reference in the state;&lt;/p&gt;
  &lt;p id=&quot;8dVZ&quot;&gt;b) Components acting as intermediaries for data they don&amp;#x27;t actually operate with;&lt;/p&gt;
  &lt;p id=&quot;eaxY&quot;&gt;c) Complexity in replacing a component in the middle of the chain;&lt;/p&gt;
  &lt;p id=&quot;LCAZ&quot;&gt;d) A long and convoluted graph of state references.&lt;/p&gt;
  &lt;p id=&quot;Up6c&quot;&gt;Additionally, the task was further complicated by the fact that in the early versions of React, simple functional components did not have internal state (hooks didn&amp;#x27;t exist back then).&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;SPrz&quot;&gt;const Child3 = ({ c }) =&amp;gt; {
  // A functional component does not have this.state, the useState()
  // hook was introduced only in 2019 starting from version 16.8.0.
  return &amp;lt;div&amp;gt;{c}&amp;lt;/div&amp;gt;;
};&lt;/pre&gt;
  &lt;h2 id=&quot;5iKR&quot;&gt;Flux&lt;/h2&gt;
  &lt;p id=&quot;yoSv&quot;&gt;Needless to say, the approach shown above led to a large number of errors and exponentially increasing code complexity. Even the creators of &amp;quot;React&amp;quot; could not avoid this. In the past years, errors related to various asynchronous processes such as disappearing or not displaying notifications and others constantly appeared on the &amp;quot;Facebook&amp;quot; website. In response, developers began to work on a new approach to organizing data management in the application, giving rise to the &lt;strong&gt;Flux&lt;/strong&gt; pattern. The precursor of this pattern was the classic and very popular &lt;a href=&quot;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&quot; target=&quot;_blank&quot;&gt;MVC&lt;/a&gt; pattern at the time. The essence of Flux is that data should always move in one direction and be stored in a separate large store. Data updates are carried out using special action methods that are not tied to any specific component.&lt;/p&gt;
  &lt;p id=&quot;6wrm&quot;&gt;Based on the Flux pattern, many state management libraries appeared, such as &lt;strong&gt;Fluxxor&lt;/strong&gt;, &lt;strong&gt;Flummox&lt;/strong&gt;, &lt;strong&gt;Baobab&lt;/strong&gt;, and many others. The most popular of them, over the years and to this day, is &lt;strong&gt;Redux&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;ap6o&quot;&gt;Redux&lt;/h2&gt;
  &lt;p id=&quot;hiu3&quot;&gt;The application state management library &lt;strong&gt;Redux&lt;/strong&gt; appeared in 2015 and has since gained immense popularity. Redux has proven the effectiveness of the Flux approach and until recently has been considered the standard in the React world.&lt;/p&gt;
  &lt;p id=&quot;mBW0&quot;&gt;The architecture of a typical application has taken the following form.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;tyaU&quot;&gt;import { connect, Provider } from &amp;#x27;react-redux&amp;#x27;;

const defaultState1 = {
  a: &amp;quot;&amp;quot;,
  b: 0
};

const reducer1 = function(state = defaultState1, action) {
  switch(action.type){
    case &amp;quot;A_TYPE&amp;quot;:
      return { ...state, a: action.payload };
    case &amp;quot;B_TYPE&amp;quot;:
      return { ...state, b: action.payload };
  }

  return state;
};

const defaultState2 = {
  c: false,
  d: []
};

const reducer2 = function(state = defaultState2, action) {
  switch(action.type){
    case &amp;quot;C_TYPE&amp;quot;:
      return { ...state, c: action.payload };
    case &amp;quot;D_TYPE&amp;quot;:
      return { ...state, c: action.payload };
  }

  return state;
};

const store = createStore(
  combineReducers({
    reducer1,
    reducer2
  })
);

const App = connect(
  (state) =&amp;gt; ({ b: state.reducer1.b }),
  (dispatch) =&amp;gt; {
    setB: (value) =&amp;gt; dispatch({ type: &amp;quot;B_TYPE&amp;quot;, payload: value }) 
  }
)(({ b }) =&amp;gt; {
  return (
    &amp;lt;Provider store={store}&amp;gt;
      &amp;lt;button
        onClick={() =&amp;gt; {
          setB(b + 1)
        }}
      &amp;gt;
        Increase {b}
      &amp;lt;/button&amp;gt;
    &amp;lt;/Provider&amp;gt;
  );
});&lt;/pre&gt;
  &lt;p id=&quot;h34l&quot;&gt;Now all the data in the application is stored in a separate large tree outside of any component. It is possible to &amp;quot;connect&amp;quot; to the tree, meaning to listen to all the changes in the necessary branch and react to these changes. To update a value in the tree, you can send a so-called action - an event containing an object with the field &lt;code&gt;type&lt;/code&gt;. Based on this type, the reducer will understand what kind of action it is and execute the corresponding mutation of the tree.&lt;/p&gt;
  &lt;p id=&quot;pPEZ&quot;&gt;In general, the issue that seemed to be resolved. Data live separately, components separately. No race or prop drilling. However, the price for this is bulky constructions to ensure the viability of the storage itself. A reducer and action are needed for each property of the tree. Moreover, in more complex applications, as the tree grows, specific cases begin to arise. For example, in deeply nested objects, it becomes harder to maintain immutability, leading to unresponsive reactions to state changes. Additional libraries have come to the help, such as &lt;strong&gt;immutable-js&lt;/strong&gt; and &lt;strong&gt;reselect&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;JB2X&quot;&gt;Then the issue of working with requests arose, as requests are the main source of data in the application in most cases. This meant that there was a need to add asynchrony to reducers. That&amp;#x27;s how &lt;strong&gt;redux-thunk&lt;/strong&gt; and &lt;strong&gt;redux-saga &lt;/strong&gt;came about. However, even that was not enough.&lt;/p&gt;
  &lt;p id=&quot;cpzj&quot;&gt;Requests, in addition to data, also have their own state. The average application needs to know at least whether the request is still in progress or the response has already been received. Traditionally, the request execution flag (usually called &amp;quot;isFetching&amp;quot; or &amp;quot;isLoading&amp;quot;) was stored right in the tree, which required keeping the same mechanism for setting this flag for each request. Along with the loading flag in the same situation was the handling of request errors. The error message was also stored in the tree next to the data and loading flag. Digging deeper, advanced applications wanted to cache responses to requests for optimization purposes to avoid requesting data over the network every time a component is mounted, for example. All these routine operations and optimizations further bloated the already not small code serving the state tree. Therefore, the appearance of libraries like &lt;strong&gt;redux-toolkit&lt;/strong&gt; (RTK) seems quite logical. The RTK library allows organizing work with the state tree in a unified pattern. And in combination with the additional &lt;strong&gt;rtk-query&lt;/strong&gt;, you can get request caching and information on their status out of the box. However, it is still impossible to completely get rid of actions and reducers in this way.&lt;/p&gt;
  &lt;h2 id=&quot;qVXH&quot;&gt;useReducer&lt;/h2&gt;
  &lt;p id=&quot;7FFy&quot;&gt;&amp;quot;Redux&amp;quot; and other Flux-like libraries are third-party developments. Of course, the &amp;quot;React&amp;quot; team did not stand aside and added the ability to work in a &amp;quot;Flux&amp;quot; style to the API. In fact, this is reflected in the introduction of the &lt;strong&gt;useReducer&lt;/strong&gt; hook in React version &lt;a href=&quot;https://github.com/facebook/react/releases/tag/v16.8.0&quot; target=&quot;_blank&quot;&gt;16.8.0&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;mcVR&quot;&gt;The same example that was mentioned above can now be implemented without using &amp;quot;Redux.&amp;quot;&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;TOAp&quot;&gt;const reducer = (state, action) =&amp;gt; {
  switch (action.type) {
    case &amp;#x27;A_TYPE&amp;#x27;:
      return { ...state, a: action.payload };
    case &amp;#x27;B_TYPE&amp;#x27;:
      return { ...state, b: action.payload };

    default:
      return state;
  }
};

export const App = () =&amp;gt; {
  const [state, dispatch] = useReducer(reducer, { a: &amp;quot;&amp;quot;, b: 0 });

  return (
    &amp;lt;button
      onClick={() =&amp;gt; {
        dispatch({ type: &amp;quot;A_TYPE&amp;quot;, payload: b + 1 })
      }}
    &amp;gt;
      Increase {state.b}
    &amp;lt;/button&amp;gt;
  );
};&lt;/pre&gt;
  &lt;p id=&quot;S27k&quot;&gt;However, all the previously mentioned issues related to working with Redux are also relevant to useReducer. Since Redux has grown with a multitude of additional libraries and tools, the hook has not gained much popularity among developers.&lt;/p&gt;
  &lt;h2 id=&quot;tVWp&quot;&gt;React context&lt;/h2&gt;
  &lt;p id=&quot;N5JV&quot;&gt;The context in React has existed since the earliest versions. However, it was officially included in the API only starting from version &lt;a href=&quot;https://github.com/facebook/react/releases/tag/v16.3.0&quot; target=&quot;_blank&quot;&gt;16.3.0&lt;/a&gt; in 2018. The essence of the approach is as follows: data that needs to be made available to multiple components is placed in a separate independent location using the &lt;code&gt;createContext()&lt;/code&gt; API method.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;iv4b&quot;&gt;const MyContext = createContext({ a: &amp;quot;&amp;quot;, b: 0 });&lt;/pre&gt;
  &lt;p id=&quot;G6BX&quot;&gt;The result of the createContext method is an object containing references to the provider and consumer of the created context.&lt;/p&gt;
  &lt;p id=&quot;2bZF&quot;&gt;The provider should wrap a component or a tree of components that will have access to the data inside this context.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;CHSa&quot;&gt;class App extends React.Component&amp;lt;any, any&amp;gt; {
  render() {
    return (
      &amp;lt;MyContext.Provider value={{ a: &amp;#x27;&amp;#x27;, b: 0 }}&amp;gt;
        &amp;lt;Child /&amp;gt;
      &amp;lt;/MyContext.Provider&amp;gt;
    );
  }
}

class Child extends React.Component&amp;lt;any, any&amp;gt; {
  contextType = MyContext;

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;{this.context.a}&amp;lt;/div&amp;gt;
        &amp;lt;Child2 /&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}

const Child2 = () =&amp;gt; {
  return (
    &amp;lt;MyContext.Consumer&amp;gt;
      {(context) =&amp;gt; &amp;lt;div&amp;gt;{context.b}&amp;lt;/div&amp;gt;}
    &amp;lt;/MyContext.Consumer&amp;gt;
  );
};&lt;/pre&gt;
  &lt;p id=&quot;fx7O&quot;&gt;Access to the context is provided through a consumer link. In a class component, you can bind the context to the component by defining the &lt;code&gt;contextType&lt;/code&gt; property. In functional components, you can use a direct reference to the consumer - &lt;code&gt;MyContext.Consumer&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;uAc8&quot;&gt;With the official support for context API in functional components, the &lt;code&gt;useContext&lt;/code&gt; hook became available.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;3YC3&quot;&gt;const Child2 = () =&amp;gt; {
  const { b } = useContext(MyContext);

  return (
    &amp;lt;div&amp;gt;{b}&amp;lt;/div&amp;gt;
  );
};&lt;/pre&gt;
  &lt;p id=&quot;42Wr&quot;&gt;The component that created the context is responsible for updating the data in the context.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;OZlM&quot;&gt;class App extends React.Component&amp;lt;any, any&amp;gt; {
  const [a, setA] = useState(&amp;quot;&amp;quot;);
  const [b, setB] = useState(0);

  render() {
    return (
      &amp;lt;MyContext.Provider value={{ a, b }}&amp;gt;
        &amp;lt;Child /&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; setB(b =&amp;gt; b + 1)}&amp;gt;
          Increase {b}
        &amp;lt;/button&amp;gt;
      &amp;lt;/MyContext.Provider&amp;gt;
    );
  }
}&lt;/pre&gt;
  &lt;p id=&quot;jYp9&quot;&gt;If you need to delegate the ability to update data to lower-level components, you can also place a reference to the action in the context itself.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;lQTq&quot;&gt;class App extends React.Component&amp;lt;any, any&amp;gt; {
  const [a, setA] = useState(&amp;quot;&amp;quot;);
  const [b, setB] = useState(0);

  render() {
    return (
      &amp;lt;MyContext.Provider value={{ a, b, setB }}&amp;gt;
        &amp;lt;Child /&amp;gt;
      &amp;lt;/MyContext.Provider&amp;gt;
    );
  }
}

const Child = () =&amp;gt; {
  const { b, setB } = useContext(MyContext);

  return (
    &amp;lt;button onClick={() =&amp;gt; setB(b =&amp;gt; b + 1)}&amp;gt;
      Increase {b}
    &amp;lt;/button&amp;gt;
  );
};&lt;/pre&gt;
  &lt;p id=&quot;87xF&quot;&gt;This way, you can control the context mutation process and allow modification of only those parts that are needed. Moreover, you can add intermediate actions similar to &amp;quot;Redux middleware&amp;quot; for even greater control over mutations.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;imwm&quot;&gt;class App extends React.Component&amp;lt;any, any&amp;gt; {
  const [a, setA] = useState(&amp;quot;&amp;quot;);
  const [b, setB] = useState(0);
  
  const increaseB = () =&amp;gt; {
    setB((b) =&amp;gt; {
      return b &amp;lt; 10 ? b + 1 : b;
    });
  };

  render() {
    return (
      &amp;lt;MyContext.Provider value={{ a, b, increaseB }}&amp;gt;
        &amp;lt;Child /&amp;gt;
      &amp;lt;/MyContext.Provider&amp;gt;
    );
  }
}

const Child = () =&amp;gt; {
  const { b, increaseB } = useContext(MyContext);

  return (
    &amp;lt;button onClick={increaseB}&amp;gt;
      Increase {b}
    &amp;lt;/button&amp;gt;
  );
};&lt;/pre&gt;
  &lt;p id=&quot;nXq8&quot;&gt;Among the advantages of context are:&lt;/p&gt;
  &lt;h3 id=&quot;x8hs&quot;&gt;Flexibility&lt;/h3&gt;
  &lt;p id=&quot;vhCU&quot;&gt;The context storage is extremely primitive, and the API consists of simple wrapper methods. Therefore, the developer has maximum control at all stages of context design.&lt;/p&gt;
  &lt;h3 id=&quot;s0TQ&quot;&gt;Selectivity&lt;/h3&gt;
  &lt;p id=&quot;diui&quot;&gt;Unlike Redux with its global tree, each specific context has the ability to wrap only the necessary components. Conversely, a component can be wrapped in several contexts at once. This allows you to control which data will be available and where.&lt;/p&gt;
  &lt;h3 id=&quot;eFAx&quot;&gt;Uniqueness&lt;/h3&gt;
  &lt;p id=&quot;gdlS&quot;&gt;In some cases, there may be a need to duplicate the context model. This may be necessary, for example, when working with arrays.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;jMzd&quot;&gt;import { connect, Provider } from &amp;#x27;react-redux&amp;#x27;;

const defaultState = {
  items: [
    { id: 1, name: &amp;quot;item 1&amp;quot; },
    { id: 2, name: &amp;quot;item 2&amp;quot; },
    { id: 3, name: &amp;quot;item 3&amp;quot; },
  ],
  selectedId: undefined,
};

const reducer = function(state = defaultState, action) {
  switch(action.type){
    case &amp;quot;SET_SELECTED_ID_TYPE&amp;quot;:
      return {
        ...state,
        selectedId: action.id,
      };
    default:
      return state;
  }

  return state;
};

const store = createStore(reducer);

const SelectedItem = connect(
  (state) =&amp;gt; ({ item: state.reducer.items.find(item =&amp;gt; item.id === state.selectedId) }),
)(({ item }) =&amp;gt; {
  return item ? null : (
    &amp;lt;div&amp;gt;
      Seleceted item: {item?.title}
    &amp;lt;/div&amp;gt;
  );
});

const ItemsList = connect(
  (state) =&amp;gt; ({
    items: state.reducer.items,
  }),
  (dispatch = {
    setItem: (id) =&amp;gt; dispatch({ type: &amp;#x27;SET_SELECTED_ID_TYPE&amp;#x27;, id }),
  })
)(({ items, setItem }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      {items.map((item) =&amp;gt; (
        &amp;lt;button key={item.id} onClick={() =&amp;gt; setItem(item.id)}&amp;gt;
          {item.title}
        &amp;lt;/button&amp;gt;
      ))}

      &amp;lt;SelectedItem /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});&lt;/pre&gt;
  &lt;p id=&quot;fTJb&quot;&gt;In this example, everything seems fine as long as the &lt;code&gt;ItemsList&lt;/code&gt; component is mounted. However, if we unmount it, for example, change the route, and then return to it again, the old value of &lt;code&gt;selectedId&lt;/code&gt; will remain in the Redux tree. If we expect to see the default value here rather than the old one, we will have to take care of this separately and add the corresponding logic.&lt;/p&gt;
  &lt;p id=&quot;cKzJ&quot;&gt;This example is quite simple. In practice, much more complex cases are encountered, which cannot be solved with a simple reset action.&lt;/p&gt;
  &lt;p id=&quot;WpJb&quot;&gt;In the case of context, the architecture could look like this, for example.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;xuRw&quot;&gt;const ItemContext = createContext();

const SelectedItem: FC = ({ title }) =&amp;gt; {
  const { title } = useContext(ItemContext);

  return &amp;lt;div&amp;gt;Selected item: {title}&amp;lt;/div&amp;gt;;
};

const ItemsList: FC = () =&amp;gt; {
  const [items] = useState([
    { id: 1, name: &amp;#x27;item 1&amp;#x27; },
    { id: 2, name: &amp;#x27;item 2&amp;#x27; },
    { id: 3, name: &amp;#x27;item 3&amp;#x27; },
  ]);

  const [selectedItemId, setSelectedItemId] = useState();

  const item = useMemo(
    () =&amp;gt; items.find((item) =&amp;gt; item.id === selectedItemId),
    [items, selectedItemId]
  );

  return (
    &amp;lt;div&amp;gt;
      {items.map((item) =&amp;gt; (
        &amp;lt;button key={item.id} onClick={() =&amp;gt; setSelectedItemId(item.id)}&amp;gt;
          {item.title}
        &amp;lt;/button&amp;gt;
      ))}

      {item &amp;amp;&amp;amp; (
        &amp;lt;ItemContext.Provider value={item}&amp;gt;
          &amp;lt;SelectedItem /&amp;gt;
        &amp;lt;/ItemContext.Provider&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
};&lt;/pre&gt;
  &lt;p id=&quot;XGYn&quot;&gt;Since the data lives inside the &lt;code&gt;ItemsList&lt;/code&gt; component, upon its remount, the state will always have the original appearance, and we do not need to worry additionally about its reset.&lt;/p&gt;
  &lt;p id=&quot;ETL3&quot;&gt;Meanwhile, the &lt;code&gt;SelectedItem&lt;/code&gt; component operates within the &lt;code&gt;ItemContext&lt;/code&gt; and even has no idea where and how the data has placed there.&lt;/p&gt;
  &lt;h2 id=&quot;z56W&quot;&gt;Universal pattern of using React context&lt;/h2&gt;
  &lt;p id=&quot;fqnV&quot;&gt;Below, I will present a universal template for typical usage of context in a real application. Like Redux, contexts work well with TypeScript, so I will provide an example with TS typing.&lt;/p&gt;
  &lt;p id=&quot;gyy9&quot;&gt;To begin with, let&amp;#x27;s create an independent context and a separate provider component for it.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;4afi&quot;&gt;// MyContext.tsx
import React, {
  createContext,
  Dispatch,
  FC,
  PropsWithChildren,
  SetStateAction,
  useCallback,
  useContext,
  useMemo,
  useState,
} from &amp;#x27;react&amp;#x27;;

export interface MyContextProps {
  myVar: string;
  setMyVar: Dispatch&amp;lt;SetStateAction&amp;lt;MyContextProps[&amp;#x27;myVar&amp;#x27;]&amp;gt;&amp;gt;;

  someAction: (a: number, b?: boolean) =&amp;gt; void;
}

export const MyContext = createContext&amp;lt;MyContextProps&amp;gt;({
  myVar: &amp;#x27;&amp;#x27;,
  setMyVar: () =&amp;gt; {},

  someAction: () =&amp;gt; {},
});

// instead of using WithMyContext, you can use a more traditional
// name - MyContextProvider
export const WithMyContext: FC&amp;lt;PropsWithChildren&amp;gt; = ({ children }) =&amp;gt; {
  const [myVar, setMyVar] = useState&amp;lt;MyContextProps[&amp;#x27;myVar&amp;#x27;]&amp;gt;(&amp;#x27;&amp;#x27;);

  const someAction = useCallback&amp;lt;MyContextProps[&amp;#x27;someAction&amp;#x27;]&amp;gt;(
    (a, b) =&amp;gt; {},
    []
  );

  const value = useMemo&amp;lt;MyContextProps&amp;gt;(
    () =&amp;gt; ({
      myVar,
      setMyVar,

      someAction,
    }),
    [myVar, someAction]
  );

  return &amp;lt;MyContext.Provider value={value}&amp;gt;{children}&amp;lt;/MyContext.Provider&amp;gt;;
};

// let&amp;#x27;s also create a separate hook for convenience.
export const useMyContext = () =&amp;gt; {
  return useContext(MyContext);
};&lt;/pre&gt;
  &lt;p id=&quot;f3XI&quot;&gt;The context is ready, now we just need to wrap the necessary component in it.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Qlkp&quot;&gt;&amp;lt;WithMyContext&amp;gt;
  &amp;lt;MyComponent /&amp;gt;
&amp;lt;/WithMyContext&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;4Mdj&quot;&gt;Or, for instance, a specific route in &amp;quot;react-router&amp;quot;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;E9gc&quot;&gt;&amp;lt;Route element={(
  &amp;lt;WithMyContext&amp;gt;
    &amp;lt;Outlet /&amp;gt;
  &amp;lt;/WithMyContext&amp;gt;
)}&amp;gt;
  &amp;lt;Route …&amp;gt;
&amp;lt;/Route&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;tEGV&quot;&gt;Now the context is available for use within nested components.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;uW3e&quot;&gt;import { FC } from &amp;#x27;react&amp;#x27;;

import { useMyContext } from ‘./MyContext&amp;#x27;;

export const MyComponent: FC = () =&amp;gt; {
  const { myVar } = useMyContext();

  return &amp;lt;div&amp;gt;My component {myVar}&amp;lt;/div&amp;gt;;
};&lt;/pre&gt;
  &lt;p id=&quot;7pWR&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;cHgy&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4FEw&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/react-state-management&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/react-state-management&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:v8-garbage-collection</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/v8-garbage-collection?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>Garbage Collection in V8</title><published>2024-03-07T17:36:35.286Z</published><updated>2024-03-19T22:03:30.759Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/9c/ad/9cad7d4a-930c-43e3-a98a-0203fd62a3f1.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/97/89/97896713-401b-4757-a4e9-c53cea7570c1.png&quot;&gt;In this article, we will delve into the process of garbage collection by the V8 engine in detail. We will familiarize ourselves with the concepts of generations, Minor and Major Garbage Collections, see how objects are allocated, traced, and marked in memory. We will also explore what happens to empty spaces after cleaning and how garbage collection is performed in the background.</summary><content type="html">
  &lt;p id=&quot;uI1W&quot;&gt;In this article, we will delve into the process of garbage collection by the V8 engine in detail. We will familiarize ourselves with the concepts of generations, Minor and Major Garbage Collections, see how objects are allocated, traced, and marked in memory. We will also explore what happens to empty spaces after cleaning and how garbage collection is performed in the background.&lt;/p&gt;
  &lt;h2 id=&quot;wQlK&quot;&gt;What the garbage collector collects&lt;/h2&gt;
  &lt;p id=&quot;1hvg&quot;&gt;Before diving into the garbage collection process itself, let&amp;#x27;s first understand what exactly V8 collects and how it traces objects.&lt;/p&gt;
  &lt;p id=&quot;K3h4&quot;&gt;During V8 work, there can be various data structures in memory. Some of these structures are internal representations of V8 itself, such as hidden classes, lists, queues, and more. Another part consists of JavaScript objects. Practically all data types inside V8 are objects, except for so-called SMI - small numbers not exceeding &lt;code&gt;2^30 - 1&lt;/code&gt;, which are stored in memory as direct binary values, and all variables that have such a value store a reference to the memory area with this specific SMI value. I have written more on this in the article &lt;a href=&quot;https://blog.frontend-almanac.com/p14TDUH-R4o&quot; target=&quot;_blank&quot;&gt;Deep JS. In memory of data and types&lt;/a&gt;. All other types are represented in memory as objects and simulate their mutability/immutability in accordance with the &lt;a href=&quot;http://262.ecma-international.org&quot; target=&quot;_blank&quot;&gt;ECMA-262&lt;/a&gt; specification, issuing, as we commonly say, &amp;quot;primitive&amp;quot; or &amp;quot;reference&amp;quot; types.&lt;/p&gt;
  &lt;p id=&quot;Bxhl&quot;&gt;Indeed, the garbage collection process is not limited to JavaScript objects. The Chromium engine provides garbage collection for anything that can be collected, including removed HTML elements, unused CSS stylesheets, completed animations, and more. Since Chromium is written in C++, its internal engine objects are, obviously, C++ classes. However, JavaScript objects, although created using the same language, are more abstract concepts and must adhere to the requirements of the &lt;a href=&quot;http://262.ecma-international.org&quot; target=&quot;_blank&quot;&gt;ECMA-262&lt;/a&gt; specification. In particular, JavaScript objects do not have a fixed size and can change dynamically. The structure of a JavaScript heap differs from that of a C++ heap. Let&amp;#x27;s take a closer look at how such objects are placed in memory. It is important to understand that the process of collecting dead JavaScript objects will somewhat differ from the collection of C++ objects.&lt;/p&gt;
  &lt;p id=&quot;i562&quot;&gt;A few years ago, in approximately 2018, the Chromium team began developing a system for collecting dead C++ objects. The system was named Oilpan and became part of the internal rendering engine Blink. Later, in 2020, the developers decided to move the system inside the V8 engine. On one hand, V8 uses the same garbage collection mechanisms as Blink, and on the other hand, migrating to V8 makes the system available to third-party developers who integrate the V8 engine but do not use Blink, such as Node.js. As a result, the &lt;a href=&quot;https://chromium.googlesource.com/v8/v8/+/refs/heads/main/include/cppgc/README.md&quot; target=&quot;_blank&quot;&gt;cppgc&lt;/a&gt; directory appeared in the V8 API.&lt;/p&gt;
  &lt;h2 id=&quot;jFJo&quot;&gt;Objects Tracing&lt;/h2&gt;
  &lt;p id=&quot;cqlY&quot;&gt;As I mentioned before, the essence of garbage collection is to free memory from &amp;quot;dead&amp;quot; objects. An object is considered dead when there is no longer any reference to it, meaning it cannot be accessed. Therefore, such an object can no longer be used and should be cleared. Conversely, a &amp;quot;live&amp;quot; object is an object that can be reached through references from the root.&lt;/p&gt;
  &lt;figure id=&quot;7S9D&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/97/89/97896713-401b-4757-a4e9-c53cea7570c1.png&quot; width=&quot;1400&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wJOY&quot;&gt;The simplified tracing scheme is depicted in the figure above. References to all created objects are stored in a separate global table called &lt;strong&gt;GlobalGCInfoTable&lt;/strong&gt;. During garbage collection, an iterative traversal of objects occurs from roots (there can be multiple roots, and the traversal is carried out through all active roots). Objects reachable by the garbage collector are considered &amp;quot;live&amp;quot;, and they are marked in black. All other objects are considered &amp;quot;dead&amp;quot; and marked in white.&lt;/p&gt;
  &lt;h3 id=&quot;1Hre&quot;&gt;Tracing C++ Objects&lt;/h3&gt;
  &lt;p id=&quot;szKQ&quot;&gt;As I said earlier, C++ objects differ from JavaScript objects. The key difference is that JavaScript objects are standardized and described in section &lt;a href=&quot;https://262.ecma-international.org/#sec-object-type&quot; target=&quot;_blank&quot;&gt;6.1.7 The Object Type&lt;/a&gt; of the specification, making them predictable for the garbage collector. On the other hand, C++ objects are not standardized, and their structures can vary significantly. Moreover, not every C++ object needs to be potentially collectible. Therefore, a special API was provided for C++ objects in Oilpan.&lt;/p&gt;
  &lt;p id=&quot;HoOX&quot;&gt;There is a post in the V8 blog titled &lt;a href=&quot;https://v8.dev/blog/high-performance-cpp-gc&quot; target=&quot;_blank&quot;&gt;High-performance garbage collection for C++&lt;/a&gt;. I won&amp;#x27;t retell the entire post; I&amp;#x27;ll just outline the main concept. C++ objects that are intended to be collected by the garbage collector must implement &lt;strong&gt;GarbageCollected&lt;/strong&gt; or &lt;strong&gt;GarbageCollectedMixin&lt;/strong&gt; interfaces. In fact, this means that the object must have a &lt;strong&gt;Trace()&lt;/strong&gt; method in which the object itself must call the tracing of other objects it references.&lt;/p&gt;
  &lt;p id=&quot;j5Om&quot;&gt;For example, here is what the tracing of the &lt;strong&gt;RunningAnimation&lt;/strong&gt; class looks like (at the time of writing the article, in Chromium version &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/tags/124.0.6339.1&quot; target=&quot;_blank&quot;&gt;124.0.6339.1&lt;/a&gt;):&lt;/p&gt;
  &lt;p id=&quot;v1vw&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/tags/124.0.6339.1/third_party/blink/renderer/core/animation/css/css_animations.h#155&quot; target=&quot;_blank&quot;&gt;src/third_party/blink/renderer/core/animation/css/css_animations.h&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;W8Km&quot;&gt;class RunningAnimation final : public GarbageCollected&amp;lt;RunningAnimation&amp;gt; {
 public:
  RunningAnimation(Animation* animation, NewCSSAnimation new_animation)
      : animation(animation),
        name(new_animation.name),
        name_index(new_animation.name_index),
        specified_timing(new_animation.timing),
        style_rule(new_animation.style_rule),
        style_rule_version(new_animation.style_rule_version),
        play_state_list(new_animation.play_state_list) {}

  AnimationTimeline* Timeline() const {
    return animation-&amp;gt;TimelineInternal();
  }
  const std::optional&amp;lt;TimelineOffset&amp;gt;&amp;amp; RangeStart() const {
    return animation-&amp;gt;GetRangeStartInternal();
  }
  const std::optional&amp;lt;TimelineOffset&amp;gt;&amp;amp; RangeEnd() const {
    return animation-&amp;gt;GetRangeEndInternal();
  }

  void Update(UpdatedCSSAnimation update) {
    DCHECK_EQ(update.animation, animation);
    style_rule = update.style_rule;
    style_rule_version = update.style_rule_version;
    play_state_list = update.play_state_list;
    specified_timing = update.specified_timing;
  }

  void Trace(Visitor* visitor) const {
    visitor-&amp;gt;Trace(animation);
    visitor-&amp;gt;Trace(style_rule);
  }

  Member&amp;lt;Animation&amp;gt; animation;
  AtomicString name;
  size_t name_index;
  Timing specified_timing;
  Member&amp;lt;StyleRuleKeyframes&amp;gt; style_rule;
  unsigned style_rule_version;
  Vector&amp;lt;EAnimPlayState&amp;gt; play_state_list;
};&lt;/pre&gt;
  &lt;p id=&quot;l0R9&quot;&gt;Here we see that the class implements the &lt;strong&gt;GarbageCollected&lt;/strong&gt; interface, and its &lt;strong&gt;Trace&lt;/strong&gt; method triggers tracing of internal objects &lt;code&gt;animation&lt;/code&gt; and &lt;code&gt;style_rule&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;u4PQ&quot;&gt;Tracing JavaScript Objects.&lt;/h3&gt;
  &lt;p id=&quot;AUss&quot;&gt;Unlike C++ objects and the Oilpan system, there are no official publications about JavaScript objects. Therefore, it is common to mistakenly equate tracing of JavaScript objects with C++ objects. In reality, JavaScript objects are fully controlled by the V8 engine and reside in a dedicated V8 heap. This means there is no need for an additional externally managed API implementation. Each JavaScript object extend the HeadObject class, which has Iterate, IterateFast, IterateBody, and IterateBodyFast methods. The heap itself has a Visit method that initiates tracing through objects in this heap (at the time of writing the article, in V8 version &lt;a href=&quot;https://chromium.googlesource.com/v8/v8/+/refs/tags/12.4.147&quot; target=&quot;_blank&quot;&gt;12.4.147&lt;/a&gt;).&lt;/p&gt;
  &lt;p id=&quot;GC5R&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8/+/refs/tags/12.4.147/src/objects/heap-object.h#204&quot; target=&quot;_blank&quot;&gt;src/objects/heap-object.h#204&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;ALj5&quot;&gt;// HeapObject is the superclass for all classes describing heap allocated
// objects.
class HeapObject : public TaggedImpl&amp;lt;HeapObjectReferenceType::STRONG, Address&amp;gt; {
 public:
  
  ...
  
  // Iterates over pointers contained in the object (including the Map).
  // If it&amp;#x27;s not performance critical iteration use the non-templatized
  // version.
  void Iterate(PtrComprCageBase cage_base, ObjectVisitor* v);

  template &amp;lt;typename ObjectVisitor&amp;gt;
  inline void IterateFast(PtrComprCageBase cage_base, ObjectVisitor* v);

  template &amp;lt;typename ObjectVisitor&amp;gt;
  inline void IterateFast(Tagged&amp;lt;Map&amp;gt; map, ObjectVisitor* v);

  template &amp;lt;typename ObjectVisitor&amp;gt;
  inline void IterateFast(Tagged&amp;lt;Map&amp;gt; map, int object_size, ObjectVisitor* v);

  // Iterates over all pointers contained in the object except the
  // first map pointer.  The object type is given in the first
  // parameter. This function does not access the map pointer in the
  // object, and so is safe to call while the map pointer is modified.
  // If it&amp;#x27;s not performance critical iteration use the non-templatized
  // version.
  void IterateBody(PtrComprCageBase cage_base, ObjectVisitor* v);
  void IterateBody(Tagged&amp;lt;Map&amp;gt; map, int object_size, ObjectVisitor* v);

  template &amp;lt;typename ObjectVisitor&amp;gt;
  inline void IterateBodyFast(PtrComprCageBase cage_base, ObjectVisitor* v);

  template &amp;lt;typename ObjectVisitor&amp;gt;
  inline void IterateBodyFast(Tagged&amp;lt;Map&amp;gt; map, int object_size,
                            ObjectVisitor* v);
  
  ...
} &lt;/pre&gt;
  &lt;h2 id=&quot;8XvU&quot;&gt;Generations of Garbage Collection&lt;/h2&gt;
  &lt;p id=&quot;haGn&quot;&gt;The procedure of finding dead objects and cleaning them up can be quite slow. In modern applications, memory can contain thousands of objects, with sizes exceeding &lt;code&gt;1 Gb&lt;/code&gt;. When it comes to C++ object collection, work can be offloaded to separate threads and executed on-demand. However, when we talk about JavaScript, being single-threaded and without a mechanism for synchronization between threads, performing a full object tracing on a regular basis will likely lead to delays and reduced performance. From this point on, we will discuss the implementation of garbage collection specifically for JavaScript objects.&lt;/p&gt;
  &lt;p id=&quot;sKvm&quot;&gt;To avoid system overload and conduct the garbage collection process unnoticed by the user, the V8 team decided to break down the process into parts and work on each of them separately.&lt;/p&gt;
  &lt;p id=&quot;oiUf&quot;&gt;There is a hypothesis called the &lt;a href=&quot;https://www.memorymanagement.org/glossary/g.html#term-generational-hypothesis&quot; target=&quot;_blank&quot;&gt;infant mortality or generational hypothesis&lt;/a&gt;, according to which, in most cases, young objects are more likely to die sooner than old ones.&lt;/p&gt;
  &lt;p id=&quot;92RP&quot;&gt;Thus, in V8, the concept of &lt;strong&gt;generation&lt;/strong&gt; was introduced. There are the &lt;strong&gt;young generation&lt;/strong&gt; and the &lt;strong&gt;old generation&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;OjP0&quot;&gt;The heap is conventionally divided into small &lt;strong&gt;young generations&lt;/strong&gt; (up to &lt;code&gt;16 Mb&lt;/code&gt;), where all newly allocated objects are placed. The &lt;strong&gt;old generation&lt;/strong&gt; is intended for old objects and can be up to &lt;code&gt;1.4 Gb&lt;/code&gt; in size.&lt;/p&gt;
  &lt;p id=&quot;9Rou&quot;&gt;Additionally, both generations are organized into so-called &lt;strong&gt;pages&lt;/strong&gt; of &lt;code&gt;1 Mb&lt;/code&gt; each. Objects larger than &lt;code&gt;600 Kb&lt;/code&gt; are placed in separate pages and considered part of the &lt;strong&gt;old generation&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;e25Q&quot;&gt;The &lt;strong&gt;old generation&lt;/strong&gt; also includes &lt;strong&gt;code space&lt;/strong&gt; with all executable code objects and &lt;strong&gt;map space&lt;/strong&gt; with involved hidden classes.&lt;br /&gt;&lt;/p&gt;
  &lt;h2 id=&quot;DOWs&quot;&gt;Semi-space Objects Allocation in Young Generation&lt;/h2&gt;
  &lt;p id=&quot;9Dn3&quot;&gt;To move forward and start understanding the process of removing objects from memory, let&amp;#x27;s first grasp how these objects are stored in memory.&lt;/p&gt;
  &lt;p id=&quot;MuQh&quot;&gt;I have already mentioned that JavaScript operates in a single thread. It does not require synchronization, and each JavaScript context gets its own personal heap. The entire subsequent algorithm will be built based on these facts and the need to fit all processes into a single thread.&lt;/p&gt;
  &lt;p id=&quot;VZAr&quot;&gt;So, the &lt;strong&gt;young generation&lt;/strong&gt; is divided into two &lt;strong&gt;semi-spaces&lt;/strong&gt;, active and inactive. All new objects are initially placed in the current active semi-space using the &lt;strong&gt;bump-pointer&lt;/strong&gt; method.&lt;/p&gt;
  &lt;figure id=&quot;v63j&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/74/f0/74f0e4eb-89b7-4dfd-a90d-ed64d0be11ed.png&quot; width=&quot;250.31034482758622&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;jQhg&quot;&gt;The method involves always having a pointer in the heap to the current free area. When creating a new object, this pointer will be the beginning of the object. The end of the object is determined simply by calculating its size. Having determined the size and, consequently, the end of the object, the pointer is shifted to the next free address.&lt;/p&gt;
  &lt;p id=&quot;BdAo&quot;&gt;Once the semi-space is fully occupied, the &lt;strong&gt;Scavenger&lt;/strong&gt; mechanism kicks in. Its task is to go through the live objects and move them to the new, inactive semi-space. This operation is called &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;wp4Y&quot;&gt;After that, the two semi-spaces swap places. The current active semi-space becomes inactive, and the inactive one is cleared and becomes active. Therefore, starting from the second iteration, there might still be references to live objects in the inactive semi-space. During the next execution of &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt;, if the objects have already been transferred to the second semi-space once, they are considered old and moved to the &lt;strong&gt;old generation&lt;/strong&gt;, while the dead ones are deleted.&lt;/p&gt;
  &lt;figure id=&quot;Q6Qx&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/29/42/29421987-096f-4a1d-b978-f025f7e98552.png&quot; width=&quot;1400&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7cdr&quot;&gt;The duration of &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt; depends on the number of live objects in the &lt;strong&gt;young generation&lt;/strong&gt;. If all objects are dead, the process will take less than &lt;code&gt;1 ms&lt;/code&gt;. However, if all or most objects are alive, it will take significantly longer.&lt;/p&gt;
  &lt;h2 id=&quot;ZCaU&quot;&gt;Old generation&lt;/h2&gt;
  &lt;p id=&quot;mAtG&quot;&gt;The &lt;strong&gt;old generation&lt;/strong&gt; also uses the &lt;strong&gt;bump-pointer&lt;/strong&gt; method for objects allocation, with pointers stored in separate summary tables, similar to what I mentioned at the beginning of the article.&lt;/p&gt;
  &lt;p id=&quot;E2RM&quot;&gt;This generation is cleaned up by another process called &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; when the size of live objects in the &lt;strong&gt;old generation&lt;/strong&gt; exceeds a heuristically calculated limit.&lt;/p&gt;
  &lt;p id=&quot;CSki&quot;&gt;To reduce latency and memory consumption, the &lt;strong&gt;old generation&lt;/strong&gt; uses the &lt;strong&gt;mark-sweep-compactor&lt;/strong&gt; method. The delay during marking depends on the number of live objects that need to be marked. Marking the entire heap can take up to &lt;code&gt;100 ms&lt;/code&gt; for large web pages. To prevent this, V8 marks objects in small portions so that each step does not exceed &lt;code&gt;5 ms&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;ioqt&quot;&gt;The scheme itself is called the &lt;strong&gt;tricolor marking scheme&lt;/strong&gt;. More detailed information on the marking process can be found in the post &lt;a href=&quot;https://v8.dev/blog/concurrent-marking&quot; target=&quot;_blank&quot;&gt;Concurrent marking in V8&lt;/a&gt; in the V8 blog. Again, retelling the entire post is meaningless. I will outline the general essence of the process. Each object is in one of three statuses, symbolically indicated by colors. In reality, the object&amp;#x27;s status is a 2-bit field, where:&lt;/p&gt;
  &lt;ul id=&quot;HgKp&quot;&gt;
    &lt;li id=&quot;vBSy&quot;&gt;&lt;code&gt;00&lt;/code&gt; - &lt;strong&gt;white&lt;/strong&gt; - the initial status of all new objects. It means that the object has not been detected by the garbage collector yet.&lt;/li&gt;
    &lt;li id=&quot;yLU2&quot;&gt;&lt;code&gt;01&lt;/code&gt; - &lt;strong&gt;gray&lt;/strong&gt; - the object transitions to this status as soon as the collector reaches it. Such an object is placed on the list for further tracing.&lt;/li&gt;
    &lt;li id=&quot;G3q2&quot;&gt;&lt;code&gt;11&lt;/code&gt; - &lt;strong&gt;black&lt;/strong&gt; - the final status, indicating that the collector has visited all child nodes of the object, and it can be removed from the tracing list.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;XFUq&quot;&gt;This scheme allows marking tasks to be queued in parts without blocking the main thread for a long time. The process will be completed when the list of objects for tracing becomes empty.&lt;/p&gt;
  &lt;p id=&quot;PV5W&quot;&gt;However, there is one problem here. Between marking steps, JavaScript code is executed, which can add new objects or remove old ones. This makes the status of already marked objects irrelevant. To solve this problem, the engine must notify the collector of all changes in the objects tree. This is done through a so-called &lt;strong&gt;write barrier&lt;/strong&gt;. An example of implementing such a barrier is provided in the V8 blog.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;7915&quot;&gt;// Called after &amp;#x60;object.field = value&amp;#x60;.
write_barrier(object, field_offset, value) {
  if (color(object) == black &amp;amp;&amp;amp; color(value) == white) {
    set_color(value, grey);
    marking_worklist.push(value);
  }
}&lt;/pre&gt;
  &lt;p id=&quot;JNZ8&quot;&gt;Every time a new object is added to an existing one, a barrier function is triggered which checks if the parent has already been marked. In case the child object is not associated with anything yet, the parent will be returned to the tracing list, and the object itself will immediately transition to the gray status.&lt;/p&gt;
  &lt;p id=&quot;8LhL&quot;&gt;This code is no longer relevant these days, of course. Barriers are much more complex and look somewhat different now, but the essence remains the same. There are various implementation options for different scenarios. For a more complete picture, I will provide a code of real V8 barrier.&lt;/p&gt;
  &lt;p id=&quot;G3sz&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/v8/v8/+/refs/tags/12.4.147/src/heap/marking-barrier-inl.h#64&quot; target=&quot;_blank&quot;&gt;src/heap/marking-barrier-inl.h#64&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;8K94&quot;&gt;void MarkingBarrier::MarkValueShared(Tagged&amp;lt;HeapObject&amp;gt; value) {
  // Value is either in read-only space or shared heap.
  DCHECK(InAnySharedSpace(value));
  // We should only reach this on client isolates (= worker isolates).
  DCHECK(!is_shared_space_isolate_);
  DCHECK(shared_heap_worklist_.has_value());
  // Mark shared object and push it onto shared heap worklist.
  if (marking_state_.TryMark(value)) {
    shared_heap_worklist_-&amp;gt;Push(value);
  }
}

void MarkingBarrier::MarkValueLocal(Tagged&amp;lt;HeapObject&amp;gt; value) {
  DCHECK(!InReadOnlySpace(value));
  if (is_minor()) {
    // We do not need to insert into RememberedSet&amp;lt;OLD_TO_NEW&amp;gt; here because the
    // C++ marking barrier already does this for us.
    // TODO(v8:13012): Consider updating C++ barriers to respect
    // POINTERS_TO_HERE_ARE_INTERESTING and POINTERS_FROM_HERE_ARE_INTERESTING
    // page flags and make the following branch a DCHECK.
    if (Heap::InYoungGeneration(value)) {
      WhiteToGreyAndPush(value);  // NEW-&amp;gt;NEW
    }
  } else {
    if (WhiteToGreyAndPush(value)) {
      if (V8_UNLIKELY(v8_flags.track_retaining_path)) {
        heap_-&amp;gt;AddRetainingRoot(Root::kWriteBarrier, value);
      }
    }
  }
}

...

bool MarkingBarrier::WhiteToGreyAndPush(Tagged&amp;lt;HeapObject&amp;gt; obj) {
  if (marking_state_.TryMark(obj)) {
    current_worklist_-&amp;gt;Push(obj);
    return true;
  }
  return false;
}&lt;/pre&gt;
  &lt;p id=&quot;SVy3&quot;&gt;After the entire object graph has been marked, those objects reached by the garbage collector are marked with a black status. All others remain white. As with &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt;, live objects are moved to a new &lt;strong&gt;semi-space&lt;/strong&gt; or a new &lt;strong&gt;old generation page&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;AloH&quot;&gt;The duration of &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; is linear and depends on the number of live objects in the &lt;strong&gt;old generation&lt;/strong&gt;. Using an incremental garbage collection algorithm, V8 tries to keep the &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; execution time within &lt;code&gt;6 ms&lt;/code&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;BiUq&quot;&gt;Page fragmentation&lt;/h2&gt;
  &lt;p id=&quot;yEEo&quot;&gt;After marking, the collector can assess the level of fragmentation of the page. Empty areas appear between live objects after cleaning. Depending on which and how many objects are cleared, the final page can end up with different level of fragmentation.&lt;/p&gt;
  &lt;figure id=&quot;2znI&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/57/4f/574fb93b-1e09-44f2-a631-13003158ece3.png&quot; width=&quot;1400&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dcWa&quot;&gt;The examples of highly fragmented (left) and lowly fragmented (right) pages are shown in the image above. The V8 engine evaluates the degree of fragmentation and decides what to do next with the page.&lt;/p&gt;
  &lt;h3 id=&quot;u1M9&quot;&gt;Compaction&lt;/h3&gt;
  &lt;p id=&quot;43zQ&quot;&gt;In case of high fragmentation, the collector copies live objects to another page that has not yet been compacted, concurrently shifting them, getting rid of empty areas. This process is called &lt;strong&gt;compaction&lt;/strong&gt;. As a result, a defragmented memory area with live objects is obtained. Dead objects are cleared along with the old semi-space. However, there are often many long-living objects in memory. Compacting each page may be too costly. Therefore, in the case of low fragmentation, the collector takes a different path.&lt;/p&gt;
  &lt;h3 id=&quot;8LqV&quot;&gt;Sweeping&lt;/h3&gt;
  &lt;p id=&quot;qztS&quot;&gt;With low fragmentation, relatively large areas remain between live objects. Instead of copying and rearranging objects, the scheduler simply puts addresses of merged dead regions into a special &lt;strong&gt;free-list&lt;/strong&gt;. Later, when the engine needs to allocate space for a new object in memory, it first checks the &lt;strong&gt;free-list&lt;/strong&gt;, and if there is a suitable space, it will place the new object there. This process is called &lt;strong&gt;sweeping&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;pp7I&quot;&gt;Idle Tasks Scheduling&lt;/h2&gt;
  &lt;p id=&quot;kfxL&quot;&gt;In the article &lt;a href=&quot;https://blog.frontend-almanac.com/chromium-rendering&quot; target=&quot;_blank&quot;&gt;Chromium. Web page rendering using Blink, CC and scheduler&lt;/a&gt;, I have described in detail the process of rendering web pages and task scheduling. Let&amp;#x27;s briefly recall what was discussed.&lt;/p&gt;
  &lt;p id=&quot;MfHV&quot;&gt;The Blink engine initiates the CC process in a separate compositor thread. CC receives signals from various Chromium systems and decides when and what to display on the screen. For instance, when an animation or scrolling is initiated, CC sends a signal to the main thread indicating the start of a new frame. Along with this signal, a deadline is passed – the time by which the frame must be generated. Since Chromium aims to maintain a frame rate of around &lt;code&gt;60 FPS&lt;/code&gt;, each frame lasts approximately &lt;code&gt;16.6 ms (1000/60)&lt;/code&gt;. Blink has exactly this amount of time to sequentially perform tasks like processing user input, executing JavaScript code, handling other priority tasks, and updating the RenderTree. If Blink exceeds the specified time, the frame will be delayed, impacting the visual user experience (resulting in animation stuttering, flickering, etc.). However, in many cases, the engine requires much less time. With a small number of tasks, Chromium can handle a frame in less than &lt;code&gt;1 ms&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;7gm4&quot;&gt;The remaining unused time is called &lt;strong&gt;idle time&lt;/strong&gt;. However, &lt;strong&gt;idle time&lt;/strong&gt; is not really idle. It is a task, just like the other tasks in the queue, but with a low priority. The duration of this task is equal to the time remaining until the next frame is formed. If there is no next frame expected, idle time is set to &lt;code&gt;50 ms&lt;/code&gt;. This number was chosen based on research showing that a user perceives a response to user input within &lt;code&gt;100 ms&lt;/code&gt; as instantaneous. If no new frames appear after &lt;code&gt;50 ms&lt;/code&gt;, a new &lt;strong&gt;idle time&lt;/strong&gt; is queued and so on. However, if a signal is received to start a new frame, &lt;strong&gt;idle time&lt;/strong&gt; will be interrupted without waiting for the time to end.&lt;/p&gt;
  &lt;p id=&quot;PB3I&quot;&gt;When idle time starts, tasks from a special low-priority queue can begin to be executed. These tasks are called &lt;strong&gt;idle tasks&lt;/strong&gt;. To avoid exceeding the deadline, the end time of the &lt;strong&gt;idle time&lt;/strong&gt; is being sent to the task. The responsibility of the task itself is to be completed by the specified deadline. The task can perform part of the work within the allotted time, and the remaining part can be queued as a new task. Similarly, if a task cannot complete any significant work within the specified time, it should reposition itself in the queue as a new task.&lt;/p&gt;
  &lt;p id=&quot;b6Os&quot;&gt;&lt;strong&gt;Idle tasks&lt;/strong&gt; are not only the prerogative of the V8 engine. This mechanism is also available to web developers. Web browsers provide a method through the Web API - &lt;a href=&quot;https://w3c.github.io/requestidlecallback/#the-requestidlecallback-method&quot; target=&quot;_blank&quot;&gt;requestIdleCallback&lt;/a&gt;, which queues a task in the &lt;strong&gt;idle tasks&lt;/strong&gt;. However, at the time of writing this article, the method is still in the process of development and is not supported by the Safari browser.&lt;/p&gt;
  &lt;h2 id=&quot;fA7d&quot;&gt;Idle Time Garbage Collection Scheduling&lt;/h2&gt;
  &lt;p id=&quot;eo1g&quot;&gt;Earlier, we talked about how the garbage collection process can be quite intensive. In order not to block the system with this utility operation, V8 breaks down the entire process into small steps, with each step potentially taking up to &lt;code&gt;5 ms&lt;/code&gt; of CPU time. To make the process as unobtrusive as possible for the rest of the system, V8 queues garbage collection tasks specifically as idle tasks.&lt;/p&gt;
  &lt;p id=&quot;YQXX&quot;&gt;&lt;strong&gt;Minor Garbage Collection&lt;/strong&gt; cannot be broken into parts and must be completed in a single try.&lt;/p&gt;
  &lt;p id=&quot;jjpT&quot;&gt;&lt;strong&gt;Major Garbage Collection&lt;/strong&gt; consists of three parts:&lt;/p&gt;
  &lt;ul id=&quot;xMZb&quot;&gt;
    &lt;li id=&quot;cRvv&quot;&gt;Start of the incremental marking&lt;/li&gt;
    &lt;li id=&quot;Suh4&quot;&gt;Multiple marking steps&lt;/li&gt;
    &lt;li id=&quot;yORl&quot;&gt;Finalization&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;GvqK&quot;&gt;Each of the phases has its own metrics for latency and memory impact. While the start of incremental marking phase itself is a fast operation, it leads to multiple marking steps and finalization, which can cause long pauses. Therefore, the start of marking can lead to worsened latency but has a significant impact on memory consumption metrics.&lt;/p&gt;
  &lt;p id=&quot;92bI&quot;&gt;Launching &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt; reduces latency but has a relatively minor impact on memory consumption. However, regular execution of &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt; helps prevent objects from entering the phase of conservative garbage collection, which is performed during &lt;strong&gt;non-idle time&lt;/strong&gt;. Conservative garbage collection can be initiated in critical situations, such as when a lack of allocated memory is detected.&lt;/p&gt;
  &lt;p id=&quot;OBLR&quot;&gt;Therefore, the scheduler should balance between starting too early (resulting in prematurely promoting objects to the &lt;strong&gt;old generation&lt;/strong&gt;) and starting too late, which can lead to the &lt;strong&gt;young generation&lt;/strong&gt; size growing excessively.&lt;/p&gt;
  &lt;h3 id=&quot;zdRt&quot;&gt;Minor Garbage Collection Idle Time Scheduling&lt;/h3&gt;
  &lt;p id=&quot;6Tfv&quot;&gt;In order to implement &lt;strong&gt;idle time Minor Garbage Collection&lt;/strong&gt; the following is needed:&lt;/p&gt;
  &lt;ol id=&quot;wtOJ&quot;&gt;
    &lt;li id=&quot;H3YO&quot;&gt;a predicate that tells whether to perform Minor Garbage Collection or not, given the idle task’s deadline and the state of the new generation&lt;/li&gt;
    &lt;li id=&quot;UzJr&quot;&gt;a mechanism to post an idle task on the Blink scheduler that performs a Minor Garbage Collection at an appropriate time&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;s4OC&quot;&gt;V8 performs &lt;strong&gt;idle time Minor Garbage Collection&lt;/strong&gt; only if its estimated time fits within the given idle task’s deadline and there are enough objects allocated in the &lt;strong&gt;young generation&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;9v32&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;TLtg&quot;&gt;Let &lt;code&gt;H&lt;/code&gt; be the total size in bytes of objects in the &lt;strong&gt;young generation&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;Mj1x&quot;&gt;&lt;code&gt;S&amp;#x27;&lt;/code&gt; be the average speed of previously observed mi- nor garbage collection in bytes per second&lt;/p&gt;
  &lt;p id=&quot;ye4r&quot;&gt;&lt;code&gt;T&lt;/code&gt; be the current idle task deadline in seconds&lt;/p&gt;
  &lt;p id=&quot;O7Eu&quot;&gt;&lt;code&gt;T&amp;#x27;&lt;/code&gt; be the average idle task deadline in seconds&lt;/p&gt;
  &lt;p id=&quot;Vk53&quot;&gt;V8 performs &lt;strong&gt;Minor Garbage Collection&lt;/strong&gt; if:&lt;/p&gt;
  &lt;pre data-lang=&quot;haskell&quot; id=&quot;2qnp&quot;&gt;max(T&amp;#x27; * S&amp;#x27; - N, Hmin) &amp;lt; H &amp;lt;= S&amp;#x27; * T&lt;/pre&gt;
  &lt;p id=&quot;oLGA&quot;&gt;where, &lt;code&gt;N&lt;/code&gt; is the estimated number of bytes that will be allocated before the next idle task&lt;/p&gt;
  &lt;p id=&quot;qaNM&quot;&gt;&lt;code&gt;Hmin&lt;/code&gt; is the minimum &lt;strong&gt;young generation&lt;/strong&gt; size that warrants garbage collection.&lt;/p&gt;
  &lt;p id=&quot;wk3g&quot;&gt;The &lt;code&gt;(T&amp;#x27; * S&amp;#x27; - N) &amp;lt; H&lt;/code&gt; condition can be rewritten as &lt;code&gt;(T&amp;#x27; * S&amp;#x27;) &amp;lt; (H + N)&lt;/code&gt;, чwhich estimates if the next &lt;strong&gt;idle time Minor Garbage Collection&lt;/strong&gt; would overrun the average idle task deadline. If it does then V8 needs to perform &lt;strong&gt;idle time Minor Garbage Collection&lt;/strong&gt; in the current idle task. If not, in the &lt;strong&gt;young generation&lt;/strong&gt; there is no enough objects for effective work.&lt;/p&gt;
  &lt;p id=&quot;4x8Z&quot;&gt;In order to request idle time for Minor Garbage Collection from the Blink scheduler, V8 puts allocation bailout markers at every &lt;code&gt;N&lt;/code&gt; bytes of the new generation. When an allocation from the JavaScript code crosses an allocation bailout marker, the allocation takes the slow path and invokes a V8 runtime function that posts an idle task to the Blink scheduler.&lt;/p&gt;
  &lt;p id=&quot;3McP&quot;&gt;Through experimentation, it has been established that setting &lt;code&gt;N = 512 Kb&lt;/code&gt; is sufficient to maintain throughput without compromising performance significantly. This value is low enough to enable V8 to have several opportunities to schedule idle tasks.&lt;/p&gt;
  &lt;h3 id=&quot;KKEg&quot;&gt;Major Garbage Collection Idle Time Scheduling&lt;/h3&gt;
  &lt;p id=&quot;aLDu&quot;&gt;The process of launching the incremental marking will be discussed in more detail later. For now, let&amp;#x27;s assume that it is already running. Immediately upon initialization, V8 inserts an &lt;strong&gt;idle task&lt;/strong&gt; into the Blink scheduler. This task directly handles the marking process.&lt;/p&gt;
  &lt;p id=&quot;FW3c&quot;&gt;Based on the average marking speed, the idle task aims to achieve the maximum amount of work possible during the current idle period.&lt;/p&gt;
  &lt;p id=&quot;12yM&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4Cay&quot;&gt;Let &lt;code&gt;Tidle&lt;/code&gt; be the deadline in seconds of the idle task,&lt;/p&gt;
  &lt;p id=&quot;jUnZ&quot;&gt;&lt;code&gt;M&lt;/code&gt; be the mark- ing speed in byte per second.&lt;/p&gt;
  &lt;p id=&quot;Ovy8&quot;&gt;Then,&lt;/p&gt;
  &lt;p id=&quot;W6WM&quot;&gt;&lt;code&gt;Tidle * M&lt;/code&gt; is a number of bytes will be marked in the idle task.&lt;/p&gt;
  &lt;p id=&quot;QH4X&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;vArI&quot;&gt;The &lt;strong&gt;idle task&lt;/strong&gt; iteratively requeues itself until the entire heap is marked. Afterward, V8 schedules the task to finalize &lt;strong&gt;Major Garbage Collection&lt;/strong&gt;. The finalization process will be executed if the estimated time for this task meets the deadline. V8 determines the finalization time based on the speed of previous finalizations.&lt;/p&gt;
  &lt;h2 id=&quot;T9jn&quot;&gt;Memory Reducer&lt;/h2&gt;
  &lt;p id=&quot;cwAK&quot;&gt;&lt;strong&gt;Memory reducer&lt;/strong&gt; is a controller for scheduling Major Garbage Collections which tries to reclaim memory for inactive webpages during idle time.&lt;/p&gt;
  &lt;p id=&quot;r9tm&quot;&gt;The &lt;strong&gt;Major Garbage Collector&lt;/strong&gt; starts working when the heap reaches a certain predefined size. This size is calculated and set at the end of the previous &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; based on the heap growing factor &lt;code&gt;f&lt;/code&gt; and the total size of live objects in the &lt;strong&gt;old generation&lt;/strong&gt;.&lt;/p&gt;
  &lt;pre id=&quot;DjKM&quot;&gt;limit&amp;#x27; = f * size&lt;/pre&gt;
  &lt;p id=&quot;ko2o&quot;&gt;The next &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; is queued when the number of bytes allocated since the previous collection exceeds this threshold limit.&lt;/p&gt;
  &lt;p id=&quot;lROy&quot;&gt;Everything is fine as long as the web page steadily allocates memory. However, if the web page becomes idle, memory allocation stops, and thus, the set limit will not be reached, and &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; will not be triggered.&lt;/p&gt;
  &lt;p id=&quot;20n6&quot;&gt;Often, many web pages demonstrate high memory allocation intensity at the moment of loading because they initialize their internal data structures. A few seconds or minutes after loading, the web page often becomes inactive, holding a large number of unnecessary objects in memory. This can lead to a decrease in memory allocation speed and, consequently, a decrease in the speed of executing JavaScript code.&lt;/p&gt;
  &lt;figure id=&quot;FKj6&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/70/81/7081977d-a82c-4d17-8d4b-5514222a46de.png&quot; width=&quot;2048&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3Bwh&quot;&gt;The diagram above shows an example of &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; scheduling. The first collection occurs at time &lt;code&gt;t1&lt;/code&gt;, as the heap size limit is reached. V8 sets a new limit based on the heap growing coefficient and the heap size. The next call for &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; will happen at time &lt;code&gt;t2&lt;/code&gt; when the new limit is reached, then at time &lt;code&gt;t3&lt;/code&gt;, and so on. The dotted line represents the heap size without using of the &lt;strong&gt;memory reducer&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;CWoG&quot;&gt;In this way, the &lt;strong&gt;memory reducer&lt;/strong&gt; can trigger &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; without waiting for the allocation limit to be reached. However, uncontrolled reduction of the limit can lead to deteriorating latency performance. Therefore, the V8 team has developed a heuristic method that relies not only on the &lt;strong&gt;idle time&lt;/strong&gt; provided by the Blink scheduler between frames but also on whether the page is currently inactive. The indicators of page activity are the JavaScript allocation speed and the rate of JavaScript calls from the browser. When both speeds drop below a specified threshold, the page is considered inactive.&lt;/p&gt;
  &lt;figure id=&quot;Mbxu&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/58/a3/58a3df4b-efce-42b7-91ec-bf1a9fb50f10.png&quot; width=&quot;1712&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;e8a1&quot;&gt;The diagram illustrates the states and transitions of the &lt;strong&gt;memory reducer&lt;/strong&gt;. In the &lt;code&gt;done&lt;/code&gt; state, the controller waits for a signal indicating that a sufficient number of allocations have been made to guarantee the initiation of garbage collection. At this point, a so-called &lt;strong&gt;non-idle Major Garbage Collection&lt;/strong&gt; is triggered upon reaching the allocation limit, signaling a transition to the &lt;code&gt;wait(i)&lt;/code&gt; state. In this state, the controller waits for the web page to become inactive. Once this happens, the incremental marking process is initiated, transitioning to the &lt;code&gt;run(i)&lt;/code&gt; state until the &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; completes its operation. If subsequent calculations suggest that another &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; could likely free up more memory, the controller moves to the &lt;code&gt;wait(i+1)&lt;/code&gt; state. Otherwise, it returns to the initial &lt;code&gt;done&lt;/code&gt; state.&lt;/p&gt;
  &lt;p id=&quot;nXns&quot;&gt;The key part of the controller is a well-tuned allocation rate threshold. Initially, this threshold was of a fixed value. Overall, this approach worked well on desktop devices. However, on slower systems, such as mobile devices, it did not work. Therefore, developers had to implement dynamic adaptation for different hardware configurations.&lt;/p&gt;
  &lt;p id=&quot;xWTA&quot;&gt;Let &lt;code&gt;g&lt;/code&gt; is the &lt;strong&gt;Major Garbage Collection&lt;/strong&gt; speed,&lt;/p&gt;
  &lt;p id=&quot;Ehdz&quot;&gt;&lt;code&gt;a&lt;/code&gt; is the allocation rate.&lt;/p&gt;
  &lt;p id=&quot;Gz8q&quot;&gt;Then,&lt;/p&gt;
  &lt;pre id=&quot;s8zB&quot;&gt;μ = g/(g+a)&lt;/pre&gt;
  &lt;p id=&quot;rX7T&quot;&gt;This ratio &lt;code&gt;μ&lt;/code&gt; can be thought of as the mutator utilization for the time window from now until the next &lt;strong&gt;Major Garbage Collection&lt;/strong&gt;, under the assumption that the current allocation rate stays constant and the heap is currently empty:&lt;/p&gt;
  &lt;pre id=&quot;0EPx&quot;&gt;Tmu = limit/a               (mutator time)

  Tgc = limit/g             (GC time)
  
    μ = Tmu /(Tmu + Tgc )   (mutator utilization)
    
      = (limit /a)/(limit /a + limit /g)
      
      = g/(g + a)&lt;/pre&gt;
  &lt;p id=&quot;FTR5&quot;&gt;This gives us the condition for inactive allocation rate: &lt;code&gt;μ ≥ μinactive&lt;/code&gt; , where &lt;code&gt;μinactive&lt;/code&gt; is a fixed constant.&lt;/p&gt;
  &lt;p id=&quot;TcjW&quot;&gt;In V8 code, &lt;code&gt;µinactive&lt;/code&gt; has a value of &lt;code&gt;0.993&lt;/code&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;RMS0&quot;&gt;bool Heap::HasLowOldGenerationAllocationRate() {
  double mu = ComputeMutatorUtilization(
      &amp;quot;Old generation&amp;quot;,
      tracer()-&amp;gt;OldGenerationAllocationThroughputInBytesPerMillisecond(),
      tracer()-&amp;gt;CombinedMarkCompactSpeedInBytesPerMillisecond());
  const double kHighMutatorUtilization = 0.993;
  return mu &amp;gt; kHighMutatorUtilization;
}&lt;/pre&gt;
  &lt;p id=&quot;5eFp&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;7pWR&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4FEw&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/v8-garbage-collection&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/v8-garbage-collection&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:chromium-rendering</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/chromium-rendering?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>Chromium. Web page rendering using Blink, CC and scheduler</title><published>2024-02-27T22:29:51.055Z</published><updated>2024-03-09T16:39:47.787Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/c7/9a/c79a1c19-768d-46be-b96a-2d6873c6425b.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/ac/84/ac84758c-8b7a-4d7d-a0bc-2f46c837c8c8.png&quot;&gt;The Chromium engine from Google consists of a vast number of internal mechanisms, subsystems, and other engines. In this article, we will delve into the process of composing and rendering web pages directly on the screen, as well as get a little closer acquainted with the Blink engine, the composer (or, as it is also called, the content collator), and the task scheduler.</summary><content type="html">
  &lt;p id=&quot;KFhc&quot;&gt;The &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt; engine from Google consists of a vast number of internal mechanisms, subsystems, and other engines. In this article, we will delve into the process of composing and rendering web pages directly on the screen, as well as get a little closer acquainted with the Blink engine, the composer (or, as it is also called, the content collator), and the task scheduler.&lt;/p&gt;
  &lt;h2 id=&quot;5DMX&quot;&gt;Web Page Parsing&lt;/h2&gt;
  &lt;p id=&quot;Pjij&quot;&gt;First of all, let&amp;#x27;s remember how the rendering of a web page actually happens.&lt;/p&gt;
  &lt;p id=&quot;wIqF&quot;&gt;After receiving the HTML document, the browser parses it. Since HTML was originally developed to be compatible with the traditional XML structure, there are no interesting features for us at this stage. As a result of parsing, the browser obtains a hierarchical tree of objects - the &lt;a href=&quot;https://en.wikipedia.org/wiki/Document_Object_Model&quot; target=&quot;_blank&quot;&gt;DOM&lt;/a&gt; (Document Object Model).&lt;/p&gt;
  &lt;p id=&quot;u1J9&quot;&gt;As the browser goes through the structure of the HTML and parses it into the &lt;a href=&quot;https://en.wikipedia.org/wiki/Document_Object_Model&quot; target=&quot;_blank&quot;&gt;DOM&lt;/a&gt;, it encounters elements such as styles and JS scripts (both inline and as remote resources). Such elements require additional processing. JS script is parsed by the JavaScript engine into an &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot; target=&quot;_blank&quot;&gt;AST&lt;/a&gt; structure and then is laid out in memory in the form of internal objects of the engine itself. Styles, on the other hand, are arranged into a cascading &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model&quot; target=&quot;_blank&quot;&gt;CSSOM&lt;/a&gt; tree. Inline styles of elements will also be added to this tree.&lt;/p&gt;
  &lt;p id=&quot;No92&quot;&gt;Having obtained the &lt;a href=&quot;https://en.wikipedia.org/wiki/Document_Object_Model&quot; target=&quot;_blank&quot;&gt;DOM&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model&quot; target=&quot;_blank&quot;&gt;CSSOM&lt;/a&gt;, the browser now has the possibility to perform all the necessary calculations for the positioning and display of elements and, as a result, to construct a single Render Tree, based on which the graphics will be rendered directly on the screen.&lt;/p&gt;
  &lt;h2 id=&quot;EMAr&quot;&gt;Web Page Rendering&lt;/h2&gt;
  &lt;p id=&quot;vi0w&quot;&gt;At present, most browsers are multi-threaded, and browsers based on the &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt; engine are no exception.&lt;/p&gt;
  &lt;h3 id=&quot;iL4N&quot;&gt;Blink&lt;/h3&gt;
  &lt;p id=&quot;pNsc&quot;&gt;A separate system called &lt;strong&gt;Blink&lt;/strong&gt; is responsible for all content rendering in the browser tab. Overall, &lt;strong&gt;Blink&lt;/strong&gt; is a large and complex engine that includes functions such as implementing the &lt;a href=&quot;https://html.spec.whatwg.org&quot; target=&quot;_blank&quot;&gt;HTML specification&lt;/a&gt; in terms of DOM, CSS, and Web IDL, integrating the &lt;a href=&quot;https://v8.dev&quot; target=&quot;_blank&quot;&gt;V8&lt;/a&gt; engine and running JavaScript code, rendering graphics on the screen (via the &lt;a href=&quot;https://en.wikipedia.org/wiki/Skia_Graphics_Engine&quot; target=&quot;_blank&quot;&gt;Skia&lt;/a&gt; engine), requesting network resources, handling input operations, building trees (DOM, CSSOM, Render Tree), calculating styles and positioning, and much more, including the &lt;strong&gt;Chrome Compositor (CC)&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;PzfH&quot;&gt;The engine itself is not a standalone &amp;quot;out-of-the-box&amp;quot; solution and cannot be launched independently. It is a kind of fork of the WebCore component of the &lt;a href=&quot;https://en.wikipedia.org/wiki/WebKit&quot; target=&quot;_blank&quot;&gt;WebKit&lt;/a&gt; engine.&lt;/p&gt;
  &lt;p id=&quot;x4TA&quot;&gt;&lt;strong&gt;Blink&lt;/strong&gt; is used in platforms such as &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt;, &lt;a href=&quot;https://developer.android.com/reference/android/webkit/WebView&quot; target=&quot;_blank&quot;&gt;Android WebView&lt;/a&gt;, &lt;a href=&quot;https://www.opera.com&quot; target=&quot;_blank&quot;&gt;Opera&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com/edge&quot; target=&quot;_blank&quot;&gt;Microsoft Edge&lt;/a&gt;, and many other Chromium-based browsers.&lt;/p&gt;
  &lt;h3 id=&quot;hwGK&quot;&gt;Chrome Compositor (CC)&lt;/h3&gt;
  &lt;p id=&quot;3i2J&quot;&gt;In the &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt; codebase, this mechanism is located in the &lt;a href=&quot;https://cs.chromium.org/chromium/src/cc/&quot; target=&quot;_blank&quot;&gt;cc/&lt;/a&gt; directory. Historically, the abbreviation &amp;quot;CC&amp;quot; is interpreted as Chrome Compositor, although as of today, it does not function as a compositor itself. Dana Jensens (danakj) even proposed an alternative name - &lt;strong&gt;Content Collator&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;R6fH&quot;&gt;The &lt;strong&gt;CC&lt;/strong&gt; is launched by the &lt;strong&gt;Blink&lt;/strong&gt; engine and can operate in both single-threaded and multi-threaded modes. The operation of the &lt;strong&gt;CC&lt;/strong&gt; requires a separate article; we will not delve into all the mechanics of the system here. Let&amp;#x27;s just note that the main entities operated by the &lt;strong&gt;CC&lt;/strong&gt; are layers and layer trees. These entities are available from the &lt;strong&gt;CC&lt;/strong&gt; as an API. Layers can be of various types, such as picture layers, texture layers, surface layers, and more. The task of the &lt;strong&gt;CC&lt;/strong&gt; client (in our case, &lt;strong&gt;Blink&lt;/strong&gt; serves as the client for the &lt;strong&gt;CC&lt;/strong&gt;) is to construct its layer tree (&lt;strong&gt;LayerTreeHost&lt;/strong&gt;) and inform the &lt;strong&gt;CC&lt;/strong&gt; that it is ready and can be rendered. This approach allows the process of generating the final composition to be atomic.&lt;/p&gt;
  &lt;h3 id=&quot;QNxK&quot;&gt;Fundamental Rendering Scheme&lt;/h3&gt;
  &lt;p id=&quot;u2or&quot;&gt;The &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt; is a multi-threaded engine. For many specific rendering operations, separate threads are allocated. The basic threads, can be considered the &lt;strong&gt;main thread&lt;/strong&gt; and the &lt;strong&gt;compositor thread&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;WNey&quot;&gt;The &lt;strong&gt;main thread&lt;/strong&gt; is the general thread in which &lt;strong&gt;Blink&lt;/strong&gt; operates. It is here that the final &lt;strong&gt;RenderTree&lt;/strong&gt; and &lt;strong&gt;LayerTreeHost&lt;/strong&gt; are composed. Grouped input operations are also processed here, and JavaScript code is executed. After all necessary operations and calculations, &lt;strong&gt;Blink&lt;/strong&gt; notifies the &lt;strong&gt;CC&lt;/strong&gt; that the trees are ready for rendering.&lt;/p&gt;
  &lt;p id=&quot;7Yix&quot;&gt;The &lt;strong&gt;compositor thread&lt;/strong&gt; is responsible for the operation of the &lt;strong&gt;CC&lt;/strong&gt;, i.e. for the scheduling of the rendering tasks and direct drawing on a screen.&lt;/p&gt;
  &lt;figure id=&quot;TmTl&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ac/84/ac84758c-8b7a-4d7d-a0bc-2f46c837c8c8.png&quot; width=&quot;1400&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;sW5N&quot;&gt;Blink strives to display graphics at a rate of &lt;code&gt;60 FPS&lt;/code&gt; (60 frames per second), i.e., one frame should be output to the screen in approximately &lt;code&gt;16.6 ms&lt;/code&gt;. This frame rate is considered optimal for human perception. A lower frequency can lead to junking, juddering, and jittering.&lt;/p&gt;
  &lt;p id=&quot;YKdK&quot;&gt;The simplified rendering scheme is shown in the diagram above. As I mentioned earlier, the &lt;strong&gt;CC&lt;/strong&gt; runs in a separate thread. At a certain point, it decides that it&amp;#x27;s time to initiate frame rendering. The &lt;strong&gt;CC&lt;/strong&gt; signals the main thread to start a new frame. &lt;strong&gt;Blink&lt;/strong&gt; receives the signal in the main thread and performs necessary scheduled operations, such as batch processing of input, execution of JavaScript code (or more precisely, JavaScript tasks from the Event Loop), and updating the &lt;strong&gt;RenderTree&lt;/strong&gt;. Once the &lt;strong&gt;RenderTree&lt;/strong&gt; is ready, the engine makes corresponding changes in the &lt;strong&gt;LayerTreeHost&lt;/strong&gt; (in its temporary copy) and sends a &lt;code&gt;commit&lt;/code&gt; signal to the &lt;strong&gt;CC&lt;/strong&gt;, where it retrieves all calculations and sends tasks to draw graphics on the final device using the APIs of the respective OS graphics libraries, such as &lt;strong&gt;OpenGL&lt;/strong&gt; and &lt;strong&gt;DirectX&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;ndTS&quot;&gt;This entire process is given a time window of &lt;code&gt;1000/60 = ~16.6 ms&lt;/code&gt;. If the engine cannot complete all necessary operations within this time, the frame will be delayed, resulting in a decrease in the frame rate. Therefore, a crucial task for &lt;strong&gt;Blink&lt;/strong&gt; is to calculate and predict the execution time of upcoming tasks. Knowing how long a particular operation will take, the engine can only work on what it can accomplish within the allocated time, deferring the rest of the operations until later.&lt;/p&gt;
  &lt;p id=&quot;2Brx&quot;&gt;There are also operations that do not involve specific calculations and do not use JavaScript, such as scrolling. &lt;strong&gt;CC&lt;/strong&gt; can perform such operations independently in its own thread, thereby not blocking the main thread. Conversely, for example, animations require intensive calculations on the main thread throughout a large number of frames. If the main thread is occupied with other high-priority tasks, a portion of the animation operations may be postponed or delayed.&lt;/p&gt;
  &lt;h2 id=&quot;qPTk&quot;&gt;Task Scheduler&lt;/h2&gt;
  &lt;p id=&quot;a8zn&quot;&gt;The task scheduler is designed to minimize the likelihood of delays in updating the frame. It is launched in the main thread. Each task is placed by the engine mechanisms in one of the queues specific to that type of task. &lt;strong&gt;CC&lt;/strong&gt; tasks go into their own queue, input processing operations go into their own queue, JavaScript code execution goes into its own queue, and processes for loading the page go into their own queue.&lt;/p&gt;
  &lt;p id=&quot;8seV&quot;&gt;Tasks within a queue are executed in the order in which they are placed there (remember the &lt;strong&gt;Event Loop&lt;/strong&gt;). However, the scheduler, which is free to dynamically choose from which queue to execute the next task, chooses the priority of the queue according to its own discretion. When and which priority to set, the scheduler decides based on signals received from multiple different systems. For example, if a page is still loading, network requests and HTML parsing will be given priority. And if a touch event is detected, the scheduler will temporarily increase the priority of input operations for the next &lt;code&gt;100 ms&lt;/code&gt; to correctly recognize possible gestures. It is assumed that during this time interval, the next possible events might include scrolling, tapping, zooming, etc.&lt;/p&gt;
  &lt;p id=&quot;2m2H&quot;&gt;Having full information about the queues and tasks in them, as well as signals from other components, the scheduler can calculate the approximate idle time of the system. Just above, we considered an example of frame rendering. Along with the signal from &lt;strong&gt;CC&lt;/strong&gt; about the start of frame rendering, the estimated time of the next frame is also sent, if the frame is available (&lt;code&gt;+16.6ms&lt;/code&gt;). Knowing if the operations necessary for rendering the frame, input processing, and the JavaScript code to be executed are available, the scheduler can estimate the duration of these tasks. And knowing the time of the next frame, it can also calculate the time of the &lt;strong&gt;idle time&lt;/strong&gt;. In fact, this period is not exactly idle. It can be used to perform a number of low-priority tasks (&lt;strong&gt;idle tasks&lt;/strong&gt;). These tasks are placed in their own queue and executed in portions only after other queues have emptied, and within a limited period of time. In particular, the garbage collector actively utilizes this queue. This is where most of the work on marking dead objects and memory defragmentation takes place. The conservative garbage collection is only triggered with high priority in extreme cases, such as when memory shortage is detected. We will discuss this in more detail in the article &lt;a href=&quot;https://blog.frontend-almanac.com/v8-garbage-collection&quot; target=&quot;_blank&quot;&gt;Garbage Collection in V8&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;nF0A&quot;&gt;Frame Rate Regularity&lt;/h2&gt;
  &lt;p id=&quot;x9og&quot;&gt;I have already mentioned that &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt; aims to achieve a frame rate of 60 FPS. In order to achieve this goal, the engine is equipped with a scheduler, &lt;strong&gt;CC&lt;/strong&gt;, and many other systems. However, in practice, achieving perfect regularity is almost impossible, as there can be a large number of unforeseen situations that can affect the process, both internal (one or more tasks may take longer to complete than estimated by the scheduler) and external (for example, CPU or GPU load from other processes).&lt;/p&gt;
  &lt;figure id=&quot;qrhq&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c2/f9/c2f99aa0-b524-4a47-84c7-20a51fc11987.png&quot; width=&quot;2444&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;P9LU&quot;&gt;This is approximately what the scrolling of the page &lt;a href=&quot;https://www.google.com/chrome&quot; target=&quot;_blank&quot;&gt;https://www.google.com/chrome&lt;/a&gt; looks like in the tracer. Not all animation frames were able to fit into the allocated window of &lt;code&gt;16.6 ms&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;QeuN&quot;&gt;Furthermore, in addition to delays in rendering, some frames may be rejected by the &lt;strong&gt;Blink&lt;/strong&gt; engine itself. This often happens, for example, during animation. There are many reasons for dropping animation frames. &lt;strong&gt;Blink&lt;/strong&gt; supposes about twenty such reasons (at the time of writing this article, the &lt;a href=&quot;https://www.chromium.org/Home/&quot; target=&quot;_blank&quot;&gt;Chromium&lt;/a&gt; version was &lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/124.0.6326.0/&quot; target=&quot;_blank&quot;&gt;124.0.6326.0&lt;/a&gt;).&lt;/p&gt;
  &lt;p id=&quot;BiUB&quot;&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src.git/+/refs/tags/124.0.6326.0/third_party/blink/renderer/core/animation/compositor_animations.h#65&quot; target=&quot;_blank&quot;&gt;/third_party/blink/renderer/core/animation/compositor_animations.h#65&lt;/a&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;cpp&quot; id=&quot;KN0q&quot;&gt;enum FailureReason : uint32_t {
  kNoFailure = 0,
  
  // Cases where the compositing is disabled by an exterior cause.
  kAcceleratedAnimationsDisabled = 1 &amp;lt;&amp;lt; 0,
  kEffectSuppressedByDevtools = 1 &amp;lt;&amp;lt; 1,
  
  // There are many cases where an animation may not be valid (e.g. it is not
  // playing, or has no effect, etc). In these cases we would never composite
  // it in any world, so we lump them together.
  kInvalidAnimationOrEffect = 1 &amp;lt;&amp;lt; 2,
  
  // The compositor is not able to support all setups of timing values; see
  // CompositorAnimations::ConvertTimingForCompositor.
  kEffectHasUnsupportedTimingParameters = 1 &amp;lt;&amp;lt; 3,
  
  // Currently the compositor does not support any composite mode other than
  // &amp;#x27;replace&amp;#x27;.
  kEffectHasNonReplaceCompositeMode = 1 &amp;lt;&amp;lt; 4,
  
  // Cases where the target element isn&amp;#x27;t in a valid compositing state.
  kTargetHasInvalidCompositingState = 1 &amp;lt;&amp;lt; 5,
  
  // Cases where the target is invalid (but that we could feasibly address).
  kTargetHasIncompatibleAnimations = 1 &amp;lt;&amp;lt; 6,
  kTargetHasCSSOffset = 1 &amp;lt;&amp;lt; 7,
  
  // This failure reason is no longer used, as multiple transform-related
  // animations are allowed on the same target provided they target different
  // transform properties (e.g. rotate vs scale).
  kObsoleteTargetHasMultipleTransformProperties = 1 &amp;lt;&amp;lt; 8,
  
  // Cases relating to the properties being animated.
  kAnimationAffectsNonCSSProperties = 1 &amp;lt;&amp;lt; 9,
  kTransformRelatedPropertyCannotBeAcceleratedOnTarget = 1 &amp;lt;&amp;lt; 10,
  kFilterRelatedPropertyMayMovePixels = 1 &amp;lt;&amp;lt; 12,
  kUnsupportedCSSProperty = 1 &amp;lt;&amp;lt; 13,
  
  // This failure reason is no longer used, as multiple transform-related
  // animations are allowed on the same target provided they target different
  // transform properties (e.g. rotate vs scale).
  kObsoleteMultipleTransformAnimationsOnSameTarget = 1 &amp;lt;&amp;lt; 14,
  
  kMixedKeyframeValueTypes = 1 &amp;lt;&amp;lt; 15,
  
  // Cases where the scroll timeline source is not composited.
  kTimelineSourceHasInvalidCompositingState = 1 &amp;lt;&amp;lt; 16,
  
  // Cases where there is an animation of compositor properties but they have
  // been optimized out so the animation of those properties has no effect.
  kCompositorPropertyAnimationsHaveNoEffect = 1 &amp;lt;&amp;lt; 17,
  
  // Cases where we are animating a property that is marked important.
  kAffectsImportantProperty = 1 &amp;lt;&amp;lt; 18,
  
  kSVGTargetHasIndependentTransformProperty = 1 &amp;lt;&amp;lt; 19,
  
  // When adding new values, update the count below *and* add a description
  // of the value to CompositorAnimationsFailureReason in
  // tools/metrics/histograms/enums.xml .
  // The maximum number of flags in this enum (excluding itself). New flags
  // should increment this number but it should never be decremented because
  // the values are used in UMA histograms. It should also be noted that it
  // excludes the kNoFailure value.
  kFailureReasonCount = 20,
};&lt;/pre&gt;
  &lt;p id=&quot;XPlw&quot;&gt;From the entire set, the most common causes are the absence of a visual animation effect (&lt;code&gt;kCompositorPropertyAnimationsHaveNoEffect&lt;/code&gt;), when the result of the animation does not lead to changes in the graphics and therefore does not require redrawing.&lt;/p&gt;
  &lt;p id=&quot;4EzI&quot;&gt;Also, a frame reset can be caused by an unsupported CSS property (&lt;code&gt;kUnsupportedCSSProperty&lt;/code&gt;). This can happen if the engine does not understand how to recalculate a certain property, even if the property itself looks perfectly valid.&lt;/p&gt;
  &lt;pre data-lang=&quot;html&quot; id=&quot;yN25&quot;&gt;&amp;lt;style&amp;gt;
  #block1 {
    animation: expand 1s linear infinite;
  }

  @keyframes expand {
    to {
      height: auto;
    }
  }
&amp;lt;/style&amp;gt;

&amp;lt;div id=&amp;quot;block1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;r3LW&quot;&gt;In the example above, the engine does not know how to calculate the height of the block, as the final value is undefined and cannot be calculated during the animation stage. As a result, the engine will reset the first frame of the animation, and will not even attempt to calculate any further frames, since it makes no sense to do so.&lt;/p&gt;
  &lt;p id=&quot;zGsC&quot;&gt;In the tracer, in this case, we will find a record like this:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;fiQh&quot;&gt;{&amp;quot;args&amp;quot;:{&amp;quot;data&amp;quot;:{&amp;quot;compositeFailed&amp;quot;:8224,&amp;quot;unsupportedProperties&amp;quot;:[&amp;quot;height&amp;quot;]}},&amp;quot;cat&amp;quot;:&amp;quot;blink.animations,...&lt;/pre&gt;
  &lt;p id=&quot;2dKY&quot;&gt;All of this leads to the fact that the actual number of frames rendered per second may be less than 60.&lt;/p&gt;
  &lt;h2 id=&quot;AgMs&quot;&gt;Discrepancy as a Metric for Frame Rate Regularity&lt;/h2&gt;
  &lt;p id=&quot;7av9&quot;&gt;The regularity of frame rendering is important in animation-based applications, in addition to the average frame rate. If an application renders 60 (or close to it) frames per second, but the intervals between frames vary significantly, the user may experience junking, juddering, or jittering. There are numerous methods for evaluating this phenomenon, from simply measuring the longest frame to calculating the divergence in frame lengths. Each method has its advantages, but most only cover a range of cases and do not take into account the temporal order of frames. In particular, they cannot differentiate between situations where two dropped frames are close together and when they are far apart.&lt;/p&gt;
  &lt;p id=&quot;SHIB&quot;&gt;In response to this, Google developers proposed their own method for evaluating frame regularity. The method is based on the discrepancy in the sequence of frame durations, similar to the mathematical method of &lt;a href=&quot;https://en.wikipedia.org/wiki/Monte_Carlo_integration&quot; target=&quot;_blank&quot;&gt;Monte Carlo integration&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;Frgo&quot;&gt;The theoretical basis of the method was presented at the 37th annual ACM SIGPLAN conference in 2016.&lt;/p&gt;
  &lt;p id=&quot;MadK&quot;&gt;The figure below shows an example of the discrepancy of frame regularity.&lt;/p&gt;
  &lt;figure id=&quot;iziN&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/77/59/77596c6c-a058-4b5c-b951-037dada49c3b.png&quot; width=&quot;1188&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;D6Iv&quot;&gt;Each line represents a set of timestamps. The rendered frames are indicated by black dots, while the dropped frames are indicated by white dots. The distance between the dots is &lt;code&gt;1 VSYNC&lt;/code&gt;, which equals &lt;code&gt;16.6 ms&lt;/code&gt; for a 60 Hz refresh rate. The final discrepancies are calculated in terms of VSYNC intervals:&lt;/p&gt;
  &lt;pre id=&quot;94Za&quot;&gt;D(S1) = 1
D(S2) = 2
D(S3) = 2
D(S4) = 25/9
D(S5) = 3&lt;/pre&gt;
  &lt;p id=&quot;zFDP&quot;&gt;In the perfect case (&lt;code&gt;S1&lt;/code&gt;), the discrepancy is equal to the interval between frames (&lt;code&gt;1 VSYNC&lt;/code&gt;). If one of the frames was dropped (&lt;code&gt;S2&lt;/code&gt;), the discrepancy will be equal to the greatest distance between two rendered frames. In the case of &lt;code&gt;S2&lt;/code&gt;, the greatest distance will be between points 2 and 3, equal to &lt;code&gt;2 VSYNC&lt;/code&gt;. The same applies if there are two dropped frames, but far apart from each other (&lt;code&gt;S3&lt;/code&gt;). This is because the method aims to identify the worst-case performance, rather than the average, as reflected in the calculation formulas. Therefore, the method is combined with the average frame duration to differentiate a single dropped frame from a series of repeated dropouts (the last one is obviously worse). In case &lt;code&gt;S4&lt;/code&gt;, we see two dropped frames that are close to each other. Such frames are considered as a single missed area, and the discrepancy here will be &lt;code&gt;25/9 (~2.7) VSYNC&lt;/code&gt;. The situation is even worse in case &lt;code&gt;S5&lt;/code&gt;, as there were no rendered frames between the two dropped frames. The greatest distance between the rendered frames here will be between points 2 and 4, which equals 3 intervals (&lt;code&gt;3 VSYNC&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;4cDD&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;7pWR&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4FEw&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/chromium-rendering&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/chromium-rendering&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>frontend_almanac:iterators</id><link rel="alternate" type="text/html" href="https://blog.frontend-almanac.com/iterators?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=frontend_almanac"></link><title>Iterators in JavaScript</title><published>2024-02-20T17:08:52.741Z</published><updated>2024-02-20T17:08:52.741Z</updated><category term="the-basic-arsenal-of-the-frontend-developer" label="The basic arsenal of the Frontend developer"></category><summary type="html">In this article, we will look at the mechanism of iterators. What they are, how to apply them and how to create your own.</summary><content type="html">
  &lt;p id=&quot;OgKK&quot;&gt;In this article, we will look at the mechanism of iterators. What they are, how to apply them and how to create your own.&lt;/p&gt;
  &lt;h2 id=&quot;HRx2&quot;&gt;Basic terms&lt;/h2&gt;
  &lt;p id=&quot;lb7T&quot;&gt;To understand the iteration mechanism, let&amp;#x27;s take a look at the following interfaces.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;doWQ&quot;&gt;interface Iterable {
  [Symbol.iterator]: () =&amp;gt; Iterator;
}

interface Iterator {
  next: () =&amp;gt; IteratorResult;
  
  return?: (value?: any) =&amp;gt; IteratorResult;
  
  throw?: (exception?: any) =&amp;gt; IteratorResult;
}

interface IteratorResult {
  done?: boolean;
  value?: any;
}&lt;/pre&gt;
  &lt;p id=&quot;oVao&quot;&gt;So, an object is considered &lt;strong&gt;iterable&lt;/strong&gt; if it implements the &lt;a href=&quot;https://tc39.es/ecma262/#sec-iterable-interface&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt; interface. In other words, if it contains the &lt;strong&gt;[Symbol.iterator]&lt;/strong&gt; method - a function that directly returns an iterator object.&lt;/p&gt;
  &lt;p id=&quot;IxlR&quot;&gt;An &lt;strong&gt;iterator&lt;/strong&gt; is a &lt;a href=&quot;https://tc39.es/ecma262/#ordinary-object&quot; target=&quot;_blank&quot;&gt;ordinary&lt;/a&gt; object that contains a &lt;strong&gt;next()&lt;/strong&gt; method. The other two methods in the &lt;strong&gt;Iterator&lt;/strong&gt; interface are not mandatory. The &lt;strong&gt;next()&lt;/strong&gt; method is required to return an &lt;strong&gt;IteratorResult&lt;/strong&gt; object.&lt;/p&gt;
  &lt;p id=&quot;onQ1&quot;&gt;The essence of iterability is that such an object can be traversed with &lt;strong&gt;for..of&lt;/strong&gt; and &lt;strong&gt;for..in&lt;/strong&gt; loops.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;jO9n&quot;&gt;for (const item of [1, 2, 3]) {
    console.log(item);
}
// 1
// 2
// 3&lt;/pre&gt;
  &lt;h2 id=&quot;2JCn&quot;&gt;Built-in iterators&lt;/h2&gt;
  &lt;p id=&quot;MDX1&quot;&gt;The iteration mechanism is one of the most widely used in JavaScript. The language is literally riddled with embedded iterable objects. These include: &lt;a href=&quot;https://tc39.es/ecma262/#array-exotic-object&quot; target=&quot;_blank&quot;&gt;Array&lt;/a&gt;, &lt;a href=&quot;https://tc39.es/ecma262/#typedarray&quot; target=&quot;_blank&quot;&gt;TypedArray&lt;/a&gt;, &lt;a href=&quot;https://tc39.es/ecma262/#sec-map-objects&quot; target=&quot;_blank&quot;&gt;Map&lt;/a&gt;, &lt;a href=&quot;https://tc39.es/ecma262/#sec-set-objects&quot; target=&quot;_blank&quot;&gt;Set&lt;/a&gt;, &lt;a href=&quot;https://tc39.es/ecma262/#sec-weakmap-objects&quot; target=&quot;_blank&quot;&gt;WeakMap&lt;/a&gt;, &lt;a href=&quot;https://tc39.es/ecma262/#sec-weakset-objects&quot; target=&quot;_blank&quot;&gt;WeakSet&lt;/a&gt;, &lt;a href=&quot;https://tc39.es/ecma262/#string-exotic-object&quot; target=&quot;_blank&quot;&gt;String&lt;/a&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;Z74C&quot;&gt;// Array
for (const item of new Array(1, 2, 3)) {
    console.log(item);
}
// 1
// 2
// 3

// TypedArray
for (const item of new Int8Array(3)) {
    console.log(item);
}
// 0
// 0
// 0

// Map
for (const item of new Map([[1, &amp;#x27;one&amp;#x27;], [2, &amp;#x27;two&amp;#x27;], [3, &amp;#x27;three&amp;#x27;]])) {
    console.log(item);
}
// [1, &amp;#x27;one&amp;#x27;]
// [2, &amp;#x27;two&amp;#x27;]
// [3, &amp;#x27;three&amp;#x27;]

// Set
for (const item of new Set([1, 2, 3])) {
    console.log(item);
}
// 1
// 2
// 3

// String
for (const item of &amp;quot;abc&amp;quot;) {
    console.log(item);
}
// a
// b
// c&lt;/pre&gt;
  &lt;p id=&quot;dPpz&quot;&gt;In addition to JavaScript, some Web API objects also implement the &lt;a href=&quot;https://tc39.es/ecma262/#sec-iterable-interface&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt; interface. For example, in the &lt;a href=&quot;https://dom.spec.whatwg.org&quot; target=&quot;_blank&quot;&gt;DOM&lt;/a&gt; standard, examples of such objects are &lt;a href=&quot;https://dom.spec.whatwg.org/#nodelist&quot; target=&quot;_blank&quot;&gt;NodeList&lt;/a&gt; and &lt;a href=&quot;https://dom.spec.whatwg.org/#domtokenlist&quot; target=&quot;_blank&quot;&gt;DOMTokenList&lt;/a&gt;.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;EpEY&quot;&gt;// NodeList
for (const item of document.querySelectorAll(&amp;quot;div&amp;quot;)) {
    console.log(item);
}
// Node
// Node
// ...

// DOMTokenList
const block = document.createElement(&amp;quot;div&amp;quot;);
block.classList.add(&amp;quot;classA&amp;quot;, &amp;quot;classB&amp;quot;)

for (const item of block.classList) {
    console.log(item)
}
// classA
// classB&lt;/pre&gt;
  &lt;h2 id=&quot;a9kM&quot;&gt;Custom iterators&lt;/h2&gt;
  &lt;p id=&quot;LFvp&quot;&gt;Other than built-in iterable objects, it is possible to create our own. To do this, it is sufficient to simply implement the &lt;a href=&quot;https://tc39.es/ecma262/#sec-iterable-interface&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt; interface, following the iteration protocol.&lt;/p&gt;
  &lt;p id=&quot;yRbG&quot;&gt;So, as mentioned earlier, an object is considered iterable if its prototype contains the method &lt;strong&gt;[Symbol.iterator]()&lt;/strong&gt;. Otherwise, if an attempt is made to iterate over such an object, an exception will be thrown.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;tjBo&quot;&gt;const obj = {};

for (const item of obj) {
    console.log(item);
}

// Uncaught TypeError: obj is not iterable&lt;/pre&gt;
  &lt;p id=&quot;D3Bv&quot;&gt;Now let&amp;#x27;s add an iterator method to our object.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;5B5Q&quot;&gt;const obj = {
    [Symbol.iterator]: () =&amp;gt; {},
};

for (const item of obj) {
    console.log(item);
}

// Uncaught TypeError: Result of the Symbol.iterator method is not an object&lt;/pre&gt;
  &lt;p id=&quot;lyt5&quot;&gt;Now, formally, the object is considered iterable, but it still cannot be iterated over in its current state, as the iterator method does not return an object that implements the &lt;strong&gt;Iterator&lt;/strong&gt; interface, as required by the protocol.&lt;/p&gt;
  &lt;h3 id=&quot;U4Nb&quot;&gt;next()&lt;/h3&gt;
  &lt;p id=&quot;lZ7w&quot;&gt;According to the protocol, an iterator should be a simple object that contains at least one method, &lt;strong&gt;next()&lt;/strong&gt;. The &lt;strong&gt;next()&lt;/strong&gt; method may take arguments, but it&amp;#x27;s not strictly required. The &lt;a href=&quot;https://tc39.es/ecma262&quot; target=&quot;_blank&quot;&gt;ECMA-262&lt;/a&gt; specification guarantees that all built-in consumers of iterable objects call this method without arguments. However, in rare cases, arguments may be useful when manually calling the iterator.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;spw2&quot;&gt;const obj = {
    [Symbol.iterator]: () =&amp;gt; {
        return {
            next: () =&amp;gt; {},
        };
    },
};

for (const item of obj) {
    console.log(item);
}

// Uncaught TypeError: Iterator result undefined is not an object&lt;/pre&gt;
  &lt;p id=&quot;qRjK&quot;&gt;In the above example, we added the &lt;strong&gt;next()&lt;/strong&gt; method, but this method still doesn&amp;#x27;t return the correct &lt;strong&gt;IteratorResult&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;1ZNQ&quot;&gt;An &lt;strong&gt;IteratorResult&lt;/strong&gt; is also a simple object with two properties, &lt;strong&gt;done&lt;/strong&gt; and &lt;strong&gt;value&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;VAxO&quot;&gt;The &lt;strong&gt;done&lt;/strong&gt; property is a boolean and expresses the completion of the iteration process. In other words, as long as the object can be iterated further, its iterator returns &lt;strong&gt;done: false&lt;/strong&gt;; once it reaches the last step of iteration, the iterator must return &lt;strong&gt;done: true&lt;/strong&gt;, signaling the user of the iterable object (e.g., a loop) to end the process (exit the loop).&lt;/p&gt;
  &lt;p id=&quot;UWkm&quot;&gt;The &lt;strong&gt;value&lt;/strong&gt; property is the actual value associated with a given step of iteration. There are no restrictions on the format of the returned value.&lt;/p&gt;
  &lt;p id=&quot;3AWh&quot;&gt;As I just mentioned, if the iterator returns &lt;strong&gt;done: true&lt;/strong&gt;, the iteration will be considered complete, the next iteration will not occur, and therefore, there is no point in the &lt;strong&gt;value&lt;/strong&gt;. Consequently, passing it together with &lt;strong&gt;done: true&lt;/strong&gt; is not necessary in this case.&lt;/p&gt;
  &lt;p id=&quot;OtRK&quot;&gt;It is worth noting that the &lt;a href=&quot;https://tc39.es/ecma262&quot; target=&quot;_blank&quot;&gt;ECMA-262&lt;/a&gt; specification has taken a very liberal approach to the issue of iterators. In fact, it is not mandatory to return either the &lt;strong&gt;done&lt;/strong&gt; property or the &lt;strong&gt;value&lt;/strong&gt; property. Default values are provided for both (&lt;strong&gt;false&lt;/strong&gt; and &lt;strong&gt;undefined&lt;/strong&gt;, respectively). In other words, in order for our iterable object to finally work, the iterator only needs to return an empty object.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;uHBI&quot;&gt;const obj = {
    [Symbol.iterator]: () =&amp;gt; {
        return {
            next: () =&amp;gt; {
                return {}; // { done: false, value: undefined }
            },
        };
    },
};

for (const item of obj) {
    console.log(item);
}

// Caution!!! This code leads to an infinite loop&lt;/pre&gt;
  &lt;p id=&quot;1MdV&quot;&gt;However, there is little benefit from such an iterator. Moreover, since the iterator will never return &lt;strong&gt;done: true&lt;/strong&gt;, the entire code will go into an infinite loop.&lt;/p&gt;
  &lt;p id=&quot;KNEF&quot;&gt;So, let&amp;#x27;s describe a functional iterator according to all the rules.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;SKYu&quot;&gt;const obj = {
    [Symbol.iterator]: () =&amp;gt; {
        let i = 0;
        
        return {
            next: () =&amp;gt; {
                return {
                    done: i &amp;gt;= 3,
                    value: i++,
                };
            },
        };
    },
};

for (const item of obj) {
    console.log(item);
}

// 0
// 1
// 2&lt;/pre&gt;
  &lt;p id=&quot;IYon&quot;&gt;In this example, we sequentially return the numbers &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;. Exiting the loop is done when &lt;code&gt;i === 3&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;wOqb&quot;&gt;return()&lt;/h3&gt;
  &lt;p id=&quot;nGzW&quot;&gt;In some cases, the iteration process can be forcibly stopped in one way or another from the outside. In this case, the user of the iterable object must call the &lt;strong&gt;return()&lt;/strong&gt; iterator method, if it exists.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;UkjY&quot;&gt;const obj = {
    [Symbol.iterator]: () =&amp;gt; {
        let i = 0;
        
        return {
            next: () =&amp;gt; {
                return {
                    done: i &amp;gt;= 3,
                    value: i++,
                };
            },
            
            return: () =&amp;gt; {
                console.log(&amp;quot;Return&amp;quot;, i);
                return {
                    done: i &amp;gt;= 3,
                    value: i,
                }
            }
        };
    },
};

for (const item of obj) {
    console.log(item);
    
    if (item === 1) {
        break;
    }
}
// 0
// 1
// Return 2&lt;/pre&gt;
  &lt;p id=&quot;Tlv7&quot;&gt;Like the &lt;strong&gt;next()&lt;/strong&gt; method, the &lt;strong&gt;return()&lt;/strong&gt; method must return an &lt;strong&gt;IteratorResult&lt;/strong&gt; object. This method is, in a way, a callback at the time of forced interruption of the iteration. It is assumed that specific operations necessary to complete the process can be performed here. For example, unbinding listeners or clearing memory.&lt;/p&gt;
  &lt;h3 id=&quot;MgMG&quot;&gt;throw()&lt;/h3&gt;
  &lt;p id=&quot;4p8l&quot;&gt;This is another callback method similar to &lt;strong&gt;return()&lt;/strong&gt;. Its essence is to allow the user of the iterable object to report a discovered error. There are no built-in JavaScript users that can invoke this method in any situation. However, this callback can be manually invoked.&lt;/p&gt;
  &lt;p id=&quot;uK7D&quot;&gt;The protocol allows passing an argument to the &lt;strong&gt;throw()&lt;/strong&gt; method, which is suggested to be used as a reference to the exception, although formally there are no restrictions on the number or types of arguments.&lt;/p&gt;
  &lt;p id=&quot;EllE&quot;&gt;When called, the &lt;strong&gt;throw()&lt;/strong&gt; method should throw the passed exception to halt the further iteration process. Again, such behavior is recommended and anticipated, but not mandatory. The iterator&amp;#x27;s author has the right to define the further logic.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;X97b&quot;&gt;const obj = {
  [Symbol.iterator]: () =&amp;gt; {
    let i = 0;
    
    return {
      next: () =&amp;gt; {
        return {
          done: i &amp;gt;= 3,
          value: i++,
        };
      },
      
      throw: (exception) =&amp;gt; {
        console.log(&amp;quot;Thrown:&amp;quot;, exception);
        
        if (exception) {
          throw exception;
        }
        
        return {
          done: true,
        };
      },
    };
  },
};

for (const item of obj) {
  console.log(item);
  
  if (item === 1) {
    obj[Symbol.iterator]().throw(&amp;#x60;Reached ${item}&amp;#x60;);
  }
}

// 0
// 1
// Thrown: Reached 1
// Uncaught Reached 1&lt;/pre&gt;
  &lt;p id=&quot;nihK&quot;&gt;Most often, the &lt;strong&gt;throw()&lt;/strong&gt; method is used in asynchronous iterators in conjunction with generators. More on this later.&lt;/p&gt;
  &lt;h2 id=&quot;2AqH&quot;&gt;Performing the iterator manually&lt;/h2&gt;
  &lt;p id=&quot;yq4o&quot;&gt;As it has already become clear, the main users of iterable objects are the constructs &lt;strong&gt;for..of&lt;/strong&gt; and &lt;strong&gt;for..in&lt;/strong&gt;. These built-in users independently implement the iteration protocol. However, sometimes there is a need to create your own user, or simply to manually iterate through the process. As we saw in the last example, the iterator can be accessed directly to iterate through the entire loop on its own.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;ruTZ&quot;&gt;const obj = {
  [Symbol.iterator]: () =&amp;gt; {
    let i = 0;
    
    return {
      next: () =&amp;gt; {
        return {
          done: i &amp;gt;= 5,
          value: i++,
        };
      },
      
      return: () =&amp;gt; {
        return {
          done: true,
          value: i,
        };
      },
      
      throw: (exception) =&amp;gt; {
        if (exception) {
          throw exception;
        }
        
        return {
          done: true,
        };
      },
    };
  },
};

// getting the iterator by calling the iterator method
const iterator = obj[Symbol.iterator]();

// first step of the iteration
let result = iterator.next();
console.log(result.value);

// the remaining steps will be called in a loop until
// the iterator returns &amp;#x60;done: true&amp;#x60;
while (!result.done) {
  const result = iterator.next();
  console.log(result.value);
  
  if (isSuccess(result.value)) {
    // if necessary, we can stop the iteration process
    iterator.return();
    break;
  }
  
  if (isFailed(result.value)) {
    // or throw an exception
    iterator.throw(new Error(&amp;quot;failed&amp;quot;));
  }
}&lt;/pre&gt;
  &lt;h2 id=&quot;HpfL&quot;&gt;Self-iterating objects&lt;/h2&gt;
  &lt;p id=&quot;kQUN&quot;&gt;We have already acquainted ourselves with two concepts: iterable object (&lt;a href=&quot;https://tc39.es/ecma262/#sec-iterable-interface&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt;) and iterator (&lt;a href=&quot;https://tc39.es/ecma262/#sec-iterator-interface&quot; target=&quot;_blank&quot;&gt;Iterator&lt;/a&gt;). So far, we have considered both entities separately. The following technique allows us to combine everything into a single self-iterating object.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;qEpy&quot;&gt;const obj = {
  i: 0,
  
  [Symbol.iterator]() {
    return this;
  },
  
  next() {
    return {
      done: this.i &amp;gt;= 3,
      value: this.i++,
    };
  },
  
  return() {
    return {
      done: true,
      value: this.i,
    };
  },
  
  throw(exception) {
    if (exception) {
      throw exception;
    }
    
    return {
      done: true,
    };
  },
};

for (const item of obj) {
  console.log(item);
}&lt;/pre&gt;
  &lt;p id=&quot;rxxf&quot;&gt;In this example, in the iterator method, we returned a reference to the iterable object as the iterator, so &lt;code&gt;obj&lt;/code&gt; simultaneously serves as both the iterator and the iterable object.&lt;/p&gt;
  &lt;p id=&quot;HfFn&quot;&gt;A similar pattern might be seen in an iterable class.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;PHNJ&quot;&gt;class Obj {
  i = 0;
  
  [Symbol.iterator]() {
    return this;
  }
  
  next() {
    return {
      done: this.i &amp;gt;= 3,
      value: this.i++,
    };
  }
  
  return() {
    return {
      done: true,
      value: this.i,
    };
  }
  
  throw(exception) {
    if (exception) {
      throw exception;
    }
    
    return {
      done: true,
    };
  }
}

const obj = new Obj();

for (const item of obj) {
  console.log(item);
}&lt;/pre&gt;
  &lt;h2 id=&quot;ggH7&quot;&gt;Asynchronous iterators&lt;/h2&gt;
  &lt;p id=&quot;BFsz&quot;&gt;So far we&amp;#x27;ve only been talking about synchronous iterators. In June 2018, the &lt;a href=&quot;https://262.ecma-international.org/9.0/&quot; target=&quot;_blank&quot;&gt;9th edition of the ECMA-262 specification&lt;/a&gt; was published. This version introduced asynchronous iterators and the &lt;strong&gt;AsyncIterator&lt;/strong&gt; protocol.&lt;/p&gt;
  &lt;p id=&quot;xVLr&quot;&gt;In essence, an asynchronous iterator differs little from a synchronous one. Let&amp;#x27;s take a look at the following interfaces.&lt;/p&gt;
  &lt;pre data-lang=&quot;typescript&quot; id=&quot;gwr9&quot;&gt;interface AsyncIterable {
  [Symbol.asyncIterator]: () =&amp;gt; AsyncIterator;
}

interface AsyncIterator {
  next: () =&amp;gt; Promise&amp;lt;IteratorResult&amp;gt;;
  
  return?: (value?: any) =&amp;gt; Promise&amp;lt;IteratorResult&amp;gt;;
  
  throw?: (exception?: any) =&amp;gt; Promise&amp;lt;IteratorResult&amp;gt;;
}&lt;/pre&gt;
  &lt;p id=&quot;bW7X&quot;&gt;So, the main difference between synchronous and asynchronous iterable objects is that an asynchronous object must have an iterator method &lt;strong&gt;[Symbol.asyncIterator]&lt;/strong&gt; (instead of &lt;strong&gt;[Symbol.iterator]&lt;/strong&gt; as in the case of a synchronous object). As for the return values of the &lt;strong&gt;next()&lt;/strong&gt;, &lt;strong&gt;return()&lt;/strong&gt;, and&lt;strong&gt; throw(&lt;/strong&gt;) methods, they are expected to be Promises.&lt;/p&gt;
  &lt;p id=&quot;l8Qn&quot;&gt;Asynchronous iterators themselves wouldn&amp;#x27;t be as convenient to use if the specification didn&amp;#x27;t provide for a new structure - the asynchronous &lt;strong&gt;for..await..of&lt;/strong&gt; loop.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;ORKG&quot;&gt;const obj = {
  [Symbol.asyncIterator]: () =&amp;gt; {
    let i = 0;
    
    return {
      next: () =&amp;gt; {
        return Promise.resolve({
          done: i &amp;gt;= 3,
          value: i++,
        });
      },
      
      return: () =&amp;gt; {
        return Promise.resolve({
          done: true,
          value: i,
        });
      },
      
      throw: (exception) =&amp;gt; {
        if (exception) {
          return Promise.reject(exception);
        }
        
        return Promise.resolve({
          done: true,
        });
      },
    };
  },
};

(async () =&amp;gt; {
  for await (const item of obj) {
    console.log(item);
    
    if (item === 1) {
      await obj[Symbol.asyncIterator]().throw(&amp;#x60;Reached ${1}&amp;#x60;);
    }
  }
})();&lt;/pre&gt;
  &lt;h2 id=&quot;suzu&quot;&gt;Asynchronous generators&lt;/h2&gt;
  &lt;p id=&quot;uEzh&quot;&gt;One important addition in the &lt;a href=&quot;https://262.ecma-international.org/9.0/&quot; target=&quot;_blank&quot;&gt;9th edition of ECMA-262&lt;/a&gt; is the introduction of asynchronous generators closely related to asynchronous iterators.&lt;/p&gt;
  &lt;p id=&quot;8QCi&quot;&gt;A simple asynchronous generator may look as follows.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;FH8h&quot;&gt;async function* gen() {
  yield 0;
  yield 1;
  yield 2;
}

(async () =&amp;gt; {
  for await (const item of gen()) {
    console.log(item);
  }
})();

// 0
// 1
// 2&lt;/pre&gt;
  &lt;p id=&quot;0HCq&quot;&gt;An asynchronous generator implements the &lt;strong&gt;AsyncIterator&lt;/strong&gt; protocol, so we can use all available iterator methods manually.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;lLG3&quot;&gt;async function* gen() {
  yield 0;
  yield 1;
  yield 2;
}

const iterator = gen();

(async () =&amp;gt; {
  console.log(await iterator.next());
  console.log(await iterator.next());
  console.log(await iterator.next());
  console.log(await iterator.next());
})();

// {value: 0, done: false}
// {value: 1, done: false}
// {value: 2, done: false}
// {value: undefined, done: true}&lt;/pre&gt;
  &lt;p id=&quot;8HKM&quot;&gt;This includes pausing the iteration process.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;inAp&quot;&gt;async function* gen() {
  yield 0;
  yield 1;
  yield 2;
}

const iterator = gen();

(async () =&amp;gt; {
  console.log(await iterator.next());
  console.log(await iterator.return(&amp;quot;My return reason&amp;quot;));
})();

// {value: 0, done: false}
// {value: &amp;#x27;My return reason&amp;#x27;, done: true}&lt;/pre&gt;
  &lt;p id=&quot;GRPu&quot;&gt;Or throwing an exception&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;mo1u&quot;&gt;async function* gen() {
  yield 0;
  yield 1;
  yield 2;
}

const iterator = gen();

(async () =&amp;gt; {
  console.log(await iterator.next());
  console.log(await iterator.throw(&amp;quot;My error&amp;quot;));
})();

// {value: 0, done: false}
// Uncaught (in promise) My error&lt;/pre&gt;
  &lt;h2 id=&quot;WzZ5&quot;&gt;Async-from-Sync iterator&lt;/h2&gt;
  &lt;p id=&quot;5yz6&quot;&gt;An &lt;a href=&quot;https://tc39.es/ecma262/#sec-async-from-sync-iterator-objects&quot; target=&quot;_blank&quot;&gt;Async-from-Sync iterator&lt;/a&gt; is an asynchronous iterator obtained from a synchronous one through the abstract operation &lt;a href=&quot;https://tc39.es/ecma262/#sec-createasyncfromsynciterator&quot; target=&quot;_blank&quot;&gt;CreateAsyncFromSyncIterator&lt;/a&gt;. I remind you that an abstract operation is an internal mechanism of the JavaScript language. Normally, such operations are not available within the execution context. However, for example, the V8 engine allows the use of these operations when the &lt;code&gt;--allow-natives-syntax&lt;/code&gt; flag is enabled.&lt;/p&gt;
  &lt;pre data-lang=&quot;shell&quot; id=&quot;gbTm&quot;&gt;&amp;gt; v8-debug --allow-natives-syntax iterator.js&lt;/pre&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;V20y&quot;&gt;// iterator.js
const syncIterator = {
  [Symbol.iterator]() {
    return this;
  },
  
  next() {
    return {
      done: true,
      value: Promise.resolve(1),
    };
  },
};

const asyncIterator = %CreateAsyncFromSyncIterator(syncIterator);

console.log(asyncIterator);
// [object Async-from-Sync Iterator]

console.log(asyncIterator[Symbol.asyncIterator]);
// function [Symbol.asyncIterator]() { [native code] }&lt;/pre&gt;
  &lt;p id=&quot;q7Hb&quot;&gt;As we can see, this abstract operation converts a synchronous iterator to an asynchronous one. As I mentioned before, we cannot call an abstract operation in the normal execution context. The engine itself handles this where necessary. According to the specification, this is required in one specific case:&lt;/p&gt;
  &lt;ul id=&quot;MhIr&quot;&gt;
    &lt;li id=&quot;zZPe&quot;&gt;The current environment is asynchronous.&lt;/li&gt;
    &lt;li id=&quot;GzYG&quot;&gt;The iterable object does not have a &lt;strong&gt;[Symbol.asyncIterator]&lt;/strong&gt; method.&lt;/li&gt;
    &lt;li id=&quot;j2be&quot;&gt;The iterable object has a &lt;strong&gt;[Symbol.iterator]&lt;/strong&gt; method.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;PoRl&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;zziW&quot;&gt;Как мы можем видеть, эта абстрактная операция преобразует синхронный итератор в асинхронный. Как я уже говорил, в обычном исполняемом контексте мы не можем вызвать абстрактную операцию. Это делает сам движок в тех местах, где требуется. А требуется это, согласно спецификации, в одном конкретном случае:&lt;/p&gt;
  &lt;ul id=&quot;l0pP&quot;&gt;
    &lt;li id=&quot;Ys7r&quot;&gt;Текущее окружение является асинхронным&lt;/li&gt;
    &lt;li id=&quot;p731&quot;&gt;Итерируемый объект не имеет метода [Symbol.asyncIterarot]&lt;/li&gt;
    &lt;li id=&quot;VdGa&quot;&gt;Итерируемый объект имеет метод [Symbol.iterator]&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;XWNn&quot;&gt;The easiest way to demonstrate this process is with generators.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;JFGO&quot;&gt;function* syncGen() {
  yield 1;
  yield 2;
  yield Promise.resolve(3);
  yield Promise.resolve(4);
  yield 5;
}

for (const item of syncGen()) {
  console.log(item);
}

// 1
// 2
// Promise
// Promise
// 5&lt;/pre&gt;
  &lt;p id=&quot;PEQT&quot;&gt;In this case, a synchronous generator will return unresolved promises at steps 3 and 4. However, an asynchronous user &lt;strong&gt;for..await..of&lt;/strong&gt; can convert these steps into asynchronous ones.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;2dvG&quot;&gt;function* syncGen() {
  yield 1;
  yield 2;
  yield Promise.resolve(3);
  yield Promise.resolve(4);
  yield 5;
}

(async () =&amp;gt; {
  for await (const item of syncGen()) {
    console.log(item);
  }
})();

// 1
// 2
// 3
// 4
// 5&lt;/pre&gt;
  &lt;p id=&quot;MSLY&quot;&gt;The same applies to regular synchronous iterators.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;MQut&quot;&gt;const syncIterator = {
  i: 0,
  
  [Symbol.iterator]() {
    return this;
  },
  
  next() {
    const done = this.i &amp;gt;= 3;

    if (this.i === 1) {
      return {
        done: false,
        value: Promise.resolve(this.i++),
      };
    }

    return {
      done,
      value: this.i++,
    };
  },
};

for (const item of syncIterator) {
  console.log(item);
}

// 0
// Promise
// 2&lt;/pre&gt;
  &lt;p id=&quot;Jv55&quot;&gt;However, when an asynchronous user is applied, the second step will be transformed into an asynchronous one.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot; id=&quot;N2Vg&quot;&gt;const syncIterator = {
  i: 0,
  
  [Symbol.iterator]() {
    return this;
  },
  
  next() {
    const done = this.i &amp;gt;= 3;

    if (this.i === 1) {
      return {
        done: false,
        value: Promise.resolve(this.i++),
      };
    }

    return {
      done,
      value: this.i++,
    };
  },
};

(async () =&amp;gt; {
  for await (const item of syncIterator) {
    console.log(item);
  }
})();

// 0
// 1
// 2&lt;/pre&gt;
  &lt;p id=&quot;dVtS&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;VlsD&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4FEw&quot;&gt;&lt;strong&gt;My telegram channels:&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;YnAq&quot;&gt;EN - &lt;a href=&quot;https://t.me/frontend_almanac&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac&lt;/a&gt;&lt;br /&gt;RU - &lt;a href=&quot;https://t.me/frontend_almanac_ru&quot; target=&quot;_blank&quot;&gt;https://t.me/frontend_almanac_ru&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Z73G&quot;&gt;&lt;em&gt;Русская версия: &lt;a href=&quot;https://blog.frontend-almanac.ru/iterators&quot; target=&quot;_blank&quot;&gt;https://blog.frontend-almanac.ru/iterators&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content></entry></feed>