
A horrifying globalThis polyfill in JavaScript - fagnerbrack
https://mathiasbynens.be/notes/globalthis
======
Arbalest
I think what this demonstrates is the patch upon patch nature that Javascript
has taken on. Something we already knew, but with ES2015+, was kind of hidden
from us. That isn't so say the new ES revisions aren't significant
improvements, but just that they still rely on this old infrastructure.

~~~
underwater
An alternative way of looking at things: the dynamic nature of JavaScript and
ability to monkey-patch things like this has let the language move forward.

The fact that I can use classes, block scopes, const and let, new array
methods, decorators, etc. without having to wait for every client to update to
JacaScript N+1 is pretty sweet.

~~~
IgorPartola
That’s the job of a compiler/transpiler though, i.e. praising assembly for the
benefits that C provides.

JavaScript/ES is just now getting features that other languages have enjoyed
for one or more decades because JS people have discovered the features and how
to build the tooling. So good for JS for finally getting these basics, just
wish it happened about 15 years ago.

~~~
Klathmon
I hate this trope that "js people just discovered this feature and now rush to
get it included".

Not only is it not true in many cases (I've been using pattern matching for
years in other functional languages and I'm anxiously hoping the proposal will
get closer to inclusion in JS, but I'm sure if/when it lands I'll be told
about how sad it is that I just learned about this feature when others have
had it for decades...), but it's also pointlessly "holier than thou".

If JS doesn't have a feature, people say that they want it. Once it gets it,
another crowd complains about how they should have had it 15 years ago.

The fact is that there wasn't much of a need for something like globalThis 15
years ago. We had window, and that's all that we needed. Then we got global
with node, and then self in workers, and then JS started getting more usage in
embedded contexts with none of that. A problem was found, a proposal created
and refined, a polyfill made, and finally it's going to be part of the
language.

This is progress, this is the maintainers identifying and fixing a deficiency,
only to be met with "who cares they should have done it years ago [when nobody
was asking for it]".

My point being there's just no need to say that. It doesn't contribute
anything, it's just belittling the work of some very talented people who are
making things that people are asking for in ways that don't break past code.
That may not have been your intention, and I'm sorry for coming into this with
a lot of baggage here, but it's just something that really gets under my skin
and I've seen multiple TC39 people talk about how they avoid places like HN
because of those kinds of attitudes, and I feel that everyone is missing out
because of it.

~~~
IgorPartola
The day JS stops making _’1’ + 2_ a string and throw an error is the day I
will stop pointing it out. Yes lots of experienced devs use JS. Some of them
come from other languages with lots of experience. But at the same time, JS
language feature development is slow and awkward. This article is a good
example of that. I know of no other popular language where accessing the
global object is such a mess. And we will wind up with _globalThis_ as some
built in language feature which makes no goddamn sense. Read that variable
name out loud a few times. Even reading the article you start bumping into
“this globalThis variable is this and that”. I swear keeping _window_ would
have been preferable. Or calling it _global_. But instead we have a language
spec that allows this this to be different from that this and this global this
is not the same as this globalThis and that globalThis.

I point this out because it is important to contextualize why JS is in the
state it is in. The powers that be seem less interested in fixing the issues
of the languages and more interested in plastering over them. A language with
this many polyfills is broken. I use JS almost every day at work, same as
Python. 100% prefer Python.

~~~
jraph
> The day JS stops making ’1’ + 2 a string and throw an error

