Mojimi
Mojimi

Reputation: 3161

A function that relies on a variable outside its scope causes memory leak in Javascript?

Consider the following code that creates a list of buttons that are attached to a point in a web map.

Initially I thought about extending element and adding the point as a property, but since that isn't recommended, I just went with referencing the object in the event function.

 pointsInMap = [
    {
      x : 20.2,
      y : 15.2,
      name : "point1"
    },
    {
      x : 20.2,
      y : 15.2,
      name : "point2"
    },
    {
      x : 20.2,
      y : 15.2,
      name : "point3"
    }
  ]

  function addZoomToButtons(points){
    points.forEach( point => {
      const button = document.createElement('button');
      button.text = `Zoom to ${point.name}`;
      //I'm overwriting this variable 3 times but javascript needs to keep reference to it, where will it be stored since it's outside the event scope?
      const pointObj = new Point(point.x, point.y)
      button.addEventListener( () => {
        myMap.panAndZoomToPoint(pointObj)
      })
      document.body.appendChild(button);
    })
  }

  addZoomtoButtons(pointsInMap);

Is there anything "wrong" with the code above in terms of performance/memory? I feel like there is but I don't know enough about memory management in Javascript to figure it out.

If there isn't anything wrong please help me understand why.

In my point of view, instead of just adding to memory 3 events, it will also keep 3 "copies" of the addZoomToButtons/forEach function scope since the it has variables that the events require. This is just a small example, but please consider that things can get really big.

The code calls const pointObj = new Point(point.x, point.y) outside the event scope 3 times, but it can't just overwrite pointObj because the event references it, it also can't just shift the scope to be inside the event, so I'm assuming the scope imediately outside the event is also being stored unnecesarily.

If there is something wrong what's another way to design the solution?

Another way to word this question is how to bind object references to an event in a proper and recommended way?

Upvotes: 0

Views: 204

Answers (1)

marzelin
marzelin

Reputation: 11600

I'm overwriting this variable 3 times but javascript needs to keep reference to it, where will it be stored since it's outside the event scope?

JS keeps it in a closure. What is a closure? You might know of prototype object, closure is similar but works with identifiers instead of properties.

const obj = {};

obj.__proto__.someProp = "someProp";

console.log(obj.someProp); // logs "someProp"

Since obj doesn't have its own someProp property, JS goes up the prototype chain and finds the prop on its prototype object.

Pretty similar is going on for identifiers. You can think of functions as having a hidden __closure__ object that stores identifiers from the upper scope. If an identifier is not found in the local scope a closest closure is checked. If it isn't found there, the closure's closure is checked.

var global = "global";

function f() {
  var outer = "outer";
  function g() {
    var local = "local";
  }
};

/*

in g:
g.__localScope__ has one identifier: `local`
g.__closure__ has one identifier: `outer`
g.__closure__.__closure__ is __global_scope__ and has identifier `global`

*/

When you have functions that are pluck out from other functions to the upper scope

function f(k) {
  return function g() {
    console.log(k);
  }
}


const g1 = f(1);
const g2 = f(2);

/*

  g1.__closure__ has identifier k that has value: 1
  g2.__closure__ also has identifier k but it has value: 2
  
  g1 can't acces k of g2 and vice versa
  
*/

g1()  // 1
g2()  // 2

You can see closure chain in dev tools under scope panel: enter image description here

It's also good to know that modern javascript engines are very efficient are have lots of very clever optimizations.

Consider this code:

function f(k) {
  const l = "l value";
  return function g() {
    debugger;
    console.log(k);
  };
}

f(1)();

Event though l is in the outer scope of g() function it is not listed in scope panel in dev tools:

enter image description here

Because this identifier is not used inside the g() function, Chrome JS engine V8 doesn't keep it in memory to save resources. If you change log statement to console.log(k, l);, both variables will be visible and accessible in dev tools:

enter image description here

Upvotes: 1

Related Questions