In this post, I want to cover a Closure optimization meant to help us preventing memory leaks. This optimization is implemented by the three major browsers: Chrome, Firefox and IE. Closure is a popular subject and you can find very good articles out there. Although the post won't cover Closures internals, I will briefly cover the basics in order to keep the reading fluent.
Basics
To put it shortly; any time we access a method in JS, a special context object is created. This object contains the variables to be searched in the scope chain search process.
var x = 10; (function parentFunc () { console.log(x); }()); //* Result: 10
The code above will print 10 because when the parentFunc method is executed, a special context object containing its parent's variables is created and is pointed by parentFunc method scope.
When the interpreter starts searching for 'x' value it searches inside the parentFunc scope. If it can not find it, its parent scope gets evaluated. In this case, the parent scope is the global scope (window in browsers implementation, global in nodeJS).
Inner functions actually capture the entire parent scope and add it to the chain:
var x = 10; var funcResult = (function parentFunc () { var x = 20; var y = 30; var sonFunc = function () { console.log(x + y); } return sonFunc; })(); funcResult();//* Result 50
When sonFunc is returned, the parentFunc scope with x (20) and y (30) variables is captured and added to the search chain. When funcResult is called, and console.log(x + y) is encountered, the interpreter searches the variables 'x' and 'y' inside sonFunc. Once it concludes it can't find them, it searches them inside its parent scope, (as I've explained, parentFunc was added to the chain).
Note that if we comment-out var x = 20 code line, the result will be 40. Because the interpreter won't find the 'x' variable inside parentFunc, it will search in parentFunc's parent scope where 'x' value is 10.
Classic Memory Leak
JS's function-callback nature leads a lot of developers to make use of Closures, but the first thing you should note when using Closures is that they can lead to memory leaks. In order to prevent leaks while using callbacks/closures you must be always aware of what your code is keeping in memory and make sure it vanishes as soon as possible. For example, let's examine the following code snippet:
var listFunc= []; setInterval(function hello(){ var funcResult = (function parentFunc () { var x = 20; var y = 30; var sonFunc = function () { var text = "X value is: " + x + " and Y value is: " + y; console.log(text); } return sonFunc; })(); listFunc.push(funcResult); },100);
In the above sample for each interval iteration parameters, 'x' and 'y' are re-created and kept in memory. Newbies might think that sonFunc keeps in memory only the string value but in practice 'x' and 'y' values are kept in memory as well, making this a classic memory leak.
Browsers' Closure's Optimization - Avoid Severe Memory Leaks
In the previous sample, the inner function had a reference to both external values, as result 'x' and 'y' were kept in memory by the internal closure function. In the next example the inner function holds a reference to x variable only:
var listFunc= []; setInterval(function hello(){ var funcResult = (function parentFunc () { var x = 20; var y = 30; var sonFunc = function () { var text = "X value is: " + x; console.log(text); } return sonFunc; })(); listFunc.push(funcResult); },100);
ECMAScript specification, in this case, doesn't refer to the preferred behavior to be implemented. The original browsers' behavior was to keep all the variables inside the closure, even those who were not referenced. In the case above, 'y' was kept in memory, creating a memory leak.
The good news is that as of this writing, modern browsers had implemented a very useful optimization and only referenced variables are now kept in memory by the internal method.
eval is evil
Ok, so browsers scan the function's values and keep only the referenced once. But what if the internal function makes use of eval(), would the browser smart enough to detect the referenced values?
var listFunc= []; setInterval(function hello(){ var funcResult = (function parentFunc () { var x = 20; var y = 30; var sonFunc = function () { var text = "X value is: " + x; eval("alert(some_other_text)"); } return sonFunc; })(); listFunc.push(funcResult); },100);
In this case, it can be very difficult for the browser to detect the referenced variables. As a result if the function makes use of eval() the optimization won't work and all the parent function's variable will be kept in memory by the internal function.
Conclusion
Closures are very useful in Javascript, but using them requires the developers to be very careful. Although browsers are trying to prevent memory leaks they can do it only in certain circumstances. Keep references only to required variables and avoid using eval() inside closures because it will completely prevent browsers' optimization, completely exposing you to memory leaks.
Comments