依赖变量范围之外的函数会导致Javascript内存泄漏吗?

时间:2018-12-04 17:32:02

标签: javascript performance memory

请考虑以下代码,该代码创建连接到网络地图中某个点的按钮列表。

最初,我考虑过扩展元素并将点添加为属性,但是由于不是recommended,因此我只是在事件函数中引用了对象。

 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);

在性能/内存方面,上述代码是否存在“错误”之处?我觉得有,但是我对Java中的内存管理了解不足,无法弄清楚。

如果没有任何问题,请帮助我理解原因。

根据我的观点,它不仅保留了3个events的内存,而且还保留了addZoomToButtons / forEach函数范围的3个“副本”,因为它具有变量events的要求。 这只是一个小例子,但请注意事情会变得非常大。

代码在事件范围之外调用了const pointObj = new Point(point.x, point.y) 3次,但是它不能仅仅覆盖pointObj,因为事件引用了它,也不能仅仅将范围移到事件内部,因此我假设事件外部的范围也被不必要地存储了。

如果出了问题,设计解决方案的另一种方法是什么?

表达此问题的另一种方式是如何以适当且推荐的方式将对象引用绑定到事件?

1 个答案:

答案 0 :(得分:1)

  

我要将此变量覆盖3次,但是javascript需要保留   引用,由于它不在事件中,因此它将存储在哪里   范围?

JS将其封闭。什么是封包?您可能知道prototype对象,闭包是类似的,但是使用标识符而不是属性。

const obj = {};

obj.__proto__.someProp = "someProp";

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

由于obj没有自己的someProp属性,因此JS进入原型链,并在其原型对象上找到了道具。

标识符的过程非常相似。您可以认为函数具有隐藏的__closure__对象,该对象存储上层作用域的标识符。如果在本地范围内找不到标识符,则会检查最接近的闭包。如果没有找到该闭包,则检查其闭包。

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`

*/

当您具有从其他功能中拔出的最高功能时

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

您可以在作用域面板下的开发工具中看到闭包链: enter image description here

很高兴知道现代javascript引擎非常高效,并且有很多非常聪明的优化方法。

考虑以下代码:

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

f(1)();

尽管lg()函数的外部作用域中,但事件未在开发工具的作用域面板中列出:

enter image description here

由于g()函数中未使用此标识符,因此Chrome JS引擎V8不会将其保留在内存中以节省资源。如果将日志语句更改为console.log(k, l);,则两个变量将在开发工具中可见且可访问:

enter image description here