The author | Andreas Hanss

Source | JS IN PLAIN ENGLISH

Link | How the memory management works in JavaScript

To be honest, I’m the one who tries to do something before reading the whole document… Most of the time, this is fine, and if I have a problem, I review the document to find the answer.

But recently, I’ve had some problems trying to use JavaScript references with the React application, which isn’t easy to detect because my code doesn’t change in a predictable way.

I thought I had it all figured out, but I read the document a long time ago, and I can’t quite remember it.

These questions helped me a lot, and I relearned something that was missing, something that was probably very basic… Even though I’ve had years of programming experience. I’d like to share with you what I’ve learned.

Today’s question is about understanding memory management in JavaScript.

JavaScript Memory Management

To understand JS memory management, we need to remember two rules, which are quite simple:

  • Primitive types (strings, numbers, booleans) take the form of copies as function arguments.
  • The object takes the form of a reference as a function parameter
  • Functions and arrays (null as well, although it is actually immutable) are treated as objects in JavaScript

You just have to try typeof [] in the browser console to verify, and you can see that object is printed.

How does a reference work in JavaScript and how do you interpret it

We’ll talk about memory management in JavaScript engines, regardless of the framework and environment, but we’ll briefly explain why it gets tedious to talk about it in ReactJS and hooks later.

const someObject = { a: 5 };
const someArray = [1.2];
Copy the code

Const someObject = {a: 5}; Understood as a

SomeObject is {a:5} the reference location (memory location) is createdCopy the code

But actually, you should think of it as

The someObject variable refers to an object {a:5} memory locationCopy the code

The above statement can be split into

| const someObject   | =                   | { a: 5 };
| const someArray    | =                   | [1.2]
| variable reference | assignment operator | object Reference
Copy the code

See siwalikm/ smemory.md and rtablada/memory.md for more explanations

Now that we have the basics, let’s look at why confusion can cause problems.

JavaScript closures and references

Quickly understand closures

A closure is a function that remembers its external variables and can access them. In JavaScript, almost all functions are naturally closures.

When you use JavaScript, you’re basically using closures and managing scopes, and doing so requires an understanding of how references work.

Within a closure, there is the concept of a lexical environment that contains a different range of values. I won’t go into too much detail here, but if you want to learn more, please read more here.

Parameters of primitive types are passed as copies. Here is a simple example.

In the example below, modifying a or B has no side effect on any ARGs in scope 2. Similarly, modifying the ARG in scope 2 has no effect on A or B.

Remember: scope is limited by {}

// Scope 1
let a = 5;
let b = "hello";
function test(arg) {
  // Scope 2
  arg = "Something else";
}
test(a)
test(b)
Copy the code

** Parameters of object types are passed by reference. ** We’ll explore this problem with closures with modifications and assignments.

Closure assignment and modification in JavaScript

Closures and callbacks are at the heart of JavaScript programming and event-driven programming, so you’ll need to rely on them most of the time.

In the example below, there is a NodeJS fragment that mimics the behavior of displaying a message to a user who is interested in a topic. But this code does a few things:

  • The subscriber will change during the execution of the program. I use it every 10 secondssetIntervalThe function imitates once. Sometimes an interested user is added, sometimes one is removed.
  • Every second we send random messages to subscribers.

At the end of the file, there are two setInterval functions that modify the subscribed user array. We through startListeningForMessages closure function creates a variable scope.

Let’s try to run the code.

Feel free to use your own NodeJS environment.

const events = require("events");

// Vars
let interestedUsers = [];
const goodPlanTopic = new events.EventEmitter();

function startListeneningForMessages(topic, listeningUsers) {
  // Each time a message is received, we sent a notification to user interest in our topic
  function onNewMessage(newMessage) {
    listeningUsers.forEach(user= >
      console.log("Sent message to [" + user + "]." + newMessage),
    );
  }

  // Subscribe to topic
  topic.on("message", onNewMessage);

  // We return function used to cancel listener
  return (a)= > topic.off("message", onNewMessage);
}

// Every second we have a new appearing message (pushed in emitter).
setInterval((a)= > {
  goodPlanTopic.emit(
    "message"."Text message N °" +
      Math.random()
        .toFixed(6)
        .substr(2)); },1000);

// Start programm
startListeningForMessages(goodPlanTopic, interestedUsers); // We are passing our array

console.log(Started, wait 10 seconds before first event...);

// Every ten seconds, we add or remove a new random user to the interested users list

// First scenario by re-assigning value

// setInterval(() => {
// if (math.random > 0.5) {
// console.log("New user will be removed (but not repercuted)");
// interestedUsers = interestedUsers.splice(
// Math.floor(Math.random() * interestedUsers.length),
/ / 1.
/ /);
// } else {
// console.log("New user will be added (but not repercuted)");
// interestedUsers = [].concat(interestedUsers).push(
// "user" +
// Math.random()
// .toFixed(3)
// .substr(2),
/ /);
/ /}
// }, 10000);

// Second scenario by mutating
setInterval((a)= > {
  if (Math.random > 0.5) {
    console.log("New user will be removed (and repercuted)");
    interestedUsers.splice(
      Math.floor(Math.random() * interestedUsers.length),
      1,); }else {
    console.log("New user will be added (and repercuted)");
    interestedUsers.push(
      "user" +
        Math.random()
          .toFixed(3)
          .substr(2)); }},10000);
Copy the code

The first setInterval is annotated without commenting on the first setInterval and the second setInterval is annotated without commenting on the second setInterval.

See the difference? ** Let’s explain:

  • In the firstintervalIn, nothing is displayed. That’s because we’re replacing a reference to an array. Even if passed by reference,listeningUsersThe argument still points to the old oneinterestedUsersReferences. This way, when the oldinterestedUsersIf it still exists, it can be reassignedinterestedUsers, but because the reference still exists and cannot be accessed outside of the closure scope, garbage collection cannot take place. This could be a memory leak, but in our case it is even worse because we are no longer in sync with external lists.
  • In the secondintervalThe situation has changed. That’s because we’re using the mutation method in an Array object. This way, the reference does not change, but the elements in the array do. This way, we don’t lose context through closure functions.

That’s why you should always ask yourself whether you should modify or reassign something and consider side effects when using variables that are out of scope.

This also applies to objects!

For nearly identical objects, if you edit the object’s key value in the closure function, that’s fine, because you’re still pointing to the same reference. However, if you reassign an object inside a function, the original reference is lost.

What about React hooks?

Similarly, if you use useState and do not change the reference, useState will only cause a re-rendering when the object.is comparison returns false after calling the setter, and your UI will not be updated.

This is why we typically require useEffect and clean closure scopes for state changes, but sometimes you can lose events from event listeners in the time period between unsubscribe/re-subscribe to new values. It is also important when you have object state or array state.