16 comments

[ 3.1 ms ] story [ 50.0 ms ] thread
Author here! In this post I'll explain a method to debug what JavaScript code is adding global variables to the window object. Hope it can help! Open to questions and feedback.
Your approach is very clever! I think it would be even fancier as a script hosted on a CDN a la Firebug, e.g. https://debugjs.global?var=foobar

A sort of “Drop this script tag into your app to track down that pesky variable.”

this is excellent and I will definitely be using it in the future - thankyou!
I've known about JS scope lifting for a very long time but even still I had no idea that it would lift iterators in a for-loop all the way to global scope and not just to the function scope without strict mode, that's nuts.
Well, any time you define a variable without var/let/const, I just imagine it's being defined as a property of the global state. I remember back in the day having to remind people to even use `var`.
Oh! I have some code to go fix...
(comment deleted)
I often want to do the opposite: I know that the value "HelloWorld123" ends up somewhere in a variable in the global scope. Probably in some variable, nested deep deep down in got knows what. And I want to find it. Is there a good way to go about this?
I’ve never really understood why javascript developers hate global scope and state in general.

I always make a point of having some form of app object that I can drill into and inspect from the console to see what’s going on anywhere in the application. It just speeds up development so much to be able to know what’s going on without having to set a breakpoint deep in some nest of function calls and hope you’ve been handed some form of scope that tells you what you need. It’s also handy not to have to pass a user object down through every single component in you app so that it trickles down to the corner of the navbar where you’re going to use it.

I can certainly understand if you’re publishing a library that other people are going to use, you wouldn’t want to leak out of your own box. But when it’s your own app, it just feels silly to stick in these artificial restrictions so that some junior dev who took a functional class doesn’t make snarky comments.

To me, there are two many reasons to avoid the global scope:

1. Like prototype extensions of JS types like "String", you're basically using a namespace that in the future might be used by a built-in API of the JavaScript engine — which would break your code.

2. You don't know for sure where your code will run in the future. A couple of years ago I worked on a migration where "standard" SPA were migrated to a micro-frontend app (where multiple SPA could be loaded on after another at the root level and each SPA would have to clean-up its own globals and listeners).

BTW I'm not against it — I DO put some stuff in the global object. If you put your entire state in a namespace like __MY_CUSTOM_SPACE__ the risk of encountering those two issues is basically 0. But I still thing there aren't many valid reasons to do so, especially with nowadays' toolings.

One thing I missed above is "But what about Testing???", since that's a common reason for building things this way.

For me, the most important aspects of a codebase are "is it Readable" and "is it Debuggable". In my mind, the problem with building in a functional style for Testability sake is that you have to trade off a lot of Debugability to get there. It's the "know what's going on" bit I wrote above. For me, it's not an acceptable tradeoff. Bug reports come in from users as "debugging requests" rather than unit tests, so I want to optimize for that sort of future work.

I actually wrote an article about this a dozen years ago [1] listing my priorities in a codebase, and I don't even think Testable made the list. I'm all for testing where it makes sense, but I'm not going to rearchitect my thing just so that testing is easier.

[1] https://www.expatsoftware.com/articles/2007/06/getting-your-...

Not all global variables get assigned to the window object. For example:

  <script>
  let foo = "bar";
  </script>
  <script>
  console.log(foo); // prints "bar"
  console.log(window.foo); // prints "undefined"
  </script>
Hey! Was this a response to another comment? In the post I'm explaining why it's happening (and it's just purposely bad code to show a global instantiation example)