5

Lets say I create an arrow function for each element of a huge array

someHugeArray.forEach(record => {
  const someValues = [...getAnotherHugeArray()]
  const sum = _.sumBy(someValues, 'total')

  record.getPrice = () => sum / record.quantity
})

it is just an example... so inside the environment, where getPrice is created, we have a huge array someValues, which we use, but actually for getPrice we don't need it any more as we got a required value and saved it to sum.

Is it helpful to destroy its value with code

someValues = null

or javascript engines are smart enough to not keep in memory values for function's lexical environment, which is not used by it?

Dmitry Reutov
  • 2,995
  • 1
  • 5
  • 20
  • 2
    No it's not helpful since nothing points to that array. It was only accessed once with `_.sum()`. Memory leaks occur when two objects hold a reference to each other. Consider extracting the first two lines of the `forEach()` callback because they don't change. – try-catch-finally May 25 '20 at 05:14

2 Answers2

4

tl;dr

  • according to ECMAScript, the complete lexical environment is bound
  • in practice, engines optimize this if possible by binding only the used variables
  • the optimization is not possible for example when eval() is being used inside

I found a great article series where this is discussed in-depth:

The articles are quite old but still valid, which you can verify by yourself (see below).

For your example: in theory someValues would be bound (and not garbage collected) although it's not used in the record.getPrice closure. But in practice only the variable you use there (sum) is bound. And the fact that sum is bound has no effect on the binding of someValues, because sum is derived from someValues, but needs no further reference to it (it's a different thing it had been defined as const sum = () => _.sumBy(someValues, 'total'))

Verfication: execute the following in the browser console:

(() => {
    //eval(); // <- uncomment this line for second test
    const thisIsUsed = 1;
    const isThisBound = 2;
    return () => {
        debugger;
        return ('result: ' + thisIsUsed);
    }
})()();

When the debugger kicks in, take a look at the "Scope" (Chrome). You could also add thisIsUsed and isThisBound to the "Watch" list.

Here's a screenshot using Chrome (Canary, version 85.0.4154.0):

Screenshot of Chrome Developer Tools Debugger

The same behavior can be observed with a current Firefox (version 76.0.1).

According to Dmitry Soshnikov's articles, eval() can break the optimization. This is easy to understand as the engine then to assume that any variable may be accessed. This behavior can also be verified, just uncomment the line in the code sample above.

Sebastian B.
  • 2,141
  • 13
  • 21
0

As someValues is a block scoped variable it no longer exists after loop complete.

Correction after Sebastein's comment

According MDN it is useful to make it unreachable by removing references to it explicitly (if object is going to stay longer in the scope).

Limitation: Releasing memory manually

There are times when it would be convenient to manually decide when and what memory is released. In order to release the memory of an object, it needs to be made explicitly unreachable.

As of 2019, it is not possible to explicitly or programmatically trigger garbage collection in JavaScript.

Community
  • 1
  • 1
Vaibhav
  • 1,412
  • 1
  • 10
  • 14
  • You misread MDN. "In order to release the memory of an object, it needs to be made explicitly unreachable." That can be achieved by setting a variable to `null`. The text states only that the GC cannot **explicity** be triggered. – Sebastian B. May 25 '20 at 05:53