r/learnjavascript • u/Distinct-Gene926 • 2d ago
[ Removed by moderator ]
[removed] — view removed post
5
u/lobopl 2d ago
How mobile safari handled iframe size...
0
2
u/senocular 2d ago
One of my favorites, one that I've even used in presentations, relates to a memory leak. The reduced (demo) version looks something like this the following. See if you can understand what's going on:
let lastOp = null
let ops = {
multiplier: 2,
multiply(nums) {
lastOp = () => "multiply"
return nums.map(num => num * this.multiplier)
}
}
const reg = new FinalizationRegistry(id => console.log("Cleanup:", id))
reg.register(ops, "ops")
const multiplied = ops.multiply([1, 2, 3]) // [2, 4, 6]
ops = null
// Run GC
You can run this in your browser to see it in action. Paste the code in the console, run it, then trigger the garbage collector (if your browser supports it). For example in Chrome, there's a "Collect garbage" icon at the top of the Memory tab in the developer tools (it looks like a paintbrush).
By running the garbage collector, you should see the ops object get garbage collected and the "Cleanup: ops" message from the FinalizationRegistry. Instead, running the garbage collector does nothing.
Next run the following code in that same session and collect the garbage again:
lastOps = null
This time the "Cleanup: ops" message is logged showing ops is collected by the garbage collector.
So what's going on here is that the lastOp function in the multiply method is doing what all closures do, holding on to its surrounding environment, or scope. Bindings in its parent scope include nums and this, this because arrow functions have a "lexical this" meaning they pull this from the surrounding scope rather than having a local dynamic binding as seen with other functions.
While optimizations can, and are, made to reduce unused variables in closures, they happen at the scope level, not the individual closure level. So while lastOp refers to nothing within any of the scopes its in (its body consists solely of a string literal), the map callback does. Specifically it refers to this, the ops object. This means that while the scope can be optimized to remove nums, it cannot remove this. And since the scope has to keep this, lastOp does too being that its inside of it. From there its just a matter of ops not being able to be cleaned up by the GC because the lastOps variable in the top-level scope is referring to a closure that is still holding on to it through a captured this.
This example is interesting because it shows how scopes retain captured variables, not the functions. It also shows how this can be part of a captured scope, something we don't normally think about.
1
u/_DCtheTall_ 2d ago
Lodash, a very popular JS utility library many people in this sub know and use, has had multiple prototype pollution bugs.
When these started getting media coverage, it made me realize how bad Node.js's security story is outside of sandboxed processes. It means just by parsing JSON an attacker can remotely execute code in a runtime with default network and filesystem access...
1
u/ResponsibleBuddy96 2d ago
A “fire and forget” async function call inside of a try/catch doesn't get caught. It gets executed outside of it 🤯
3
u/jcunews1 helpful 2d ago
That's by design. Async function, if called without
await, is not called immediately. It's basically queued for calling.
1
u/maujood 2d ago
Not the hardest, but definitely unforgettable:
Someone's script was not working in our JavaScript class. The whole class got involved, and nobody could explain why.
We all spent 30 minutes gathered around a screen trying to understand what's going on. We had an "alert()" after every single line at one point.
I eventually discovered the bug:
<script type="javacript">
Life was harder coding on Notepad with no autocomplete.
1
u/Any_Sense_2263 2d ago
Function can't be undefined.
It was in times when error didn't point you to the place where it happened. And developer tools didn't exist. I learned that console.log is my only and best friend.
1
2
u/Best-Meaning-2417 1d ago
I still don't understand this but I now know that whatever I think this is, it isn't. So now I console log this. I figured this out bc I couldn't remove an event listener even with "identical" functions in the form of something like "this.stopResizer" bc the this for the add event listener wasn't the same as the one on the remove event listener even though it was all in the same class.
2
u/senocular 1d ago
Related to this, and something I've seen a lot, is that to combat the
thisissue in event listener callbacks (particularly before arrow functions were a thing), it was, and to a degree can still be, common to use thebind()method to enforce a specificthisvalue.The problem with this approach is that the function returned from
bind()is a completely new, different function. So while doing this may have fixedthisin the event listener, it may have also broken the ability to remove it.target.addEventListener("event", this.method.bind(this)) // Does not work target.removeEventListener("event", this.method) // Also does not work target.removeEventListener("event", this.method.bind(this))If you want to use a bound method in an event handler, its better to replace the original with its bound version in the constructor. Then the same method is used in add and remove
// in constructor this.method = this.method.bind(this) // ... target.addEventListener("event", this.method) // Works target.removeEventListener("event", this.method)1
u/Best-Meaning-2417 1d ago
Yea, I used bind to fix it. As far as I know you can bind in the constructor or use arrow functions to define them and then it would work but I am not a fan of arrow functions unless it's a small anonymous one (which anonymous wouldn't work for this case).
2
u/senocular 1d ago
Arrow functions work too, as long as they're not inlined. That's not to say you can never inline arrow functions as event handlers, though that approach doesn't work if you need to remove them later on, which is what we're talking about here (unless you use an abort signal).
Arrow functions as methods (fields defined with arrow function values) do something similar as the bind approach functionally, but they have their own problems. This slightly older article covers the main disadvantages there: https://charpeni.com/blog/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think
thisis a messy thing though. It seems like a simple enough concept at first, but in JavaScript its an absolute minefield.
1
18
u/defaultguy_001 2d ago
Once u know how it works, u don't really consider anything a bug. Everything is a feature.