Closures (or arrow functions, aka lambdas) don’t cause memory leaks
Can someone to confirm or infirm if the local variables (here
el
) can’t be cleared by the garbage collector? Or, are modern browsers capable to detect they are unused in the closure?
Yes, modern JavaScript engines are able to detect variables from parent scopes that are visible from a closure but unused. I found a way to prove that.
Step 1: the closure uses a variable of 10 MB
I used this code in Chromium:
class Abc {
constructor() {
let arr = new Uint8Array(1024*1024*10) // 10 MB
let el = document.getElementById("my-btn")
if (el)
el.addEventListener("click", ev => this.onClick(ev, arr))
}
onClick(ev) {
console.log("Clicked!", ev.target)
}
}
new Abc()
Notice the variable arr
of type Uint8Array
. It is a typed array with a size of 10 megabytes. In this first version, the variable arr
is used in the closure.
Then, in the developer tools of Chromium, tab “Profiles”, I take a Heap Snapshot:
After ordering by decreasing size, the first row is: “system / JSArrayBufferData” with a size of 10 MB. It is our variable arr
.
Step 2: the variable of 10 MB is visible but unused in the closure
Now I just remove the arr
parameter in this line of code:
el.addEventListener("click", ev => this.onClick(ev))
Then, a second snapshot:
The first row has vanished.
This experience confirms that the garbage collector is capable to clean variables from parent scopes that are visible but unused in active closures.
About Function.prototype.bind
I quote the Google JavaScript Style Guide, section on arrow functions:
Never call
f.bind(this)
orgoog.bind(f, this)
(and avoid writingconst self = this
). All of these can be expressed more clearly and less error-prone with an arrow function. This is particularly useful for callbacks, which sometimes pass unexpected additional arguments.
Google clearly recommends to use lambdas rather than Function.prototype.bind
.
Related:
- Why is bind slower than a closure?
- A benchmark
- Arrow functions vs. bind() from Dr. Axel Rauschmayer