Please, no. I'm bitten by this in Python every other day, with its otherwise
weak typing. It feels like you can do many horrible things with types in
Python, but no, you shall explicitly cast those numbers to strings. It is
being more pedantic than Java by doing this. This never caught a real error in
my code but it did make it needlessly fail at runtime because I did not put
"str()". I know that the newer Python 3.x versions provide a nice bash-like
way to embed expressions in strings and would let me avoid the problem
entirely, but this would prevent my code to run in many places (and "Hello,
I'm {}".format(age) is painful, hence the new feature by the way).

Thank you for trying to save my back from unwanted implicit type casts, but
provide me with static typing instead if you want to do so, and fail at
compile time.

If you want '1' \+ 2 to fail in JavaScript (You might want to, especially when
keys of objects are coerced to strings in Javascript, which is actually
crappy, and when you are constantly fetching numbers as strings from the web
page), use TypeScript. It's amazing and provides a far better way of handling
silly type errors.

edit: and by the way, C++ is a bit surprising in this respect. "Hello" \+ 2 is
equal to... "llo".

~~~
shakna
> edit: and by the way, C++ is a bit surprising in this respect. "Hello" \+ 2
> is equal to... "llo".

Only surprising if you think "Hello" is an object of a string type. If however
you think of it as a pointer, then moving the pointer is entirely
unsurprising, especially as a pointer "is" an integer.

------
underwater
The line:

    
    
       __magic__.globalThis = __magic__; // lolwat
    

Seems needlessly, well, magic. Keeping it at two lines would make it easier to
grok:

    
    
       Object.prototype.__defineGetter__('__getValueOfThis__', function() {
           return this;
       });
       const globalThisValue = __getValueOfThis__; // Invoke getter on "global object" 
       globalThisValue.globalThis = globalThisValue;
    

Just because it magical doesn't mean it has to be confusing.

~~~
lucideer
Leaving it at one line, but just naming it something more explanatory than
`__magic__` would also improve it slightly (without needing an extra var
declaration).

`__getValueOfThis__` is good. Something like `__globalThisGetter__` or
`__globalThisPolyfillGetter__` might be even clearer given the deletion line.
e.g.:

    
    
      __globalThisPolyfillGetter__.globalThis = __globalThisPolyfillGetter__;
      delete Object.prototype.__globalThisPolyfillGetter__;

~~~
IgorPartola
__getGlobal__()

~~~
lucideer
Maybe. But as the article discusses, globalThis !== global

------
codedokode
I don't understand why they need access to global variables. If you are
writing a portable module using strict mode, you should't need access to
"window" or "global". If you are writing non-portable code then you can access
"window" or "global" by its name.

I mean, they intentionally closed access to global object through "this" in
strict mode and now they want it back.

~~~
amitport
would you prefer the current: (?)

let global = typeof window === 'undefined' ? global : window

// do something with global.theGlobalProp

(this is used to refer to currently available globals which are not going away
anytime soon)

~~~
IgorPartola
I would rather do:

    
    
        export default foo(theGlobalProp) {...; return ...;}
    

Why should a module reach outside its own scope?

------
daurnimator
Thanks for the new method!

In fengari we currently use [https://github.com/fengari-lua/fengari-
interop/blob/8e59efb7...](https://github.com/fengari-lua/fengari-
interop/blob/8e59efb7e25469ed1d56dcd065361a1481112e9e/src/js.js#L92-L107)

    
    
        const global_env = (function() {
            /* global WorkerGlobalScope */ /* see https://github.com/sindresorhus/globals/issues/127 */
            if (typeof process !== "undefined") {
                /* node */
                return global;
            } else if (typeof window !== "undefined") {
                /* browser window */
                return window;
            } else if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
                /* web worker */
                return self;
            } else {
                /* unknown global env */
                return (0, eval)('this'); /* use non-strict mode to get global env */
            }
        })();
    

I created [https://github.com/fengari-lua/fengari-
interop/issues/45](https://github.com/fengari-lua/fengari-interop/issues/45)
to track.

~~~
codedokode
You use "global_env" only inside luaopen_js(). Why don't you just add it as an
argument, for example:

function luaopen_js(L, global) { ... }

instead of writing this ugly hack?

~~~
daurnimator
`luaopen_js` is the entry point for the library with a set API. (that follows
[https://www.lua.org/manual/5.3/manual.html#lua_CFunction](https://www.lua.org/manual/5.3/manual.html#lua_CFunction)
). The caller cannot pass more arguments.

------
ahmeni
This is interesting from both a technical challenge point of view as well as a
neat introduction into the various differences for ECMA run-times, especially
for the module scope. It's impressive how small this polyfill ends up being,
even with the Object.prototype munging avoidance.

~~~
Klathmon
Yeah I've see a lot of people (the author included) call this horrifying, I
think it's beautiful!

It's a piece of code that is simple on it's surface but has an incredible
amount of depth to it. Every single line has a purpose and a backstory. And it
solves a very simple to understand problem that many don't even know is a
problem until it bites them (most often when trying to use a library in a web
worker the first time).

~~~
chii
> simple on it's surface but has an incredible amount of depth to it

that's the definition of bad code: looks simple and innocuous. Does something
completely out of the world and "unexpected" (from the point of view of a
novice/unknowledgable programmer).

~~~
Klathmon
But that's what makes this beautiful in my opinion, it can't really be
simplified anymore and still do it's job!

It's bad code that has to be bad, it has to be complex, it's inherently
difficult and needs to tiptoe around edge cases and avoid pitfalls that 99% of
us don't know or have to care about. And yet it's able to do that and still be
small, concise, and relatively easy to understand on a basic level, even if
you can't quite understand the full reasoning behind why it was created that
way on the surface.

I have similar feelings about the fast square root function from Quake III.
It's horrible and ugly and confusing on the surface, but incredibly powerful,
fast, and humbling when you really look at it. And it served a purpose that
enabled the game to work!

------
hardwaresofton
What's next with JS talk @ Google IO where Mathias presents this work:
[https://www.youtube.com/watch?v=c0oy0vQKEZE](https://www.youtube.com/watch?v=c0oy0vQKEZE)

------
josteink
> TL;DR globalThis is not “the global object”; it’s simply the this from the
> global scope. Thanks to Domenic for helping me understand this important
> nuance.

Can someone in the JavaScript community please enlighten me and tell me why
you would want to be able to obtain a reference to “globalThis” rather than
just the global object itself?

Maybe I’m just being presumptuous, but I suspect that’s what this object is
going to be used for anyway, for storing global state. And then it might as
well just be called global, right?

Unless of course you want to permeate the chaos that is JS “this” into this
new global context, but who would deliberately want to do that?

~~~
bzbarsky
A few things:

1) In browsers, the actual global (the Window) is _never_ exposed to script.
The reasons for that used to do with same-origin policy enforcement, with
exposure of the Window to script being considered a security bug in various
browsers for a while, but I'm not sure those reasons are still relevant
nowadays. At this point, the main reason the WindowProxy is what's exposed is
that this is what web authors and web code expect. It's actually quite
terrible, because it conflates two objects: "the web page" (corresponding to
the non-exposed Window) and "the thing we load web pages in" (corresponding to
the WindowProxy). _That_ is a design mistake in the web's object model that
dates back to the mid-'90s...

In any case, the upshot is that in browser window contexts you can't get a
reference to the global; the only thing you can get is the thing that is
globalThis. In other contexts (web workers, Node), the two are the same thing.

2) The property naming is ... well, read [https://github.com/tc39/proposal-
global/blob/master/NAMING.m...](https://github.com/tc39/proposal-
global/blob/master/NAMING.md) for a (longish) summary. Node uses "global", but
browsers couldn't do that because it broke some websites. Browsers use "self",
but Node wasn't willing to do that, citing concern over the same sort of
breakage. And so on, and so forth.

------
DonHopkins
"globalThis" too shall pass.

JavaScript transit gloria mundi.

------
chii
As the comment on the page says:

The real WTF is (0, eval)('this')

wtf is that syntax??!!one!

~~~
Klathmon
That's a really cool trick/abuse of syntax that goes back to the confusing
`this` in javascript.

calling a method or function directly (when in the global scope) will set
`this` to the global object. But calling a method or bound function could have
a different `this`, and calling a function directly in another context can
have a different `this`.

the `(0, funcName)` syntax works by evaluating each item in the parens, but
returns the last item. (don't ask me why this is in the syntax, I genuinely
don't know and have never seen it used beyond this as far as I know)

So that syntax is abused to basically (simplified here) reassign the funcName
to strip out the `this` that is bound to it and reassign it to the global
context.

It then evals that `this`, and returns it! Meaning it will always get the
global `this`, or `globalThis`!

It would be similar to doing this:

    
    
        var thing = eval
        thing('this')

~~~
saagarjha
> the `(0, funcName)` syntax works by evaluating each item in the parens, but
> returns the last item.

This is just how the comma operator works in other languages such as C and
C++:
[https://en.m.wikipedia.org/wiki/Comma_operator](https://en.m.wikipedia.org/wiki/Comma_operator)

~~~
Klathmon
it is! And I just now realized that I have seen it used many times before, in
for loops and variable declarations.

It just seems so foreign and out of place when used outside of those instances
though, and for me at least the purpose of it wasn't intuitive at all!

~~~
mturmon
In C, the commas are also used in macro definitions where you need multiple
statements to be output by the macro, but can’t use semicolons because the
macro might expand in a context where only one statement is expected. In both
the OP and this one,it comes off as clever but tricky.

------
silverwind
I wonder if it is possibly to access the local scope with similar trickery. It
would be useful to get a reference to a local variable via a string:

    
    
        import 'foo'
        import 'bar'
    
        (function (module) {
          console.info(localThis[module]);
        })('foo')

------
moomin
Concrete question: could this be used to find the XmlHttpRequest constructor?

~~~
Klathmon
Yup! This can get XmlHttpRequest in any environment that has it. Which means
it's extremely easy to write code that works both in the main thread and a
worker transparently.

------
kozak
So, we made the polyfill horribly hideous in order to make it run everywhere
JavaScript can run, and yet, it doesn't work on IE10-. Maybe it's not a good
way to set globalThis after all :)

~~~
folkrav
IE>11 is 0.35% of global usage according to caniuse, and even being generous
and pushing it to 0.5%, it's horribly low. Maybe we just shouldn't support
IE>11 anymore.

~~~
TheCoelacanth
That's still much, much higher usage than jsvu[1] which they gave as the
justification for needing this polyfill. 0.35% of Internet users is ~14
million people. jsvu has been on npm for about a year and has never had more
than 1000 downloads per week, so it has under 50k users, more than two orders
of magnitude less than IE<11.

[1] [https://www.npmjs.com/package/jsvu](https://www.npmjs.com/package/jsvu)

~~~
mathias
Author of the referenced article here.

The article includes a working polyfill that includes old IE support, so I’m
not sure what you’re complaining about.

Also, saying “jsvu is the justification for the polyfill” doesn’t make much
sense. jsvu is just how I happen to test in various standalone JavaScript
engines. There are other, non-jsvu ways of getting such binaries, e.g.
compiling one yourself, and there are JS engine binaries that jsvu doesn’t
provide (Rhino, Ringo, Nashorn). jsvu usage does not correspond to anything
you seem to be talking about, and comparing its download count with IE10 usage
is one of the more extreme apples-to-oranges comparison I’ve seen. ️

------
dmitriid
Well, if TC39 weren’t a bunch of kids running with scissors, you probably
wouldn’t have this problem, would you?

You wouldn’t have to have to bolt on .meta onto import after the fact. You
wouldn’t have to create horrible polyfills for the horribly named globalthis.
And so on and so on and so on.

Hint: just spend actual time actually designing the language.

Hint: Introduce System namespace/module with System.globals. There. Solved.
Trivially polyfilled. The BS about “we can’t easily introduce new global
objects/modules” is BS after WeakMaps and SharedBuffers and god knows what
else.

~~~
realityking
How would System.globals be any easier to polyfill? The problem described in
this article is how to reliable get access to the global object (or bow
globalThis) in currently shopping JS environments in the first place.

~~~
codedokode
Access to global object was intentionally restricted in strict mode and
modules and now you (JS devs) want it back. That's not what rational people
do.

~~~
realityking
Access to the global object is not at all restricted in strict mode. The
changes in strict mode related to the global object are: \- can’t define
implicit global variables (you need to be explicit) \- “this” doesn’t refer to
the global object

The problem is that different host environment have different names for the
global object. That’s what make it hard to write code that works independent
of the host environment and that’s what globalThis is aiming to fix.

